Пишем SOAP клиент-серверное приложение на PHP / Хабр

3 введение в xml-schema

Теперь мы много чего знаем о то, что такое SOAP, что находится у него внутри и имеем обзорное представление о том, какой стек технологий его окружает. Поскольку, прежде всего SOAP представляет собой способ взаимодействия между клиентом и сервером, и в качестве транспорта для него используется язык разметки XML, то в данном разделе мы немного разберемся каким образом происходит автоматическая валидация данных посредством XML-схем.

Основная задачи схемы – описать структуру данных которые мы собираемся обрабатывать. Все данные в XML-схемах делятся на простые (скалярные) и коплексные (структуры) типы. К простым типам относятся такие типы как:

Что-то очень простое, у чего внутри нет расширений. Их антиподом являются сложные комплексные типы. Самый простой пример комплексного типа, который приходит всем в голову – объекты. Например, книга. Книга состоит из свойств:

авторназваниеценаISBN номер

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

Предлагаю далеко не ходить и написать XML-схему для нашего sms-сообщения! Ниже представлено xml-описание sms-сообщения:

Схема нашего комплексного типа будет выглядеть следующим образом:

Эта запись читается следующим образом: у нас есть переменная «

message

» типа «

Message

» и есть комплексный тип с именем «

Message

», который состоит из последовательного набора элементов «

phone

» типа

string

, «

text

» типа

string

, «

date

» типа

dateTime

, «

type

» типа

decimal

. Эти типы простые и уже определены в описании схемы. Поздравляю! Мы только что написали нашу первую XML-схему!

Думаю, что значение элементов «element» и «complexType» вам стало все более-менее понятно, поэтому не будем на них больше заострять внимание и переключимся сразу же на элемент-композитор «sequence». Когда мы используем элемент-композитор «sequence» мы сообщаем о том, что элементы включенные в него должны всегда располагаться в указанной в схеме последовательности, а также все из них являются обязательными.

Но не стоит отчаиваться! В XML-схемах есть еще два элемента-композитора: «choice» и «all». Композитор «choice» сообщает о том, что должен быть какой-то один из перечисленных в нем элементов, а композитор «all» – любая комбинация перечисленных элементов.

Как вы помните, то в первом разделе топика мы договорились о том, что в пакете может передаваться от одного до бесконечности sms-сообщений. Поэтому предлагаю разобраться как такие данные декларируются в XML-схеме. Общая структура пакета может выглядеть следующим образом:


Схема для такого комплексного типа будет выглядеть так:

В первом блоке идет знакомое нам декларирование комплексного типа «

Message

». Если вы заметили, то в каждом простом типе, входящем в «

Message

», были добавлены новые уточняющие атрибуты «

minOccurs

» и «

maxOccurs

». Как не трудно догадаться из названия, первый (

minOccurs

) сообщает о том, что в данной последовательности должно быть минимум по одному элементу типа «

phone

», «

text

», «

date

» и «

type

», в то время как следующий (

maxOccurs

) атрибут нам декларирует, что таких элементов в нашей последовательности максимум по-одному. В результате, когда мы пишем свои схемы для каких-либо данных, нам предоставляется широчайший выбор по их настройке!

Второй блок схемы декларирует элемент «messageList» типа «MessageList». Видно, что «MessageList» представляет собой комплексный тип, который включает минимум один элемент «message», но максимальное число таких элементов не ограничено!

На этом будем считать, что ЛикБез по схемам завершен и далее нас ждет еще одно не менее увлекательное приключение – мы будем писать свой собственный WSDL!

1 Границы

В начале предлагаю разобраться с тем результатом, которого мы достигнем в конце топика. Как было объявлено выше, мы будем писать сервис по отправке sms-сообщений, а если еще точнее, то к нам будут поступать сообщения из разных источников по протоколу SOAP.

Пишем SOAP клиент-серверное приложение на PHP / Хабр

2 Какими данными будем меняться?


Отлично, с границами мы определились! Следующий шаг, который необходимо сделать – решить какими данными мы будем обмениваться между сервером и клиентом. На эту тему предлагаю долго не мудрить и сразу для себя ответить на главные вопросы:

Что-то мне подсказывает, что для этого необходимо посылать следующее:

В принципе, двух этих характеристик достаточно для отправки, но мне сразу представляется случай, как sms-ка с поздравлением о дне рождения приходит вам в 3 часа утра, или 4! В этот момент я буду всем очень благодарен за то, что про меня не забыли! Поэтому, мы также будем посылать на сервер и

Следующее, что я бы хотел отправлять на сервер, так это

