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 |