用 Prolog 提供 HTTP 服务

序言

书接上回,在上一篇文章中,我们通过尝试用 Prolog 编写 Hello World 程序,了解了 Prolog 程序的基本结构和运行方式。本篇将会在hello_world.pl的基础上,实现一个简单的 HTTP 服务器。

启动 HTTP 服务器

利用 SWI-Prolog 内置的库,很容易就可以启动一个 HTTP 服务器。只需要调用函数http_server即可

:- initialization(main, main).

:- use_module(library(http/http_server), [http_server/1]).

main(_) :-
    http_server([port(8082)]),
    sleep(100000000).

将上述代码保存到文件http_server.pl中并运行便可以启动 HTTP 服务器

启动HTTP服务器 启动HTTP服务器

由于在源文件中没有定义任何接口,因此任何请求都只会返回 404 的响应

404的响应 404的响应

使用模块

在上文中出现的use_module也是一种命令(directive),它用于从模块中导入函数。例如,:- use_module(library(http/http_server), [http_server/1]).的意思,就是从模块http/http_server中导入一个参数个数为 1 的函数http_server到当前模块中,如此一来,就可以直接调用它。

提供接口

正如前文所述,由于当前应用没有注册任何的路由规则,因此无论如何请求都只会返回 404 的响应。接下来我将给这个 HTTP 服务器添加一个非常简单的接口,来固定返回文本Hello, world!。要注册一个接口,需要用到来自于模块http/http_dispatch的函数http_handler,从它的文档可以看到,它需要三个参数:

  • 接口路径Path,如用一个原子来表示的路径/api/shorten
  • 负责实现接口业务逻辑的函数对象Closure
  • 描述接口属性的列表Options,如描述该接口的请求方法。

这样一个固定返回 Hello World 字符串的接口可能是下面这样子的

:- initialization(main, main).

:- use_module(library(http/http_dispatch), [http_handler/3]).
:- use_module(library(http/http_server), [http_server/1]).

hello(_Request) :-
    format('Content-Type: text/html~n~n'),
    format('Hello, world!').

main(_) :-
    http_handler('/api/shorten', hello, [methods([get])]),
    http_server([port(8082)]),
    sleep(100000000).

用老办法启动服务后的测试结果如下

HelloWorld的响应 HelloWorld的响应

接口路径

作为http_handler的第一个参数,'/api/shorten'看起来很像是其它主流语言中为人熟知的字符串,然而在 Prolog 中,它是一个原子(atom)。尽管引号内的内容看起来一样,但原子与字符串并不相等,如下图所示

原子与字符串不相等 原子与字符串不相等

接口输出

从 SWI-Prolog 的文档可以知道,担任http_handler第二个参数的Closure必须负责输出至少Content-Type和数据体部分。此外,尽管hello的写法看起来是将响应的内容输出到了标准输出(颇有上古时期的 CGI 脚本的风格),但其实format输出的目的地已经被框架所替换,可以换成下面的代码来更好地感受这一点

:- initialization(main, main).

:- use_module(library(http/http_dispatch), [http_handler/3]).
:- use_module(library(http/http_server), [http_server/1]).

hello(_Request) :-
    current_output(Stream),
    format(Stream, 'Content-Type: text/html~n~n', []),
    format(Stream, 'Hello, world!', []).

main(_) :-
    http_handler('/api/shorten', hello, [methods([get])]),
    http_server([port(8082)]),
    sleep(100000000).

而请求结果则是相同的。