Pull to refresh

Пишем веб сервис используя gSOAP

Reading time 6 min
Views 23K

О чем речь?


Иногда в старом и добром C++ возникает потребность в реализации SOAP сервисов. Конечно, истинные любители программирования бросаются писать сервер самостоятельно, я же предпочитаю не тратить время впустую и использовать готовые библиотеки. Сегодня я хочу осветить (совсем чуть-чуть) тему использования библиотеки gSOAP в своих приложениях на C++ в качестве сервера.

Инструментарий


Я буду использовать Visual Studio 2010 SP1 в своём рассказе, однако сразу упомяну что gSOAP прекрасно работает и на других платформах и компиляторах, а не только на Windows.

Саму библиотеку можно скачать тут [http://www.cs.fsu.edu/~engelen/soap.html] (я использовал v2.8.2).

Постановка задачи


Требуется разработать сервис приема платежей (ну это я конечно вру), работающий по протоколу.
Система имеет всего один метод с псевдо-сигнатурой:
void MakePayment([IN] int qty, [OUT]int& errorCode, [OUT]string& message)

Поехали!


Процесс формирования кода сервера в gSOAP почти полностью автоматизирован. Внутри разработан свой язык описания сервисов, очень похожий по грамматике на Си с элементами "++". Так что нам не придется париться даже с изучением WSDL (но вообще иметь представление о нём все же стоит).

Более того нам несказанно повезло, т.к. дистрибутив gSOAP уже содержит скомпилированные процессоры для этого языка под популярные платформы, включая Windows (уж поверьте, компилирование этого руками (да еще на Windows) врядли доставит вам удовольствие всвязи с большим количеством зависимостей от opensource библиотек, которые тоже придется собрать…).

Итак, лезем в каталог gsoap\bin\win32\ и видим там две программы. soapcpp2.exe отвечает как раз за то что нам надо – процессинг си-подобного языка описания сервиса в код сервера, клиентского прокси (если надо), wsdl описания и примеры soap request- reply- конвертов. Рядом лежит wsdl2h.exe – эта утилита позволяет прогнать WSDL описание сервиса и на выходе получить описание сервиса на си-подобном языке для дальнейшего использования в soapcpp2.exe. Нам понадобится только первая из них, т.к. описания сервиса на WSDL у нас пока нет.

Посмотреть справочную страницу возможно для каждой утилиты используя ключ –help. Я не стану перечислять тут все опции, т.к. многое нас устроит по умолчанию, а те опции, которые я использую, достаточно очевидны из справки.

Сервер


Начнем с описания сервиса. Оно производится в файлах обычно с расширением h. Назовем файл paymentservice.h и впишем туда буквально следующее:
//gsoap ns service name:	paymentssl
//gsoap ns service style:	rpc
//gsoap ns service encoding:	encoded
//gsoap ns service namespace:	urn:paymentssl
//gsoap ns service location:	http://localhost:9999
//gsoap ns service method-action: MakePayment urn:MakePayment
typedef int xsd__int;
typedef char* xsd__string;

enum t__status				// remote status:
{ 
  STATE_OK,					// ok
  STATE_FAIL				// fail to process
};

class t__result
{
public:
	enum t__status errCode;
	xsd__string message;
};

int ns__MakePayment(xsd__int qty, t__result* result);


Параметры для процессинга непосредственно SOAP сообщения задаются в виде «магического комментария» (первые строки). На них особо нет смысла останавливаться т.к. они являются почти копией стандартных SOAP-ких. Разве что формат method-action стоит пояснить — первым аргументом для него указывается имя метода, а вторым собственно SOAPAction.

Запускаем soapcpp2.exe -2 -i -e paymentservice.h
Поясню параметры:
  • -2 — мы используем SOAP v1.2;
  • -i — генерировать код C++ классов, наследуемых от структуры soap. Данная структура является частью сишного ядра gSOAP (в основе gSOAP лежит Си, поверх которого как обычно можно сделать C++ обертку);
  • -e — генерировать биндинги в стиле SOAP RPC


Результатом станет кучка файлов. Разберем их по-порядку:
  • paymentssl.wsdl – WSDL описание сервиса;
  • t.xsd — типы, объявленные нами (в нашем случае туда попадет наш enum);
  • paymentssl.MakePayment.req.xml и paymentssl.MakePayment.res.xml — примеры SOAP сообщений (Request и Reply части), которые должен будет принять/выдать сервис;
  • soappaymentsslProxy.cpp и soappaymentsslProxy.h – код клиентской прокси;
  • soappaymentsslService.cpp и soappaymentsslService.h – код серверной части;
  • paymentssl.nsmap – константы, используемые сервером и клиентом при формировании SOAP заголовка;
  • soapC.cpp и soapH.h – код парсера сообщений на Си;
  • soapStub.h – заголовочная часть, описывающая классы, используемые сервером и клиентом.


Вот как выглядит полученное WSDL описание:


Пришло время заняться программированием сервера (в описании сервера присутствуют строки SSL, но про шифрование (а оно поддерживается) мы тут говорить пока не станем).

Программируем сервер


Создаем пустое консольное приложение, которое не будет использовать Precompiled Headers. И добавляем в проект сгенерированные файлы.

Проект приобретает вид:


В него также добавлены также файлы ядра gSOAP: stdsoap2.cpp и stdsoap2.h.

Я сразу добавил класс PaymentServiceImpl, в котором планирую реализовать сервис.
Для того чтобы всё собралось необходимо определить фунцию
paymentsslService::MakePayment(int qty, t__result *result)

Она виртуальная (но не чисто виртуальная), а мой класс имплементации PaymentServiceImpl наследован от paymentsslService и будет перегружать данную операцию, поэтому надо где-то объявить её как заглушку как-то так:
//
//implementation for generated code always return FAIL.
//This method will be override in PaymentServiceImpl class
//
int paymentsslService::MakePayment(int qty, t__result *result)
{
	return SOAP_ERR;
}

Константа SOAP_ERR определена в stdsoap2.h среди прочих.

Также мы допишем реализцию перегруженной функции MakePayment в классе PaymentServiceImpl.

Пришло время наделить сервис логикой возврата сообщений об успехе операции с числовым кодом и текстовым сообщением.

Логика у нас будет следующая:
1) если qty<0 то мы генерируем исключение SOAP (SOAP FAULT)
2) если qty>100 то мы возращаем код ошибки FAIL с сообщением «error»
3) иначе возвращаем код ошибки OK и без сообщения

