Работаем с веб-сервисом 1С из приложения на Android

    При работе над фронтом для кафе появилась задача обращаться к веб-сервису 1С из приложения, разрабатываемого на Android. Google мне дал несколько ответов на тему как вообще работать с SOAP, используя библиотеку ksoap2-android. Они помогли в передаче простых типов, но когда дело дошло до передачи массива, пришлось немного подумать.

    Веб-сервис на стороне 1С


    В конфигурации 1С создан веб-сервис с методом WriteSale. Метод принимает несколько параметров, один из которых, items, имеет тип ItemsSold (задан в пакете XDTO конфигурации). Остальные параметры имеют простые типы (string, datetime). Скрин конфигурации:



    Тип ItemsSold имеет единственное свойство Items, для которого установлено свойство «Максимальное количество» в -1, указывая на то, что это массив. Тип этого свойства — ItemSold. Скрин:



    У типа ItemSold все свойства простого типа. Метод WriteSale веб-сервиса имеет следующий код:

    Функция WriteSale(id, date, clientCardNumber, discountRate, items, deptId, bonuses, premiumBonuses)
    	Текст = "OK";
    	Попытка
    		Карточка = ПолучитьКартуПоНомеру(clientCardNumber);
    		ПроцентСкидки = Число(discountRate);
    		Подразделение = НайтиПодразделениеПоПрефиксу(deptId);
    		//...
    		
    		Документ = НайтиДокументПоКодуДате(date, Число(id), Подразделение);
    		Если Не ЗначениеЗаполнено(Документ) Тогда
    			Объект = Документы.ПолныйЧек.СоздатьДокумент();
    			Объект.Дата = date;
    			Объект.КафеНомерДокумента = id;
    		Иначе
    			Объект = Документ.ПолучитьОбъект();
    		КонецЕсли;
    		
    		Объект.ОплатаБонусами = Число(bonuses);
    		//...
    		Объект.Продажи.Очистить();
    		// Здесь идет использование массива
    		Для Каждого item Из items.Items Цикл
    			Номенклатура = НайтиНоменклатуруПоКоду(item.Code);
    			Строка = Объект.Продажи.Добавить();
    			Строка.Количество = Число(item.Quantity);
    			//...
    		КонецЦикла;
    		
    		Объект.Записать(РежимЗаписиДокумента.Проведение);
    		
    	Исключение
    		Текст = ОписаниеОшибки();
    		ЗаписьЖурналаРегистрации("Cafe.WriteSale - исключение: " + Текст, УровеньЖурналаРегистрации.Ошибка);
    		ВызватьИсключение;
    	КонецПопытки;
    	// Возвращаем текст ошибки или "ОК"
    	Возврат Текст;
    КонецФункции
    


    Клиент на стороне Android



    Для обращения к веб-сервису из приложения Android написал следующий код (в соответствии с хорошим примером простого клиента):

    protected String call() throws Exception {
                result = null;
                HttpTransportSE httpTransport = new HttpTransportSE(uri);
                httpTransport.debug = true;
                String resultString;
    
                SoapObject request = new SoapObject(namespace, methodName);
                request.addProperty("id", sale.getId());
                SimpleDateFormat dateFormat = new SimpleDateFormat(
                        "yyyy-MM-dd'T'HH:mm:ss");
                request.addProperty("date", dateFormat.format(sale.getDate()));
                request.addProperty("clientCardNumber", sale.getCardNumber());
                request.addProperty("bonuses", Double.toString(sale.getBonuses()));
                //...
    
                // see - http://code.google.com/p/ksoap2-android/wiki/CodingTipsAndTricks#Adding_an_array_of_complex_objects_to_the_request
                SoapObject sales = new SoapObject(namespace, "items");
                for (SaleItemInformation item : sale.getSales()) {
                    SoapObject itemSoap = new SoapObject(namespace,
                            "Items");
                    itemSoap.addProperty("Code", item.getItem().getSourceCode());
                    itemSoap.addProperty("Quantity",
                            Double.toString(item.getQuantity()));
                    //...
                    sales.addSoapObject(itemSoap);
                }
                request.addSoapObject(sales);
                SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(
                        SoapEnvelope.VER11);
                // Тоже важный элемент - не выводит типы данных в элементах xml
                envelope.implicitTypes = true;
                envelope.setOutputSoapObject(request);
                try {
                    httpTransport.call(soapAction, envelope);
                } catch (Exception e) {
                    e.printStackTrace();
                    throw e;
                }
                resultString = envelope.getResponse().toString();
                return resultString;
            }
    


    Вроде бы код выглядит правильно, формирует красивый xml-запрос:

    <v:Envelope xmlns:i="http://www.w3.org/2001/XMLSchema-instance" xmlns:d="http://www.w3.org/2001/XMLSchema" xmlns:c="http://schemas.xmlsoap.org/soap/encoding/" xmlns:v="http://schemas.xmlsoap.org/soap/envelope/">
    <v:Header />
    <v:Body>
    	<n0:WriteSale id="o0" c:root="1" xmlns:n0="http://www.xxxxx.ru">
    		<date i:type="d:string">Thu May 31 16:13:08 YEKST 2012</date>
    		<clientCardNumber i:type="d:string">120</clientCardNumber>
    		<discountRate i:type="d:string">5.0</discountRate>
    		<id i:type="d:long">11</id>
    		<n0:items i:type="n0:items">
    			<n0:Items i:type="n0:Items">
    				<Code i:type="d:string">3000</Code>
    				<Price i:type="d:string">100.0</Price>
    				<Quantity i:type="d:string">2.0</Quantity>
    				<Sum i:type="d:string">200.0</Sum>
    			</n0:Items>
    			<n0:Items i:type="n0:Items">
    				<Code i:type="d:string">3001</Code>
    				<Price i:type="d:string">110.0</Price>
    				<Quantity i:type="d:string">1.0</Quantity>
    				<Sum i:type="d:string">110.0</Sum>
    			</n0:Items>
    		</n0:items>
    	</n0:WriteSale>
    </v:Body>
    </v:Envelope>
    


    Но веб-сервис отвечает на него 500-й ошибкой. При этом, обращаясь к другому методу с параметрами простого типа на том же веб-сервисе, мы получаем корректный ответ. Более того, обращаясь из другой базы 1С через WS-ссылка к приведенному выше методу веб-сервиса, мы получаем корректный ответ и выполнение необходимых действий на стороне веб-сервиса. Поэтому пришлось перехватить запрос, формируемый другой базой 1С. Сделать это фидлером не получилось, так как он каким-то образом обрезал само тело запроса с xml и не передавал его веб-сервису. Нормально перехватить запрос удалось только с помощью WireShark. Итак, текст запроса от 1С:

    <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
    	<soap:Header/>
    	<soap:Body> <m:WriteSale xmlns:m="http://www.xxxxx.ru">
    	<m:id xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">1</m:id>
    	<m:date xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">2</m:date>
    	<m:clientCardNumber xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">3</m:clientCardNumber>
    	<m:discountRate xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">4</m:discountRate>
    	<m:items xmlns:xsd="http://www.w3.org/2001/XMLSchema"
    			xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    		<m:Items>
    			<m:Code>123</m:Code>
    			<m:Price>12.2</m:Price>
    			<m:Quantity>2</m:Quantity>
    			<m:Sum>2</m:Sum>
    		</m:Items>
    		<m:Items>
    			<m:Code>2</m:Code>
    			<m:Price>1</m:Price>
    			<m:Quantity>2</m:Quantity>
    			<m:Sum>2</m:Sum>
    		</m:Items>
    	</m:items>
    </m:WriteSale></soap:Body>
    </soap:Envelope>
    


    Несложно заметить, что для вложенных элементов массивов (Code, Price...) библиотека ksoap2-android не проставляет префиксы с пространством имен. Для корневых элементов (id, date...) они также не проставлены, но этот факт 1С в ступор не вводит. А их отсутствие у под-элементов заставляет программу усомниться в корректности входных данных, прочитать она их не может.

    Изучив код библиотеки, решил, что наиболее рациональным будет модифицировать метод SoapObject#addProperty(String, Object) следующим образом:

    public static class SoapObjectCustom extends SoapObject {
    
            public SoapObjectCustom(String namespace, String name) {
                super(namespace, name);
            }
    
            @Override
            public SoapObject addProperty(String name, Object value) {
                PropertyInfo propertyInfo = new PropertyInfo();
                propertyInfo.name = name;
                propertyInfo.type = value == null ? PropertyInfo.OBJECT_CLASS
                        : value.getClass();
                propertyInfo.setValue(value);
    
                // Добавил эту строку
                propertyInfo.setNamespace(this.namespace);
    
                return addProperty(propertyInfo);
            }
        }
    


    В исходном коде я заменил объекты SoapObject на SoapObjectCustom в следующих местах:

    //...
    SoapObjectCustom request = new SoapObjectCustom(namespace, methodName);
    //...
    SoapObject sales = new SoapObject(namespace, "items");
    for (SaleItemInformation item : sale.getSales()) {
        SoapObjectCustom itemSoap = new SoapObjectCustom(namespace,
                "Items");
        //...
    }
    //...
    


    Заключение



    Скорее всего, есть смысл в том, что авторы не включали префиксы пространства имен в свойства элементов. И вполне возможно, что в работе с другими веб-сервисами такие коррективы приведут к некорректному поведению программы. Тем не менее, данный метод работает с веб-сервисами 1С, надеюсь это описание кому-нибудь поможет в работе.

    Описанное выше было протестировано с 1С v.8.2.15.294 и Android 12 (3.0).
    Поделиться публикацией

    Комментарии 4

      0
      Спасибо, очень нужная информация, для одного проекта
        0
        А не может быть, что namespace — пустая строка- в примере не видно, что она была инициализирована?
          0
          this.namespace, если речь про нее, инициализируется в конструкторе, так что пустой она быть не должна.
          0
          Когда разбирался с подобной ситуацией, очень помог Apache TCPMon. Клиент был на устройстве, подключенном через 3G, сервис 1С — в Интернете. Да и в любом случае, так удобнее получается.

          Еще одна проблема, с которой столкнулся на Android 2.2 и ksoap2-android — очень медленное кодирование в Base64 библиотечным же методом (необходимо было передавать фотографии на сервер 1С). При этом на 2.3 на том же устройстве всё работает на порядки (без преувеличения) быстрее. Родной метод из API ОС при этом работает отлично и очень быстро.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое