Сегодня? Мы делаем SOAP*

в 16:53, , рубрики: erlang, Erlang/OTP, soap, метки: , ,

Представьте, есть две комманды разработчиков: одни пишут на C#, другие — на Эрланге.
Первые хотят использовать функции из продуктов вторых.
Отлично, надо договориться об API, реализовать его и выдать документацию Шарповым ребятам.
На основе чего будет реализован этот API?
Подумав, пришли к выбору между SOAP и REST.

Поговорим о поддержке данных технологий нашими платформами.

C REST всё понятно: нужны инструменты для работы с HTTP и XML/JSON.

А как обстоят дела с SOAP?

На стороне .Net присутствует хорошая поддержка и client-side, и server-side: System.Web.Services.dll.

Всё несколько сложнее с server-side Эрланга…

Как это обычно делают?

Открываем мануал по YAWS и видим, что сначала надо написать WSDL. Потом нужно скормить этот WSDL модулю yaws_soap_lib: он сделает hrl-файл. Потом надо написать хендлер, который будет обрабатывать примитивы, описанные в этом hrl-файле.

На мой взгляд, это несколько неудобно: сначала WSDL, потом — код. Написать WSDL с первого раза сможет далеко не каждый. А при внесении изменений в API нужно подгонять код сервиса под получившиеся автогенерированные структуры данных.
Да и вообще, как-то всё должно быть проще, не так ли?
Хочется, чтобы, как в System.Web.Services.dll: пишешь сервис, помечаешь, что должно быть доступно извне — получаешь WSDL.

Как бы мне хотелось это делать?

Во-первых, встраиваемый cowboy лично мне нравится гораздо больше, чем stand-alone Yaws, который с основным приложением надо будет как-то связывать.

Во-вторых, хочется, всё таки, просто писать код, а WSDL пусть генерирует кто-нибудь другой.

На пример, вот так (auth_ws.erl):

-module(auth_ws).
-compile({parse_transform, durden_pt}).
-include_lib("durden/include/predefined_types.hrl").

-soap_target_ns("http://rgafiyatullin.github.com/habr/auth-ws/v0").
-soap_service_name("AuthAPI").
-soap_actions([
		'Authenticate'/2
	]).

-record('User', {
	'Valid' :: boolean(),

	'ID' :: uuid(),
	'DisplayName' :: string()
	}).

user_new() ->
	#'User' {
		'Valid' = false,
		'ID' = "00000000-0000-0000-0000-000000000000",
		'DisplayName' = ""
	}.

-spec 'Authenticate'( Name :: string(), PW :: string() ) -> #'User'{}.
'Authenticate'(Name, PW) ->
	case {Name, PW} of
		{"RG", "pw#here"} ->
			( user_new() ) #'User' {
				'Valid' = true,
				'ID' = "f47ac10b-58cc-4372-a567-0e02b2c3d479",
				'DisplayName' = "Roma"
			};
		_ ->
			user_new()
	end.

Что происходит:

  • Написали модуль
  • Пометили функции, которые открываем для внешнего мира
  • Позволили трансформации 'durden_pt' проанализировать типы публичных вызовов и составить заготовку для генерации WSDL
Подключаем к cowboy

-module(cowboy_dispatch).
-compile(export_all).

'Test'() -> ok.

start_cowboy() ->
	Dispatch = [
		{'_', [
			{[<<"habr">>, <<"auth.asmx">>, '...'], auth_ws, [ "http://localhost:8080/habr/auth.asmx" ] }
		]}
	],
	cowboy:start_listener(my_http_listener, 100,
		cowboy_tcp_transport, [{port, 8080}],
		cowboy_http_protocol, [{dispatch, Dispatch}]
	).
Используем сервис
using System;
using Auth.Client;

namespace RG.Habr.Durden {
	public class ClientProgram {
		private static string VarDump( User u ) {
			if (u.Valid) {
				return String.Format(
					"{2}{{ ID : '{0}', DisplayName : '{1}' }}",
					u.ID, u.DisplayName, u.GetType()
				);
			}
			else {
				return String.Format("{0}{{ Unauthorized }}", u.GetType());
			}
		}

		public static int Main(string[] args) {
			var authAPI = new AuthAPI();
			var authedUser = authAPI.Authenticate("RG", "pw#here");
			var unAuthedUser = authAPI.Authenticate("Stranger", "bad pw");
			Console.WriteLine(VarDump(authedUser));
			Console.WriteLine(VarDump(unAuthedUser));
			return 0;
		}
	}
}
rg@aluminumcan [cs-example] * master > make && mono bin/auth_client.exe
wsdl 
		-n:Auth.Client 
		-out:src/Auth.Client.cs 
		'http://localhost:8080/habr/auth.asmx?wsdl=0'
Web Services Description Language Utility
Mono Framework v4.0.30319.1
Writing file 'src/Auth.Client.cs'
dmcs 
		-r:System.dll 
		-r:System.Web.Services.dll 
		-target:exe -out:bin/auth_client.exe 
		src/Auth.Client.cs 
		src/ClientMain.cs
Auth.Client.User{ ID : 'f47ac10b-58cc-4372-a567-0e02b2c3d479', DisplayName : 'Roma' }
Auth.Client.User{ Unauthorized }

Заключение

Взять durden можно у меня на GitHub.
К сожалению, на данный момент (2012-09-05) есть некоторые ограничения:

Решение упомянутых проблем запланировано в том порядке, в котором они перечислены.

Как и раньше, конструктивной критике и, тем паче, коллаборации — Ура!

* Tyler: «Tonight? We make soap.»

Автор: RomkoGoofique

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js