diff options
| -rw-r--r-- | src/http.lua | 556 |
1 files changed, 311 insertions, 245 deletions
diff --git a/src/http.lua b/src/http.lua index 576118e..e68cf22 100644 --- a/src/http.lua +++ b/src/http.lua | |||
| @@ -1,22 +1,32 @@ | |||
| 1 | ----------------------------------------------------------------------------- | 1 | ----------------------------------------------------------------------------- |
| 2 | -- Full HTTP/1.1 client support for the Lua language using the | 2 | -- Full HTTP/1.1 client support for the Lua language using the |
| 3 | -- LuaSocket 1.2 toolkit. | 3 | -- LuaSocket 1.4a toolkit. |
| 4 | -- Author: Diego Nehab | 4 | -- Author: Diego Nehab |
| 5 | -- Date: 26/12/2000 | 5 | -- Date: 26/12/2000 |
| 6 | -- Conforming to: RFC 2068 | 6 | -- Conforming to: RFC 2068 |
| 7 | ----------------------------------------------------------------------------- | 7 | ----------------------------------------------------------------------------- |
| 8 | 8 | ||
| 9 | local Public, Private = {}, {} | ||
| 10 | HTTP = Public | ||
| 11 | |||
| 9 | ----------------------------------------------------------------------------- | 12 | ----------------------------------------------------------------------------- |
| 10 | -- Program constants | 13 | -- Program constants |
| 11 | ----------------------------------------------------------------------------- | 14 | ----------------------------------------------------------------------------- |
| 12 | -- connection timeout in seconds | 15 | -- connection timeout in seconds |
| 13 | local TIMEOUT = 60 | 16 | Private.TIMEOUT = 60 |
| 14 | -- default port for document retrieval | 17 | -- default port for document retrieval |
| 15 | local PORT = 80 | 18 | Private.PORT = 80 |
| 16 | -- user agent field sent in request | 19 | -- user agent field sent in request |
| 17 | local USERAGENT = "LuaSocket 1.3b HTTP 1.1" | 20 | Private.USERAGENT = "LuaSocket 1.4a" |
| 18 | -- block size used in transfers | 21 | -- block size used in transfers |
| 19 | local BLOCKSIZE = 8192 | 22 | Private.BLOCKSIZE = 8192 |
| 23 | |||
| 24 | ----------------------------------------------------------------------------- | ||
| 25 | -- Required libraries | ||
| 26 | ----------------------------------------------------------------------------- | ||
| 27 | dofile "buffer.lua" | ||
| 28 | dofile "url.lua" | ||
| 29 | dofile "encode.lua" | ||
| 20 | 30 | ||
| 21 | ----------------------------------------------------------------------------- | 31 | ----------------------------------------------------------------------------- |
| 22 | -- Tries to get a pattern from the server and closes socket on error | 32 | -- Tries to get a pattern from the server and closes socket on error |
| @@ -26,9 +36,9 @@ local BLOCKSIZE = 8192 | |||
| 26 | -- data: line received or nil in case of error | 36 | -- data: line received or nil in case of error |
| 27 | -- err: error message if any | 37 | -- err: error message if any |
| 28 | ----------------------------------------------------------------------------- | 38 | ----------------------------------------------------------------------------- |
| 29 | local try_get = function(...) | 39 | function Private.try_receive(...) |
| 30 | local sock = arg[1] | 40 | local sock = arg[1] |
| 31 | local data, err = call(sock.receive, arg) | 41 | local data, err = call(sock.receive, arg) |
| 32 | if err then | 42 | if err then |
| 33 | sock:close() | 43 | sock:close() |
| 34 | return nil, err | 44 | return nil, err |
| @@ -43,8 +53,8 @@ end | |||
| 43 | -- Returns | 53 | -- Returns |
| 44 | -- err: error message if any, nil if successfull | 54 | -- err: error message if any, nil if successfull |
| 45 | ----------------------------------------------------------------------------- | 55 | ----------------------------------------------------------------------------- |
| 46 | local try_send = function(sock, data) | 56 | function Private.try_send(sock, data) |
| 47 | err = sock:send(data) | 57 | local err = sock:send(data) |
| 48 | if err then sock:close() end | 58 | if err then sock:close() end |
| 49 | return err | 59 | return err |
| 50 | end | 60 | end |
| @@ -56,13 +66,14 @@ end | |||
| 56 | -- Returns | 66 | -- Returns |
| 57 | -- code: integer with status code | 67 | -- code: integer with status code |
| 58 | ----------------------------------------------------------------------------- | 68 | ----------------------------------------------------------------------------- |
| 59 | local get_statuscode = function(line) | 69 | function Private.get_statuscode(line) |
| 60 | local _,_, code = strfind(line, " (%d%d%d) ") | 70 | local code, _ |
| 71 | _, _, code = strfind(line, "HTTP/%d*%.%d* (%d%d%d)") | ||
| 61 | return tonumber(code) | 72 | return tonumber(code) |
| 62 | end | 73 | end |
| 63 | 74 | ||
| 64 | ----------------------------------------------------------------------------- | 75 | ----------------------------------------------------------------------------- |
| 65 | -- Receive server reply messages | 76 | -- Receive server reply messages, parsing status code |
| 66 | -- Input | 77 | -- Input |
| 67 | -- sock: socket connected to the server | 78 | -- sock: socket connected to the server |
| 68 | -- Returns | 79 | -- Returns |
| @@ -70,29 +81,29 @@ end | |||
| 70 | -- line: full http status line | 81 | -- line: full http status line |
| 71 | -- err: error message if any | 82 | -- err: error message if any |
| 72 | ----------------------------------------------------------------------------- | 83 | ----------------------------------------------------------------------------- |
| 73 | local get_status = function(sock) | 84 | function Private.receive_status(sock) |
| 74 | local line, err | 85 | local line, err |
| 75 | line, err = %try_get(sock) | 86 | line, err = %Private.try_receive(sock) |
| 76 | if not err then return %get_statuscode(line), line | 87 | if not err then return %Private.get_statuscode(line), line |
| 77 | else return nil, nil, err end | 88 | else return nil, nil, err end |
| 78 | end | 89 | end |
| 79 | 90 | ||
| 80 | ----------------------------------------------------------------------------- | 91 | ----------------------------------------------------------------------------- |
| 81 | -- Receive and parse responce header fields | 92 | -- Receive and parse response header fields |
| 82 | -- Input | 93 | -- Input |
| 83 | -- sock: socket connected to the server | 94 | -- sock: socket connected to the server |
| 84 | -- hdrs: a table that might already contain headers | 95 | -- headers: a table that might already contain headers |
| 85 | -- Returns | 96 | -- Returns |
| 86 | -- hdrs: a table with all headers fields in the form | 97 | -- headers: a table with all headers fields in the form |
| 87 | -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} | 98 | -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} |
| 88 | -- all name_i are lowercase | 99 | -- all name_i are lowercase |
| 89 | -- nil and error message in case of error | 100 | -- nil and error message in case of error |
| 90 | ----------------------------------------------------------------------------- | 101 | ----------------------------------------------------------------------------- |
| 91 | local get_hdrs = function(sock, hdrs) | 102 | function Private.receive_headers(sock, headers) |
| 92 | local line, err | 103 | local line, err |
| 93 | local name, value | 104 | local name, value, _ |
| 94 | -- get first line | 105 | -- get first line |
| 95 | line, err = %try_get(sock) | 106 | line, err = %Private.try_receive(sock) |
| 96 | if err then return nil, err end | 107 | if err then return nil, err end |
| 97 | -- headers go until a blank line is found | 108 | -- headers go until a blank line is found |
| 98 | while line ~= "" do | 109 | while line ~= "" do |
| @@ -104,34 +115,35 @@ local get_hdrs = function(sock, hdrs) | |||
| 104 | end | 115 | end |
| 105 | name = strlower(name) | 116 | name = strlower(name) |
| 106 | -- get next line (value might be folded) | 117 | -- get next line (value might be folded) |
| 107 | line, err = %try_get(sock) | 118 | line, err = %Private.try_receive(sock) |
| 108 | if err then return nil, err end | 119 | if err then return nil, err end |
| 109 | -- unfold any folded values | 120 | -- unfold any folded values |
| 110 | while not err and strfind(line, "^%s") do | 121 | while not err and strfind(line, "^%s") do |
| 111 | value = value .. line | 122 | value = value .. line |
| 112 | line, err = %try_get(sock) | 123 | line, err = %Private.try_receive(sock) |
| 113 | if err then return nil, err end | 124 | if err then return nil, err end |
| 114 | end | 125 | end |
| 115 | -- save pair in table | 126 | -- save pair in table |
| 116 | if hdrs[name] then hdrs[name] = hdrs[name] .. ", " .. value | 127 | if headers[name] then headers[name] = headers[name] .. ", " .. value |
| 117 | else hdrs[name] = value end | 128 | else headers[name] = value end |
| 118 | end | 129 | end |
| 119 | return hdrs | 130 | return headers |
| 120 | end | 131 | end |
| 121 | 132 | ||
| 122 | ----------------------------------------------------------------------------- | 133 | ----------------------------------------------------------------------------- |
| 123 | -- Receives a chunked message body | 134 | -- Receives a chunked message body |
| 124 | -- Input | 135 | -- Input |
| 125 | -- sock: socket connected to the server | 136 | -- sock: socket connected to the server |
| 137 | -- headers: header set in which to include trailer headers | ||
| 126 | -- receive_cb: function to receive chunks | 138 | -- receive_cb: function to receive chunks |
| 127 | -- Returns | 139 | -- Returns |
| 128 | -- nil if successfull or an error message in case of error | 140 | -- nil if successfull or an error message in case of error |
| 129 | ----------------------------------------------------------------------------- | 141 | ----------------------------------------------------------------------------- |
| 130 | local try_getchunked = function(sock, receive_cb) | 142 | function Private.receivebody_bychunks(sock, headers, receive_cb) |
| 131 | local chunk, size, line, err, go, uerr, _ | 143 | local chunk, size, line, err, go, uerr, _ |
| 132 | repeat | 144 | while 1 do |
| 133 | -- get chunk size, skip extention | 145 | -- get chunk size, skip extention |
| 134 | line, err = %try_get(sock) | 146 | line, err = %Private.try_receive(sock) |
| 135 | if err then | 147 | if err then |
| 136 | local _, uerr = receive_cb(nil, err) | 148 | local _, uerr = receive_cb(nil, err) |
| 137 | return uerr or err | 149 | return uerr or err |
| @@ -143,8 +155,10 @@ local try_getchunked = function(sock, receive_cb) | |||
| 143 | _, uerr = receive_cb(nil, err) | 155 | _, uerr = receive_cb(nil, err) |
| 144 | return uerr or err | 156 | return uerr or err |
| 145 | end | 157 | end |
| 158 | -- was it the last chunk? | ||
| 159 | if size <= 0 then break end | ||
| 146 | -- get chunk | 160 | -- get chunk |
| 147 | chunk, err = %try_get(sock, size) | 161 | chunk, err = %Private.try_receive(sock, size) |
| 148 | if err then | 162 | if err then |
| 149 | _, uerr = receive_cb(nil, err) | 163 | _, uerr = receive_cb(nil, err) |
| 150 | return uerr or err | 164 | return uerr or err |
| @@ -155,13 +169,21 @@ local try_getchunked = function(sock, receive_cb) | |||
| 155 | sock:close() | 169 | sock:close() |
| 156 | return uerr or "aborted by callback" | 170 | return uerr or "aborted by callback" |
| 157 | end | 171 | end |
| 158 | -- skip blank line | 172 | -- skip CRLF on end of chunk |
| 159 | _, err = %try_get(sock) | 173 | _, err = %Private.try_receive(sock) |
| 160 | if err then | 174 | if err then |
| 161 | _, uerr = receive_cb(nil, err) | 175 | _, uerr = receive_cb(nil, err) |
| 162 | return uerr or err | 176 | return uerr or err |
| 163 | end | 177 | end |
| 164 | until size <= 0 | 178 | end |
| 179 | -- the server should not send trailer headers because we didn't send a | ||
| 180 | -- header informing it we know how to deal with them. we do not risk | ||
| 181 | -- being caught unprepaired. | ||
| 182 | headers, err = %Private.receive_headers(sock, headers) | ||
| 183 | if err then | ||
| 184 | _, uerr = receive_cb(nil, err) | ||
| 185 | return uerr or err | ||
| 186 | end | ||
| 165 | -- let callback know we are done | 187 | -- let callback know we are done |
| 166 | _, uerr = receive_cb("") | 188 | _, uerr = receive_cb("") |
| 167 | return uerr | 189 | return uerr |
| @@ -175,10 +197,10 @@ end | |||
| 175 | -- Returns | 197 | -- Returns |
| 176 | -- nil if successfull or an error message in case of error | 198 | -- nil if successfull or an error message in case of error |
| 177 | ----------------------------------------------------------------------------- | 199 | ----------------------------------------------------------------------------- |
| 178 | local try_getbylength = function(sock, length, receive_cb) | 200 | function Private.receivebody_bylength(sock, length, receive_cb) |
| 179 | local uerr, go | 201 | local uerr, go |
| 180 | while length > 0 do | 202 | while length > 0 do |
| 181 | local size = min(%BLOCKSIZE, length) | 203 | local size = min(%Private.BLOCKSIZE, length) |
| 182 | local chunk, err = sock:receive(size) | 204 | local chunk, err = sock:receive(size) |
| 183 | if err then | 205 | if err then |
| 184 | go, uerr = receive_cb(nil, err) | 206 | go, uerr = receive_cb(nil, err) |
| @@ -203,10 +225,10 @@ end | |||
| 203 | -- Returns | 225 | -- Returns |
| 204 | -- nil if successfull or an error message in case of error | 226 | -- nil if successfull or an error message in case of error |
| 205 | ----------------------------------------------------------------------------- | 227 | ----------------------------------------------------------------------------- |
| 206 | local try_getuntilclosed = function(sock, receive_cb) | 228 | function Private.receivebody_untilclosed(sock, receive_cb) |
| 207 | local err, go, uerr | 229 | local err, go, uerr |
| 208 | while 1 do | 230 | while 1 do |
| 209 | local chunk, err = sock:receive(%BLOCKSIZE) | 231 | local chunk, err = sock:receive(%Private.BLOCKSIZE) |
| 210 | if err == "closed" or not err then | 232 | if err == "closed" or not err then |
| 211 | go, uerr = receive_cb(chunk) | 233 | go, uerr = receive_cb(chunk) |
| 212 | if not go then | 234 | if not go then |
| @@ -227,62 +249,36 @@ end | |||
| 227 | -- Receives http response body | 249 | -- Receives http response body |
| 228 | -- Input | 250 | -- Input |
| 229 | -- sock: socket connected to the server | 251 | -- sock: socket connected to the server |
| 230 | -- resp_hdrs: response header fields | 252 | -- headers: response header fields |
| 231 | -- receive_cb: function to receive chunks | 253 | -- receive_cb: function to receive chunks |
| 232 | -- Returns | 254 | -- Returns |
| 233 | -- nil if successfull or an error message in case of error | 255 | -- nil if successfull or an error message in case of error |
| 234 | ----------------------------------------------------------------------------- | 256 | ----------------------------------------------------------------------------- |
| 235 | local try_getbody = function(sock, resp_hdrs, receive_cb) | 257 | function Private.receive_body(sock, headers, receive_cb) |
| 236 | local err | 258 | local te = headers["transfer-encoding"] |
| 237 | if resp_hdrs["transfer-encoding"] == "chunked" then | 259 | if te and te ~= "identity" then |
| 238 | -- get by chunked transfer-coding of message body | 260 | -- get by chunked transfer-coding of message body |
| 239 | return %try_getchunked(sock, receive_cb) | 261 | return %Private.receivebody_bychunks(sock, headers, receive_cb) |
| 240 | elseif tonumber(resp_hdrs["content-length"]) then | 262 | elseif tonumber(headers["content-length"]) then |
| 241 | -- get by content-length | 263 | -- get by content-length |
| 242 | local length = tonumber(resp_hdrs["content-length"]) | 264 | local length = tonumber(headers["content-length"]) |
| 243 | return %try_getbylength(sock, length, receive_cb) | 265 | return %Private.receivebody_bylength(sock, length, receive_cb) |
| 244 | else | 266 | else |
| 245 | -- get it all until connection closes | 267 | -- get it all until connection closes |
| 246 | return %try_getuntilclosed(sock, receive_cb) | 268 | return %Private.receivebody_untilclosed(sock, receive_cb) |
| 247 | end | 269 | end |
| 248 | end | 270 | end |
| 249 | 271 | ||
| 250 | ----------------------------------------------------------------------------- | 272 | ----------------------------------------------------------------------------- |
| 251 | -- Parses a url and returns its scheme, user, password, host, port | 273 | -- Drop http response body |
| 252 | -- and path components, according to RFC 1738 | ||
| 253 | -- Input | 274 | -- Input |
| 254 | -- url: uniform resource locator of request | 275 | -- sock: socket connected to the server |
| 255 | -- default: table containing default values to be returned | 276 | -- headers: response header fields |
| 256 | -- Returns | 277 | -- Returns |
| 257 | -- table with the following fields: | 278 | -- nil if successfull or an error message in case of error |
| 258 | -- host: host to connect | 279 | ----------------------------------------------------------------------------- |
| 259 | -- path: url path | 280 | function Private.drop_body(sock, headers) |
| 260 | -- port: host port to connect | 281 | return %Private.receive_body(sock, headers, function (c, e) return 1 end) |
| 261 | -- user: user name | ||
| 262 | -- pass: password | ||
| 263 | -- scheme: protocol | ||
| 264 | ----------------------------------------------------------------------------- | ||
| 265 | local split_url = function(url, default) | ||
| 266 | -- initialize default parameters | ||
| 267 | local parsed = default or {} | ||
| 268 | -- get scheme | ||
| 269 | url = gsub(url, "^(.+)://", function (s) %parsed.scheme = s end) | ||
| 270 | -- get user name and password. both can be empty! | ||
| 271 | -- moreover, password can be ommited | ||
| 272 | url = gsub(url, "^([^@:/]*)(:?)([^:@/]-)@", function (u, c, p) | ||
| 273 | %parsed.user = u | ||
| 274 | -- there can be an empty password, but the ':' has to be there | ||
| 275 | -- or else there is no password | ||
| 276 | %parsed.pass = nil -- kill default password | ||
| 277 | if c == ":" then %parsed.pass = p end | ||
| 278 | end) | ||
| 279 | -- get host | ||
| 280 | url = gsub(url, "^([%w%.%-]+)", function (h) %parsed.host = h end) | ||
| 281 | -- get port if any | ||
| 282 | url = gsub(url, "^:(%d+)", function (p) %parsed.port = p end) | ||
| 283 | -- whatever is left is the path | ||
| 284 | if url ~= "" then parsed.path = url end | ||
| 285 | return parsed | ||
| 286 | end | 282 | end |
| 287 | 283 | ||
| 288 | ----------------------------------------------------------------------------- | 284 | ----------------------------------------------------------------------------- |
| @@ -294,20 +290,20 @@ end | |||
| 294 | -- Returns | 290 | -- Returns |
| 295 | -- nil if successfull, or an error message in case of error | 291 | -- nil if successfull, or an error message in case of error |
| 296 | ----------------------------------------------------------------------------- | 292 | ----------------------------------------------------------------------------- |
| 297 | local try_sendindirect = function(data, send_cb, chunk, size) | 293 | function Private.send_indirect(data, send_cb, chunk, size) |
| 298 | local sent, err | 294 | local sent, err |
| 299 | sent = 0 | 295 | sent = 0 |
| 300 | while 1 do | 296 | while 1 do |
| 301 | if type(chunk) ~= "string" or type(size) ~= "number" then | 297 | if type(chunk) ~= "string" or type(size) ~= "number" then |
| 302 | data:close() | 298 | data:close() |
| 303 | if not chunk and type(size) == "string" then return size | 299 | if not chunk and type(size) == "string" then return size |
| 304 | else return "invalid callback return" end | 300 | else return "invalid callback return" end |
| 305 | end | 301 | end |
| 306 | err = data:send(chunk) | 302 | err = data:send(chunk) |
| 307 | if err then | 303 | if err then |
| 308 | data:close() | 304 | data:close() |
| 309 | return err | 305 | return err |
| 310 | end | 306 | end |
| 311 | sent = sent + strlen(chunk) | 307 | sent = sent + strlen(chunk) |
| 312 | if sent >= size then break end | 308 | if sent >= size then break end |
| 313 | chunk, size = send_cb() | 309 | chunk, size = send_cb() |
| @@ -320,243 +316,313 @@ end | |||
| 320 | -- sock: socket connected to the server | 316 | -- sock: socket connected to the server |
| 321 | -- method: request method to be used | 317 | -- method: request method to be used |
| 322 | -- path: url path | 318 | -- path: url path |
| 323 | -- req_hdrs: request headers to be sent | 319 | -- headers: request headers to be sent |
| 324 | -- req_body_cb: callback to send request message body | 320 | -- body_cb: callback to send request message body |
| 325 | -- Returns | 321 | -- Returns |
| 326 | -- err: nil in case of success, error message otherwise | 322 | -- err: nil in case of success, error message otherwise |
| 327 | ----------------------------------------------------------------------------- | 323 | ----------------------------------------------------------------------------- |
| 328 | local send_request = function(sock, method, path, req_hdrs, req_body_cb) | 324 | function Private.send_request(sock, method, path, headers, body_cb) |
| 329 | local chunk, size, done, err | 325 | local chunk, size, done, err |
| 330 | -- send request line | 326 | -- send request line |
| 331 | err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") | 327 | err = %Private.try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") |
| 332 | if err then return err end | 328 | if err then return err end |
| 333 | -- if there is a request message body, add content-length header | 329 | -- if there is a request message body, add content-length header |
| 334 | if req_body_cb then | 330 | if body_cb then |
| 335 | chunk, size = req_body_cb() | 331 | chunk, size = body_cb() |
| 336 | if type(chunk) == "string" and type(size) == "number" then | 332 | if type(chunk) == "string" and type(size) == "number" then |
| 337 | req_hdrs["content-length"] = tostring(size) | 333 | headers["content-length"] = tostring(size) |
| 338 | else | 334 | else |
| 339 | sock:close() | 335 | sock:close() |
| 340 | if not chunk and type(size) == "string" then return size | 336 | if not chunk and type(size) == "string" then return size |
| 341 | else return "invalid callback return" end | 337 | else return "invalid callback return" end |
| 342 | end | 338 | end |
| 343 | end | 339 | end |
| 344 | -- send request headers | 340 | -- send request headers |
| 345 | for i, v in req_hdrs do | 341 | for i, v in headers do |
| 346 | err = %try_send(sock, i .. ": " .. v .. "\r\n") | 342 | err = %Private.try_send(sock, i .. ": " .. v .. "\r\n") |
| 347 | if err then return err end | 343 | if err then return err end |
| 348 | end | 344 | end |
| 349 | -- mark end of request headers | 345 | -- mark end of request headers |
| 350 | err = %try_send(sock, "\r\n") | 346 | err = %Private.try_send(sock, "\r\n") |
| 351 | if err then return err end | 347 | if err then return err end |
| 352 | -- send request message body, if any | 348 | -- send request message body, if any |
| 353 | if req_body_cb then | 349 | if body_cb then |
| 354 | return %try_sendindirect(sock, req_body_cb, chunk, size) | 350 | return %Private.send_indirect(sock, body_cb, chunk, size) |
| 355 | end | 351 | end |
| 356 | end | 352 | end |
| 357 | 353 | ||
| 358 | ----------------------------------------------------------------------------- | 354 | ----------------------------------------------------------------------------- |
| 359 | -- Determines if we should read a message body from the server response | 355 | -- Determines if we should read a message body from the server response |
| 360 | -- Input | 356 | -- Input |
| 361 | -- method: method used in request | 357 | -- request: a table with the original request information |
| 362 | -- code: server response status code | 358 | -- response: a table with the server response information |
| 363 | -- Returns | 359 | -- Returns |
| 364 | -- 1 if a message body should be processed, nil otherwise | 360 | -- 1 if a message body should be processed, nil otherwise |
| 365 | ----------------------------------------------------------------------------- | 361 | ----------------------------------------------------------------------------- |
| 366 | function has_respbody(method, code) | 362 | function Private.has_body(request, response) |
| 367 | if method == "HEAD" then return nil end | 363 | if request.method == "HEAD" then return nil end |
| 368 | if code == 204 or code == 304 then return nil end | 364 | if response.code == 204 or response.code == 304 then return nil end |
| 369 | if code >= 100 and code < 200 then return nil end | 365 | if response.code >= 100 and response.code < 200 then return nil end |
| 370 | return 1 | 366 | return 1 |
| 371 | end | 367 | end |
| 372 | 368 | ||
| 373 | ----------------------------------------------------------------------------- | 369 | ----------------------------------------------------------------------------- |
| 374 | -- We need base64 convertion routines for Basic Authentication Scheme | ||
| 375 | ----------------------------------------------------------------------------- | ||
| 376 | dofile("base64.lua") | ||
| 377 | |||
| 378 | ----------------------------------------------------------------------------- | ||
| 379 | -- Converts field names to lowercase and adds a few needed headers | 370 | -- Converts field names to lowercase and adds a few needed headers |
| 380 | -- Input | 371 | -- Input |
| 381 | -- hdrs: request header fields | 372 | -- headers: request header fields |
| 382 | -- parsed: parsed url components | 373 | -- parsed: parsed url components |
| 383 | -- Returns | 374 | -- Returns |
| 384 | -- lower: a table with the same headers, but with lowercase field names | 375 | -- lower: a table with the same headers, but with lowercase field names |
| 385 | ----------------------------------------------------------------------------- | 376 | ----------------------------------------------------------------------------- |
| 386 | local fill_hdrs = function(hdrs, parsed) | 377 | function Private.fill_headers(headers, parsed) |
| 387 | local lower = {} | 378 | local lower = {} |
| 388 | hdrs = hdrs or {} | 379 | headers = headers or {} |
| 389 | for i,v in hdrs do | 380 | -- set default headers |
| 381 | lower["user-agent"] = %Private.USERAGENT | ||
| 382 | lower["host"] = parsed.host | ||
| 383 | -- override with user values | ||
| 384 | for i,v in headers do | ||
| 390 | lower[strlower(i)] = v | 385 | lower[strlower(i)] = v |
| 391 | end | 386 | end |
| 387 | -- this cannot be overriden | ||
| 392 | lower["connection"] = "close" | 388 | lower["connection"] = "close" |
| 393 | lower["host"] = parsed.host | ||
| 394 | lower["user-agent"] = %USERAGENT | ||
| 395 | if parsed.user and parsed.pass then -- Basic Authentication | ||
| 396 | lower["authorization"] = "Basic ".. | ||
| 397 | base64(parsed.user .. ":" .. parsed.pass) | ||
| 398 | end | ||
| 399 | return lower | 389 | return lower |
| 400 | end | 390 | end |
| 401 | 391 | ||
| 402 | ----------------------------------------------------------------------------- | 392 | ----------------------------------------------------------------------------- |
| 403 | -- Sends a HTTP request and retrieves the server reply using callbacks to | 393 | -- Decides wether we should follow retry with authorization formation |
| 404 | -- send the request body and receive the response body | ||
| 405 | -- Input | 394 | -- Input |
| 406 | -- method: "GET", "PUT", "POST" etc | 395 | -- request: a table with the original request information |
| 407 | -- url: target uniform resource locator | 396 | -- parsed: parsed request url |
| 408 | -- resp_body_cb: response message body receive callback | 397 | -- response: a table with the server response information |
| 409 | -- req_hdrs: request headers to send, or nil if none | ||
| 410 | -- req_body_cb: request message body send callback, or nil if none | ||
| 411 | -- stay: should we refrain from following a server redirect message? | ||
| 412 | -- Returns | 398 | -- Returns |
| 413 | -- resp_hdrs: response header fields received, or nil if failed | 399 | -- 1 if we should retry, nil otherwise |
| 414 | -- resp_line: server response status line, or nil if failed | 400 | ----------------------------------------------------------------------------- |
| 415 | -- err: error message, or nil if successfull | 401 | function Private.should_authorize(request, parsed, response) |
| 416 | ----------------------------------------------------------------------------- | 402 | -- if there has been an authorization attempt, it must have failed |
| 417 | function http_requestindirect(method, url, resp_body_cb, req_hdrs, | 403 | if request.headers["authorization"] then return nil end |
| 418 | req_body_cb, stay) | 404 | -- if we don't have authorization information, we can't retry |
| 419 | local sock, err | 405 | if parsed.user and parsed.password then return 1 |
| 420 | local resp_hdrs | 406 | else return nil end |
| 421 | local resp_line, resp_code | ||
| 422 | -- get url components | ||
| 423 | local parsed = %split_url(url, {port = %PORT, path ="/"}) | ||
| 424 | -- methods are case sensitive | ||
| 425 | method = strupper(method) | ||
| 426 | -- fill default headers | ||
| 427 | req_hdrs = %fill_hdrs(req_hdrs, parsed) | ||
| 428 | -- try connection | ||
| 429 | sock, err = connect(parsed.host, parsed.port) | ||
| 430 | if not sock then return nil, nil, err end | ||
| 431 | -- set connection timeout | ||
| 432 | sock:timeout(%TIMEOUT) | ||
| 433 | -- send request | ||
| 434 | err = %send_request(sock, method, parsed.path, req_hdrs, req_body_cb) | ||
| 435 | if err then return nil, nil, err end | ||
| 436 | -- get server message | ||
| 437 | resp_code, resp_line, err = %get_status(sock) | ||
| 438 | if err then return nil, nil, err end | ||
| 439 | -- deal with reply | ||
| 440 | resp_hdrs, err = %get_hdrs(sock, {}) | ||
| 441 | if err then return nil, line, err end | ||
| 442 | -- did we get a redirect? should we automatically retry? | ||
| 443 | if not stay and (resp_code == 301 or resp_code == 302) and | ||
| 444 | (method == "GET" or method == "HEAD") then | ||
| 445 | sock:close() | ||
| 446 | return http_requestindirect(method, resp_hdrs["location"], | ||
| 447 | resp_body_cb, req_hdrs, req_body_cb, stay) | ||
| 448 | end | ||
| 449 | -- get response message body if status and method combination allow one | ||
| 450 | if has_respbody(method, resp_code) then | ||
| 451 | err = %try_getbody(sock, resp_hdrs, resp_body_cb) | ||
| 452 | if err then return resp_hdrs, resp_line, err end | ||
| 453 | end | ||
| 454 | sock:close() | ||
| 455 | return resp_hdrs, resp_line | ||
| 456 | end | 407 | end |
| 457 | 408 | ||
| 458 | ----------------------------------------------------------------------------- | 409 | ----------------------------------------------------------------------------- |
| 459 | -- We need fast concatenation routines for direct requests | 410 | -- Returns the result of retrying a request with authorization information |
| 460 | ----------------------------------------------------------------------------- | 411 | -- Input |
| 461 | dofile("buffer.lua") | 412 | -- request: a table with the original request information |
| 413 | -- parsed: parsed request url | ||
| 414 | -- response: a table with the server response information | ||
| 415 | -- Returns | ||
| 416 | -- response: result of target redirection | ||
| 417 | ----------------------------------------------------------------------------- | ||
| 418 | function Private.authorize(request, parsed, response) | ||
| 419 | request.headers["authorization"] = "Basic " .. | ||
| 420 | base64(parsed.user .. ":" .. parsed.password) | ||
| 421 | local authorize = { | ||
| 422 | redirects = request.redirects, | ||
| 423 | method = request.method, | ||
| 424 | url = request.url, | ||
| 425 | body_cb = request.body_cb, | ||
| 426 | headers = request.headers | ||
| 427 | } | ||
| 428 | return %Public.request_indirect(authorize, response) | ||
| 429 | end | ||
| 462 | 430 | ||
| 463 | ----------------------------------------------------------------------------- | 431 | ----------------------------------------------------------------------------- |
| 464 | -- Sends a HTTP request and retrieves the server reply | 432 | -- Decides wether we should follow a server redirect message |
| 465 | -- Input | 433 | -- Input |
| 466 | -- method: "GET", "PUT", "POST" etc | 434 | -- request: a table with the original request information |
| 467 | -- url: target uniform resource locator | 435 | -- response: a table with the server response information |
| 468 | -- req_hdrs: request headers to send, or nil if none | ||
| 469 | -- req_body: request message body as a string, or nil if none | ||
| 470 | -- stay: should we refrain from following a server redirect message? | ||
| 471 | -- Returns | 436 | -- Returns |
| 472 | -- resp_body: response message body, or nil if failed | 437 | -- 1 if we should redirect, nil otherwise |
| 473 | -- resp_hdrs: response header fields received, or nil if failed | 438 | ----------------------------------------------------------------------------- |
| 474 | -- resp_line: server response status line, or nil if failed | 439 | function Private.should_redirect(request, response) |
| 475 | -- err: error message, or nil if successfull | 440 | local follow = not request.stay |
| 476 | ----------------------------------------------------------------------------- | 441 | follow = follow and (response.code == 301 or response.code == 302) |
| 477 | function http_request(method, url, req_hdrs, req_body, stay) | 442 | follow = follow and (request.method == "GET" or request.method == "HEAD") |
| 478 | local resp_hdrs, resp_line, err | 443 | follow = follow and not (request.redirects and request.redirects >= 5) |
| 479 | local req_body_cb = function() | 444 | return follow |
| 480 | return %req_body, strlen(%req_body) | ||
| 481 | end | ||
| 482 | local resp_body = { buf = buf_create() } | ||
| 483 | local resp_body_cb = function(chunk, err) | ||
| 484 | if not chunk then %resp_body.buf = nil end | ||
| 485 | buf_addstring(%resp_body.buf, chunk) | ||
| 486 | return 1 | ||
| 487 | end | ||
| 488 | if not req_body then req_body_cb = nil end | ||
| 489 | resp_hdrs, resp_line, err = http_requestindirect(method, url, resp_body_cb, | ||
| 490 | req_hdrs, req_body_cb, stay) | ||
| 491 | return buf_getresult(resp_body.buf), resp_hdrs, resp_line, err | ||
| 492 | end | 445 | end |
| 493 | 446 | ||
| 494 | ----------------------------------------------------------------------------- | 447 | ----------------------------------------------------------------------------- |
| 495 | -- Retrieves a URL by the method "GET" | 448 | -- Returns the result of a request following a server redirect message. |
| 496 | -- Input | 449 | -- Input |
| 497 | -- url: target uniform resource locator | 450 | -- request: a table with the original request information |
| 498 | -- req_hdrs: request headers to send, or nil if none | 451 | -- response: a table with the following fields: |
| 499 | -- stay: should we refrain from following a server redirect message? | 452 | -- body_cb: response method body receive-callback |
| 500 | -- Returns | 453 | -- Returns |
| 501 | -- resp_body: response message body, or nil if failed | 454 | -- response: result of target redirection |
| 502 | -- resp_hdrs: response header fields received, or nil if failed | 455 | ----------------------------------------------------------------------------- |
| 503 | -- resp_line: server response status line, or nil if failed | 456 | function Private.redirect(request, response) |
| 504 | -- err: error message, or nil if successfull | 457 | local redirects = request.redirects or 0 |
| 458 | redirects = redirects + 1 | ||
| 459 | local redirect = { | ||
| 460 | redirects = redirects, | ||
| 461 | method = request.method, | ||
| 462 | -- the RFC says the redirect url has to be absolute, but some | ||
| 463 | -- servers do not respect that | ||
| 464 | url = URL.build_absolute(request.url,response.headers["location"]), | ||
| 465 | body_cb = request.body_cb, | ||
| 466 | headers = request.headers | ||
| 467 | } | ||
| 468 | return %Public.request_indirect(redirect, response) | ||
| 469 | end | ||
| 470 | |||
| 505 | ----------------------------------------------------------------------------- | 471 | ----------------------------------------------------------------------------- |
| 506 | function http_get(url, req_hdrs, stay) | 472 | -- Computes the request URI from the given URL |
| 507 | return http_request("GET", url, req_hdrs, stay) | 473 | -- Input |
| 474 | -- parsed: parsed url | ||
| 475 | -- Returns | ||
| 476 | -- uri: request URI for parsed URL | ||
| 477 | ----------------------------------------------------------------------------- | ||
| 478 | function Private.request_uri(parsed) | ||
| 479 | local uri = "" | ||
| 480 | if parsed.path then uri = uri .. parsed.path end | ||
| 481 | if parsed.params then uri = uri .. ";" .. parsed.params end | ||
| 482 | if parsed.query then uri = uri .. "?" .. parsed.query end | ||
| 483 | if parsed.fragment then uri = uri .. "#" .. parsed.fragment end | ||
| 484 | return uri | ||
| 508 | end | 485 | end |
| 509 | 486 | ||
| 510 | ----------------------------------------------------------------------------- | 487 | ----------------------------------------------------------------------------- |
| 511 | -- Retrieves a URL by the method "GET" | 488 | -- Sends a HTTP request and retrieves the server reply using callbacks to |
| 489 | -- send the request body and receive the response body | ||
| 512 | -- Input | 490 | -- Input |
| 513 | -- url: target uniform resource locator | 491 | -- request: a table with the following fields |
| 514 | -- resp_body_cb: response message body receive callback | 492 | -- method: "GET", "PUT", "POST" etc (defaults to "GET") |
| 515 | -- req_hdrs: request headers to send, or nil if none | 493 | -- url: target uniform resource locator |
| 516 | -- stay: should we refrain from following a server redirect message? | 494 | -- user, password: authentication information |
| 495 | -- headers: request headers to send, or nil if none | ||
| 496 | -- body_cb: request message body send-callback, or nil if none | ||
| 497 | -- stay: should we refrain from following a server redirect message? | ||
| 498 | -- response: a table with the following fields: | ||
| 499 | -- body_cb: response method body receive-callback | ||
| 517 | -- Returns | 500 | -- Returns |
| 518 | -- resp_body: response message body, or nil if failed | 501 | -- response: a table with the following fields: |
| 519 | -- resp_hdrs: response header fields received, or nil if failed | 502 | -- headers: response header fields received, or nil if failed |
| 520 | -- resp_line: server response status line, or nil if failed | 503 | -- status: server response status line, or nil if failed |
| 521 | -- err: error message, or nil if successfull | 504 | -- code: server status code, or nil if failed |
| 505 | -- error: error message, or nil if successfull | ||
| 522 | ----------------------------------------------------------------------------- | 506 | ----------------------------------------------------------------------------- |
| 523 | function http_getindirect(url, resp_body_cb, req_hdrs, stay) | 507 | function Public.request_indirect(request, response) |
| 524 | return http_requestindirect("GET", url, resp_body_cb, req_hdrs, nil, stay) | 508 | -- get url components |
| 509 | local parsed = URL.parse(request.url, {port = %Private.PORT, path ="/"}) | ||
| 510 | -- explicit authentication info overrides that given by the url | ||
| 511 | parsed.user = request.user or parsed.user | ||
| 512 | parsed.password = request.password or parsed.password | ||
| 513 | -- default method | ||
| 514 | request.method = request.method or "GET" | ||
| 515 | -- fill default headers | ||
| 516 | request.headers = %Private.fill_headers(request.headers, parsed) | ||
| 517 | -- try to connect to server | ||
| 518 | local sock | ||
| 519 | sock, response.error = connect(parsed.host, parsed.port) | ||
| 520 | if not sock then return response end | ||
| 521 | -- set connection timeout so that we do not hang forever | ||
| 522 | sock:timeout(%Private.TIMEOUT) | ||
| 523 | -- send request message | ||
| 524 | response.error = %Private.send_request(sock, request.method, | ||
| 525 | %Private.request_uri(parsed), request.headers, request.body_cb) | ||
| 526 | if response.error then return response end | ||
| 527 | -- get server response message | ||
| 528 | response.code, response.status, response.error = | ||
| 529 | %Private.receive_status(sock) | ||
| 530 | if response.error then return response end | ||
| 531 | -- receive all headers | ||
| 532 | response.headers, response.error = %Private.receive_headers(sock, {}) | ||
| 533 | if response.error then return response end | ||
| 534 | -- decide what to do based on request and response parameters | ||
| 535 | if %Private.should_redirect(request, response) then | ||
| 536 | %Private.drop_body(sock, response.headers) | ||
| 537 | sock:close() | ||
| 538 | return %Private.redirect(request, response) | ||
| 539 | elseif %Private.should_authorize(request, parsed, response) then | ||
| 540 | %Private.drop_body(sock, response.headers) | ||
| 541 | sock:close() | ||
| 542 | return %Private.authorize(request, parsed, response) | ||
| 543 | elseif %Private.has_body(request, response) then | ||
| 544 | response.error = %Private.receive_body(sock, response.headers, | ||
| 545 | response.body_cb) | ||
| 546 | if response.error then return response end | ||
| 547 | sock:close() | ||
| 548 | return response | ||
| 549 | end | ||
| 550 | sock:close() | ||
| 525 | end | 551 | end |
| 526 | 552 | ||
| 527 | ----------------------------------------------------------------------------- | 553 | ----------------------------------------------------------------------------- |
| 528 | -- Retrieves a URL by the method "POST" | 554 | -- Sends a HTTP request and retrieves the server reply |
| 529 | -- Input | 555 | -- Input |
| 530 | -- method: "GET", "PUT", "POST" etc | 556 | -- request: a table with the following fields |
| 531 | -- url: target uniform resource locator | 557 | -- method: "GET", "PUT", "POST" etc (defaults to "GET") |
| 532 | -- req_hdrs: request headers to send, or nil if none | 558 | -- url: target url, i.e. the document to be retrieved |
| 533 | -- req_body: request message body, or nil if none | 559 | -- user, password: authentication information |
| 534 | -- stay: should we refrain from following a server redirect message? | 560 | -- headers: request header fields, or nil if none |
| 561 | -- body: request message body as a string, or nil if none | ||
| 562 | -- stay: should we refrain from following a server redirect message? | ||
| 535 | -- Returns | 563 | -- Returns |
| 536 | -- resp_body: response message body, or nil if failed | 564 | -- response: a table with the following fields: |
| 537 | -- resp_hdrs: response header fields received, or nil if failed | 565 | -- body: response message body, or nil if failed |
| 538 | -- resp_line: server response status line, or nil if failed | 566 | -- headers: response header fields, or nil if failed |
| 539 | -- err: error message, or nil if successfull | 567 | -- status: server response status line, or nil if failed |
| 568 | -- code: server response status code, or nil if failed | ||
| 569 | -- error: error message if any | ||
| 570 | ----------------------------------------------------------------------------- | ||
| 571 | function Public.request(request) | ||
| 572 | local response = {} | ||
| 573 | if request.body then | ||
| 574 | request.body_cb = function() | ||
| 575 | return %request.body, strlen(%request.body) | ||
| 576 | end | ||
| 577 | end | ||
| 578 | local auxiliar = { buf = buf_create() } | ||
| 579 | response.body_cb = function(chunk, err) | ||
| 580 | if not chunk then %auxiliar.buf = nil end | ||
| 581 | buf_addstring(%auxiliar.buf, chunk) | ||
| 582 | return 1 | ||
| 583 | end | ||
| 584 | %Public.request_indirect(request, response) | ||
| 585 | response.body = buf_getresult(auxiliar.buf) | ||
| 586 | response.body_cb = nil | ||
| 587 | return response | ||
| 588 | end | ||
| 589 | |||
| 540 | ----------------------------------------------------------------------------- | 590 | ----------------------------------------------------------------------------- |
| 541 | function http_post(url, req_body, req_hdrs, stay) | 591 | -- Retrieves a URL by the method "GET" |
| 542 | return http_request("POST", url, req_hdrs, req_body, stay) | 592 | -- Input |
| 593 | -- url: target url, i.e. the document to be retrieved | ||
| 594 | -- Returns | ||
| 595 | -- body: response message body, or nil if failed | ||
| 596 | -- headers: response header fields received, or nil if failed | ||
| 597 | -- status: server response status line, or nil if failed | ||
| 598 | -- error: error message if any | ||
| 599 | ----------------------------------------------------------------------------- | ||
| 600 | function Public.get(url) | ||
| 601 | local response = %Public.request { | ||
| 602 | method = "GET", | ||
| 603 | url = url | ||
| 604 | } | ||
| 605 | return response.body, response.headers, | ||
| 606 | response.status, response.error | ||
| 543 | end | 607 | end |
| 544 | 608 | ||
| 545 | ----------------------------------------------------------------------------- | 609 | ----------------------------------------------------------------------------- |
| 546 | -- Retrieves a URL by the method "POST" | 610 | -- Retrieves a URL by the method "POST" |
| 547 | -- Input | 611 | -- Input |
| 548 | -- url: target uniform resource locator | 612 | -- url: target url, i.e. the document to be retrieved |
| 549 | -- resp_body_cb: response message body receive callback | 613 | -- body: request message body, or nil if none |
| 550 | -- req_body_cb: request message body send callback | ||
| 551 | -- req_hdrs: request headers to send, or nil if none | ||
| 552 | -- stay: should we refrain from following a server redirect message? | ||
| 553 | -- Returns | 614 | -- Returns |
| 554 | -- resp_body: response message body, or nil if failed | 615 | -- body: response message body, or nil if failed |
| 555 | -- resp_hdrs: response header fields received, or nil if failed | 616 | -- headers: response header fields received, or nil if failed |
| 556 | -- resp_line: server response status line, or nil if failed | 617 | -- status: server response status line, or nil if failed |
| 557 | -- err: error message, or nil if successfull | 618 | -- error: error message, or nil if successfull |
| 558 | ----------------------------------------------------------------------------- | 619 | ----------------------------------------------------------------------------- |
| 559 | function http_getindirect(url, resp_body_cb, req_body_cb, req_hdrs, stay) | 620 | function Public.post(url, body) |
| 560 | return http_requestindirect("GET", url, resp_body_cb, req_hdrs, | 621 | local response = %Public.request { |
| 561 | req_body_cb, stay) | 622 | method = "POST", |
| 623 | url = url, | ||
| 624 | body = body | ||
| 625 | } | ||
| 626 | return response.body, response.headers, | ||
| 627 | response.status, response.error | ||
| 562 | end | 628 | end |
