4 Request = Request-Line ; Section 5.1
5 *(( general-header ; Section 4.5
6 | request-header ; Section 5.3
7 | entity-header ) CRLF) ; Section 7.1
9 [ message-body ] ; Section 4.3
11 general-header = Cache-Control ; Section 14.9
12 | Connection ; Section 14.10
13 | Date ; Section 14.18
14 | Pragma ; Section 14.32
15 | Trailer ; Section 14.40
16 | Transfer-Encoding ; Section 14.41
17 | Upgrade ; Section 14.42
19 | Warning ; Section 14.46
21 request-header = Accept ; Section 14.1
22 | Accept-Charset ; Section 14.2
23 | Accept-Encoding ; Section 14.3
24 | Accept-Language ; Section 14.4
25 | Authorization ; Section 14.8
26 | Expect ; Section 14.20
27 | From ; Section 14.22
28 | Host ; Section 14.23
29 | If-Match ; Section 14.24
30 | If-Modified-Since ; Section 14.25
31 | If-None-Match ; Section 14.26
32 | If-Range ; Section 14.27
33 | If-Unmodified-Since ; Section 14.28
34 | Max-Forwards ; Section 14.31
35 | Proxy-Authorization ; Section 14.34
36 | Range ; Section 14.35
37 | Referer ; Section 14.36
39 | User-Agent ; Section 14.43
41 Response = Status-Line ; Section 6.1
42 *(( general-header ; Section 4.5
43 | response-header ; Section 6.2
44 | entity-header ) CRLF) ; Section 7.1
46 [ message-body ] ; Section 7.2
48 Request-Line = Method SP Request-URI SP HTTP-Version CRLF
50 Status-Line = HTTP-Version SP Status-Code SP Reason-Phrase CRLF
53 response-header = Accept-Ranges ; Section 14.5
55 | ETag ; Section 14.19
56 | Location ; Section 14.30
57 | Proxy-Authenticate ; Section 14.33
58 | Retry-After ; Section 14.37
59 | Server ; Section 14.38
60 | Vary ; Section 14.44
61 | WWW-Authenticate ; Section 14.47
63 entity-header = Allow ; Section 14.7
64 | Content-Encoding ; Section 14.11
65 | Content-Language ; Section 14.12
66 | Content-Length ; Section 14.13
67 | Content-Location ; Section 14.14
68 | Content-MD5 ; Section 14.15
69 | Content-Range ; Section 14.16
70 | Content-Type ; Section 14.17
71 | Expires ; Section 14.21
72 | Last-Modified ; Section 14.29
75 extension-header = message-header
78 read chunk-size, chunk-extension (if any) and CRLF
79 while (chunk-size > 0) {
80 read chunk-data and CRLF
81 append chunk-data to entity-body
83 read chunk-size and CRLF
86 while (entity-header not empty) {
87 append entity-header to existing header fields
90 Content-Length := length
91 Remove chunked from Transfer-Encoding
96 Net define: #HttpClient &parents: {Cloneable}
97 &slots: {#userAgent -> 'slateweb/1.0'.
98 #statusResponseRegex -> (Regex Matcher newOn: '^(HTTP/\\d+\\.\\d+) (\\d+) ([ \\w]+)$').
99 #headerResponseRegex -> (Regex Matcher newOn: '^([^:]+)\\:\\s*(.*)$').
100 #chunkHeaderRegex -> (Regex Matcher newOn: '^([0-9a-fA-F]+)\\s*$').
103 Net define: #HttpMessage &parents: {Cloneable} &slots: {#version. #headers}.
104 Net define: #HttpRequest &parents: {Net HttpMessage} &slots: {#uri. #method.}.
106 Net define: #HttpResponse &parents: {Net HttpMessage} &slots: {#status. #reason. #body}.
107 Net define: #SimpleHttpResponse &parents: {Net HttpResponse} &slots: {}.
108 Net define: #ChunkedHttpResponse &parents: {Net HttpResponse} &slots: {}.
110 Net define: #HttpAcceptableDictionary &parents: {Dictionary}.
113 m@(Net HttpMessage traits) new
115 resend `>> [version := 'HTTP/1.1'. headers := Dictionary new. ]
118 d@(Net HttpAcceptableDictionary traits) as: s@(String traits)
121 d keysAndValuesDo: [| :key :val |
122 val ifNil: [result ; ', ' ; key printString]
123 ifNotNil: [result ; ', ' ; key printString ; ';q=' ; val printString]].
124 ] streamingAs: s) allButFirst: 2
127 hc@(Net HttpClient traits) simpleQuery: s@(String traits) &on: stream &method: method
128 [| str url closeStream |
129 closeStream := False.
130 url := (Net URL newFrom: s).
131 stream ifNil: [closeStream := True.
132 stream := hc newConnectionFor: url].
133 [str := ((hc request: url &method: method) as: String).
134 "inform: 'sending request'."
135 stream nextPutAll: (str as: ByteArray). "fixme unicode"
137 "inform: 'reading reply'."
138 hc readResponse: stream
139 ] ensure: [closeStream ifTrue: [stream close]]
142 Net define: #AsyncSimpleHttpRequest &parents: {Net AsyncBlockOperation}
143 &slots: {#url. #request}.
145 _@(Net AsyncSimpleHttpRequest traits) new
146 [| o con discon resolv send recv retval hostname portIp port |
149 discon := Net AsyncCloseOperation new.
150 recv := Net AsyncHttpReader new.
151 "con/send bound later because request unknown at time of #new"
153 recv completionBlock := [| :me | o workBlock := [discon workOn]. retval := me result].
154 discon completionBlock := [| :me | o complete &result: retval].
157 [hostname := o url authority.
158 portIp := (Net SocketAddress separatePort: hostname).
159 portIp second ifNil: [hostname := hostname ; ':80'. port := '80'] ifNotNil: [port := portIp second].
160 resolv := (Net AsyncResolveOperation newOn: portIp first &service: port).
161 resolv completionBlock :=
163 con := (Net AsyncConnectOperation newOn: (me result: 0 as: Net SocketAddress)).
164 con completionBlock :=
166 {discon. send. recv. } do: [| :x | x handle := me result].
167 o workBlock := [send workOn]].
168 o workBlock := [con workOn]].
170 send := ((o request as: String) as: Net AsyncSendOperation).
171 send completionBlock := [|:me| o workBlock := [recv workOn]].
172 o workBlock := [resolv workOn].
177 hc@(Net HttpClient traits) simpleAsyncQuery: s@(String traits) &method: method
179 url := (Net URL newFrom: s).
180 request := (hc request: url &method: method).
181 Net AsyncSimpleHttpRequest new `>> [url := url. request := request. ]
184 hc@(Net HttpClient traits) request: s@(String traits) &method: method
186 hc request: (Net URL newFrom: s) &method: method
189 hc@(Net HttpClient traits) request: url@(Net URL traits) &method: method
191 method `defaultsTo: 'GET'.
192 req := Net HttpRequest new `>> [uri := url. method := method. ].
193 req headers at: 'Host' put: url authority.
194 hc userAgent ifNotNil: [req headers at: 'User-Agent' put: hc userAgent].
198 r@(Net HttpRequest traits) as: s@(String traits)
200 r method isNil \/ [r uri path isNil] \/ [r version isNil] ifTrue: [error: 'Nil request'].
202 result ; r method ; ' ' ; r uri path ; ' ' ; r version ; '\r\n'.
203 r headers keysAndValuesDo: [|:key :val| result ; key ; ': ' ; (val as: String) ; '\r\n'].
208 "this needs to be resolved.. fixme"
209 hc@(Net HttpClient traits) newConnectionFor: url@(Net URL traits)
210 [| addr socket hostname portIp |
211 hostname := url authority.
212 portIp := (Net SocketAddress separatePort: hostname).
213 portIp second ifNil: [hostname := hostname ; ':80'].
214 addr := (Net SocketAddress newOn: hostname).
215 socket := (Net Socket newFor: addr domain type: Net Socket Types Stream protocol: Net Socket Protocols Default).
216 socket connectTo: addr.
217 Net SocketStream newOn: socket
220 hc@(Net HttpClient traits) readLineFrom: stream@(ReadStream traits)
222 "inform: 'reading line...'."
223 str := (stream upTo: $\r code).
224 stream next ~= $\n code ifTrue: [error: 'invalid newline separator in http response'].
225 str := (str as: ASCIIString).
226 "inform: 'read: ' ; str escaped."
230 hc@(Net HttpClient traits) readHeaders: stream@(ReadStream traits)
232 headers := Dictionary new.
233 "inform: 'reading headers'."
235 line := (hc readLineFrom: stream).
236 line isEmpty ifTrue: [^ headers].
237 re := hc headerResponseRegex clone.
238 (re match: line) = -1 ifTrue: [error: 'malformed header line'].
239 headers at: (re subexpression: 0) toLowercase put: (re subexpression: 1).
243 hc@(Net HttpClient traits) newResponseFromHeaders: headers
245 "fixme... checked for transfer-encoding/TE to see if we need chunked"
246 (headers at: 'content-length' ifAbsent: [Nil])
247 ifNotNil: [^ (Net SimpleHttpResponse new `>> [headers := headers. ])].
249 tc := (headers at: 'transfer-coding' ifAbsent: [Nil]).
250 tc ifNotNil: [(tc first: 7) toLowercase = 'chunked'
251 ifTrue: [^ (Net ChunkedHttpResponse new `>> [headers := headers. ])]].
253 Net SimpleHttpResponse new `>> [headers := headers. ]
254 error: 'not sure how to read response body...missing transfer encoding'
257 hc@(Net HttpClient traits) readBodyInto: response@(Net SimpleHttpResponse traits) from: stream@(ReadStream traits)
259 length := ((response headers at: 'content-length' ifAbsent: [error: 'Content-Length field required'])
261 inform: 'reading body len: ' ; length printString.
262 response body := (stream next: length).
265 hc@(Net HttpClient traits) readBodyInto: response@(Net ChunkedHttpResponse traits) from: stream@(ReadStream traits)
266 [| chunkHeader chunkMatch length trailers |
267 response body := ExtensibleByteArray new.
269 chunkHeader := (hc readLineFrom: stream).
270 chunkMatch := hc chunkHeaderRegex clone.
271 (chunkMatch match: chunkHeader) = -1 ifTrue: [error: 'error reading chunk header: ' ; chunkHeader].
272 length := ((chunkMatch subexpression: 0) as: Integer &radix: 16)
273 inform: 'reading chunk len: ' ; length printString.
274 len = 0 ifTrue: [trailers := (hc readHeaders: stream).
275 response headers addAll: trailers.
278 response body addAllLast: (stream next: length).
279 stream next = $\r code /\ [stream next = $\n code] ifFalse: [error: 'expected CRLF after chunk'].
284 hc@(Net HttpClient traits) readResponse: stream@(ReadStream traits)
285 [| status re httpVersion statusCode statusReason headers response |
286 status := (hc readLineFrom: stream).
287 re := hc statusResponseRegex clone.
288 (re match: status) = -1
289 ifTrue: [error: 'Invalid status line in http response']
290 ifFalse: [httpVersion := (re subexpression: 0).
291 statusCode := (re subexpression: 1).
292 statusReason := (re subexpression: 2)].
294 httpVersion ~= 'HTTP/1.1' ifTrue: [error: 'Only supporting http version 1.1.'].
295 headers := (hc readHeaders: stream).
296 response := (hc newResponseFromHeaders: headers) `>> [version := httpVersion.
297 status := statusCode.
298 reason := statusReason. ].
299 hc readBodyInto: response from: stream.
304 Net define: #AsyncHttpLineReader &parents: {Net AsyncReceiveUntilOperation}.
306 _@(Net AsyncHttpLineReader traits) new
308 (Net AsyncReceiveUntilOperation newOn: Nil &until: $\n code
309 &resultModifier: [|:res| ((res last: 2) as: String) = '\r\n' ifFalse: [error: 'malformed line in http response.'].
310 (res allButLast: 2) as: String])
313 Net define: #AsyncHttpReader &parents: {Net AsyncBlockOperation}
316 _@(Net AsyncHttpReader traits) new
317 [| o statusReader headerReader bodyReader re httpVersion statusCode statusReason headers |
319 re := Net HttpClient statusResponseRegex clone.
320 statusReader := Net AsyncHttpLineReader new.
321 headerReader := (Net AsyncHttpHeaderReader new).
322 statusReader completionBlock :=
324 headerReader handle := me handle.
325 (re match: me result) = -1
326 ifTrue: [error: 'Invalid status line in http response']
327 ifFalse: [httpVersion := (re subexpression: 0).
328 statusCode := (re subexpression: 1).
329 statusReason := (re subexpression: 2)].
330 httpVersion ~= 'HTTP/1.1' ifTrue: [error: 'Only supporting http version 1.1.'].
331 o workBlock := [headerReader workOn]].
333 bodyReader := (Net AsyncHttpBodyReader new).
335 headerReader completionBlock :=
337 bodyReader handle := me handle.
338 headers := me result.
339 bodyReader headers := headers.
340 o workBlock := [bodyReader workOn]].
342 bodyReader completionBlock :=
344 headers addAll: me trailers.
346 (Net SimpleHttpResponse new `>>
347 [version := httpVersion.
348 status := statusCode.
349 reason := statusReason.
351 body := me result. ]).
355 [statusReader handle := o handle.
356 o workBlock := [statusReader workOn].
361 Net define: #AsyncHttpHeaderReader &parents: {Net AsyncBlockOperation}
364 _@(Net AsyncHttpHeaderReader traits) new
365 [| o re headers lineReader |
367 re := Net HttpClient headerResponseRegex clone.
368 headers := Dictionary new.
369 lineReader := Net AsyncHttpLineReader new.
371 lineReader completionBlock :=
374 ifTrue: [o workBlock := [].
375 o complete &result: headers]
376 ifFalse: [(re match: me result) = -1
377 ifTrue: [error: 'Invalid header line in http response']
378 ifFalse: [headers at: (re subexpression: 0) toLowercase put: (re subexpression: 1).
383 o workBlock := [lineReader handle := o handle.
384 o workBlock := [lineReader workOn].
389 Net define: #AsyncHttpBodyReader &parents: {Net AsyncBlockOperation}
390 &slots: {#handle. #headers. #trailers}.
392 _@(Net AsyncHttpBodyReader traits) new
393 [| o re lineReader chunkReader headerReader body tc |
395 re := Net HttpClient chunkHeaderRegex clone.
396 lineReader := Net AsyncHttpLineReader new.
397 chunkReader := Net AsyncReceiveUntilOperation new.
398 headerReader := (Net AsyncHttpHeaderReader new). "chunked encoding has trailers"
399 body := ExtensibleByteArray new.
400 o trailers := Dictionary new.
402 [lineReader handle := o handle.
403 chunkReader handle := o handle.
404 headerReader handle := o handle.
405 (o headers at: 'content-length' ifAbsent: [Nil])
406 ifNil: [tc := (o headers at: 'transfer-coding'). "error if not there"
407 tc ifNotNil: [(tc first: 7) toLowercase = 'chunked'
408 ifFalse: [error: 'unknown http transfer encoding']].
409 "we know it's chunked transfer"
410 headerReader completionBlock :=
412 o trailers := me result.
414 o complete &result: body].
416 lineReader completionBlock :=
418 (re match: me result) = -1 ifTrue: [error: 'error reading chunk header: ' ; me result].
419 length := ((chunkMatch subexpression: 0) as: Integer &radix: 16).
420 inform: 'reading chunk len: ' ; length printString.
422 chunkReader amount := length + 2. "plus CRLF"
423 o workBlock := [chunkReader workOn]].
424 chunkReader completionBlock :=
427 ifTrue: [o workBlock := [headerReader workOn]]
428 ifFalse: [((me result last: 2) as: String) = '\r\n' ifFalse: [error: 'bad http chunk'].
429 body addAllLast: (me result allButLast: 2).
431 o workBlock := [lineReader workOn]]].
432 o workBlock := [lineReader workOn]]
434 ifNotNil: [chunkReader amount := ((o headers at: 'content-length'
435 ifAbsent: [error: 'Content-Length field required'])
437 o workBlock := [chunkReader workOn].
438 chunkReader completionBlock := [| :me | o complete &result: me result]].