diff options
| author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-06-06 20:55:45 +0000 |
|---|---|---|
| committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-06-06 20:55:45 +0000 |
| commit | c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea (patch) | |
| tree | 07d34dbcd03c4ac81353fb716ea4e7c914e0e633 /src | |
| parent | 77090c53fe558c48680516410a928bece08dd0d5 (diff) | |
| download | luasocket-c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea.tar.gz luasocket-c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea.tar.bz2 luasocket-c53ad62b00b9dbf2a54214c2c6bf3f06d7c43eea.zip | |
Streaming by callbacks implemented.
Diffstat (limited to 'src')
| -rw-r--r-- | src/ftp.lua | 173 | ||||
| -rw-r--r-- | src/http.lua | 389 |
2 files changed, 366 insertions, 196 deletions
diff --git a/src/ftp.lua b/src/ftp.lua index a9dac71..229bbc6 100644 --- a/src/ftp.lua +++ b/src/ftp.lua | |||
| @@ -15,6 +15,8 @@ local PORT = 21 | |||
| 15 | -- this is the default anonymous password. used when no password is | 15 | -- this is the default anonymous password. used when no password is |
| 16 | -- provided in url. should be changed for your e-mail. | 16 | -- provided in url. should be changed for your e-mail. |
| 17 | local EMAIL = "anonymous@anonymous.org" | 17 | local EMAIL = "anonymous@anonymous.org" |
| 18 | -- block size used in transfers | ||
| 19 | local BLOCKSIZE = 4096 | ||
| 18 | 20 | ||
| 19 | ----------------------------------------------------------------------------- | 21 | ----------------------------------------------------------------------------- |
| 20 | -- Parses a url and returns its scheme, user, password, host, port | 22 | -- Parses a url and returns its scheme, user, password, host, port |
| @@ -68,7 +70,7 @@ local get_pasv = function(pasv) | |||
| 68 | local ip, port | 70 | local ip, port |
| 69 | _,_, a, b, c, d, p1, p2 = | 71 | _,_, a, b, c, d, p1, p2 = |
| 70 | strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") | 72 | strfind(pasv, "(%d*),(%d*),(%d*),(%d*),(%d*),(%d*)") |
| 71 | if not a or not b or not c or not d or not p1 or not p2 then | 73 | if not (a and b and c and d and p1 and p2) then |
| 72 | return nil, nil | 74 | return nil, nil |
| 73 | end | 75 | end |
| 74 | ip = format("%d.%d.%d.%d", a, b, c, d) | 76 | ip = format("%d.%d.%d.%d", a, b, c, d) |
| @@ -82,6 +84,8 @@ end | |||
| 82 | -- control: control connection socket | 84 | -- control: control connection socket |
| 83 | -- cmd: command | 85 | -- cmd: command |
| 84 | -- arg: command argument if any | 86 | -- arg: command argument if any |
| 87 | -- Returns | ||
| 88 | -- error message in case of error, nil otherwise | ||
| 85 | ----------------------------------------------------------------------------- | 89 | ----------------------------------------------------------------------------- |
| 86 | local send_command = function(control, cmd, arg) | 90 | local send_command = function(control, cmd, arg) |
| 87 | local line, err | 91 | local line, err |
| @@ -284,35 +288,84 @@ local logout = function(control) | |||
| 284 | end | 288 | end |
| 285 | 289 | ||
| 286 | ----------------------------------------------------------------------------- | 290 | ----------------------------------------------------------------------------- |
| 291 | -- Receives data and send it to a callback | ||
| 292 | -- Input | ||
| 293 | -- data: data connection | ||
| 294 | -- callback: callback to return file contents | ||
| 295 | -- Returns | ||
| 296 | -- nil if successfull, or an error message in case of error | ||
| 297 | ----------------------------------------------------------------------------- | ||
| 298 | local receive_indirect = function(data, callback) | ||
| 299 | local chunk, err, res | ||
| 300 | while not err do | ||
| 301 | chunk, err = data:receive(%BLOCKSIZE) | ||
| 302 | if err == "closed" then err = "done" end | ||
| 303 | res = callback(chunk, err) | ||
| 304 | if not res then break end | ||
| 305 | end | ||
| 306 | end | ||
| 307 | |||
| 308 | ----------------------------------------------------------------------------- | ||
| 287 | -- Retrieves file or directory listing | 309 | -- Retrieves file or directory listing |
| 288 | -- Input | 310 | -- Input |
| 289 | -- control: control connection with server | 311 | -- control: control connection with server |
| 290 | -- server: server socket bound to local address | 312 | -- server: server socket bound to local address |
| 291 | -- file: file name under current directory | 313 | -- file: file name under current directory |
| 292 | -- isdir: is file a directory name? | 314 | -- isdir: is file a directory name? |
| 315 | -- callback: callback to receive file contents | ||
| 293 | -- Returns | 316 | -- Returns |
| 294 | -- file: string with file contents, nil if error | 317 | -- err: error message in case of error, nil otherwise |
| 295 | -- answer: server answer or error message | ||
| 296 | ----------------------------------------------------------------------------- | 318 | ----------------------------------------------------------------------------- |
| 297 | local retrieve_file = function(control, server, file, isdir) | 319 | local retrieve = function(control, server, file, isdir, callback) |
| 320 | local code, answer | ||
| 298 | local data | 321 | local data |
| 299 | -- ask server for file or directory listing accordingly | 322 | -- ask server for file or directory listing accordingly |
| 300 | if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) | 323 | if isdir then code, answer = %try_command(control, "nlst", file, {150, 125}) |
| 301 | else code, answer = %try_command(control, "retr", file, {150, 125}) end | 324 | else code, answer = %try_command(control, "retr", file, {150, 125}) end |
| 302 | data, answer = server:accept() | 325 | data, answer = server:accept() |
| 303 | server:close() | 326 | server:close() |
| 304 | if not data then return nil, answer end | 327 | if not data then |
| 305 | -- download whole file | ||
| 306 | file, err = data:receive("*a") | ||
| 307 | data:close() | ||
| 308 | if err then | ||
| 309 | control:close() | 328 | control:close() |
| 310 | return nil, err | 329 | return answer |
| 311 | end | 330 | end |
| 331 | answer = %receive_indirect(data, callback) | ||
| 332 | if answer then | ||
| 333 | control:close() | ||
| 334 | return answer | ||
| 335 | end | ||
| 336 | data:close() | ||
| 312 | -- make sure file transfered ok | 337 | -- make sure file transfered ok |
| 313 | code, answer = %check_answer(control, {226, 250}) | 338 | code, answer = %check_answer(control, {226, 250}) |
| 314 | if not code then return nil, answer | 339 | if not code then return answer end |
| 315 | else return file, answer end | 340 | end |
| 341 | |||
| 342 | ----------------------------------------------------------------------------- | ||
| 343 | -- Sends data comming from a callback | ||
| 344 | -- Input | ||
| 345 | -- data: data connection | ||
| 346 | -- send_cb: callback to produce file contents | ||
| 347 | -- chunk, size: first callback results | ||
| 348 | -- Returns | ||
| 349 | -- nil if successfull, or an error message in case of error | ||
| 350 | ----------------------------------------------------------------------------- | ||
| 351 | local try_sendindirect = function(data, send_cb, chunk, size) | ||
| 352 | local sent, err | ||
| 353 | sent = 0 | ||
| 354 | while 1 do | ||
| 355 | if type(chunk) ~= "string" or type(size) ~= "number" then | ||
| 356 | data:close() | ||
| 357 | if not chunk and type(size) == "string" then return size | ||
| 358 | else return "invalid callback return" end | ||
| 359 | end | ||
| 360 | err = data:send(chunk) | ||
| 361 | if err then | ||
| 362 | data:close() | ||
| 363 | return err | ||
| 364 | end | ||
| 365 | sent = sent + strlen(chunk) | ||
| 366 | if sent >= size then break end | ||
| 367 | chunk, size = send_cb() | ||
| 368 | end | ||
| 316 | end | 369 | end |
| 317 | 370 | ||
| 318 | ----------------------------------------------------------------------------- | 371 | ----------------------------------------------------------------------------- |
| @@ -321,29 +374,34 @@ end | |||
| 321 | -- control: control connection with server | 374 | -- control: control connection with server |
| 322 | -- server: server socket bound to local address | 375 | -- server: server socket bound to local address |
| 323 | -- file: file name under current directory | 376 | -- file: file name under current directory |
| 324 | -- bytes: file contents in string | 377 | -- send_cb: callback to produce the file contents |
| 325 | -- Returns | 378 | -- Returns |
| 326 | -- code: return code, nil if error | 379 | -- code: return code, nil if error |
| 327 | -- answer: server answer or error message | 380 | -- answer: server answer or error message |
| 328 | ----------------------------------------------------------------------------- | 381 | ----------------------------------------------------------------------------- |
| 329 | local store_file = function (control, server, file, bytes) | 382 | local store = function(control, server, file, send_cb) |
| 330 | local data | 383 | local data |
| 331 | local code, answer = %try_command(control, "stor", file, {150, 125}) | 384 | local code, answer = %try_command(control, "stor", file, {150, 125}) |
| 332 | if not code then | 385 | if not code then |
| 333 | data:close() | 386 | control:close() |
| 334 | return nil, answer | 387 | return nil, answer |
| 335 | end | 388 | end |
| 389 | -- start data connection | ||
| 336 | data, answer = server:accept() | 390 | data, answer = server:accept() |
| 337 | server:close() | 391 | server:close() |
| 338 | if not data then return nil, answer end | 392 | if not data then |
| 339 | -- send whole file and close connection to mark file end | ||
| 340 | answer = data:send(bytes) | ||
| 341 | data:close() | ||
| 342 | if answer then | ||
| 343 | control:close() | 393 | control:close() |
| 344 | return nil, answer | 394 | return nil, answer |
| 395 | end | ||
| 396 | -- send whole file | ||
| 397 | err = %try_sendindirect(data, send_cb, send_cb()) | ||
| 398 | if err then | ||
| 399 | control:close() | ||
| 400 | return nil, err | ||
| 345 | end | 401 | end |
| 346 | -- check if file was received right | 402 | -- close connection to inform that file transmission is complete |
| 403 | data:close() | ||
| 404 | -- check if file was received correctly | ||
| 347 | return %check_answer(control, {226, 250}) | 405 | return %check_answer(control, {226, 250}) |
| 348 | end | 406 | end |
| 349 | 407 | ||
| @@ -365,55 +423,53 @@ end | |||
| 365 | -- Retrieve a file from a ftp server | 423 | -- Retrieve a file from a ftp server |
| 366 | -- Input | 424 | -- Input |
| 367 | -- url: file location | 425 | -- url: file location |
| 426 | -- receive_cb: callback to receive file contents | ||
| 368 | -- type: "binary" or "ascii" | 427 | -- type: "binary" or "ascii" |
| 369 | -- Returns | 428 | -- Returns |
| 370 | -- file: downloaded file or nil in case of error | ||
| 371 | -- err: error message if any | 429 | -- err: error message if any |
| 372 | ----------------------------------------------------------------------------- | 430 | ----------------------------------------------------------------------------- |
| 373 | function ftp_get(url, type) | 431 | function ftp_getindirect(url, receive_cb, type) |
| 374 | local control, server, data, err | 432 | local control, server, data, err |
| 375 | local answer, code, server, pfile, file | 433 | local answer, code, server, pfile, file |
| 376 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | 434 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) |
| 377 | -- start control connection | 435 | -- start control connection |
| 378 | control, err = connect(parsed.host, parsed.port) | 436 | control, err = connect(parsed.host, parsed.port) |
| 379 | if not control then return nil, err end | 437 | if not control then return err end |
| 380 | control:timeout(%TIMEOUT) | 438 | control:timeout(%TIMEOUT) |
| 381 | -- get and check greeting | 439 | -- get and check greeting |
| 382 | code, answer = %check_greeting(control) | 440 | code, answer = %check_greeting(control) |
| 383 | if not code then return nil, answer end | 441 | if not code then return answer end |
| 384 | -- try to log in | 442 | -- try to log in |
| 385 | code, answer = %login(control, parsed.user, parsed.pass) | 443 | code, answer = %login(control, parsed.user, parsed.pass) |
| 386 | if not code then return nil, answer end | 444 | if not code then return answer end |
| 387 | -- go to directory | 445 | -- go to directory |
| 388 | pfile = %split_path(parsed.path) | 446 | pfile = %split_path(parsed.path) |
| 389 | if not pfile then return nil, "invalid path" end | 447 | if not pfile then return "invalid path" end |
| 390 | code, answer = %cwd(control, pfile.path) | 448 | code, answer = %cwd(control, pfile.path) |
| 391 | if not code then return nil, answer end | 449 | if not code then return answer end |
| 392 | -- change to binary type? | 450 | -- change to binary type? |
| 393 | code, answer = %change_type(control, type) | 451 | code, answer = %change_type(control, type) |
| 394 | if not code then return nil, answer end | 452 | if not code then return answer end |
| 395 | -- setup passive connection | 453 | -- setup passive connection |
| 396 | server, answer = %port(control) | 454 | server, answer = %port(control) |
| 397 | if not server then return nil, answer end | 455 | if not server then return answer end |
| 398 | -- ask server to send file or directory listing | 456 | -- ask server to send file or directory listing |
| 399 | file, answer = %retrieve_file(control, server, pfile.name, pfile.isdir) | 457 | err = %retrieve(control, server, pfile.name, pfile.isdir, receive_cb) |
| 400 | if not file then return nil, answer end | 458 | if err then return err end |
| 401 | -- disconnect | 459 | -- disconnect |
| 402 | %logout(control) | 460 | %logout(control) |
| 403 | -- return whatever file we received plus a possible error message | ||
| 404 | return file, answer | ||
| 405 | end | 461 | end |
| 406 | 462 | ||
| 407 | ----------------------------------------------------------------------------- | 463 | ----------------------------------------------------------------------------- |
| 408 | -- Uploads a file to a FTP server | 464 | -- Uploads a file to a FTP server |
| 409 | -- Input | 465 | -- Input |
| 410 | -- url: file location | 466 | -- url: file location |
| 411 | -- bytes: file contents | 467 | -- send_cb: callback to produce the file contents |
| 412 | -- type: "binary" or "ascii" | 468 | -- type: "binary" or "ascii" |
| 413 | -- Returns | 469 | -- Returns |
| 414 | -- err: error message if any | 470 | -- err: error message if any |
| 415 | ----------------------------------------------------------------------------- | 471 | ----------------------------------------------------------------------------- |
| 416 | function ftp_put(url, bytes, type) | 472 | function ftp_putindirect(url, send_cb, type) |
| 417 | local control, data | 473 | local control, data |
| 418 | local answer, code, server, file, pfile | 474 | local answer, code, server, file, pfile |
| 419 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) | 475 | parsed = %split_url(url, {user = "anonymous", port = 21, pass = %EMAIL}) |
| @@ -439,10 +495,51 @@ function ftp_put(url, bytes, type) | |||
| 439 | server, answer = %port(control) | 495 | server, answer = %port(control) |
| 440 | if not server then return answer end | 496 | if not server then return answer end |
| 441 | -- ask server to send file | 497 | -- ask server to send file |
| 442 | code, answer = %store_file(control, server, pfile.name, bytes) | 498 | code, answer = %store(control, server, pfile.name, send_cb) |
| 443 | if not code then return answer end | 499 | if not code then return answer end |
| 444 | -- disconnect | 500 | -- disconnect |
| 445 | %logout(control) | 501 | %logout(control) |
| 446 | -- no errors | 502 | -- no errors |
| 447 | return nil | 503 | return nil |
| 448 | end | 504 | end |
| 505 | |||
| 506 | ----------------------------------------------------------------------------- | ||
| 507 | -- Uploads a file to a FTP server | ||
| 508 | -- Input | ||
| 509 | -- url: file location | ||
| 510 | -- bytes: file contents | ||
| 511 | -- type: "binary" or "ascii" | ||
| 512 | -- Returns | ||
| 513 | -- err: error message if any | ||
| 514 | ----------------------------------------------------------------------------- | ||
| 515 | function ftp_put(url, bytes, type) | ||
| 516 | local send_cb = function() | ||
| 517 | return %bytes, strlen(%bytes) | ||
| 518 | end | ||
| 519 | return ftp_putindirect(url, send_cb, type) | ||
| 520 | end | ||
| 521 | |||
| 522 | ----------------------------------------------------------------------------- | ||
| 523 | -- We need fast concatenation routines for direct requests | ||
| 524 | ----------------------------------------------------------------------------- | ||
| 525 | dofile("buffer.lua") | ||
| 526 | |||
| 527 | ----------------------------------------------------------------------------- | ||
| 528 | -- Retrieve a file from a ftp server | ||
| 529 | -- Input | ||
| 530 | -- url: file location | ||
| 531 | -- type: "binary" or "ascii" | ||
| 532 | -- Returns | ||
| 533 | -- data: file contents as a string | ||
| 534 | -- err: error message in case of error, nil otherwise | ||
| 535 | ----------------------------------------------------------------------------- | ||
| 536 | function ftp_get(url, type) | ||
| 537 | local bytes = { buf = buf_create() } | ||
| 538 | local receive_cb = function(chunk, err) | ||
| 539 | if not chunk then %bytes.buf = nil end | ||
| 540 | buf_addstring(%bytes.buf, chunk) | ||
| 541 | return 1 | ||
| 542 | end | ||
| 543 | err = ftp_getindirect(url, receive_cb, type) | ||
| 544 | return buf_getresult(bytes.buf), err | ||
| 545 | end | ||
diff --git a/src/http.lua b/src/http.lua index d1e4894..38d54d0 100644 --- a/src/http.lua +++ b/src/http.lua | |||
| @@ -14,7 +14,9 @@ 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.3 HTTP 1.1" | 17 | local USERAGENT = "LuaSocket 1.3b HTTP 1.1" |
| 18 | -- block size used in transfers | ||
| 19 | local BLOCKSIZE = 4096 | ||
| 18 | 20 | ||
| 19 | ----------------------------------------------------------------------------- | 21 | ----------------------------------------------------------------------------- |
| 20 | -- Tries to get a pattern from the server and closes socket on error | 22 | -- Tries to get a pattern from the server and closes socket on error |
| @@ -26,7 +28,7 @@ local USERAGENT = "LuaSocket 1.3 HTTP 1.1" | |||
| 26 | ----------------------------------------------------------------------------- | 28 | ----------------------------------------------------------------------------- |
| 27 | local try_get = function(...) | 29 | local try_get = function(...) |
| 28 | local sock = arg[1] | 30 | local sock = arg[1] |
| 29 | local data, err = call(sock.receive, arg) | 31 | local data, err = call(sock.receive, arg) |
| 30 | if err then | 32 | if err then |
| 31 | sock:close() | 33 | sock:close() |
| 32 | return nil, err | 34 | return nil, err |
| @@ -79,14 +81,14 @@ end | |||
| 79 | -- Receive and parse responce header fields | 81 | -- Receive and parse responce header fields |
| 80 | -- Input | 82 | -- Input |
| 81 | -- sock: socket connected to the server | 83 | -- sock: socket connected to the server |
| 82 | -- headers: a table that might already contain headers | 84 | -- hdrs: a table that might already contain headers |
| 83 | -- Returns | 85 | -- Returns |
| 84 | -- headers: a table with all headers fields in the form | 86 | -- hdrs: a table with all headers fields in the form |
| 85 | -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} | 87 | -- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} |
| 86 | -- all name_i are lowercase | 88 | -- all name_i are lowercase |
| 87 | -- nil and error message in case of error | 89 | -- nil and error message in case of error |
| 88 | ----------------------------------------------------------------------------- | 90 | ----------------------------------------------------------------------------- |
| 89 | local get_hdrs = function(sock, headers) | 91 | local get_hdrs = function(sock, hdrs) |
| 90 | local line, err | 92 | local line, err |
| 91 | local name, value | 93 | local name, value |
| 92 | -- get first line | 94 | -- get first line |
| @@ -111,106 +113,114 @@ local get_hdrs = function(sock, headers) | |||
| 111 | if err then return nil, err end | 113 | if err then return nil, err end |
| 112 | end | 114 | end |
| 113 | -- save pair in table | 115 | -- save pair in table |
| 114 | if headers[name] then headers[name] = headers[name] .. ", " .. value | 116 | if hdrs[name] then hdrs[name] = hdrs[name] .. ", " .. value |
| 115 | else headers[name] = value end | 117 | else hdrs[name] = value end |
| 116 | end | 118 | end |
| 117 | return headers | 119 | return hdrs |
| 118 | end | 120 | end |
| 119 | 121 | ||
| 120 | ----------------------------------------------------------------------------- | 122 | ----------------------------------------------------------------------------- |
| 121 | -- Receives a chunked message body | 123 | -- Receives a chunked message body |
| 122 | -- Input | 124 | -- Input |
| 123 | -- sock: socket connected to the server | 125 | -- sock: socket connected to the server |
| 124 | -- callback: function to receive chunks | 126 | -- receive_cb: function to receive chunks |
| 125 | -- Returns | 127 | -- Returns |
| 126 | -- nil if successfull or an error message in case of error | 128 | -- nil if successfull or an error message in case of error |
| 127 | ----------------------------------------------------------------------------- | 129 | ----------------------------------------------------------------------------- |
| 128 | local try_getchunked = function(sock, callback) | 130 | local try_getchunked = function(sock, receive_cb) |
| 129 | local chunk, size, line, err | 131 | local chunk, size, line, err, go, uerr, _ |
| 130 | repeat | 132 | repeat |
| 131 | -- get chunk size, skip extention | 133 | -- get chunk size, skip extention |
| 132 | line, err = %try_get(sock) | 134 | line, err = %try_get(sock) |
| 133 | if err then | 135 | if err then |
| 134 | callback(nil, err) | 136 | local _, uerr = receive_cb(nil, err) |
| 135 | return err | 137 | return uerr or err |
| 136 | end | 138 | end |
| 137 | size = tonumber(gsub(line, ";.*", ""), 16) | 139 | size = tonumber(gsub(line, ";.*", ""), 16) |
| 138 | if not size then | 140 | if not size then |
| 141 | err = "invalid chunk size" | ||
| 139 | sock:close() | 142 | sock:close() |
| 140 | callback(nil, "invalid chunk size") | 143 | _, uerr = receive_cb(nil, err) |
| 141 | return "invalid chunk size" | 144 | return uerr or err |
| 142 | end | 145 | end |
| 143 | -- get chunk | 146 | -- get chunk |
| 144 | chunk, err = %try_get(sock, size) | 147 | chunk, err = %try_get(sock, size) |
| 145 | if err then | 148 | if err then |
| 146 | callback(nil, err) | 149 | _, uerr = receive_cb(nil, err) |
| 147 | return err | 150 | return uerr or err |
| 148 | end | 151 | end |
| 149 | -- pass chunk to callback | 152 | -- pass chunk to callback |
| 150 | if not callback(chunk) then | 153 | go, uerr = receive_cb(chunk) |
| 151 | sock:close() | 154 | if not go then |
| 152 | return "aborted by callback" | 155 | sock:close() |
| 153 | end | 156 | return uerr or "aborted by callback" |
| 157 | end | ||
| 154 | -- skip blank line | 158 | -- skip blank line |
| 155 | _, err = %try_get(sock) | 159 | _, err = %try_get(sock) |
| 156 | if err then | 160 | if err then |
| 157 | callback(nil, err) | 161 | _, uerr = receive_cb(nil, err) |
| 158 | return err | 162 | return uerr or err |
| 159 | end | 163 | end |
| 160 | until size <= 0 | 164 | until size <= 0 |
| 161 | -- let callback know we are done | 165 | -- let callback know we are done |
| 162 | callback("", "done") | 166 | _, uerr = receive_cb("") |
| 167 | return uerr | ||
| 163 | end | 168 | end |
| 164 | 169 | ||
| 165 | ----------------------------------------------------------------------------- | 170 | ----------------------------------------------------------------------------- |
| 166 | -- Receives a message body by content-length | 171 | -- Receives a message body by content-length |
| 167 | -- Input | 172 | -- Input |
| 168 | -- sock: socket connected to the server | 173 | -- sock: socket connected to the server |
| 169 | -- callback: function to receive chunks | 174 | -- receive_cb: function to receive chunks |
| 170 | -- Returns | 175 | -- Returns |
| 171 | -- nil if successfull or an error message in case of error | 176 | -- nil if successfull or an error message in case of error |
| 172 | ----------------------------------------------------------------------------- | 177 | ----------------------------------------------------------------------------- |
| 173 | local try_getbylength = function(sock, length, callback) | 178 | local try_getbylength = function(sock, length, receive_cb) |
| 174 | while length > 0 do | 179 | local uerr, go |
| 175 | local size = min(4096, length) | 180 | while length > 0 do |
| 176 | local chunk, err = sock:receive(size) | 181 | local size = min(%BLOCKSIZE, length) |
| 177 | if err then | 182 | local chunk, err = sock:receive(size) |
| 178 | callback(nil, err) | 183 | if err then |
| 179 | return err | 184 | go, uerr = receive_cb(nil, err) |
| 180 | end | 185 | return uerr or err |
| 181 | if not callback(chunk) then | 186 | end |
| 182 | sock:close() | 187 | go, uerr = receive_cb(chunk) |
| 183 | return "aborted by callback" | 188 | if not go then |
| 184 | end | 189 | sock:close() |
| 185 | length = length - size | 190 | return uerr or "aborted by callback" |
| 186 | end | 191 | end |
| 187 | callback("", "done") | 192 | length = length - size |
| 193 | end | ||
| 194 | go, uerr = receive_cb("") | ||
| 195 | return uerr | ||
| 188 | end | 196 | end |
| 189 | 197 | ||
| 190 | ----------------------------------------------------------------------------- | 198 | ----------------------------------------------------------------------------- |
| 191 | -- Receives a message body by content-length | 199 | -- Receives a message body by content-length |
| 192 | -- Input | 200 | -- Input |
| 193 | -- sock: socket connected to the server | 201 | -- sock: socket connected to the server |
| 194 | -- callback: function to receive chunks | 202 | -- receive_cb: function to receive chunks |
| 195 | -- Returns | 203 | -- Returns |
| 196 | -- nil if successfull or an error message in case of error | 204 | -- nil if successfull or an error message in case of error |
| 197 | ----------------------------------------------------------------------------- | 205 | ----------------------------------------------------------------------------- |
| 198 | local try_getuntilclosed = function(sock, callback) | 206 | local try_getuntilclosed = function(sock, receive_cb) |
| 199 | local err | 207 | local err, go, uerr |
| 200 | while 1 do | 208 | while 1 do |
| 201 | local chunk, err = sock:receive(4096) | 209 | local chunk, err = sock:receive(%BLOCKSIZE) |
| 202 | if err == "closed" or not err then | 210 | if err == "closed" or not err then |
| 203 | if not callback(chunk) then | 211 | go, uerr = receive_cb(chunk) |
| 204 | sock:close() | 212 | if not go then |
| 205 | return "aborted by callback" | 213 | sock:close() |
| 206 | end | 214 | return uerr or "aborted by callback" |
| 207 | if err then break end | 215 | end |
| 208 | else | 216 | if err then break end |
| 209 | callback(nil, err) | 217 | else |
| 210 | return err | 218 | go, uerr = callback(nil, err) |
| 211 | end | 219 | return uerr or err |
| 212 | end | 220 | end |
| 213 | callback("", "done") | 221 | end |
| 222 | go, uerr = receive_cb("") | ||
| 223 | return uerr | ||
| 214 | end | 224 | end |
| 215 | 225 | ||
| 216 | ----------------------------------------------------------------------------- | 226 | ----------------------------------------------------------------------------- |
| @@ -218,22 +228,22 @@ end | |||
| 218 | -- Input | 228 | -- Input |
| 219 | -- sock: socket connected to the server | 229 | -- sock: socket connected to the server |
| 220 | -- resp_hdrs: response header fields | 230 | -- resp_hdrs: response header fields |
| 221 | -- callback: function to receive chunks | 231 | -- receive_cb: function to receive chunks |
| 222 | -- Returns | 232 | -- Returns |
| 223 | -- nil if successfull or an error message in case of error | 233 | -- nil if successfull or an error message in case of error |
| 224 | ----------------------------------------------------------------------------- | 234 | ----------------------------------------------------------------------------- |
| 225 | local try_getbody = function(sock, resp_hdrs, callback) | 235 | local try_getbody = function(sock, resp_hdrs, receive_cb) |
| 226 | local err | 236 | local err |
| 227 | if resp_hdrs["transfer-encoding"] == "chunked" then | 237 | if resp_hdrs["transfer-encoding"] == "chunked" then |
| 228 | -- get by chunked transfer-coding of message body | 238 | -- get by chunked transfer-coding of message body |
| 229 | return %try_getchunked(sock, callback) | 239 | return %try_getchunked(sock, receive_cb) |
| 230 | elseif tonumber(resp_hdrs["content-length"]) then | 240 | elseif tonumber(resp_hdrs["content-length"]) then |
| 231 | -- get by content-length | 241 | -- get by content-length |
| 232 | local length = tonumber(resp_hdrs["content-length"]) | 242 | local length = tonumber(resp_hdrs["content-length"]) |
| 233 | return %try_getbylength(sock, length, callback) | 243 | return %try_getbylength(sock, length, receive_cb) |
| 234 | else | 244 | else |
| 235 | -- get it all until connection closes | 245 | -- get it all until connection closes |
| 236 | return %try_getuntilclosed(sock, callback) | 246 | return %try_getuntilclosed(sock, receive_cb) |
| 237 | end | 247 | end |
| 238 | end | 248 | end |
| 239 | 249 | ||
| @@ -276,51 +286,73 @@ local split_url = function(url, default) | |||
| 276 | end | 286 | end |
| 277 | 287 | ||
| 278 | ----------------------------------------------------------------------------- | 288 | ----------------------------------------------------------------------------- |
| 289 | -- Sends data comming from a callback | ||
| 290 | -- Input | ||
| 291 | -- data: data connection | ||
| 292 | -- send_cb: callback to produce file contents | ||
| 293 | -- chunk, size: first callback results | ||
| 294 | -- Returns | ||
| 295 | -- nil if successfull, or an error message in case of error | ||
| 296 | ----------------------------------------------------------------------------- | ||
| 297 | local try_sendindirect = function(data, send_cb, chunk, size) | ||
| 298 | local sent, err | ||
| 299 | sent = 0 | ||
| 300 | while 1 do | ||
| 301 | if type(chunk) ~= "string" or type(size) ~= "number" then | ||
| 302 | data:close() | ||
| 303 | if not chunk and type(size) == "string" then return size | ||
| 304 | else return "invalid callback return" end | ||
| 305 | end | ||
| 306 | err = data:send(chunk) | ||
| 307 | if err then | ||
| 308 | data:close() | ||
| 309 | return err | ||
| 310 | end | ||
| 311 | sent = sent + strlen(chunk) | ||
| 312 | if sent >= size then break end | ||
| 313 | chunk, size = send_cb() | ||
| 314 | end | ||
| 315 | end | ||
| 316 | |||
| 317 | ----------------------------------------------------------------------------- | ||
| 279 | -- Sends a http request message through socket | 318 | -- Sends a http request message through socket |
| 280 | -- Input | 319 | -- Input |
| 281 | -- sock: socket connected to the server | 320 | -- sock: socket connected to the server |
| 282 | -- method: request method to be used | 321 | -- method: request method to be used |
| 283 | -- path: url path | 322 | -- path: url path |
| 284 | -- req_hdrs: request headers to be sent | 323 | -- req_hdrs: request headers to be sent |
| 285 | -- callback: callback to send request message body | 324 | -- req_body_cb: callback to send request message body |
| 286 | -- Returns | 325 | -- Returns |
| 287 | -- err: nil in case of success, error message otherwise | 326 | -- err: nil in case of success, error message otherwise |
| 288 | ----------------------------------------------------------------------------- | 327 | ----------------------------------------------------------------------------- |
| 289 | local send_request = function(sock, method, path, req_hdrs, callback) | 328 | local send_request = function(sock, method, path, req_hdrs, req_body_cb) |
| 290 | local chunk, size, done | 329 | local chunk, size, done, err |
| 291 | -- send request line | 330 | -- send request line |
| 292 | local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") | 331 | err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n") |
| 293 | if err then return err end | 332 | if err then return err end |
| 294 | -- send request headers | 333 | -- if there is a request message body, add content-length header |
| 334 | if req_body_cb then | ||
| 335 | chunk, size = req_body_cb() | ||
| 336 | if type(chunk) == "string" and type(size) == "number" then | ||
| 337 | req_hdrs["content-length"] = tostring(size) | ||
| 338 | else | ||
| 339 | sock:close() | ||
| 340 | if not chunk and type(size) == "string" then return size | ||
| 341 | else return "invalid callback return" end | ||
| 342 | end | ||
| 343 | end | ||
| 344 | -- send request headers | ||
| 295 | for i, v in req_hdrs do | 345 | for i, v in req_hdrs do |
| 296 | err = %try_send(sock, i .. ": " .. v .. "\r\n") | 346 | err = %try_send(sock, i .. ": " .. v .. "\r\n") |
| 297 | if err then return err end | 347 | if err then return err end |
| 298 | end | 348 | end |
| 299 | -- if there is a request message body, add content-length header | 349 | -- mark end of request headers |
| 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 | ||
| 311 | err = %try_send(sock, "\r\n") | 350 | err = %try_send(sock, "\r\n") |
| 312 | if err then return err end | 351 | if err then return err end |
| 313 | -- send message request body, getting it chunk by chunk from callback | 352 | -- send request message body, if any |
| 314 | if callback then | 353 | if req_body_cb then |
| 315 | done = 0 | 354 | return %try_sendindirect(sock, req_body_cb, chunk, size) |
| 316 | while chunk and chunk ~= "" and done < size do | 355 | end |
| 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 | ||
| 324 | end | 356 | end |
| 325 | 357 | ||
| 326 | ----------------------------------------------------------------------------- | 358 | ----------------------------------------------------------------------------- |
| @@ -344,18 +376,17 @@ end | |||
| 344 | dofile("base64.lua") | 376 | dofile("base64.lua") |
| 345 | 377 | ||
| 346 | ----------------------------------------------------------------------------- | 378 | ----------------------------------------------------------------------------- |
| 347 | -- Converts field names to lowercase and add message body size specification | 379 | -- Converts field names to lowercase and adds a few needed headers |
| 348 | -- Input | 380 | -- Input |
| 349 | -- headers: request header fields | 381 | -- hdrs: request header fields |
| 350 | -- parsed: parsed url components | 382 | -- parsed: parsed url components |
| 351 | -- body: request message body, if any | ||
| 352 | -- Returns | 383 | -- Returns |
| 353 | -- lower: a table with the same headers, but with lowercase field names | 384 | -- lower: a table with the same headers, but with lowercase field names |
| 354 | ----------------------------------------------------------------------------- | 385 | ----------------------------------------------------------------------------- |
| 355 | local fill_hdrs = function(headers, parsed, body) | 386 | local fill_hdrs = function(hdrs, parsed) |
| 356 | local lower = {} | 387 | local lower = {} |
| 357 | headers = headers or {} | 388 | hdrs = hdrs or {} |
| 358 | for i,v in headers do | 389 | for i,v in hdrs do |
| 359 | lower[strlower(i)] = v | 390 | lower[strlower(i)] = v |
| 360 | end | 391 | end |
| 361 | lower["connection"] = "close" | 392 | lower["connection"] = "close" |
| @@ -374,15 +405,17 @@ end | |||
| 374 | -- Input | 405 | -- Input |
| 375 | -- method: "GET", "PUT", "POST" etc | 406 | -- method: "GET", "PUT", "POST" etc |
| 376 | -- url: target uniform resource locator | 407 | -- url: target uniform resource locator |
| 377 | -- req_hdrs: request headers to send | 408 | -- resp_body_cb: response message body receive callback |
| 378 | -- req_body: function to return request message body | 409 | -- req_hdrs: request headers to send, or nil if none |
| 379 | -- resp_body: function to receive response message body | 410 | -- req_body_cb: request message body send callback, or nil if none |
| 411 | -- stay: should we refrain from following a server redirect message? | ||
| 380 | -- Returns | 412 | -- Returns |
| 381 | -- resp_hdrs: response header fields received, if sucessfull | 413 | -- resp_hdrs: response header fields received, or nil if failed |
| 382 | -- resp_line: server response status line, if successfull | 414 | -- resp_line: server response status line, or nil if failed |
| 383 | -- err: error message if any | 415 | -- err: error message, or nil if successfull |
| 384 | ----------------------------------------------------------------------------- | 416 | ----------------------------------------------------------------------------- |
| 385 | function http_requestindirect(method, url, req_hdrs, req_body, resp_body) | 417 | function http_requestindirect(method, url, resp_body_cb, req_hdrs, |
| 418 | req_body_cb, stay) | ||
| 386 | local sock, err | 419 | local sock, err |
| 387 | local resp_hdrs | 420 | local resp_hdrs |
| 388 | local resp_line, resp_code | 421 | local resp_line, resp_code |
| @@ -398,7 +431,7 @@ function http_requestindirect(method, url, req_hdrs, req_body, resp_body) | |||
| 398 | -- set connection timeout | 431 | -- set connection timeout |
| 399 | sock:timeout(%TIMEOUT) | 432 | sock:timeout(%TIMEOUT) |
| 400 | -- send request | 433 | -- send request |
| 401 | err = %send_request(sock, method, parsed.path, req_hdrs, req_body) | 434 | err = %send_request(sock, method, parsed.path, req_hdrs, req_body_cb) |
| 402 | if err then return nil, nil, err end | 435 | if err then return nil, nil, err end |
| 403 | -- get server message | 436 | -- get server message |
| 404 | resp_code, resp_line, err = %get_status(sock) | 437 | resp_code, resp_line, err = %get_status(sock) |
| @@ -407,15 +440,15 @@ function http_requestindirect(method, url, req_hdrs, req_body, resp_body) | |||
| 407 | resp_hdrs, err = %get_hdrs(sock, {}) | 440 | resp_hdrs, err = %get_hdrs(sock, {}) |
| 408 | if err then return nil, line, err end | 441 | if err then return nil, line, err end |
| 409 | -- did we get a redirect? should we automatically retry? | 442 | -- did we get a redirect? should we automatically retry? |
| 410 | if (resp_code == 301 or resp_code == 302) and | 443 | if not stay and (resp_code == 301 or resp_code == 302) and |
| 411 | (method == "GET" or method == "HEAD") then | 444 | (method == "GET" or method == "HEAD") then |
| 412 | sock:close() | 445 | sock:close() |
| 413 | return http_requestindirect(method, resp_hdrs["location"], req_hdrs, | 446 | return http_requestindirect(method, resp_hdrs["location"], |
| 414 | req_body, resp_body) | 447 | resp_body_cb, req_hdrs, req_body_cb, stay) |
| 415 | end | 448 | end |
| 416 | -- get body if status and method combination allow one | 449 | -- get response message body if status and method combination allow one |
| 417 | if has_respbody(method, resp_code) then | 450 | if has_respbody(method, resp_code) then |
| 418 | err = %try_getbody(sock, resp_hdrs, resp_body) | 451 | err = %try_getbody(sock, resp_hdrs, resp_body_cb) |
| 419 | if err then return resp_hdrs, resp_line, err end | 452 | if err then return resp_hdrs, resp_line, err end |
| 420 | end | 453 | end |
| 421 | sock:close() | 454 | sock:close() |
| @@ -423,67 +456,107 @@ function http_requestindirect(method, url, req_hdrs, req_body, resp_body) | |||
| 423 | end | 456 | end |
| 424 | 457 | ||
| 425 | ----------------------------------------------------------------------------- | 458 | ----------------------------------------------------------------------------- |
| 459 | -- We need fast concatenation routines for direct requests | ||
| 460 | ----------------------------------------------------------------------------- | ||
| 461 | dofile("buffer.lua") | ||
| 462 | |||
| 463 | ----------------------------------------------------------------------------- | ||
| 426 | -- Sends a HTTP request and retrieves the server reply | 464 | -- Sends a HTTP request and retrieves the server reply |
| 427 | -- Input | 465 | -- Input |
| 428 | -- method: "GET", "PUT", "POST" etc | 466 | -- method: "GET", "PUT", "POST" etc |
| 429 | -- url: target uniform resource locator | 467 | -- url: target uniform resource locator |
| 430 | -- headers: request headers to send | 468 | -- req_hdrs: request headers to send, or nil if none |
| 431 | -- body: request message body | 469 | -- req_body: request message body as a string, or nil if none |
| 470 | -- stay: should we refrain from following a server redirect message? | ||
| 432 | -- Returns | 471 | -- Returns |
| 433 | -- resp_body: response message body, if successfull | 472 | -- resp_body: response message body, or nil if failed |
| 434 | -- resp_hdrs: response header fields received, if sucessfull | 473 | -- resp_hdrs: response header fields received, or nil if failed |
| 435 | -- resp_line: server response status line, if successfull | 474 | -- resp_line: server response status line, or nil if failed |
| 436 | -- err: error message if any | 475 | -- err: error message, or nil if successfull |
| 437 | ----------------------------------------------------------------------------- | 476 | ----------------------------------------------------------------------------- |
| 438 | function http_request(method, url, req_hdrs, body) | 477 | function http_request(method, url, req_hdrs, req_body, stay) |
| 439 | local resp_hdrs, resp_line, err | 478 | local resp_hdrs, resp_line, err |
| 440 | local req_callback = function() | 479 | local req_body_cb = function() |
| 441 | return %body, strlen(%body) | 480 | return %req_body, strlen(%req_body) |
| 442 | end | 481 | end |
| 443 | local resp_aux = { resp_body = "" } | 482 | local resp_body = { buf = buf_create() } |
| 444 | local resp_callback = function(chunk, err) | 483 | local resp_body_cb = function(chunk, err) |
| 445 | if not chunk then | 484 | if not chunk then %resp_body.buf = nil end |
| 446 | %resp_aux.resp_body = nil | 485 | buf_addstring(%resp_body.buf, chunk) |
| 447 | %resp_aux.err = err | 486 | return 1 |
| 448 | return nil | 487 | end |
| 449 | end | 488 | if not req_body then req_body_cb = nil end |
| 450 | %resp_aux.resp_body = %resp_aux.resp_body .. chunk | 489 | resp_hdrs, resp_line, err = http_requestindirect(method, url, resp_body_cb, |
| 451 | return 1 | 490 | req_hdrs, req_body_cb, stay) |
| 452 | end | 491 | return buf_getresult(resp_body.buf), resp_hdrs, resp_line, err |
| 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 | ||
| 458 | end | 492 | end |
| 459 | 493 | ||
| 460 | ----------------------------------------------------------------------------- | 494 | ----------------------------------------------------------------------------- |
| 461 | -- Retrieves a URL by the method "GET" | 495 | -- Retrieves a URL by the method "GET" |
| 462 | -- Input | 496 | -- Input |
| 463 | -- url: target uniform resource locator | 497 | -- url: target uniform resource locator |
| 464 | -- headers: request headers to send | 498 | -- req_hdrs: request headers to send, or nil if none |
| 499 | -- stay: should we refrain from following a server redirect message? | ||
| 465 | -- Returns | 500 | -- Returns |
| 466 | -- body: response message body, if successfull | 501 | -- resp_body: response message body, or nil if failed |
| 467 | -- headers: response header fields, if sucessfull | 502 | -- resp_hdrs: response header fields received, or nil if failed |
| 468 | -- line: response status line, if successfull | 503 | -- resp_line: server response status line, or nil if failed |
| 469 | -- err: error message, if any | 504 | -- err: error message, or nil if successfull |
| 470 | ----------------------------------------------------------------------------- | 505 | ----------------------------------------------------------------------------- |
| 471 | function http_get(url, headers) | 506 | function http_get(url, req_hdrs, stay) |
| 472 | return http_request("GET", url, headers) | 507 | return http_request("GET", url, req_hdrs, stay) |
| 473 | end | 508 | end |
| 474 | 509 | ||
| 475 | ----------------------------------------------------------------------------- | 510 | ----------------------------------------------------------------------------- |
| 476 | -- Retrieves a URL by the method "GET" | 511 | -- Retrieves a URL by the method "GET" |
| 477 | -- Input | 512 | -- Input |
| 478 | -- url: target uniform resource locator | 513 | -- url: target uniform resource locator |
| 479 | -- body: request message body | 514 | -- resp_body_cb: response message body receive callback |
| 480 | -- headers: request headers to send | 515 | -- req_hdrs: request headers to send, or nil if none |
| 516 | -- stay: should we refrain from following a server redirect message? | ||
| 481 | -- Returns | 517 | -- Returns |
| 482 | -- body: response message body, if successfull | 518 | -- resp_body: response message body, or nil if failed |
| 483 | -- headers: response header fields, if sucessfull | 519 | -- resp_hdrs: response header fields received, or nil if failed |
| 484 | -- line: response status line, if successfull | 520 | -- resp_line: server response status line, or nil if failed |
| 485 | -- err: error message, if any | 521 | -- err: error message, or nil if successfull |
| 522 | ----------------------------------------------------------------------------- | ||
| 523 | function http_getindirect(url, resp_body_cb, req_hdrs, stay) | ||
| 524 | return http_requestindirect("GET", url, resp_body_cb, req_hdrs, nil, stay) | ||
| 525 | end | ||
| 526 | |||
| 486 | ----------------------------------------------------------------------------- | 527 | ----------------------------------------------------------------------------- |
| 487 | function http_post(url, body, headers) | 528 | -- Retrieves a URL by the method "POST" |
| 488 | return http_request("POST", url, headers, body) | 529 | -- Input |
| 530 | -- method: "GET", "PUT", "POST" etc | ||
| 531 | -- url: target uniform resource locator | ||
| 532 | -- req_hdrs: request headers to send, or nil if none | ||
| 533 | -- req_body: request message body, or nil if none | ||
| 534 | -- stay: should we refrain from following a server redirect message? | ||
| 535 | -- Returns | ||
| 536 | -- resp_body: response message body, or nil if failed | ||
| 537 | -- resp_hdrs: response header fields received, or nil if failed | ||
| 538 | -- resp_line: server response status line, or nil if failed | ||
| 539 | -- err: error message, or nil if successfull | ||
| 540 | ----------------------------------------------------------------------------- | ||
| 541 | function http_post(url, req_body, req_hdrs, stay) | ||
| 542 | return http_request("POST", url, req_hdrs, req_body, stay) | ||
| 543 | end | ||
| 544 | |||
| 545 | ----------------------------------------------------------------------------- | ||
| 546 | -- Retrieves a URL by the method "POST" | ||
| 547 | -- Input | ||
| 548 | -- url: target uniform resource locator | ||
| 549 | -- resp_body_cb: response message body receive callback | ||
| 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 | ||
| 554 | -- resp_body: response message body, or nil if failed | ||
| 555 | -- resp_hdrs: response header fields received, or nil if failed | ||
| 556 | -- resp_line: server response status line, or nil if failed | ||
| 557 | -- err: error message, or nil if successfull | ||
| 558 | ----------------------------------------------------------------------------- | ||
| 559 | function http_getindirect(url, resp_body_cb, req_body_cb, req_hdrs, stay) | ||
| 560 | return http_requestindirect("GET", url, resp_body_cb, req_hdrs, | ||
| 561 | req_body_cb, stay) | ||
| 489 | end | 562 | end |
