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