REST сервер на Прологе, как это выглядит?
В качестве небольшого экскурса для непосвященных в логическое программирование
в этом тексте будет проведен сеанс магии с разоблачением приведен подход
к созданию REST-сервера и замерены его параметры.
В чем подвох? А все просто — будем делать на Прологе (в реализации SWI-prolog)…
Итак, что нужно. Во-первых скачать (если еще нет) и поставить собственно swi-prolog
с его сайта www.swi-prolog.org. Проще создать небольшой файл типа rest.pl с начальным содержимым типа
сервер.
и дальше загрузить его и редактировать уже средствами самой пролог-системы, вот так
(примеры приведены для консоли linux, но не сильно отличаются от windows):
$echo "сервер." > rest.pl
$swipl
...
?- [rest].
true.
?- edit(сервер).
После этого запустится emacs-подобный редактор и можно все набирать в нем.
Итак, в начале все же укажем что мы будем пользоваться библиотеками и напишем что значит
запуск сервера — в прологе символы :- можно читать как «это есть»:
:- use_module(library(http/http_server)).
:- use_module(library(http/http_json)).
:- http_handler(/,сервис(M), [method(M),methods([get,post]),time_limit(10000)]).
сервер :- http_server(http_dispatch, [port(8080)]).
Как видно, мы планируем что сервис будет отвечать на методы get и post.
Модельная задача
Теперь собственно, что мы будем делать. По запросу get будем отвечать страницей с полем ввода числа, при его вводе будем запрашивать метод post и вычислять это число Фиббоначии.
То есть:
сервис(get,_) :- форма(X),ответить(X).
сервис(post,Запрос) :-
http_parameters(Запрос,[val(Число,[integer])]),
обсчитать(Число).
Ну вот и все! Мы уже на самом деле написали наш сервер… Пролог же декларативный язык — вот мы и декларировали варианты обработки. Кстати, язык можно использовать любой — я решил местами писать на русском.
Но погодите, что же он делает? Ведь 'форма', 'ответить' и 'обсчитать' — это наши предикаты и они не определены пока. Давайте это исправим:
ответить(Вставка):-
format('Content-type: text/html~n~n
Вычисление чисел Фиббоначчи
~w
~n', [Вставка]).
форма('').
Ну и приведем два варианта для обсчитать — пусть у нас числа Фиббоначчи только положительные будут:
обсчитать(Число):-
Число > 0,
фиббоначи(0,1,1,Число,Ответ),форма(X),
format(atom(Строка),'~w число Фиббоначи равно ~w
~w',[Число,Ответ,X]),
ответить(Строка).
обсчитать(Число):-
форма(X),
format(atom(Строка),'заданное число ~w меньше 0
~w',[Число,X]),
ответить(Строка).
Ну и осталось определить собственно что такое число Фиббоначи:
фиббоначи(_F, F1, N, N, F1) :- !.
фиббоначи(F0, F1, I, N, F) :-
F2 is F0+F1,
I2 is I + 1,
!,фиббоначи(F1, F2, I2, N, F).
Это определение, конечно, не по фен-шую не совсем привычное, зато считается чуть ли не быстрее чем если бы мы писали на С…
Итак, будет ли это работать? Проверяем:
?- сервер.
% Started server at http://localhost:8080/
true.
Так, сервер вроде стартовал. Кстати, он многопоточный! Для проверки нужно открыть адрес
127.0.0.1:8080/ и ввести какое-нибудь число — например, 1000:
Вычисление чисел Фиббоначчи
1000 число Фиббоначи равно
4346655768693745643568852767504062580
2564660517371780402481729089536555417
9490518904038798400792551692959225930
8032263477520968962323987332247116164
2996440906533187938298969649928516003
704476137795166849228875
Ну что же, оно работает!
Небольшое исследование производительности сервера — проверим метод GET (POST очевидно сильно зависит от заданного числа, так 10000000 число считается, конечно, но несколько секунд…)
$ ab -k -c 4 -n 4000 http://127.0.0.1:8080/
...
Concurrency Level: 4
Time taken for tests: 0.283 seconds
Complete requests: 4000
Failed requests: 0
Keep-Alive requests: 4000
Total transferred: 1108000 bytes
HTML transferred: 544000 bytes
Requests per second: 14140.57 [#/sec] (mean)
Time per request: 0.283 [ms] (mean)
Time per request: 0.071 [ms] (mean, across all concurrent requests)
Transfer rate: 3825.14 [Kbytes/sec] received
...
Что же 14140 запросов в секунду при 10 потоках — это очень неплохо для обычного компа!
И да, пролог имеет такую возможность — логический вид. Так что если вы поменяете что-то в коде, то вам достаточно в консоли набрать
?- make.
И все ваши изменения будут работать в новых запросах — ничего не нужно перегружать, останавливать и т.д.
Надеюсь, вам было интересно посмотреть, как создают rest-сервера на таком простом примере. Конечно, можно описывать rest интерфейс статически, как и сделано в примере, можно вводить всякие переменные, использовать часть пути URL как переменные — ну в общем все как обычно.
Можно делать это динамически, менять логику работы программы создавая, удаляя и модифицируя предикаты — этакий самомодифицирующийся сервер, чья работа зависит от истории. Можно подключать базы данных. В общем, все как обычно, только работать просто и приятно.