Данный параметр не является обязательным, но он может нам очень сильно пригодиться в случае, если быстро понадобится сказать боссу о том, скольких из наших клиентов мы «обрадовали» своим известием, а также нарисовать какую-нибудь красивую статистику на этот счет.

И все же, я что-то забыл! Если еще немного порефлексировать, то стоит отметить, что клиент за раз может отправить на сервер как одно sms-сообщение, так и некоторое их количество. Другими словами, в одном пакете данных может быть от одного до бесконечности сообщений.

В результате мы получаем, что для отправки sms-сообщения нам необходимы следующие данные:

Пишем SOAP клиент-серверное приложение на PHP / Хабр

На первый вопрос мы ответили, теперь необходимо ответить на второй вопрос. И пожалуй, я позволю себе немного с халтурить. Поэтому, с сервера мы будем присылать только булевы данные, значение которых имеет следующий смысл:

Пишем SOAP клиент-серверное приложение на PHP / Хабр

На этом мы закончили описание постановки задачи! И наконец-то приступим к самому интересному – будем разбираться что за диковинный зверь этот SOAP!

2 с чем есть soap?

Вообще, изначально я не планировал ничего писать о том, что такое SOAP и хотел ограничиться ссылками на сайт w3.org с нужными спецификациями, а также ссылками на Wikipedia. Но в самом конце решил написать коротенькую справочку об этом протоколе.

И начну я свое повествование с того, что данный протокол обмена данными относится к подмножеству протоколов основанных на так называемой парадигме RPC (Remote Procedure Call, удалённый вызов процедур) антиподом которой является REST (Representational State Transfer, передача репрезентативного состояния).

Более подробно об этом можно прочесть в Wikipedia, ссылки на статьи находятся в самом конце топика. Из этих статей нам надо уяснить следующее: «Подход RPC позволяет использовать небольшое количество сетевых ресурсов с большим количеством методов и сложным протоколом.

При подходе REST количество методов и сложность протокола строго ограничены, из-за чего количество отдельных ресурсов может быть большим». Т.е., применительно к нам это означает, что на сайте в случае RPC подхода будет всегда один вход (ссылка) на сервис и какую процедуру вызывать для обработки поступающих данных мы передаем вместе с данными, в то время как при REST подходе на нашем сайте есть много входов (ссылок), каждая из которых принимает и обрабатывает только определенные данные. Если кто-то из читающих знает, как еще проще объяснить различие в данных подходах, то обязательно пишите в комментариях!

Следующее, что нам надо узнать про SOAP – данный протокол в качестве транспорта использует тот самый XML, что с одной стороны очень хорошо, т.к. сразу же в наш арсенал попадает вся мощь стека технологий основанных на данном языке разметки, а именно XML-Schema – язык описания структуры XML-документа (спасибо Wikipedia!), который позволяет производит автоматическую валидацию поступающих на сервер данных от клиентов.

6 soap-клиент на подходе


Прежде всего нам надо создать файл, в котором будем писать клиент. Как обычно, мы его создадим в корне хоста и назовем «

client.php

», а внутри напишем следующее:

7 отправляем сложные объекты

Давайте подумаем над тем, как же нам передать целую пачку сообщений на сервер в одном пакете? Наверно, самым простым способом будет организация массива внутри элемента messageList! Давайте это сделаем:

// создаем объект для отправки на сервер
$req = new Request();
$req->messageList = new MessageList();

$msg1 = new Message();
$msg1->phone = '79871234567';
$msg1->text = 'Тестовое сообщение 1';
$msg1->date = '2022-07-21T15:00:00.26';
$msg1->type = 15;

$msg2 = new Message();
$msg2->phone = '79871234567';
$msg2->text = 'Тестовое сообщение 2';
$msg2->date = '2022-08-22T16:01:10';
$msg2->type = 16;

$msg3 = new Message();
$msg3->phone = '79871234567';
$msg3->text = 'Тестовое сообщение 3';
$msg3->date = '2022-08-22T16:01:10';
$msg3->type = 17;

$req->messageList->message[] = $msg1;
$req->messageList->message[] = $msg2;
$req->messageList->message[] = $msg3;


В наших логах числится, что пришел следующий пакет от клиента:

Soap xml request with basic authentication in php

I am trying to setup a SOAP request in PHP using HTTP basic Authentication. For some reason I keep getting, HTTP/1.1 401 Unauthorized error.

This is an example of the request I’m trying to create:

