34
35:- module(pce_http_client, []). 36:- use_module(library(pce)). 37:- use_module(library(url)). 38:- use_module(library(option)). 39
82
83:- pce_begin_class(http_client, socket,
84 ).
85
86:- pce_global(@nl_regex, new(regex('\n'))).
87:- pce_global(@http_reply_regex,
88 new(regex('HTTP/([0-9]\\.[0-9])\\s*(\\d+)\\s*(\\w*)'))).
89:- pce_global(@http_field_regex,
90 new(regex('([a-zA-Z0-9-]+):\\s*(.*)$'))).
91:- pce_global(@http_empty_line_regex,
92 new(regex('\\s*\r?$'))).
93
94:- initialization
95 new(_, error(http_bad_header,
96 '%O: Bad header line: %s',
97 warning)),
98 new(_, error(url_bad_protocol,
99 '%O: Can only handle %s URLs',
100 error)),
101 new(_, error(url_bad_syntax,
102 '%O: Not a legal URL: %s')). 103
104variable(host, name, get, ).
105variable(location, string, get, ).
106variable(message, code*, both, ).
107variable(data_object, object*, get, ).
108variable(received, int*, get, ).
109variable(remaining, int*, get, ).
110variable(request, {header,data}*, get, ).
111variable(req_status, name*, get, ).
112variable(http_version, name, both, ).
113variable(user_agent, name, both, ).
114variable(verbose, {silent,connect,transfer} := connect,
115 both, ).
116
117initialise(S, URL:prolog) :->
118 ::
119 ( atomic(URL)
120 -> ( parse_url(URL, Parts)
121 -> true
122 ; send(S, error, url_bad_syntax, URL),
123 fail
124 )
125 ; Parts = URL
126 ),
127 ( memberchk(protocol(http), Parts)
128 -> true
129 ; send(S, error, url_bad_protocol, 'HTTP'),
130 fail
131 ),
132 option(host(Host), Parts),
133 option(port(Port), Parts, 80),
134 http_location(Parts, Location),
135 send(S, slot, host, Host),
136 send(S, slot, location, Location),
137 send(S, slot, http_version, '1.0'),
138 send(S, slot, user_agent, 'XPCE/SWI-Prolog'),
139 send_super(S, initialise, tuple(Host, Port)).
140
141:- pce_group(feedback).
142
143req_status(S, Status:{connecting,connected,header,data,complete}) :->
144 ::
145 send(S, slot, req_status, Status),
146 ( get(S, verbose, silent)
147 -> true
148 ; message(Status, S, Message),
149 ignore(send(S, Message))
150 ).
151
152message(connecting, S, report(progress, 'Connecting %s', Host)) :-
153 get(S, host, Host).
154message(connected, _, report(progress, 'Connected')).
155message(header, _, report(progress, 'Receiving header')).
156message(data, _, report(progress, 'Receiving data')).
157message(complete, _, report(done)).
158
159progress(S, Progress:int, What:{percent,bytes}) :->
160 ::
161 ( get(S, verbose, transfer),
162 send(S, report, progress, 'Received %d %s', Progress, What)
163 -> true
164 ; true
165 ).
166
167:- pce_group(connect).
168
169connect(S) :->
170 ::
171 ( get(S, status, connected)
172 -> true
173 ; send(S, req_status, connecting),
174 send_super(S, connect),
175 send(S, req_status, connected)
176 ).
177
178:- pce_group(fetch).
179
180fetch_data(S,
181 Object:into=object,
182 Confirm:confirm=[code]*,
183 Location:location=[string]) :->
184 ::
185 send(S, slot, data_object, Object),
186 ( Confirm == @default
187 -> true
188 ; send(S, slot, message, Confirm)
189 ),
190 ( Location == @default
191 -> true
192 ; send(S, slot, location, Location)
193 ),
194 send(S, slot, request, data),
195 send(S, send_header, 'GET'),
196 ( get(S, message, Msg),
197 Msg \== @nil
198 -> true
199 ; send(S, wait)
200 ).
201
202header(S, Location:[string], Header:sheet) :<-
203 ::
204 new(Object, object),
205 send(S, slot, data_object, Object),
206 ( Location == @default
207 -> true
208 ; send(S, slot, location, Location)
209 ),
210 send(S, slot, request, header),
211 send(S, send_header, 'HEAD'),
212 send(S, wait),
213 get(Object, http_header, Header),
214 send(Header, lock_object, @on),
215 send(Object, delete_attribute, http_header),
216 get(Header, unlock, _).
217
218data(S, Data:string) :<-
219 ::
220 new(TB, text_buffer),
221 send(TB, undo_buffer_size, 0),
222 send(S, fetch_data, TB),
223 get(TB, contents, Data),
224 get(TB, http_header, Header),
225 send(Data, attribute, http_header, Header),
226 free(TB).
227
228:- pce_group(internal).
229
230send_header(S, Action:name) :->
231 ::
232 get(S, host, Host),
233 send(S, slot, received, 0),
234 send(S, prepare_header),
235 send(S, slot, req_status, @nil),
236 send(S, connect),
237 get(S, http_version, Version),
238 get(S, user_agent, Agent),
239 get(S, location, Location),
240 send(S, format, '%s %s HTTP/%s\r\n', Action, Location, Version),
241 send(S, format, 'Host: %s\r\n', Host),
242 send(S, format, 'User-Agent: %s\r\n', Agent),
243 send(S, format, '\r\n').
244
245wait(S) :->
246 ::
247 repeat,
248 ( get(S, req_status, complete)
249 -> !
250 ; send(@display, dispatch),
251 fail
252 ).
253
254prepare_header(S) :->
255 ::
256 send(S, record_separator, @nl_regex),
257 send(S, input_message, message(S, header_line, @arg1)),
258 send(S?data_object, attribute, http_header, new(sheet)).
259
260header_line(S, Line:string) :->
261 ::
262 ( send(@http_field_regex, match, Line)
263 -> get(@http_field_regex, register_value, Line, 1, name, FieldName0),
264 get(FieldName0, downcase, FieldName),
265 get(@http_field_regex, register_value, Line, 2, string, Value),
266 send(Value, strip),
267 field_type(FieldName, Type),
268 get(@pce, convert, Value, Type, FieldValue),
269 get(S, data_object, DataObject),
270 send(DataObject?http_header, value, FieldName, FieldValue)
271 ; send(@http_reply_regex, match, Line)
272 -> get(S, data_object, DataObject),
273 get(@http_reply_regex, register_value, Line, 2, int, ReplyStatus),
274 send(DataObject?http_header, value, status, ReplyStatus),
275 send(S, req_status, header)
276 ; send(@http_empty_line_regex, match, Line)
277 -> send(S, prepare_data)
278 ; send(S, error, http_bad_header, Line)
279 ).
280
281field_type('content-length', int).
282field_type('date', date).
283field_type('last-modified', date).
284field_type(_, name).
285
286prepare_data(S) :->
287 ::
288 ( get(S, request, header)
289 -> send(S, req_status, complete)
290 ; ( get(S, data_object, Object),
291 get(Object?http_header, value, 'content-length', Length)
292 -> send(S, slot, remaining, Length)
293 ; send(S, slot, remaining, @nil)
294 ),
295 send(S, input_message, message(S, data_record, @arg1)),
296 send(S, record_separator, @nil)
297 ).
298
299data_record(S, Record:string) :->
300 ::
301 get(S, data_object, Data),
302 ( get(S, remaining, Rem),
303 Rem \== @nil
304 -> get(Record, size, Read),
305 NewRem is Rem - Read,
306 get(Data?http_header, value, 'content-length', Len),
307 Percent is ((Len-NewRem)*100)//Len,
308 send(S, progress, Percent, percent),
309 ( NewRem >= 0
310 -> send(Data, append, Record),
311 send(S, slot, remaining, NewRem),
312 ( NewRem == 0
313 -> send(S, complete)
314 ; true
315 )
316 ; send(Record, truncate, Rem),
317 send(Data, append, Record),
318 send(S, complete)
319 )
320 ; send(Data, append, Record),
321 get(Record, size, Bytes),
322 get(S, received, Rec0),
323 Rec is Rec0 + Bytes,
324 send(S, slot, received, Rec),
325 send(S, progress, Rec, bytes)
326 ).
327
328end_of_file(S) :->
329 ::
330 send(S, close),
331 ( get(S, req_status, complete)
332 -> true
333 ; send(S, complete)
334 ).
335
336complete(S) :->
337 ::
338 send(S, req_status, complete),
339 ( get(S, message, Message),
340 Message \== @nil
341 -> get(S, data_object, Data),
342 send(Message, forward, S, Data)
343 ; send(S, req_status, complete)
344 ).
345
346:- pce_end_class