207 lines
6.5 KiB
Perl
207 lines
6.5 KiB
Perl
|
/* $Id$
|
||
|
|
||
|
Part of SWI-Prolog
|
||
|
|
||
|
Author: Jan Wielemaker
|
||
|
E-mail: wielemak@science.uva.nl
|
||
|
WWW: http://www.swi-prolog.org
|
||
|
Copyright (C): 1985-2007, University of Amsterdam
|
||
|
|
||
|
This program is free software; you can redistribute it and/or
|
||
|
modify it under the terms of the GNU General Public License
|
||
|
as published by the Free Software Foundation; either version 2
|
||
|
of the License, or (at your option) any later version.
|
||
|
|
||
|
This program is distributed in the hope that it will be useful,
|
||
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||
|
GNU General Public License for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public
|
||
|
License along with this library; if not, write to the Free Software
|
||
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||
|
|
||
|
As a special exception, if you link this library with other files,
|
||
|
compiled with a Free Software compiler, to produce an executable, this
|
||
|
library does not by itself cause the resulting executable to be covered
|
||
|
by the GNU General Public License. This exception does not however
|
||
|
invalidate any other reasons why the executable file might be covered by
|
||
|
the GNU General Public License.
|
||
|
*/
|
||
|
|
||
|
:- module(http_json,
|
||
|
[ reply_json/1, % +JSON
|
||
|
reply_json/2, % +JSON, Options
|
||
|
http_read_json/2, % +Request, -JSON
|
||
|
http_read_json/3 % +Request, -JSON, +Options
|
||
|
]).
|
||
|
:- use_module(http_client).
|
||
|
:- use_module(http_header).
|
||
|
:- use_module(http_stream).
|
||
|
:- use_module(json).
|
||
|
:- use_module(library(option)).
|
||
|
:- use_module(library(error)).
|
||
|
:- use_module(library(lists)).
|
||
|
:- use_module(library(memfile)).
|
||
|
|
||
|
:- multifile
|
||
|
http_client:http_convert_data/4,
|
||
|
http_client:post_data_hook/3,
|
||
|
json_type/1.
|
||
|
|
||
|
|
||
|
/** <module> HTTP JSON Plugin module
|
||
|
|
||
|
This module inserts the JSON parser for documents of MIME type
|
||
|
=|application/jsonrequest|= and =|application/json|= requested through
|
||
|
the http_client.pl library.
|
||
|
|
||
|
Typically JSON is used by Prolog HTTP servers. Below is a skeleton for
|
||
|
handling a JSON request, answering in JSON.
|
||
|
|
||
|
==
|
||
|
handle(Request) :-
|
||
|
http_read_json(Request, JSONIn),
|
||
|
json_to_prolog(JSONIn, PrologIn),
|
||
|
<compute>(PrologIn, PrologOut), % application body
|
||
|
prolog_to_json(PrologOut, JSONOut),
|
||
|
reply_json(JSONOut).
|
||
|
==
|
||
|
|
||
|
This module also integrates JSON support into the http client provided
|
||
|
by http_client.pl. Posting a JSON query and processing the JSON reply
|
||
|
(or any other reply understood by http_read_data/3) is as simple as
|
||
|
below, where Term is a JSON term as described in json.pl and reply is
|
||
|
of the same format if the server replies with JSON.
|
||
|
|
||
|
==
|
||
|
...,
|
||
|
http_post(URL, json(Term), Reply, [])
|
||
|
==
|
||
|
|
||
|
@see JSON Requests are discussed in http://json.org/JSONRequest.html
|
||
|
@see json.pl describes how JSON objects are represented in Prolog terms.
|
||
|
@see json_convert.pl converts between more natural Prolog terms and json
|
||
|
terms.
|
||
|
*/
|
||
|
|
||
|
http_client:http_convert_data(In, Fields, Data, Options) :-
|
||
|
memberchk(content_type(Type), Fields),
|
||
|
is_json_type(Type), !,
|
||
|
( memberchk(content_length(Bytes), Fields)
|
||
|
-> stream_range_open(In, Range, [size(Bytes)]),
|
||
|
set_stream(Range, encoding(utf8)),
|
||
|
call_cleanup(json_read(Range, Data, Options), close(Range))
|
||
|
; set_stream(In, encoding(utf8)),
|
||
|
json_read(In, Data, Options)
|
||
|
).
|
||
|
|
||
|
|
||
|
is_json_type(Type) :-
|
||
|
json_type(Type), !.
|
||
|
is_json_type(ContentType) :-
|
||
|
json_type(Type),
|
||
|
sub_atom(ContentType, 0, _, _, Type), !,
|
||
|
strip_utf8(ContentType, Plain),
|
||
|
json_type(Plain).
|
||
|
|
||
|
|
||
|
%% strip_utf8(+ContentTypeIn, -ContentType) is det.
|
||
|
%
|
||
|
% Strip an optional =|; charset=UTF-8|=. JSON data is always
|
||
|
% UTF-8, but some clients seem to insist in sending this.
|
||
|
|
||
|
strip_utf8(ContentType, Plain) :-
|
||
|
sub_atom(ContentType, B, _, A, ;),
|
||
|
sub_atom(ContentType, _, A, 0, Ext),
|
||
|
normalize_space(atom('charset=UTF-8'), Ext), !,
|
||
|
sub_atom(ContentType, 0, B, _, CT),
|
||
|
normalize_space(atom(Plain), CT).
|
||
|
strip_utf8(ContentType, ContentType).
|
||
|
|
||
|
|
||
|
%% json_type(?MIMEType:atom) is semidet.
|
||
|
%
|
||
|
% True if MIMEType is a JSON mimetype. http_json:json_type/1 is a
|
||
|
% multifile predicate and may be extended to facilitate
|
||
|
% non-conforming clients.
|
||
|
|
||
|
json_type('application/jsonrequest').
|
||
|
json_type('application/json').
|
||
|
|
||
|
|
||
|
% http_client:post_data_hook(+Data, +Out:stream, +HdrExtra) is semidet.
|
||
|
%
|
||
|
% Hook into http_post_data/3 that allows for
|
||
|
%
|
||
|
% ==
|
||
|
% http_post(URL, json(Term), Reply, Options)
|
||
|
% ==
|
||
|
%
|
||
|
% @tbd avoid creation of intermediate data using chunked output.
|
||
|
|
||
|
http_client:post_data_hook(json(Term), Out, HdrExtra) :-
|
||
|
http_client:post_data_hook(json(Term, []), Out, HdrExtra).
|
||
|
http_client:post_data_hook(json(Term, Options), Out, HdrExtra) :-
|
||
|
option(content_type(Type), HdrExtra, 'application/json'),
|
||
|
new_memory_file(MemFile),
|
||
|
open_memory_file(MemFile, write, Handle),
|
||
|
format(Handle, 'Content-type: ~w~n~n', [Type]),
|
||
|
json_write(Handle, Term, Options),
|
||
|
close(Handle),
|
||
|
open_memory_file(MemFile, read, RdHandle),
|
||
|
call_cleanup(http_post_data(cgi_stream(RdHandle), Out, HdrExtra),
|
||
|
( close(RdHandle),
|
||
|
free_memory_file(MemFile))).
|
||
|
|
||
|
|
||
|
%% http_read_json(+Request, -JSON) is det.
|
||
|
%% http_read_json(+Request, -JSON, +Options) is det.
|
||
|
%
|
||
|
% Extract JSON data posted to this HTTP request.
|
||
|
%
|
||
|
% @error domain_error(mimetype, Found) if the mimetype is
|
||
|
% not known (see json_type/1).
|
||
|
% @error domain_error(method, Method) if the request is not
|
||
|
% a POST request.
|
||
|
|
||
|
http_read_json(Request, JSON) :-
|
||
|
http_read_json(Request, JSON, []).
|
||
|
|
||
|
http_read_json(Request, JSON, Options) :-
|
||
|
select_option(content_type(Type), Options, Rest), !,
|
||
|
delete(Request, content_type(_), Request2),
|
||
|
request_to_json([content_type(Type)|Request2], JSON, Rest).
|
||
|
http_read_json(Request, JSON, Options) :-
|
||
|
request_to_json(Request, JSON, Options).
|
||
|
|
||
|
request_to_json(Request, JSON, Options) :-
|
||
|
memberchk(method(Method), Request),
|
||
|
memberchk(content_type(Type), Request),
|
||
|
( Method == post
|
||
|
-> true
|
||
|
; domain_error(method, Method)
|
||
|
),
|
||
|
( is_json_type(Type)
|
||
|
-> true
|
||
|
; domain_error(mimetype, Type)
|
||
|
),
|
||
|
http_read_data(Request, JSON, Options).
|
||
|
|
||
|
|
||
|
%% reply_json(+JSONTerm) is det.
|
||
|
%% reply_json(+JSONTerm, +Options) is det.
|
||
|
%
|
||
|
% Formulate a JSON HTTP reply. See json_write/2 for details.
|
||
|
% Options accepts content_type(+Type) and options accepted by
|
||
|
% json_write/3.
|
||
|
|
||
|
reply_json(Term) :-
|
||
|
format('Content-type: application/json~n~n'),
|
||
|
json_write(current_output, Term).
|
||
|
|
||
|
reply_json(Term, Options) :-
|
||
|
select_option(content_type(Type), Options, Rest, 'application/json'),
|
||
|
format('Content-type: ~w~n~n', [Type]),
|
||
|
json_write(current_output, Term, Rest).
|