POST https://url/someurl/soap HTTP/1.1
Accept-Encoding: gzip,deflate
Content-Type: text/xml;charset=UTF-8
SOAPAction: "SomeSoapAction"
User-Agent: SomeUser Client-HttpClient/3.1
Content-Length: 1503
Authorization: Basic dGsomebasicreyteryheyp0ZXN0ZXI=
Host: somehost.com  

This is a snippet of my code:

ini_set('display_errors',1);
ini_set("soap.wsdl_cache_enabled", "0");
error_reporting(E_ALL);
$wsdl = "urltosomewsdlfile.wsdl";
$url = "somerl";
$username = "username";
$password = "password";
$encodedstuff = base64_encode("{$username}:{$password}");

$client = new SoapClient($wsdl, array('exceptions' => true,'trace' => true,'login' => 'somelogin', 'password' => 'somepw'));
$header = new SoapHeader($url, 'Authorization: Basic', $encodedstuff);
$client->__setSoapHeaders($header);

try {
    $result = $client->SomeSoapAction();
    } catch (SoapFault $soapFault) {
        var_dump($soapFault);
        echo "<br />Request :<br />", htmlentities($client->__getLastRequest()), "<br />";
        echo "<br />Response :<br />", htmlentities($client->__getLastResponse()), "<br />";
    }

If I take the username and password out of the new SoapClient section it throws up Uncaught SoapFault exception: [WSDL] SOAP-ERROR: Parsing WSDL: Couldn’t load from…

If I put in the base64 encoded variable to the new SoapClient section instead of the username/pw it throws the same Parsing error as above:
$client = new SoapClient($wsdl, array( ‘authentication’ => “Basic $encodedstuff”, ‘exceptions’ => true,’trace’ => true,));

The username and password work when loading the URL directly.

Can anyone point me in the right direction? Thanks for any help.


Update 12/18/18: I’ve been trying this as a new method suggested, but still receive the same 401 error message:

$opts = array( 'http'=>array( 'method'=>"POST", 'header' => "Authorization: Basic " . base64_encode("{$user}:{$pw}") ) ); 
$context = stream_context_create($opts); 
$client = new SoapClient($wsdl, array('stream_context' => $context,'exceptions' => true,'trace' => true,)); 
$header = new SoapHeader($url, 'Authorization: Basic', $encodedstuff); 
$client->__setSoapHeaders($header);

Update 15/01/19:
I’m still having issues with this.

Using SOAPUI I can make a successful request and gain authentication. I can’t see what the difference is between the SOAPUI request and my own PHP request.

I’ve noticed that the request header created by my PHP code alters the POST and Host endpoint URLs. The first part of the POST URL has the domain removed and the Host also has the first section of the URL removed.

SOAPUI request header (correct returns 200):

POST https://test-example.com/file/SOAP HTTP/1.1
Host: test-example.com

PHP request header (returns a 401):

POST /file/SOAP HTTP/1.1
Host: example.com

This seems to be causing the problem as it looks as if I’m pointing to the wrong endpoint. Does anyone have any advice on what might be wrong with my PHP code to break this request? Thanks.

Готовим soap для web-сервисов. рецепты

WEBv81cv8.cfРоссияБесплатно (free)

Цель статьи – указать на подводные камни и нюансы, о которых “не пишут на заборах” и которые встретились мне за время внедрения типового модуля интеграции 1С и Битрикс24. Будет интересна для людей, кто подумывает о том, чтобы настроить интеграцию, и хотят понять, с чем столкнутся. А также для тех, кто уже работает с подобным обменом, столкнулся с какими-то из описанных ситуаций и хочет понять, что пошло не так и “как жить дальше”. Постараюсь все описать “человеческим” языком с минимальной долей терминов, так как статья, надеюсь, будет полезна не только программистам.

07.11.2021   
5680   
freegman74   
13    

Добавляем basic auth в soap запрос средствами ksoap2-android

Так получилось, что в рамках своей работы я связался с проектом по разработке приложения для общения Android и 1С. Быстрый поиск в интернете дал достаточно четкие инструкции и куски кода, которые очень быстро оформились в готовую программу, но запускаться она не хотела. Здесь я расскажу основные тонкости и способы их решения.

Первоисточники кода расписаны здесь и тут. Необходимо только подключить стороннюю библиотеку ksoap2-android, чтобы стали доступны классы SoapObject и HttpTransportSE. Старый проверенный способ скачать jar с официального репозитория и положить его в app/libs почему-то не увенчался успехом, и я стал смотреть, как подключить библиотеку используя современный Gradle. Почти на всех ресурсах было написано, что простое добавление

