36
37:- module(http_json,
38 [ reply_json/1, 39 reply_json/2, 40 reply_json_dict/1, 41 reply_json_dict/2, 42 http_read_json/2, 43 http_read_json/3, 44 http_read_json_dict/2, 45 http_read_json_dict/3, 46
47 is_json_content_type/1 48 ]). 49:- use_module(library(http/http_client)). 50:- use_module(library(http/http_header)). 51:- use_module(library(http/http_stream)). 52:- use_module(library(json)). 53:- use_module(library(option)). 54:- use_module(library(error)). 55:- use_module(library(lists)). 56:- use_module(library(memfile)). 57
58:- multifile
59 http_client:http_convert_data/4,
60 http:post_data_hook/3,
61 json_type/1. 62
63:- public
64 json_type/1. 65
66:- predicate_options(http_read_json/3, 3,
67 [ content_type(any),
68 false(ground),
69 null(ground),
70 true(ground),
71 value_string_as(oneof([atom, string])),
72 json_object(oneof([term,dict]))
73 ]). 74:- predicate_options(reply_json/2, 2,
75 [ content_type(any),
76 status(integer),
77 json_object(oneof([term,dict])),
78 pass_to(json:json_write/3, 3)
79 ]). 80
81
150
159
160http_client:http_convert_data(In, Fields, Data, Options) :-
161 memberchk(content_type(Type), Fields),
162 is_json_content_type(Type),
163 !,
164 ( memberchk(content_length(Bytes), Fields)
165 -> setup_call_cleanup(
166 ( stream_range_open(In, Range, [size(Bytes)]),
167 set_stream(Range, encoding(utf8))
168 ),
169 json_read_to(Range, Data, Options),
170 close(Range))
171 ; set_stream(In, encoding(utf8)),
172 json_read_to(In, Data, Options)
173 ).
174
175
180
181is_json_content_type(String) :-
182 http_parse_header_value(content_type, String,
183 media(Type, _Attributes)),
184 json_type(Type),
185 !.
186
187json_read_to(In, Data, Options) :-
188 memberchk(json_object(dict), Options),
189 !,
190 json_read_dict(In, Data, Options).
191json_read_to(In, Data, Options) :-
192 json_read(In, Data, Options).
193
202
203json_type(application/jsonrequest).
204json_type(application/json).
205
206
225
226http:post_data_hook(json(Dict), Out, HdrExtra) :-
227 is_dict(Dict),
228 !,
229 http:post_data_hook(json(Dict, [json_object(dict)]),
230 Out, HdrExtra).
231http:post_data_hook(json(Term), Out, HdrExtra) :-
232 http:post_data_hook(json(Term, []), Out, HdrExtra).
233http:post_data_hook(json(Term, Options), Out, HdrExtra) :-
234 option(content_type(Type), HdrExtra, 'application/json'),
235 setup_call_cleanup(
236 ( new_memory_file(MemFile),
237 open_memory_file(MemFile, write, Handle)
238 ),
239 ( format(Handle, 'Content-type: ~w~n~n', [Type]),
240 json_write_to(Handle, Term, Options)
241 ),
242 close(Handle)),
243 setup_call_cleanup(
244 open_memory_file(MemFile, read, RdHandle,
245 [ free_on_close(true)
246 ]),
247 http_post_data(cgi_stream(RdHandle), Out, HdrExtra),
248 close(RdHandle)).
249
250json_write_to(Out, Term, Options) :-
251 memberchk(json_object(dict), Options),
252 !,
253 json_write_dict(Out, Term, Options).
254json_write_to(Out, Term, Options) :-
255 json_write(Out, Term, Options).
256
257
273
274http_read_json(Request, JSON) :-
275 http_read_json(Request, JSON, []).
276
277http_read_json(Request, JSON, Options) :-
278 select_option(content_type(Type), Options, Rest),
279 !,
280 delete(Request, content_type(_), Request2),
281 request_to_json([content_type(Type)|Request2], JSON, Rest).
282http_read_json(Request, JSON, Options) :-
283 request_to_json(Request, JSON, Options).
284
285request_to_json(Request, JSON, Options) :-
286 option(method(Method), Request),
287 option(content_type(Type), Request),
288 ( data_method(Method)
289 -> true
290 ; domain_error(method, Method)
291 ),
292 ( is_json_content_type(Type)
293 -> true
294 ; domain_error(mimetype, Type)
295 ),
296 http_read_data(Request, JSON, Options).
297
298data_method(post).
299data_method(put).
300data_method(patch).
301
307
308http_read_json_dict(Request, Dict) :-
309 http_read_json_dict(Request, Dict, []).
310
311http_read_json_dict(Request, Dict, Options) :-
312 merge_options([json_object(dict)], Options, Options1),
313 http_read_json(Request, Dict, Options1).
314
336
337reply_json(Dict) :-
338 is_dict(Dict),
339 !,
340 reply_json_dict(Dict).
341reply_json(Term) :-
342 default_json_content_type(Type),
343 format('Content-type: ~w~n~n', [Type]),
344 json_write(current_output, Term).
345
346reply_json(Dict, Options) :-
347 is_dict(Dict),
348 !,
349 reply_json_dict(Dict, Options).
350reply_json(Term, Options) :-
351 reply_json2(Term, Options).
352
361
362reply_json_dict(Dict) :-
363 default_json_content_type(Type),
364 format('Content-type: ~w~n~n', [Type]),
365 json_write_dict(current_output, Dict).
366
367reply_json_dict(Dict, Options) :-
368 merge_options([json_object(dict)], Options, Options1),
369 reply_json2(Dict, Options1).
370
371reply_json2(Term, Options) :-
372 default_json_content_type(DefType),
373 select_option(content_type(Type), Options, Rest0, DefType),
374 ( select_option(status(Code), Rest0, Rest)
375 -> format('Status: ~d~n', [Code])
376 ; Rest = Rest0
377 ),
378 format('Content-type: ~w~n~n', [Type]),
379 json_write_to(current_output, Term, Rest).
380
381default_json_content_type('application/json; charset=UTF-8').
382
383
384 387
388:- multifile
389 http:status_reply/3,
390 http:serialize_reply/2. 391
392http:serialize_reply(json(Term), body(application/json, utf8, Content)) :-
393 with_output_to(string(Content),
394 json_write_dict(current_output, Term, [])).
395
396http:status_reply(Term, json(Reply), Options) :-
397 prefer_json(Options.get(accept)),
398 json_status_reply(Term, Lines, Extra),
399 phrase(txt_message_lines(Lines), Codes),
400 string_codes(Message, Codes),
401 Reply = _{code:Options.code, message:Message}.put(Extra).
402
403txt_message_lines([]) -->
404 [].
405txt_message_lines([nl|T]) -->
406 !,
407 "\n",
408 txt_message_lines(T).
409txt_message_lines([flush]) -->
410 !.
411txt_message_lines([FmtArgs|T]) -->
412 dcg_format(FmtArgs),
413 txt_message_lines(T).
414
415dcg_format(Fmt-Args, List, Tail) :-
416 !,
417 format(codes(List,Tail), Fmt, Args).
418dcg_format(ansi(_Style, Fmt,Args), List, Tail) :-
419 !,
420 format(codes(List,Tail), Fmt, Args).
421dcg_format(url(Pos), List, Tail) :-
422 !,
423 dcg_url(Pos, List, Tail).
424dcg_format(url(_URL, Label), List, Tail) :-
425 !,
426 format(codes(List,Tail), '~w', [Label]).
427dcg_format(Fmt, List, Tail) :-
428 format(codes(List,Tail), Fmt, []).
429
430dcg_url(File:Line:Column, List, Tail) :-
431 !,
432 format(codes(List,Tail), '~w:~d:~d', [File, Line, Column]).
433dcg_url(File:Line, List, Tail) :-
434 !,
435 format(codes(List,Tail), '~w:~d', [File, Line]).
436dcg_url(File, List, Tail) :-
437 !,
438 format(codes(List,Tail), '~w', [File]).
439
440
444
445prefer_json(Accept) :-
446 memberchk(media(application/json, _, JSONP, []), Accept),
447 ( member(media(text/html, _, HTMLP, []), Accept)
448 -> JSONP > HTMLP
449 ; true
450 ).
451
453
454json_status_reply(created(Location),
455 [ 'Created: ~w'-[Location] ],
456 _{location:Location}).
457json_status_reply(moved(Location),
458 [ 'Moved to: ~w'-[Location] ],
459 _{location:Location}).
460json_status_reply(moved_temporary(Location),
461 [ 'Moved temporary to: ~w'-[Location] ],
462 _{location:Location}).
463json_status_reply(see_other(Location),
464 [ 'See: ~w'-[Location] ],
465 _{location:Location}).
466json_status_reply(bad_request(ErrorTerm), Lines, _{}) :-
467 '$messages':translate_message(ErrorTerm, Lines, []).
468json_status_reply(authorise(Method),
469 [ 'Authorization (~p) required'-[Method] ],
470 _{}).
471json_status_reply(forbidden(Location),
472 [ 'You have no permission to access: ~w'-[Location] ],
473 _{location:Location}).
474json_status_reply(not_found(Location),
475 [ 'Path not found: ~w'-[Location] ],
476 _{location:Location}).
477json_status_reply(method_not_allowed(Method,Location),
478 [ 'Method not allowed: ~w'-[UMethod] ],
479 _{location:Location, method:UMethod}) :-
480 upcase_atom(Method, UMethod).
481json_status_reply(not_acceptable(Why),
482 [ 'Request is not acceptable: ~p'-[Why]
483 ],
484 _{}).
485json_status_reply(server_error(ErrorTerm), Lines, _{}) :-
486 '$messages':translate_message(ErrorTerm, Lines, []).
487json_status_reply(service_unavailable(Why),
488 [ 'Service unavailable: ~p'-[Why]
489 ],
490 _{})