1% ----------------------------------------------------------------------
2% BEGIN LICENSE BLOCK
3% Version: CMPL 1.1
4%
5% The contents of this file are subject to the Cisco-style Mozilla Public
6% License Version 1.1 (the "License"); you may not use this file except
7% in compliance with the License.  You may obtain a copy of the License
8% at www.eclipse-clp.org/license.
9%
10% Software distributed under the License is distributed on an "AS IS"
11% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See
12% the License for the specific language governing rights and limitations
13% under the License.
14%
15% The Original Code is  The ECLiPSe Constraint Logic Programming System.
16% The Initial Developer of the Original Code is  Cisco Systems, Inc.
17% Portions created by the Initial Developer are
18% Copyright (C) 1991-2006 Cisco Systems, Inc.  All Rights Reserved.
19%
20% Contributor(s): ECRC GmbH
21% Contributor(s): IC-Parc, Imperal College London
22%
23% END LICENSE BLOCK
24%
25% System:	ECLiPSe Constraint Logic Programming System
26% Version:	$Id: http_client.pl,v 1.2 2009/07/16 09:11:24 jschimpf Exp $
27% ----------------------------------------------------------------------
28
29/*
30    RPC using HTTP/1.0 (request status line) and
31    MIME-Version:1.0 (request general header)
32
33    CLIENT
34
35    http document used is HTTP/1.0 from the Network Working Group
36    - internet draft - expiring in june, 19 1995.
37*/
38
39
40:- module(http_client).
41
42:- comment(categories, ["Interfacing"]).
43:- comment(summary, "HTTP client library").
44:- comment(author, "Ph. Bonnet, S. Bressan and M. Meier, ECRC Munich").
45:- comment(copyright, "Cisco Systems, Inc").
46:- comment(date, "$Date: 2009/07/16 09:11:24 $").
47
48:- comment(http_client/7, [
49    template:"http_client(+Method, +Uri, +ObjectBody, +HttpParams, -RespError, -RespParam, -RespObjectBody)",
50    summary:"Used to access HTML pages, given their URI (the method GET is applied)",
51    args:["Method":"A string","Uri":"A string","ObjectBody":"A string",
52    	"HttpParams":"A list of terms as defined in the DCG grammar",
53	"RespError":"Outputs a term error(ErrorCode, ErrorPhrase),
54	where ErrorCode is he error code contained in the response
55	and ErrorPhrase is the error phrase contained in the response",
56	"RespParam":"Outputs a list of terms as defined in the DCG grammar",
57	"RespObjectBody":"Outputs the object body of the response"],
58    eg:"
59    [eclipse 1]: use_module(http).
60    http_grammar.pl compiled traceable 25048 bytes in 0.38 seconds
61    http_client.pl compiled traceable 5916 bytes in 0.47 seconds
62    http_server.pl compiled traceable 5304 bytes in 0.07 seconds
63    http.pl    compiled traceable 0 bytes in 0.57 seconds
64
65    yes.
66    [eclipse 2]:  http_client(\"GET\", \"http://www.ecrc.de/staff/\", \"\", [],
67	    Status, Param, Resp).
68
69    Status = error(200, \"Document follows \")
70    Param = [date, server, contentType(mt(text, html))]
71
72    Resp = \"<HTML>...</HTML>\"
73
74    yes.
75    "]).
76
77
78
79
80
81:- export
82        http_compile/1,
83        http_compile/2,
84        http_open/2,
85        http_client/7.
86
87:- tool(http_compile/1, http_compile/2).
88
89
90:- use_module(http_grammar).
91
92
93/*
94http_client(+Method, +Uri, +ObjectBody, +HttpParams,
95	    -RespError, -RespParams, -RespObjectBody):
96Metod and Uri and ObjectBody and String are strings
97HttpParams is a list of terms defined in the DCG (http_grammar.pl)
98Error is a term error(ErrorCode, ErrorPhrase)
99   ErrorCode: the error code contained in the response
100   ErrorCode: the error phrase contained in the response
101RespParams is a list of terms defined in DCG (http_grammar.pl)
102RespObjectBody is a string containing the object body of the response
103
104The client does:
105    - encoding of a request
106    - sending of a request
107    - reception of the response
108    - decoding of the response (nothing)
109
110HttpParams are the parameteres used to constitute the header.
111
112*/
113
114http_client(Method, Uri, ObjectBody, HttpParams,
115	    RespError, RespParams, RespObjectBody):-
116    request_enco(Method, Uri, ObjectBody, HttpParams, HostName, Request),
117    request_send(HostName, Request, Soc),
118    respons_recp(Soc, RespError, RespParams, RespObjectBody),
119    close(Soc),!.
120
121/*
122The request encoding composes a full request.
123The host name and the port are obtained from the uri
124http document p.15
125
126*/
127request_enco(Method, Uri, ObjectBody, HttpParams, HostName, Request):-
128    hostname(Uri, HostName, PartialURL),
129    status_line(Method, PartialURL , SL),
130    resp_header(HttpParams, ObjectBody, H),
131    concat_strings(SL, H, St1),
132    concat_strings(St1, "\r\n", St2),
133    concat_strings(St2, ObjectBody, Request).
134
135/*
136url decoding for http
137*/
138hostname(Uri, HostName, PartialURL):-
139    append_strings("http://", S, Uri),
140    open(string(S), read, St),
141    read_string(St, "/", _, HostName), close(St),
142    append_strings(HostName, PartialURL, S), !.
143
144/*
145Constitution of The header starting from the http parameters
146and the length of the object body
147*/
148
149status_line(Method, URL, SL):-
150    concat_strings(Method, " ", St1),
151    concat_strings(St1, URL, St2),
152    concat_strings(St2, " ", St3),
153    concat_strings(St3, "HTTP/1.0\r\n", SL).
154
155resp_header(HttpParams, ObjectBody, H):-
156    params_format(HttpParams, P),
157    string_length(ObjectBody, L),
158    (L == 0 ->
159	H = P
160    ;
161	params_format([contentLength(L)], CL),
162	% could become a pb concat_strings is better
163	concat_string([P, CL], H)
164    ).
165
166params_format(HttpParams, P):-
167	params_format(HttpParams, "", P).
168
169params_format([], S, S).
170params_format([H|T], S0, S):-
171    phrase(header(H), L, _),
172    open(string(""), write, St),
173    list_to_token(L, St),
174    get_stream_info(St, name, S1),
175    close(St),
176    concat_string([S0, S1, "\r\n"], S2),
177    params_format(T, S2, S).
178
179/*
180sending of a request:
181- a socket is created  and connected to hostName
182- the encoded request is sent
183*/
184
185request_send(HostName, Request, Soc):-
186    host_and_port(HostName, HN, P),
187    socket(internet, stream, Soc),
188    connect(Soc, HN / P),
189    concat_strings(Request, "\r\n", R),
190    write(Soc, R),
191    flush(Soc).
192
193
194host_and_port(HostName, HH, 80):-
195    open(string(HostName), read, St),
196    read_string(St, ":", L, H),
197    close(St),
198    string_length(HostName, L),!,
199    atom_string(HH, H).
200host_and_port(HostName, HHH, PPP):-
201    open(string(HostName), read, St),
202    read_string(St, ":", _, H),
203    close(St),
204    concat_strings(H, ":", HH),
205    append_strings(HH, P, HostName),
206    atom_string(HHH, H),
207    atom_string(PP, P), integer_atom(PPP, PP), !.
208
209
210
211/*
212reception of a response:
213- read a message until a CRLF is found -> response status line
214    % status error management
215- analyse the response header -> length, type, encoding of the object body
216- read the object body
217*/
218
219respons_recp(S, Error, HttpParams, ObjectBody):-
220    read_Error(S, Error),!,
221    (Error = error(200,_) ->
222	read_ParamsClient(S, Params),
223        parse_Params(Params, HttpParams),
224        read_objectBody(S, HttpParams, ObjectBody)
225    ;
226	true
227    ).
228
229read_Error(S, error(ErrorCode, ErrorPhrase)):-
230	read_string(S, end_of_line, _, SL),
231	open(string(SL), read, St),
232	token_to_list(St, L),
233	phrase(status(_, ErrorCode, ErrorPhrase), L, _),
234	close(St).
235
236/*
237general header*
238request header*
239object header*
240*/
241read_ParamsClient(S, List):-
242	% \n instead of \r\n as in the http document
243	read_string(S, end_of_line, _Length, Elem0),
244	( Elem0 \= "", Elem0 \= "\r" ->
245	    append_strings(Elem0, "\n", Elem),
246	    List = [Elem|Tail],
247	    read_ParamsClient(S, Tail)
248	;
249	    List = []
250	).
251
252
253/*
254parsing using the DCG grammar
255*/
256parse_Params([], []).
257parse_Params([H|T], [P|TT]):-
258	open(string(H), read, St),
259	token_to_list(St, L),
260	close(St),
261	phrase(header(P), L, _), !,
262	parse_Params(T, TT).
263
264/*
265read object body according to the content length in the http params
266*/
267read_objectBody(S, HttpParams, ObjectBody):-
268	member(contentLength(L), HttpParams),!,
269	read_string(S, "", L, ObjectBody).
270read_objectBody(S, HttpParams, ObjectBody):-
271	member(contentType(_), HttpParams),!,
272	read_string(S, "", _, ObjectBody).
273read_objectBody(_, _, "").
274
275/*
276No response decoding.
277*/
278
279
280:- comment(http_open/2, [
281    template:"http_open(+Url, -Stream)",
282    summary:"Download a web document given its URL and open a Stream to read it",
283    args:["Url":"A string","Stream":"A variable or atom"],
284    desc:html("This utility downloads a web document (given its URL) into a
285    string stream and returns that string stream's identifier for reading."),
286    eg:"
287    [eclipse 1]: lib(http_client).
288    yes.
289    [eclipse 2]: http_open(\"http://icparc.ic.ac.uk/index.html\",S),
290                 read_string(S, end_of_line, _, L).
291    S = 19
292    L = \"<!DOCTYPE HTML PUBLIC \\\"-//W3C//DTD HTML 3.2//EN\\\">\"
293    yes.
294    "]).
295
296http_open(URL, Stream) :-
297	http_client("GET", URL, "", [], Error, _Params, Response),
298	( Error = error(200, _OK) ->
299	    open(string(Response), read, Stream)
300	;
301	    fail
302	).
303
304
305:- comment(http_compile/1, [
306    template:"http_compile(+Url)",
307    summary:"Compile an ECLiPSe source file, given its URL",
308    args:["Url":"A string"],
309    desc:html("This utility downloads an eclipse source file (given its URL)
310    and compiles it. Note that this represents a security risk: the downloaded
311    code may contain Eclipse commands that are executed on your computer.
312    Make sure you trust the code that you download!"),
313    eg:"
314    [eclipse 1]: lib(http_client).
315    yes.
316    [eclipse 2]: http_compile(\"http://icparc.ic.ac.uk/eclipse/examples/sendmore.pl\").
317    yes.
318    [eclipse 8]: sendmore1(X).
319    X = [9, 5, 6, 7, 1, 0, 8, 2]     More? (;)
320    no (more) solution.
321    "]).
322
323http_compile(URL, Module) :-
324	http_open(URL, Stream),
325	compile_stream(Stream)@Module,
326	close(Stream).
327
328