dependencies {
    compile 'com.google.code.ksoap2-android:ksoap2-android:3.6.1'
}

должно «подтягивать» нужные ресурсы. Но этого не произошло, т.к. сам ресурс не находится в стандартных репозиториях. Значит, надо указать, откуда его скачивать. Делается это добавлением в Gradle файл проекта следующих строк:

buildTypes {
    repositories {
        maven { url 'https://oss.sonatype.org/content/repositories/ksoap2-android-releases' }
    }
} 

После этих простых действий просто синхронизируем проект, и Gradle закачает все, что нужно.

Вот код, который начинает работать после вышеописанных процедур:

public class DataLoader extends AsyncTask<Void, Void, String> {
    private static final String NAMESPACE = "namespace";
    private static final String URL = "http://host/wsdlAcceptor?wsdl";
    private static final String SOAP_ACTION = "http://host/wsdlAcceptor";
    private static final String METHOD_NAME = "testOperation";

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    @Override
    protected String doInBackground(Void... params) {
        try {
            SoapObject request = new SoapObject(NAMESPACE, METHOD_NAME);
            SoapSerializationEnvelope envelope = new SoapSerializationEnvelope(SoapEnvelope.VER10);
            request.addProperty("IsFirstRequest", true);
            envelope.setOutputSoapObject(request);
            HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);
            androidHttpTransport.debug = true;
            try {
                androidHttpTransport.call(SOAP_ACTION, envelope);
                SoapObject resultsRequestSOAP = (SoapObject) envelope.bodyIn;
                System.out.println("Response::" resultsRequestSOAP.toString());
            } catch (Exception e) {
                e.printStackTrace();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return "";
    }

    @Override
    protected void onPostExecute(String result) {
        super.onPostExecute(result);
    }
}

Не секрет, что работа с любой сетевой активностью должна делаться из отдельной асинхронной задачи. doInBackground – это стандартный метод AsyncTask. Если еще не знакомы, то

вот здесь

про него написано просто и очень понятно.

Сама MainActivity выглядит следующим образом:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        DataLoader dl = new DataLoader();
        dl.execute();
    }
}

На этом приключения не заканчиваются. В моем случае запрос к серверу был закрыт Basic Auth, поэтому потребовалось куда-то вводить логин и пароль. И вот на этот вопрос однозначного ответа в интернете я не нашел. Во многих статьях был упомянут некий класс HttpTransportBasicAuthSE, в конструктор которого и передается логин с паролем. Но его было не найти в ksoap2-android, пришлось искать в интернете. Нашелся

здесь

. Привожу полный текст:

public class HttpTransportBasicAuthSE extends HttpTransportSE {
    private String username;
    private String password;

    /**
     * Constructor with username and password
     *
     * @param url
     *            The url address of the webservice endpoint
     * @param username
     *            Username for the Basic Authentication challenge RFC 2617
     * @param password
     *            Password for the Basic Authentication challenge RFC 2617
     */
    public HttpTransportBasicAuthSE(String url, String username, String password) {
        super(url);
        this.username = username;
        this.password = password;
    }

    public ServiceConnection getServiceConnection() throws IOException {
        ServiceConnectionSE midpConnection = new ServiceConnectionSE(url);
        addBasicAuthentication(midpConnection);
        return midpConnection;
    }

    protected void addBasicAuthentication(ServiceConnection midpConnection) throws IOException {
        if (username != null && password != null) {
            StringBuffer buf = new StringBuffer(username);
            buf.append(':').append(password);
            byte[] raw = buf.toString().getBytes();
            buf.setLength(0);
            buf.append("Basic ");
            org.kobjects.base64.Base64.encode(raw, 0, raw.length, buf);
            midpConnection.setRequestProperty("Authorization", buf.toString());
        }
    }
}

Полагаю, комментарии к коду излишни. Уточню только, что для применения HttpTransportBasicAuthSE достаточно просто изменить строку в DataLoader

HttpTransportSE androidHttpTransport = new HttpTransportSE(URL);

на

HttpTransportBasicAuthSE androidHttpTransport = new HttpTransportBasicAuthSE(URL, "basicLogin", "authPassword");

и все начинает работать с авторизацией очень четко!

P.S.: не забывайте добавлять в Manifest разрешение на Internet

<uses-permission android:name="android.permission.INTERNET" />

Использование soapui для работы с веб-сервисами. часть1

WEBv8v8::УФУТ11Абонемент ($m)

