提供短链访问能力
序言
在上一篇文章中,HTTP 服务已经可以将入参中的原始 URL 存储起来,并将其与一个唯一的 ID 绑定在一起。在本文中,将会新增一个接口,实现将数值 ID 转换为原始 URL,并通过重定向返回给调用者。
重定向到原始 URL
现有的接口/api/shorten
的返回值并不利于使用,为此接下来会修改为返回Content-Type: application/json
类型的数据。此外,还会有一个新接口/api/lengthen
,它接受一个参数id
,从内存中找出与这个 ID 绑定的原始 URL,并通过 302 的状态码返回。
:- dynamic url_to_id/2. % 声明一个“动态谓词”。
:- initialization(main, main).
:- use_module(library(http/http_dispatch), [http_handler/3, http_redirect/3]).
:- use_module(library(http/http_parameters), [http_parameters/2]).
:- use_module(library(http/http_server), [http_server/1]).
lengthen_url(Request) :-
http_parameters(Request, [
id(Id, [integer])
]),
url_to_id(Url, Id),
http_redirect(moved, Url, Request).
shorten_url(Request) :-
http_parameters(Request, [
url(Url, [])
]),
% 姑且用当前时间戳来作为 ID。
get_time(TimeStamp),
Id is truncate(TimeStamp),
asserta(url_to_id(Url, Id)), % 定义这个动态谓词的一组值。
format('Content-Type: application/json~n~n'),
format('{"id": ~d}', [Id]).
main(_) :-
http_handler('/api/lengthen', lengthen_url, [methods([get])]),
http_handler('/api/shorten', shorten_url, [methods([post])]),
http_server([port(8082)]),
sleep(100000000).
启动 HTTP 服务后,先请求接口/api/shorten
,获得一个短链的 ID,再访问新接口即可重定向到原始 URL。
ID 无效怎么办
如果我捏造一个不存在的 ID 来输入,那么url_to_id(Url, Id)
就无法成立,会导致接口触发异常、返回 500 的状态码。为了处理这种情况,可以定义一个新的谓词reply_by_id
,它接受一个参数Id
,并且只有在下列两种情况下才成立:
- 入参
Id
可以使url_to_id(Url, Id)
成立,此时返回 301 的状态码。或者; - 直接返回 400 的状态码。
改写后的代码如下
:- dynamic url_to_id/2. % 声明一个“动态谓词”。
:- initialization(main, main).
:- use_module(library(http/http_dispatch), [http_404/2, http_handler/3, http_redirect/3]).
:- use_module(library(http/http_parameters), [http_parameters/2]).
:- use_module(library(http/http_server), [http_server/1]).
% 辅助函数。
reply_by_id(Id, Request) :-
url_to_id(Url, Id),
http_redirect(moved, Url, Request).
reply_by_id(_, Request) :-
http_404([], Request).
lengthen_url(Request) :-
http_parameters(Request, [
id(Id, [integer])
]),
reply_by_id(Id, Request).
shorten_url(Request) :-
http_parameters(Request, [
url(Url, [])
]),
% 姑且用当前时间戳来作为 ID。
get_time(TimeStamp),
Id is truncate(TimeStamp),
asserta(url_to_id(Url, Id)), % 定义这个动态谓词的一组值。
format('Content-Type: application/json~n~n'),
format('{"id": ~d}', [Id]).
main(_) :-
http_handler('/api/lengthen', lengthen_url, [methods([get])]),
http_handler('/api/shorten', shorten_url, [methods([post])]),
http_server([port(8082)]),
sleep(100000000).
这下子如果输入一个未曾返回过的 ID,则接口将返回 404 状态码。
IF 语句
按照上文的谓词reply_by_id
的定义,Prolog 程序在运行时首先会尝试该谓词的第一种情况,即
reply_by_id(Id, Request) :-
url_to_id(Url, Id),
http_redirect(moved, Url, Request).
如果输入的Id
未曾生成过,那么查询url_to_id(Url, Id)
无法成立,整个查询reply_by_id(Id, Request)
的结果也会为假。接着,Prolog 会尝试谓词的第二个分支,即
reply_by_id(_, Request) :-
http_404([], Request).
显然,这个分支总是可以成立,最终调用方收到了 404 的响应。这种非此即彼的场景,在传统语言中可以用if-else
之类的语法结构来表达,在 Prolog 中其实也有等价的东西,就是谓词->/2
。借助它,谓词reply_by_id
可以改写为以下形式
reply_by_id(Id, Request) :-
( url_to_id(Url, Id)
-> http_redirect(moved, Url, Request)
; http_404([], Request)).
它的效果是相同的。