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 | |
| 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.
| -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 | ----------------------------------------------------------------------------- |