Многие уже знают, что в релизе платформы 8.3.14.1565, браузер Internet Explorer был заменен на Web-Kit, это на самом деле большой шаг вперед, но я уверен, многим, как и мне, пока не совсем понятно, что к чему. Возник опыт использования web-kit в 1С, вызова JS из 1С и вызова 1С из JS. Давайте вместе попробуем понять, чем одно отличается от другого, и заодно сделаем, что-нибудь полезное. Да и наверняка многим придется переписывать свои подобные поделки после обновления на новую платформу, так что надеюсь мой опыт окажется полезным.

2 стартмани

08.12.2022   
10341   
Бэнни   
25    

Обновление библиотеки openssl

Метод собирать «из исходников» и засорять систему — не наш метод. Поэтому лучший вариант — найти готовый пакет, и я его нашел в репозитории axivo:

Правка и перекомпиляция php

Основная проблема заключается в том, что для того чтобы OpenSSL использовала файл конфигурации по умолчанию (именно там у нас прописаны настройки для алгоритмов ГОСТ) перед ее использованием необходимо вызвать функцию OPENSSL_config(NULL). В расширении PHP OpenSSL этого не сделано, поэтому модуль PHP-SOAP при использовании SSL-соединения не видит алгоритмов ГОСТ. Кстати, тоже самое касается и других библиотек, например curl. Ее тоже нужно патчить если вы собираетесь с ней работать.

Итак приступим. Для того чтобы нам поправить OpenSSL необходимо перекомпилировать весь PHP, так как в CentOS расширение OpenSSL сделано не модулем.


Подготавливаем среду для сборки пакетов:

# yum install rpm-build redhat-rpm-config
# mkdir /root/rpmbuild
# cd /root/rpmbuild
# mkdir BUILD RPMS SOURCES SPECS SRPMS
# mkdir RPMS/{i386,i486,i586,i686,noarch,athlon}

Качаем исходники PHP и устанавливаем их:

Преобразуем выданный ключ и сертификат в формат, понятный openssl

Для этого нам понадобится Windows машина с установленным CryptoPRO CSP 3.6. Я его слил с сайта разработчика (там дается демонстрационный режим режим на 3 месяца).

Первое что делаем — экспортируем ключевой контейнер в реестр средствами КриптоПро. Для этого открываем КриптоПро из панели управления Windows. Вставляем наш ключевой носитель в компьютер. Для обычной флешки убеждаемся, что у нас среди считывателей на вкладке «Оборудование» есть «Все съемные диски» и «Реестр».

Далее переходим на вкладку «Сервис» и нажимаем «Копировать», нажимаем «Обзор» и выбираем среди списка свой ключевой контейнер. Если его там вдруг не находим — то возвращаемся к предыдущему параграфу и все проверяем. Далее вводим осмысленное имя нового ключевого контейнера, нажимаем «Готово» и в качестве носителя в появившемся окне выбираем «Реестр».

Новый пароль на контейнер не ставим. Теперь нам нужно установить сертификат в новый контейнер. На вкладке «Сервис» в КриптоПро нажимаем кнопку «Установить личный Сертификат», указываем файл с нашим сертификатом, нажимаем «Далее», выбираем только что созданный нами контейнер и не забываем галочку «Установить сертификат в контейнер».

Для того чтобы экспортировать данный сертификат в формате PKSC#12 вместе с закрытым ключом — нам понадобится сторонняя утилита. Которую можно либо купить здесь либо найти на просторах интернета по имени p12fromgostcsp. Запускаем данную утилиту, выбираем наш сертификат, пароль оставляем пустым и сохраняем его в файл mycert.p12.

# openssl pkcs12 -in mycert.p12 -nodes

На запрос пароля просто нажимаем Enter. И смотрим наличие строк BEGIN CERTIFICATE и BEGIN PRIVATE KEY. Если строки есть то все в порядке. Преобразуем полученный сертификат в формат PEM:

# openssl pkcs12 -in mycert.p12 -out mycert.pem -nodes -clcerts

Если вам нужна не только авторизация по клиентскому сертификату, но и также проверка валидности самого сервера — вам понадобится корневой сертификат удостоверяющего центра. Его можно просто через Windows открыть и сохранить в формате DER в кодировке X.

# openssl x509 -inform DER -in cacert.cer -outform PEM -out cacert.pem


Где cacert.cer — имя исходного файла с корневым сертификатом. Проверить коннект с сервером с использованием сертификатов можно через OpenSSL командой:

8 заключение

Наконец-то мы добрались сюда! Давайте определимся с тем, что вы теперь умеете делать:

Также, мы сделали для себя некоторые открытия в ходе нашего небольшого исследования:

Похожее:  PHP: setcookie - Manual

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *