diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-05-21 18:12:20 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-05-21 18:12:20 +0000 |
commit | f1ae9db45e692d9b143fc0eefb36b9ad2fe32f69 (patch) | |
tree | e611f1d0224ca3397037121aec7ddc31dc5eeff6 /src | |
parent | f68b4dd1361c1932f544283cb349de91831c8e7a (diff) | |
download | luasocket-f1ae9db45e692d9b143fc0eefb36b9ad2fe32f69.tar.gz luasocket-f1ae9db45e692d9b143fc0eefb36b9ad2fe32f69.tar.bz2 luasocket-f1ae9db45e692d9b143fc0eefb36b9ad2fe32f69.zip |
HTTP message bodies are transfered using a callback to return body parts
chunk by chunk.
Diffstat (limited to 'src')
-rw-r--r-- | src/http.lua | 314 |
1 files changed, 200 insertions, 114 deletions
diff --git a/src/http.lua b/src/http.lua index 7212728..d1e4894 100644 --- a/src/http.lua +++ b/src/http.lua | |||
@@ -14,7 +14,7 @@ local TIMEOUT = 60 | |||
14 | -- default port for document retrieval | 14 | -- default port for document retrieval |
15 | local PORT = 80 | 15 | local PORT = 80 |
16 | -- user agent field sent in request | 16 | -- user agent field sent in request |
17 | local USERAGENT = "LuaSocket 1.2 HTTP 1.1" | 17 | local USERAGENT = "LuaSocket 1.3 HTTP 1.1" |
18 | 18 | ||
19 | ----------------------------------------------------------------------------- | 19 | ----------------------------------------------------------------------------- |
20 | -- Tries to get a pattern from the server and closes socket on error | 20 | -- Tries to get a pattern from the server and closes socket on error |
@@ -86,7 +86,7 @@ end | |||
86 | -- all name_i are lowercase | 86 | -- all name_i are lowercase |
87 | -- nil and error message in case of error | 87 | -- nil and error message in case of error |
88 | ----------------------------------------------------------------------------- | 88 | ----------------------------------------------------------------------------- |
89 | local get_headers = function(sock, headers) | 89 | local get_hdrs = function(sock, headers) |
90 | local line, err | 90 | local line, err |
91 | local name, value | 91 | local name, value |
92 | -- get first line | 92 | -- get first line |
@@ -121,63 +121,120 @@ end | |||
121 | -- Receives a chunked message body | 121 | -- Receives a chunked message body |
122 | -- Input | 122 | -- Input |
123 | -- sock: socket connected to the server | 123 | -- sock: socket connected to the server |
124 | -- callback: function to receive chunks | ||
124 | -- Returns | 125 | -- Returns |
125 | -- body: a string containing the body of the message | 126 | -- nil if successfull or an error message in case of error |
126 | -- nil and error message in case of error | ||
127 | ----------------------------------------------------------------------------- | 127 | ----------------------------------------------------------------------------- |
128 | local try_getchunked = function(sock) | 128 | local try_getchunked = function(sock, callback) |
129 | local chunk_size, line, err | 129 | local chunk, size, line, err |
130 | local body = "" | ||
131 | repeat | 130 | repeat |
132 | -- get chunk size, skip extention | 131 | -- get chunk size, skip extention |
133 | line, err = %try_get(sock) | 132 | line, err = %try_get(sock) |
134 | if err then return nil, err end | 133 | if err then |
135 | chunk_size = tonumber(gsub(line, ";.*", ""), 16) | 134 | callback(nil, err) |
136 | if not chunk_size then | 135 | return err |
136 | end | ||
137 | size = tonumber(gsub(line, ";.*", ""), 16) | ||
138 | if not size then | ||
137 | sock:close() | 139 | sock:close() |
138 | return nil, "invalid chunk size" | 140 | callback(nil, "invalid chunk size") |
141 | return "invalid chunk size" | ||
139 | end | 142 | end |
140 | -- get chunk | 143 | -- get chunk |
141 | line, err = %try_get(sock, chunk_size) | 144 | chunk, err = %try_get(sock, size) |
142 | if err then return nil, err end | 145 | if err then |
143 | -- concatenate new chunk | 146 | callback(nil, err) |
144 | body = body .. line | 147 | return err |
148 | end | ||
149 | -- pass chunk to callback | ||
150 | if not callback(chunk) then | ||
151 | sock:close() | ||
152 | return "aborted by callback" | ||
153 | end | ||
145 | -- skip blank line | 154 | -- skip blank line |
146 | _, err = %try_get(sock) | 155 | _, err = %try_get(sock) |
147 | if err then return nil, err end | 156 | if err then |
148 | until chunk_size <= 0 | 157 | callback(nil, err) |
149 | return body | 158 | return err |
159 | end | ||
160 | until size <= 0 | ||
161 | -- let callback know we are done | ||
162 | callback("", "done") | ||
150 | end | 163 | end |
151 | 164 | ||
152 | ----------------------------------------------------------------------------- | 165 | ----------------------------------------------------------------------------- |
153 | -- Receives http body | 166 | -- Receives a message body by content-length |
154 | -- Input | 167 | -- Input |
155 | -- sock: socket connected to the server | 168 | -- sock: socket connected to the server |
156 | -- headers: response header fields | 169 | -- callback: function to receive chunks |
157 | -- Returns | 170 | -- Returns |
158 | -- body: a string containing the body of the document | 171 | -- nil if successfull or an error message in case of error |
159 | -- nil and error message in case of error | 172 | ----------------------------------------------------------------------------- |
160 | -- Obs: | 173 | local try_getbylength = function(sock, length, callback) |
161 | -- headers: headers might be modified by chunked transfer | 174 | while length > 0 do |
162 | ----------------------------------------------------------------------------- | 175 | local size = min(4096, length) |
163 | local get_body = function(sock, headers) | 176 | local chunk, err = sock:receive(size) |
164 | local body, err | 177 | if err then |
165 | if headers["transfer-encoding"] == "chunked" then | 178 | callback(nil, err) |
166 | body, err = %try_getchunked(sock) | 179 | return err |
167 | if err then return nil, err end | 180 | end |
168 | -- store extra entity headers | 181 | if not callback(chunk) then |
169 | --_, err = %get_headers(sock, headers) | 182 | sock:close() |
170 | --if err then return nil, err end | 183 | return "aborted by callback" |
171 | elseif headers["content-length"] then | 184 | end |
172 | body, err = %try_get(sock, tonumber(headers["content-length"])) | 185 | length = length - size |
173 | if err then return nil, err end | 186 | end |
187 | callback("", "done") | ||
188 | end | ||
189 | |||
190 | ----------------------------------------------------------------------------- | ||
191 | -- Receives a message body by content-length | ||
192 | -- Input | ||
193 | -- sock: socket connected to the server | ||
194 | -- callback: function to receive chunks | ||
195 | -- Returns | ||
196 | -- nil if successfull or an error message in case of error | ||
197 | ----------------------------------------------------------------------------- | ||
198 | local try_getuntilclosed = function(sock, callback) | ||
199 | local err | ||
200 | while 1 do | ||
201 | local chunk, err = sock:receive(4096) | ||
202 | if err == "closed" or not err then | ||
203 | if not callback(chunk) then | ||
204 | sock:close() | ||
205 | return "aborted by callback" | ||
206 | end | ||
207 | if err then break end | ||
208 | else | ||
209 | callback(nil, err) | ||
210 | return err | ||
211 | end | ||
212 | end | ||
213 | callback("", "done") | ||
214 | end | ||
215 | |||
216 | ----------------------------------------------------------------------------- | ||
217 | -- Receives http response body | ||
218 | -- Input | ||
219 | -- sock: socket connected to the server | ||
220 | -- resp_hdrs: response header fields | ||
221 | -- callback: function to receive chunks | ||
222 | -- Returns | ||
223 | -- nil if successfull or an error message in case of error | ||
224 | ----------------------------------------------------------------------------- | ||
225 | local try_getbody = function(sock, resp_hdrs, callback) | ||
226 | local err | ||
227 | if resp_hdrs["transfer-encoding"] == "chunked" then | ||
228 | -- get by chunked transfer-coding of message body | ||
229 | return %try_getchunked(sock, callback) | ||
230 | elseif tonumber(resp_hdrs["content-length"]) then | ||
231 | -- get by content-length | ||
232 | local length = tonumber(resp_hdrs["content-length"]) | ||
233 | return %try_getbylength(sock, length, callback) | ||
174 | else | 234 | else |
175 | -- get it all until connection closes! | 235 | -- get it all until connection closes |
176 | body, err = %try_get(sock, "*a") | 236 | return %try_getuntilclosed(sock, callback) |
177 | if err then return nil, err end | ||
178 | end | 237 | end |
179 | -- return whole body | ||
180 | return body | ||
181 | end | 238 | end |
182 | 239 | ||
183 | ----------------------------------------------------------------------------- | 240 | ----------------------------------------------------------------------------- |
@@ -219,57 +276,51 @@ local split_url = function(url, default) | |||
219 | end | 276 | end |
220 | 277 | ||
221 | ----------------------------------------------------------------------------- | 278 | ----------------------------------------------------------------------------- |
222 | -- Tries to send request body, using chunked transfer-encoding | ||
223 | -- Apache, for instance, accepts only 8kb of body in a post to a CGI script | ||
224 | -- if we use only the content-length header field... | ||
225 | -- Input | ||
226 | -- sock: socket connected to the server | ||
227 | -- body: body to be sent in request | ||
228 | -- Returns | ||
229 | -- err: nil in case of success, error message otherwise | ||
230 | ----------------------------------------------------------------------------- | ||
231 | local try_sendchunked = function(sock, body) | ||
232 | local wanted = strlen(body) | ||
233 | local first = 1 | ||
234 | local chunk_size | ||
235 | local err | ||
236 | while wanted > 0 do | ||
237 | chunk_size = min(wanted, 1024) | ||
238 | err = %try_send(sock, format("%x\r\n", chunk_size)) | ||
239 | if err then return err end | ||
240 | err = %try_send(sock, strsub(body, first, first + chunk_size - 1)) | ||
241 | if err then return err end | ||
242 | err = %try_send(sock, "\r\n") | ||
243 | if err then return err end | ||
244 | wanted = wanted - chunk_size | ||
245 | first = first + chunk_size | ||
246 | end | ||
247 | err = %try_send(sock, "0\r\n") | ||
248 | return err | ||
249 | end | ||
250 | |||
251 | ----------------------------------------------------------------------------- | ||
252 | -- Sends a http request message through socket | 279 | -- Sends a http request message through socket |
253 | -- Input | 280 | -- Input |
254 | -- sock: socket connected to the server | 281 | -- sock: socket connected to the server |
255 | -- method: request method to be used | 282 | -- method: request method to be used |
256 | -- path: url path | 283 | -- path: url path |
257 | -- headers: request headers to be sent | 284 | -- req_hdrs: request headers to be sent |
258 | -- body: request message body, if any | 285 | -- callback: callback to send request message body |
259 | -- Returns | 286 | -- Returns |
260 | -- err: nil in case of success, error message otherwise | 287 | -- err: nil in case of success, error message otherwise |
261 | ----------------------------------------------------------------------------- | 288 | ----------------------------------------------------------------------------- |
262 | local send_request = function(sock, method, path, headers, body) | 289 | local send_request = function(sock, method, path, req_hdrs, callback) |
263 | local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") | 290 | local chunk, size, done |
291 | -- send request line | ||
292 | local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") | ||
264 | if err then return err end | 293 | if err then return err end |
265 | for i, v in headers do | 294 | -- send request headers |
295 | for i, v in req_hdrs do | ||
266 | err = %try_send(sock, i .. ": " .. v .. "\r\n") | 296 | err = %try_send(sock, i .. ": " .. v .. "\r\n") |
267 | if err then return err end | 297 | if err then return err end |
268 | end | 298 | end |
299 | -- if there is a request message body, add content-length header | ||
300 | if callback then | ||
301 | chunk, size = callback() | ||
302 | if chunk and size then | ||
303 | err = %try_send(sock, "content-length: "..tostring(size).."\r\n") | ||
304 | if err then return err end | ||
305 | else | ||
306 | sock:close() | ||
307 | return size or "invalid callback return" | ||
308 | end | ||
309 | end | ||
310 | -- mark end of request headers | ||
269 | err = %try_send(sock, "\r\n") | 311 | err = %try_send(sock, "\r\n") |
270 | --if not err and body then err = %try_sendchunked(sock, body) end | 312 | if err then return err end |
271 | if not err and body then err = %try_send(sock, body) end | 313 | -- send message request body, getting it chunk by chunk from callback |
272 | return err | 314 | if callback then |
315 | done = 0 | ||
316 | while chunk and chunk ~= "" and done < size do | ||
317 | err = %try_send(sock, chunk) | ||
318 | if err then return err end | ||
319 | done = done + strlen(chunk) | ||
320 | chunk, err = callback() | ||
321 | end | ||
322 | if not chunk then return err end | ||
323 | end | ||
273 | end | 324 | end |
274 | 325 | ||
275 | ----------------------------------------------------------------------------- | 326 | ----------------------------------------------------------------------------- |
@@ -280,7 +331,7 @@ end | |||
280 | -- Returns | 331 | -- Returns |
281 | -- 1 if a message body should be processed, nil otherwise | 332 | -- 1 if a message body should be processed, nil otherwise |
282 | ----------------------------------------------------------------------------- | 333 | ----------------------------------------------------------------------------- |
283 | function has_responsebody(method, code) | 334 | function has_respbody(method, code) |
284 | if method == "HEAD" then return nil end | 335 | if method == "HEAD" then return nil end |
285 | if code == 204 or code == 304 then return nil end | 336 | if code == 204 or code == 304 then return nil end |
286 | if code >= 100 and code < 200 then return nil end | 337 | if code >= 100 and code < 200 then return nil end |
@@ -288,6 +339,11 @@ function has_responsebody(method, code) | |||
288 | end | 339 | end |
289 | 340 | ||
290 | ----------------------------------------------------------------------------- | 341 | ----------------------------------------------------------------------------- |
342 | -- We need base64 convertion routines for Basic Authentication Scheme | ||
343 | ----------------------------------------------------------------------------- | ||
344 | dofile("base64.lua") | ||
345 | |||
346 | ----------------------------------------------------------------------------- | ||
291 | -- Converts field names to lowercase and add message body size specification | 347 | -- Converts field names to lowercase and add message body size specification |
292 | -- Input | 348 | -- Input |
293 | -- headers: request header fields | 349 | -- headers: request header fields |
@@ -296,14 +352,12 @@ end | |||
296 | -- Returns | 352 | -- Returns |
297 | -- lower: a table with the same headers, but with lowercase field names | 353 | -- lower: a table with the same headers, but with lowercase field names |
298 | ----------------------------------------------------------------------------- | 354 | ----------------------------------------------------------------------------- |
299 | local fill_headers = function(headers, parsed, body) | 355 | local fill_hdrs = function(headers, parsed, body) |
300 | local lower = {} | 356 | local lower = {} |
301 | headers = headers or {} | 357 | headers = headers or {} |
302 | for i,v in headers do | 358 | for i,v in headers do |
303 | lower[strlower(i)] = v | 359 | lower[strlower(i)] = v |
304 | end | 360 | end |
305 | --if body then lower["transfer-encoding"] = "chunked" end | ||
306 | if body then lower["content-length"] = tostring(strlen(body)) end | ||
307 | lower["connection"] = "close" | 361 | lower["connection"] = "close" |
308 | lower["host"] = parsed.host | 362 | lower["host"] = parsed.host |
309 | lower["user-agent"] = %USERAGENT | 363 | lower["user-agent"] = %USERAGENT |
@@ -315,60 +369,92 @@ local fill_headers = function(headers, parsed, body) | |||
315 | end | 369 | end |
316 | 370 | ||
317 | ----------------------------------------------------------------------------- | 371 | ----------------------------------------------------------------------------- |
318 | -- We need base64 convertion routines for Basic Authentication Scheme | 372 | -- Sends a HTTP request and retrieves the server reply using callbacks to |
319 | ----------------------------------------------------------------------------- | 373 | -- send the request body and receive the response body |
320 | dofile("base64.lua") | ||
321 | |||
322 | ----------------------------------------------------------------------------- | ||
323 | -- Sends a HTTP request and retrieves the server reply | ||
324 | -- Input | 374 | -- Input |
325 | -- method: "GET", "PUT", "POST" etc | 375 | -- method: "GET", "PUT", "POST" etc |
326 | -- url: target uniform resource locator | 376 | -- url: target uniform resource locator |
327 | -- headers: request headers to send | 377 | -- req_hdrs: request headers to send |
328 | -- body: request message body | 378 | -- req_body: function to return request message body |
379 | -- resp_body: function to receive response message body | ||
329 | -- Returns | 380 | -- Returns |
330 | -- resp_body: response message body, if successfull | ||
331 | -- resp_hdrs: response header fields received, if sucessfull | 381 | -- resp_hdrs: response header fields received, if sucessfull |
332 | -- line: server response status line, if successfull | 382 | -- resp_line: server response status line, if successfull |
333 | -- err: error message if any | 383 | -- err: error message if any |
334 | ----------------------------------------------------------------------------- | 384 | ----------------------------------------------------------------------------- |
335 | function http_request(method, url, headers, body) | 385 | function http_requestindirect(method, url, req_hdrs, req_body, resp_body) |
336 | local sock, err | 386 | local sock, err |
337 | local resp_hdrs, response_body | 387 | local resp_hdrs |
338 | local line, code | 388 | local resp_line, resp_code |
339 | -- get url components | 389 | -- get url components |
340 | local parsed = %split_url(url, {port = %PORT, path ="/"}) | 390 | local parsed = %split_url(url, {port = %PORT, path ="/"}) |
341 | -- methods are case sensitive | 391 | -- methods are case sensitive |
342 | method = strupper(method) | 392 | method = strupper(method) |
343 | -- fill default headers | 393 | -- fill default headers |
344 | headers = %fill_headers(headers, parsed, body) | 394 | req_hdrs = %fill_hdrs(req_hdrs, parsed) |
345 | -- try connection | 395 | -- try connection |
346 | sock, err = connect(parsed.host, parsed.port) | 396 | sock, err = connect(parsed.host, parsed.port) |
347 | if not sock then return nil, nil, nil, err end | 397 | if not sock then return nil, nil, err end |
348 | -- set connection timeout | 398 | -- set connection timeout |
349 | sock:timeout(%TIMEOUT) | 399 | sock:timeout(%TIMEOUT) |
350 | -- send request | 400 | -- send request |
351 | err = %send_request(sock, method, parsed.path, headers, body) | 401 | err = %send_request(sock, method, parsed.path, req_hdrs, req_body) |
352 | if err then return nil, nil, nil, err end | 402 | if err then return nil, nil, err end |
353 | -- get server message | 403 | -- get server message |
354 | code, line, err = %get_status(sock) | 404 | resp_code, resp_line, err = %get_status(sock) |
355 | if err then return nil, nil, nil, err end | 405 | if err then return nil, nil, err end |
356 | -- deal with reply | 406 | -- deal with reply |
357 | resp_hdrs, err = %get_headers(sock, {}) | 407 | resp_hdrs, err = %get_hdrs(sock, {}) |
358 | if err then return nil, nil, line, err end | 408 | if err then return nil, line, err end |
359 | -- get body if status and method allow one | 409 | -- did we get a redirect? should we automatically retry? |
360 | if has_responsebody(method, code) then | 410 | if (resp_code == 301 or resp_code == 302) and |
361 | resp_body, err = %get_body(sock, resp_hdrs) | 411 | (method == "GET" or method == "HEAD") then |
362 | if err then return nil, resp_hdrs, line, err end | 412 | sock:close() |
413 | return http_requestindirect(method, resp_hdrs["location"], req_hdrs, | ||
414 | req_body, resp_body) | ||
415 | end | ||
416 | -- get body if status and method combination allow one | ||
417 | if has_respbody(method, resp_code) then | ||
418 | err = %try_getbody(sock, resp_hdrs, resp_body) | ||
419 | if err then return resp_hdrs, resp_line, err end | ||
363 | end | 420 | end |
364 | sock:close() | 421 | sock:close() |
365 | -- should we automatically retry? | 422 | return resp_hdrs, resp_line |
366 | if (code == 301 or code == 302) then | 423 | end |
367 | if (method == "GET" or method == "HEAD") and resp_hdrs["location"] then | 424 | |
368 | return http_request(method, resp_hdrs["location"], headers, body) | 425 | ----------------------------------------------------------------------------- |
369 | else return nil, resp_hdrs, line end | 426 | -- Sends a HTTP request and retrieves the server reply |
427 | -- Input | ||
428 | -- method: "GET", "PUT", "POST" etc | ||
429 | -- url: target uniform resource locator | ||
430 | -- headers: request headers to send | ||
431 | -- body: request message body | ||
432 | -- Returns | ||
433 | -- resp_body: response message body, if successfull | ||
434 | -- resp_hdrs: response header fields received, if sucessfull | ||
435 | -- resp_line: server response status line, if successfull | ||
436 | -- err: error message if any | ||
437 | ----------------------------------------------------------------------------- | ||
438 | function http_request(method, url, req_hdrs, body) | ||
439 | local resp_hdrs, resp_line, err | ||
440 | local req_callback = function() | ||
441 | return %body, strlen(%body) | ||
370 | end | 442 | end |
371 | return resp_body, resp_hdrs, line | 443 | local resp_aux = { resp_body = "" } |
444 | local resp_callback = function(chunk, err) | ||
445 | if not chunk then | ||
446 | %resp_aux.resp_body = nil | ||
447 | %resp_aux.err = err | ||
448 | return nil | ||
449 | end | ||
450 | %resp_aux.resp_body = %resp_aux.resp_body .. chunk | ||
451 | return 1 | ||
452 | end | ||
453 | if not body then resp_callback = nil end | ||
454 | resp_hdrs, resp_line, err = http_requestindirect(method, url, req_hdrs, | ||
455 | req_callback, resp_callback) | ||
456 | if err then return nil, resp_hdrs, resp_line, err | ||
457 | else return resp_aux.resp_body, resp_hdrs, resp_line, resp_aux.err end | ||
372 | end | 458 | end |
373 | 459 | ||
374 | ----------------------------------------------------------------------------- | 460 | ----------------------------------------------------------------------------- |