用 Prolog 提供短链服务

序言

前一篇文章中,HTTP 服务提供了唯一一个接口/api/shorten,但它并非真的会生成短链,而是返回固定的字符串。本文将会对这个接口进行升级,使其可以接收原始 URL 作为参数,并返回一个与其对应的短链的 ID。

提取参数

要想生成短链,首先必须能够接收到用户输入的原始 URL,而这可以使用函数http_parameters/2实现。假设现在要给接口/api/shorten支持一个参数url,要求其以表单的形式输入,并将该输入回显回去,那么代码可以是下面这样的

:- initialization(main, main).

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

echo(Request) :-
    http_parameters(Request, [
        url(Url, [])
    ]),
    format('Content-Type: text/html~n~n'),
    format('url is ~s', [Url]).

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

可以使用下列命令来测试它

curl -X POST -d 'url=https://example.com' -v 'http://localhost:8082/api/shorten'

接口回显的效果 接口回显的效果

存储 URL

若是在主流的编程语言中,想要在程序的运行过程暂时维护一些键值关系,通常会使用哈希表,如 JavaScript 中的对象,或 Python 中的字典。在 Prolog 中,有着与之类似的东西,那边是动态谓词。

:- dynamic url_to_id/2.  % 声明一个“动态谓词”。

:- initialization(main, main).

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

echo(Request) :-
    http_parameters(Request, [
        url(Url, [])
    ]),
    % 姑且用当前时间戳来作为 ID。
    get_time(TimeStamp),
    Id is truncate(TimeStamp),
    asserta(url_to_id(Url, Id)),  % 定义这个动态谓词的一组值。
    format('Content-Type: text/html~n~n'),
    format('bind url ~s to id ~d', [Url, Id]).

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

这里出现了一个新的指令dynamic,它的作用是声明一个谓词是动态的。这样一来,在程序运行的过程中,就可以使用函数asserta来定义新的“事实”——就像是在往一个关系型数据库的表中不停地插入新的行一样。

为了可以获取不重复的数值来作为每一个短链的 ID,这里采用的策略是先用函数get_time将当前时间存储到变量TimeStamp中,然后将其截断为整数来作为 ID 使用。

参数的方向

如果你想要知道为什么get_time(TimeStamp)这样的代码的作用是将当前时间戳存储到变量TimeStamp中,那么你需要先理解 Prolog 代码的运行方式。通过 REPL 可以更容易地理解,例如,在swipl中输入下列代码

get_time(TimeStamp).

可以理解为你是在想 Prolog 发问,询问它get_time(TimeStamp)这个“查询”是否成立,以及如果成立的话,变量TimeStamp的值是什么。因此,在此时此刻,Prolog 会告诉我们这个查询成立,并且是在当变量TimeStamp的值为1740758262.052527的时候成立。

向Prolog询问当前时间 向Prolog询问当前时间

你也可以输入一个具体的数值来询问 Prolog,但我想如果不是有预知能力的话,应当不会有人可以让带着具体数值的get_time成立

不成立的get_time查询 不成立的get_time查询

不仅是本文中才出现的get_time,在上一篇文章中出现的函数current_output同样也可以为参数“绑定”一个值。这样的函数的文档中,通常会为其参数加上一个减号(-)的前缀

文档中的减号 文档中的减号

函数与谓词

尽管到目前为止,我都是用“函数”来称呼mainwritewriteln等标识符的,但其实在 Prolog 的术语中,它们叫做“谓词”。谓词会返回真和假,表示一组参数关系成立或不成立。例如,length/2就是一个用来判断列表长度是否为给定的数值的谓词,可以在swipl的 REPL 中像下面这样使用

length谓词的用法 length谓词的用法

而前文中出现的truncate/1则不是一个谓词,它是一个函数。函数无法作为 Prolog 实现的“目标”(Goal),因此不能作为查询在 REPL 中提交

truncate的查询 truncate的查询

要获取“函数”的返回值,需要使用is/2,它的左边是变量(如前文中的Id),右边则是一个函数调用的表达式(如前文中的truncate(TimeStamp))。

用is获取函数返回值 用is获取函数返回值