Итоговая реализация:
// PaymentServiceImpl.h
#pragma once
#include "Autogenerated/soappaymentsslService.h"
class PaymentServiceImpl : public paymentsslService
{
public:
	virtual	int MakePayment(int qty, t__result *result);
};


// PaymentServiceImpl.cpp
#include "PaymentServiceImpl.h"
#include "Autogenerated/paymentssl.nsmap"

/*virtual*/	int PaymentServiceImpl::MakePayment(int qty, t__result *result)
{
 	this->mode = this->mode | SOAP_C_UTFSTRING;
	if(qty<0) {
		this->soap_senderfault("error!", "<error xmlns=\"http://tempuri.org/\">The input parameter must be positive</error>");
		return SOAP_CLI_FAULT;
	}
	if(qty>100){
		result->errCode = STATE_FAIL;
		char err[] = "error (2)!";
		int len=strlen(err);
		result->message = reinterpret_cast<char*>(soap_malloc(this, len+1));
		strcpy_s(result->message, len+1, err);
		result->message[len] = '\0';
	}

	return SOAP_OK;
}


Пришло время запустить сервис и проверить выдает ли он что-либо или вообще мы проделали весь путь зря.
Впишем main:
#include "PaymentServiceImpl.h"

int _tmain(int argc, _TCHAR* argv[])
{
	const int SERVICE_PORT = 9999;

	std::auto_ptr<PaymentServiceImpl> srv (new PaymentServiceImpl());
	if (srv->run(SERVICE_PORT))
	{ 
		srv->soap_stream_fault(std::cerr);
		exit(-1);
	} 

	return 0;
}


Также в целях отладки укажем препроцессору директиву DEBUG (студия ставит _DEBUG).

Пришло время проверки, для которой нам понадобится клиент

Делаем клиент


Для демонстрации мы быстренько набросаем C# клиента, это очень просто (хотя на c++ не сложнее, я покажу на пример C# чтобы показать коммуникацию разнородных средств по единому протоколу (для чего собственно SOAP когда-то и задумывался)).

Добавим новый C# Console Project, далее добавим Service Refence в него. В качестве адреса укажем путь к файлу paymentssl.wsdl.


В свойствах проекта не забудем указать что мы используем .Net4, а не .NET4 Client Profile фреймворк.

Вот проверочный код:
var proxy = new PaymentService.paymentsslPortTypeClient();
string msg;
var errCode = proxy.MakePayment(out msg, 10);

//
//да, у студии (а точнее утилиты svcutil) достаточно своеобразное понимание какие должны быть параметры,
//но тема «взамодействие с др. системами и gSOAP» достаточна обширна для этого введения
//


Также надо поправить сгенерированный app.config, достаточно такого:
<configuration>
  <system.serviceModel>
    <bindings>
      <customBinding>
        <binding name="paymentssl">
          <textMessageEncoding messageVersion="Soap12" />
          <httpTransport/>
        </binding>
      </customBinding>
    </bindings>
    <client>
      <endpoint address="http://localhost:9999" binding="customBinding" bindingConfiguration="paymentssl" contract="PaymentService.paymentsslPortType" name="paymentssl"/>
    </client>
  </system.serviceModel>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.0"/>
  </startup>
</configuration>

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

О чем я не рассказал


  • шифрование потока средствами SSL используя OpenSSL
  • авторизация клиентов по их сертификатам
  • многопоточность сервера (сейчас это однопоточный вариант)
  • кодировки строк


Если будет интересно, то быть может я опишу это отдельным постом (как появится время).
Спасибо всем и удачи с SOAP.
Tags:
Hubs:
+15
Comments 17
Comments Comments 17

Articles