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
*/