1:- encoding(utf8).
2:- module(
3 rest_server,
4 [
5 conflicting_http_parameters/1, 6 data_uri/2, 7 http_absolute_location/2, 8 http_current_location/1, 9 http_parameter_alternatives/2, 10 http_parameter_conflict/2, 11 http_is_get/1, 12 http_link_to_id/2, 13 http_media_types/2, 14 http_reply_json/1, 15 rest_media_type/2, 16 rest_method/2, 17 rest_method/4, 18 rest_options/1, 19 rest_parameters/2 20 ]
21).
27:- use_module(library(apply)). 28:- use_module(library(error)). 29:- use_module(library(http/http_dispatch)). 30:- use_module(library(http/http_json)). 31:- use_module(library(http/http_parameters)). 32:- use_module(library(http/http_path)). 33:- use_module(library(http/http_server_files)). 34:- use_module(library(http/http_wrapper)). 35:- use_module(library(http/json)). 36:- use_module(library(lists)). 37:- use_module(library(ordsets)). 38:- use_module(library(pairs)). 39:- use_module(library(settings)). 40
41:- use_module(library(pair_ext)). 42:- use_module(library(resource)). 43:- use_module(library(uri_ext)). 44
45:- dynamic
46 http:location/3. 47
48:- multifile
49 http:location/3. 50
51http:location(css, root(css), []).
52http:location(fonts, root(fonts), []).
53http:location(html, root(html), []).
54http:location(img, root(img), []).
55http:location(js, root(js), []).
56http:location(md, root(md), []).
57http:location(pdf, root(pdf), []).
58http:location(ttl, root(ttl), []).
59http:location(yaml, root(yaml), []).
60
61:- http_handler(/, http_not_found_handler,
62 [methods([get,head,options]),prefix,priority(-1)]). 63:- http_handler(css(.), serve_files_in_directory(css), [prefix]). 64:- http_handler(fonts(.), serve_files_in_directory(fonts), [prefix]). 65:- http_handler(html(.), serve_files_in_directory(html), [prefix]). 66:- http_handler(img(.), serve_files_in_directory(img), [prefix]). 67:- http_handler(js(.), serve_files_in_directory(js), [prefix]). 68:- http_handler(md(.), serve_files_in_directory(md), [prefix]). 69:- http_handler(pdf(.), serve_files_in_directory(pdf), [prefix]). 70:- http_handler(ttl(.), serve_files_in_directory(ttl), [prefix]). 71:- http_handler(yaml(.), serve_files_in_directory(yaml), [prefix]). 72
73:- meta_predicate
74 rest_media_type(+, 1),
75 rest_method(+, 2),
76 rest_method(+, +, 2, 3). 77
78:- multifile
79 error:has_type/2,
80 html:page_exception/2,
81 http:convert_parameter/3,
82 http:error_status_message_hook/3,
83 http:not_found_media_type/2,
84 http:param/2. 85
86error:has_type(or(Types), Term) :-
87 member(Type, Types),
88 error:has_type(Type, Term), !.
89
90http:convert_parameter(positive_integer, Atom, Integer) :-
91 ( atom_number(Atom, Integer)
92 -> must_be(positive_integer, Integer)
93 ; instantiation_error(positive_integer)
94 ).
95
97http:not_found_media_type(Uri, media(application/json,_)) :-
98 format(string(Msg), "ð¿ Path â~aâ does not exist on this server.", [Uri]),
99 http_reply_json(_{message: Msg, status: 404}).
100
101:- setting(
102 http:products,
103 list(pair(string)),
104 [],
105 "The products that implement the server that creates HTTP replies."
106 ).
113conflicting_http_parameters(Keys) :-
114 throw(error(conflicting_http_parameters(Keys))).
120data_uri(Segments, Uri) :-
121 setting(http:public_scheme, Scheme),
122 setting(http:public_host, Host),
123 setting(http:public_port, Port),
124 uri_comps(Uri, uri(Scheme,auth(_User,_Password,Host,Port),Segments,_,_)).
130http_absolute_location(Spec, Path) :-
131 http_absolute_location(Spec, Path, []).
137http_current_location(Uri) :-
138 http_current_request(Request),
139 memberchk(path(Uri), Request).
149http_is_get(get).
150http_is_get(head).
156http_link_to_id(HandleId, Local) :-
157 http_link_to_id(HandleId, [], Local).
164http_media_types(Request, MediaTypes) :-
165 memberchk(accept(MediaTypes0), Request),
166 clean_media_types(MediaTypes0, MediaTypes), !.
168http_media_types(_, [_]).
169
170clean_media_types(L1, L2) :-
171 maplist(clean_media_type, L1, Pairs),
172 sort(1, @>=, Pairs, Sorted),
173 pairs_values(Sorted, L2).
174
175clean_media_type(
176 media(Super/Sub,Params1,QValue,_),
177 QValue-media(Super/Sub,Params2)
178) :-
179 maplist(clean_parameter, Params1, Params2).
180
181clean_parameter(charset=Value1, Value2) :- !,
182 clean_charset(Value1, Value2).
183clean_parameter(Param, Param).
184
185clean_charset('UTF-8', utf8) :- !.
186clean_charset(Value, Value).
194http_not_found_handler(Request) :-
195 rest_method(Request, http_not_found_method(Request)).
196
198http_not_found_method(Request, Method, MediaTypes) :-
199 http_is_get(Method),
200 memberchk(request_uri(Uri), Request),
201 rest_media_type(MediaTypes, http:not_found_media_type(Uri)).
207http_parameter_alternatives(Params, Value) :-
208 convlist(http_parameter_value, Params, Pairs),
209 pairs_keys_values(Pairs, Keys, Values1),
210 ( list_to_ord_set(Values1, Values2),
211 (Values2 = [Value] ; Values2 = [])
212 -> true
213 ; conflicting_http_parameters(Keys)
214 ).
215
216http_parameter_value(Param, Key-Value) :-
217 ground(Param),
218 Param =.. [Key,Value].
224http_parameter_conflict(Param1, Param2) :-
225 ground([Param1,Param2]), !,
226 Param1 =.. [Key1,_],
227 Param2 =.. [Key2,_],
228 throw(
229 error(
230 http_error(conflicting_parameters([Key1,Key2])),
231 http_parameter_conflict/2
232 )
233 ).
234http_parameter_conflict(_, _).
240http_reply_json(Json) :-
241 format("Content-Type: application/json; charset=UTF-8\n\n"),
242 json_write_dict(current_output, Json).
248rest_exception(_, error(http_error(media_types_not_supported,MediaTypes),_Context)) :- !,
249 media_types_not_supported_(MediaTypes).
250rest_exception(MediaTypes, E) :-
251 error_status_message(E, Status, Msg),
252 member(MediaType, MediaTypes),
253 rest_exception_media_type(MediaType, Status, Msg), !.
254rest_exception(MediaTypes, _) :-
255 media_types_not_supported_(MediaTypes).
256
257media_types_not_supported_(MediaTypes) :-
258 format(
259 string(Msg),
260 "ð¿ None of the specified Media Types is supported: â~wâ.",
261 MediaTypes
262 ),
263 rest_exception_media_type(media(application/json,_), 406, Msg).
264
266rest_exception_media_type(media(application/json,_), Status, Msg) :-
267 reply_json_dict(_{message: Msg, status: Status}, [status(Status)]).
269rest_exception_media_type(media(text/html,_), Status, Msg) :-
270 html:page_exception(Status, Msg).
271
272error_status_message(E, Status, Msg) :-
273 http:error_status_message_hook(E, Status, Msg), !.
274error_status_message(error(existence_error(Type,Term),_), 404, Msg) :- !,
275 format(
276 string(Msg),
277 "ð¿ Your request is incorrect! There is no resource denoted by term â~wâ of type â~wâ.",
278 [Term,Type]
279 ).
280error_status_message(error(http_error(conflicting_http_parameters(Keys)),_), 400, Msg) :- !,
281 atomics_to_string(Keys, ", ", KeysLabel),
282 format(
283 string(Msg),
284 "ð¿ Your request is incorrect! You have specified the following conflicting HTTP parameters: â[~s]â.",
285 [KeysLabel]
286 ).
287error_status_message(error(http_error(method_not_allowed,Method)), 405, Msg) :- !,
288 format(
289 string(Msg),
290 "ð¿ HTTP method â~aâ is not allowed for this path.",
291 [Method]
292 ).
293error_status_message(error(syntax_error(grammar(Language,Source)),_), 400, Msg) :- !,
294 format(
295 string(Msg),
296 "ð¿ Could not parse the following according to the ~a grammar: â~aâ",
297 [Language,Source]
298 ).
299error_status_message(error(syntax_error(grammar(Language,Expr,Source)),_), 400, Msg) :- !,
300 format(
301 string(Msg),
302 "ð¿ Could not parse the following as a ~a expression in the ~a grammar: â~aâ",
303 [Expr,Language,Source]
304 ).
305error_status_message(error(type_error(Type,Value),context(_,http_parameter(Key))), 400, Msg) :- !,
306 format(
307 string(Msg),
308 "ð¿ Your request is incorrect! You have specified the value â~wâ for HTTP parameter â~aâ. However, values for this parameter must be of type â~wâ.",
309 [Value,Key,Type]
310 ).
311error_status_message(E, 500, Msg) :-
312 format(string(Msg), "ð¿ The following error occurred on the server: â~wâ.", [E]).
318rest_media_type(MediaTypes, Goal_1) :-
319 member(MediaType, MediaTypes),
320 call(Goal_1, MediaType), !.
321rest_media_type(MediaTypes, _) :-
322 rest_exception(
323 MediaTypes,
324 error(http_error(media_types_not_supported,MediaTypes),http_server)
325 ).
332rest_method(Request, Plural_2) :-
333 rest_method(Request, _, Plural_2, _:_).
334
335
336rest_method(Request, HandleId, Mod:Plural_2, Mod:Singular_3) :-
337 memberchk(method(Method), Request),
338 memberchk(path(Path), Request),
339 Mod:http_current_handler(Path, _, Options),
340 _{methods: Methods} :< Options,
341 ( Method == options
342 -> rest_options(Methods)
343 ; 344 \+ memberchk(Method, Methods)
345 -> http_media_types(Request, MediaTypes),
346 rest_exception(MediaTypes, error(http_error(method_not_allowed,Method),_))
347 ; 348 memberchk(request_uri(Uri), Request),
349 350 351 uri_comps(Uri, uri(Scheme,Authority,Segments,_,_)),
352 uri_comps(HandleUri, uri(Scheme,Authority,Segments,_,_)),
353 format("Strict-Transport-Security: max-age=31536000; includeSubDomains\n"),
354 http_media_types(Request, MediaTypes),
355 catch(
356 ( (var(HandleId) -> true ; http_link_to_id(HandleId, HandleUri))
357 -> call(Mod:Plural_2, Method, MediaTypes)
358 ; data_uri(Segments, Resource),
359 call(Mod:Singular_3, Resource, Method, MediaTypes)
360 ),
361 Error,
362 rest_exception(MediaTypes, Error)
363 )
364 ).
370rest_options(Methods) :-
371 format("Status: 204\n"),
372 write_allow_header(Methods),
373 write_server_header,
374 nl.
380rest_parameters(Request, Params) :-
381 http_parameters(Request, Params, [attribute_declarations(http:param)]).
392write_allow_header([H|T]) :-
393 format("Allow: ~a", [H]),
394 maplist(write_sep_allow, T),
395 nl.
396
397write_sep_allow(X) :-
398 format(", ~a", [X]).
:-
411 setting(http:products, Products),
412 write_products(Products).
413
414write_products([H|T]) :-
415 format("Server: "),
416 write_product(H),
417 maplist(write_sep_product, T),
418 nl.
419
420write_product(X-Y) :- !,
421 format("~a/~a", [X,Y]).
422write_product(X) :- !,
423 format("~a", [X]).
424
425write_sep_product(X) :-
426 format(" "),
427 write_product(X)
REST server support
*/