aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2001-01-25 22:01:37 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2001-01-25 22:01:37 +0000
commitbee46b39bf5b29e559b2152e4b32d2f37ee71a8e (patch)
tree0d05cdff82bcfda622b431819ab6492f7590a152 /src
parentf6b95052256fa9609a8f8a2c8abfe0af1e18706f (diff)
downloadluasocket-bee46b39bf5b29e559b2152e4b32d2f37ee71a8e.tar.gz
luasocket-bee46b39bf5b29e559b2152e4b32d2f37ee71a8e.tar.bz2
luasocket-bee46b39bf5b29e559b2152e4b32d2f37ee71a8e.zip
HTTP is now generic, with function http_request.
RFC is more strictly followed.
Diffstat (limited to 'src')
-rw-r--r--src/http.lua425
1 files changed, 258 insertions, 167 deletions
diff --git a/src/http.lua b/src/http.lua
index 8f08725..7212728 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -1,5 +1,6 @@
1----------------------------------------------------------------------------- 1-----------------------------------------------------------------------------
2-- Simple HTTP/1.1 support for the Lua language using the LuaSocket toolkit. 2-- Full HTTP/1.1 client support for the Lua language using the
3-- LuaSocket 1.2 toolkit.
3-- Author: Diego Nehab 4-- Author: Diego Nehab
4-- Date: 26/12/2000 5-- Date: 26/12/2000
5-- Conforming to: RFC 2068 6-- Conforming to: RFC 2068
@@ -9,165 +10,171 @@
9-- Program constants 10-- Program constants
10----------------------------------------------------------------------------- 11-----------------------------------------------------------------------------
11-- connection timeout in seconds 12-- connection timeout in seconds
12local TIMEOUT = 60 13local TIMEOUT = 60
13-- default port for document retrieval 14-- default port for document retrieval
14local PORT = 80 15local PORT = 80
15-- user agent field sent in request 16-- user agent field sent in request
16local USERAGENT = "LuaSocket/HTTP 1.0" 17local USERAGENT = "LuaSocket 1.2 HTTP 1.1"
17 18
18----------------------------------------------------------------------------- 19-----------------------------------------------------------------------------
19-- Tries to get a line from the server or close socket if error 20-- Tries to get a pattern from the server and closes socket on error
20-- sock: socket connected to the server 21-- sock: socket connected to the server
22-- pattern: pattern to receive
21-- Returns 23-- Returns
22-- line: line received or nil in case of error 24-- data: line received or nil in case of error
23-- err: error message if any 25-- err: error message if any
24----------------------------------------------------------------------------- 26-----------------------------------------------------------------------------
25local try_getline = function(sock) 27local try_get = function(...)
26 line, err = sock:receive() 28 local sock = arg[1]
27 if err then 29 local data, err = call(sock.receive, arg)
28 sock:close() 30 if err then
29 return nil, err 31 sock:close()
30 end 32 return nil, err
31 return line 33 end
34 return data
32end 35end
33 36
34----------------------------------------------------------------------------- 37-----------------------------------------------------------------------------
35-- Tries to send a line to the server or close socket if error 38-- Tries to send data to the server and closes socket on error
36-- sock: socket connected to the server 39-- sock: socket connected to the server
37-- line: line to send 40-- data: data to send
38-- Returns 41-- Returns
39-- err: error message if any 42-- err: error message if any, nil if successfull
40----------------------------------------------------------------------------- 43-----------------------------------------------------------------------------
41local try_sendline = function(sock, line) 44local try_send = function(sock, data)
42 err = sock:send(line) 45 err = sock:send(data)
43 if err then sock:close() end 46 if err then sock:close() end
44 return err 47 return err
45end 48end
46 49
47----------------------------------------------------------------------------- 50-----------------------------------------------------------------------------
48-- Retrieves status from http reply 51-- Retrieves status code from http status line
49-- Input 52-- Input
50-- reply: http reply string 53-- line: http status line
51-- Returns 54-- Returns
52-- status: integer with status code 55-- code: integer with status code
53----------------------------------------------------------------------------- 56-----------------------------------------------------------------------------
54local get_status = function(reply) 57local get_statuscode = function(line)
55 local _,_, status = strfind(reply, " (%d%d%d) ") 58 local _,_, code = strfind(line, " (%d%d%d) ")
56 return tonumber(status) 59 return tonumber(code)
57end 60end
58 61
59----------------------------------------------------------------------------- 62-----------------------------------------------------------------------------
60-- Receive server reply messages 63-- Receive server reply messages
61-- Input 64-- Input
62-- sock: server socket 65-- sock: socket connected to the server
63-- Returns 66-- Returns
64-- status: server reply status code or nil if error 67-- code: server status code or nil if error
65-- reply: full server reply 68-- line: full http status line
66-- err: error message if any 69-- err: error message if any
67----------------------------------------------------------------------------- 70-----------------------------------------------------------------------------
68local get_reply = function(sock) 71local get_status = function(sock)
69 local reply, err 72 local line, err
70 reply, err = %try_getline(sock) 73 line, err = %try_get(sock)
71 if not err then return %get_status(reply), reply 74 if not err then return %get_statuscode(line), line
72 else return nil, nil, err end 75 else return nil, nil, err end
73end 76end
74 77
75----------------------------------------------------------------------------- 78-----------------------------------------------------------------------------
76-- Receive and parse mime headers 79-- Receive and parse responce header fields
77-- Input 80-- Input
78-- sock: server socket 81-- sock: socket connected to the server
79-- mime: a table that might already contain mime headers 82-- headers: a table that might already contain headers
80-- Returns 83-- Returns
81-- mime: a table with all mime headers in the form 84-- headers: a table with all headers fields in the form
82-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"} 85-- {name_1 = "value_1", name_2 = "value_2" ... name_n = "value_n"}
83-- all name_i are lowercase 86-- all name_i are lowercase
84-- nil and error message in case of error 87-- nil and error message in case of error
85----------------------------------------------------------------------------- 88-----------------------------------------------------------------------------
86local get_mime = function(sock, mime) 89local get_headers = function(sock, headers)
87 local line, err 90 local line, err
88 local name, value 91 local name, value
89 -- get first line 92 -- get first line
90 line, err = %try_getline(sock) 93 line, err = %try_get(sock)
91 if err then return nil, err end 94 if err then return nil, err end
92 -- headers go until a blank line is found 95 -- headers go until a blank line is found
93 while line ~= "" do 96 while line ~= "" do
94 -- get field-name and value 97 -- get field-name and value
95 _,_, name, value = strfind(line, "(.-):%s*(.*)") 98 _,_, name, value = strfind(line, "(.-):%s*(.*)")
99 if not name or not value then
100 sock:close()
101 return nil, "malformed reponse headers"
102 end
96 name = strlower(name) 103 name = strlower(name)
97 -- get next line (value might be folded) 104 -- get next line (value might be folded)
98 line, err = %try_getline(sock) 105 line, err = %try_get(sock)
99 if err then return nil, err end 106 if err then return nil, err end
100 -- unfold any folded values 107 -- unfold any folded values
101 while not err and line ~= "" and (strsub(line, 1, 1) == " ") do 108 while not err and strfind(line, "^%s") do
102 value = value .. line 109 value = value .. line
103 line, err = %try_getline(sock) 110 line, err = %try_get(sock)
104 if err then return nil, err end 111 if err then return nil, err end
105 end 112 end
106 -- save pair in table 113 -- save pair in table
107 if mime[name] then 114 if headers[name] then headers[name] = headers[name] .. ", " .. value
108 -- join any multiple field 115 else headers[name] = value end
109 mime[name] = mime[name] .. ", " .. value
110 else
111 -- new field
112 mime[name] = value
113 end
114 end 116 end
115 return mime 117 return headers
118end
119
120-----------------------------------------------------------------------------
121-- Receives a chunked message body
122-- Input
123-- sock: socket connected to the server
124-- Returns
125-- body: a string containing the body of the message
126-- nil and error message in case of error
127-----------------------------------------------------------------------------
128local try_getchunked = function(sock)
129 local chunk_size, line, err
130 local body = ""
131 repeat
132 -- get chunk size, skip extention
133 line, err = %try_get(sock)
134 if err then return nil, err end
135 chunk_size = tonumber(gsub(line, ";.*", ""), 16)
136 if not chunk_size then
137 sock:close()
138 return nil, "invalid chunk size"
139 end
140 -- get chunk
141 line, err = %try_get(sock, chunk_size)
142 if err then return nil, err end
143 -- concatenate new chunk
144 body = body .. line
145 -- skip blank line
146 _, err = %try_get(sock)
147 if err then return nil, err end
148 until chunk_size <= 0
149 return body
116end 150end
117 151
118----------------------------------------------------------------------------- 152-----------------------------------------------------------------------------
119-- Receives http body 153-- Receives http body
120-- Input 154-- Input
121-- sock: server socket 155-- sock: socket connected to the server
122-- mime: initial mime headers 156-- headers: response header fields
123-- Returns 157-- Returns
124-- body: a string containing the body of the document 158-- body: a string containing the body of the document
125-- nil and error message in case of error 159-- nil and error message in case of error
126-- Obs: 160-- Obs:
127-- mime: headers might be modified by chunked transfer 161-- headers: headers might be modified by chunked transfer
128----------------------------------------------------------------------------- 162-----------------------------------------------------------------------------
129local get_body = function(sock, mime) 163local get_body = function(sock, headers)
130 local body, err 164 local body, err
131 if mime["transfer-encoding"] == "chunked" then 165 if headers["transfer-encoding"] == "chunked" then
132 local chunk_size, line 166 body, err = %try_getchunked(sock)
133 body = "" 167 if err then return nil, err end
134 repeat 168 -- store extra entity headers
135 -- get chunk size, skip extention 169 --_, err = %get_headers(sock, headers)
136 line, err = %try_getline(sock) 170 --if err then return nil, err end
137 if err then return nil, err end 171 elseif headers["content-length"] then
138 chunk_size = tonumber(gsub(line, ";.*", ""), 16) 172 body, err = %try_get(sock, tonumber(headers["content-length"]))
139 if not chunk_size then 173 if err then return nil, err end
140 sock:close()
141 return nil, "invalid chunk size"
142 end
143 -- get chunk
144 line, err = sock:receive(chunk_size)
145 if err then
146 sock:close()
147 return nil, err
148 end
149 -- concatenate new chunk
150 body = body .. line
151 -- skip blank line
152 _, err = %try_getline(sock)
153 if err then return nil, err end
154 until chunk_size <= 0
155 -- store extra mime headers
156 --_, err = %get_mime(sock, mime)
157 --if err then return nil, err end
158 elseif mime["content-length"] then
159 body, err = sock:receive(tonumber(mime["content-length"]))
160 if err then
161 sock:close()
162 return nil, err
163 end
164 else 174 else
165 -- get it all until connection closes! 175 -- get it all until connection closes!
166 body, err = sock:receive("*a") 176 body, err = %try_get(sock, "*a")
167 if err then 177 if err then return nil, err end
168 sock:close()
169 return nil, err
170 end
171 end 178 end
172 -- return whole body 179 -- return whole body
173 return body 180 return body
@@ -175,10 +182,9 @@ end
175 182
176----------------------------------------------------------------------------- 183-----------------------------------------------------------------------------
177-- Parses a url and returns its scheme, user, password, host, port 184-- Parses a url and returns its scheme, user, password, host, port
178-- and path components, according to RFC 1738, Uniform Resource Locators (URL), 185-- and path components, according to RFC 1738
179-- of December 1994
180-- Input 186-- Input
181-- url: unique resource locator desired 187-- url: uniform resource locator of request
182-- default: table containing default values to be returned 188-- default: table containing default values to be returned
183-- Returns 189-- Returns
184-- table with the following fields: 190-- table with the following fields:
@@ -213,47 +219,99 @@ local split_url = function(url, default)
213end 219end
214 220
215----------------------------------------------------------------------------- 221-----------------------------------------------------------------------------
216-- Sends a GET message through socket 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...
217-- Input 225-- Input
218-- socket: http connection socket 226-- sock: socket connected to the server
219-- path: path requested 227-- body: body to be sent in request
220-- mime: mime headers to send in request
221-- Returns 228-- Returns
222-- err: nil in case of success, error message otherwise 229-- err: nil in case of success, error message otherwise
223----------------------------------------------------------------------------- 230-----------------------------------------------------------------------------
224local send_get = function(sock, path, mime) 231local try_sendchunked = function(sock, body)
225 local err = %try_sendline(sock, "GET " .. path .. " HTTP/1.1\r\n") 232 local wanted = strlen(body)
226 if err then return err end 233 local first = 1
227 for i, v in mime do 234 local chunk_size
228 err = %try_sendline(sock, i .. ": " .. v .. "\r\n") 235 local err
229 if err then return err end 236 while wanted > 0 do
230 end 237 chunk_size = min(wanted, 1024)
231 err = %try_sendline(sock, "\r\n") 238 err = %try_send(sock, format("%x\r\n", chunk_size))
232 return err 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
253-- Input
254-- sock: socket connected to the server
255-- method: request method to be used
256-- path: url path
257-- headers: request headers to be sent
258-- body: request message body, if any
259-- Returns
260-- err: nil in case of success, error message otherwise
261-----------------------------------------------------------------------------
262local send_request = function(sock, method, path, headers, body)
263 local err = %try_send(sock, method .. " " .. path .. " HTTP/1.1\r\n")
264 if err then return err end
265 for i, v in headers do
266 err = %try_send(sock, i .. ": " .. v .. "\r\n")
267 if err then return err end
268 end
269 err = %try_send(sock, "\r\n")
270 --if not err and body then err = %try_sendchunked(sock, body) end
271 if not err and body then err = %try_send(sock, body) end
272 return err
273end
274
275-----------------------------------------------------------------------------
276-- Determines if we should read a message body from the server response
277-- Input
278-- method: method used in request
279-- code: server response status code
280-- Returns
281-- 1 if a message body should be processed, nil otherwise
282-----------------------------------------------------------------------------
283function has_responsebody(method, code)
284 if method == "HEAD" then return nil end
285 if code == 204 or code == 304 then return nil end
286 if code >= 100 and code < 200 then return nil end
287 return 1
233end 288end
234 289
235----------------------------------------------------------------------------- 290-----------------------------------------------------------------------------
236-- Converts field names to lowercase 291-- Converts field names to lowercase and add message body size specification
237-- Input 292-- Input
238-- headers: user header fields 293-- headers: request header fields
239-- parsed: parsed url components 294-- parsed: parsed url components
295-- body: request message body, if any
240-- Returns 296-- Returns
241-- mime: a table with the same headers, but with lowercase field names 297-- lower: a table with the same headers, but with lowercase field names
242----------------------------------------------------------------------------- 298-----------------------------------------------------------------------------
243local fill_headers = function(headers, parsed) 299local fill_headers = function(headers, parsed, body)
244 local mime = {} 300 local lower = {}
245 headers = headers or {} 301 headers = headers or {}
246 for i,v in headers do 302 for i,v in headers do
247 mime[strlower(i)] = v 303 lower[strlower(i)] = v
248 end 304 end
249 mime["connection"] = "close" 305 --if body then lower["transfer-encoding"] = "chunked" end
250 mime["host"] = parsed.host 306 if body then lower["content-length"] = tostring(strlen(body)) end
251 mime["user-agent"] = %USERAGENT 307 lower["connection"] = "close"
252 if parsed.user and parsed.pass then -- Basic Authentication 308 lower["host"] = parsed.host
253 mime["authorization"] = "Basic ".. 309 lower["user-agent"] = %USERAGENT
254 base64(parsed.user .. ":" .. parsed.pass) 310 if parsed.user and parsed.pass then -- Basic Authentication
255 end 311 lower["authorization"] = "Basic "..
256 return mime 312 base64(parsed.user .. ":" .. parsed.pass)
313 end
314 return lower
257end 315end
258 316
259----------------------------------------------------------------------------- 317-----------------------------------------------------------------------------
@@ -262,51 +320,84 @@ end
262dofile("base64.lua") 320dofile("base64.lua")
263 321
264----------------------------------------------------------------------------- 322-----------------------------------------------------------------------------
265-- Downloads and receives a http url, with its mime headers 323-- Sends a HTTP request and retrieves the server reply
266-- Input 324-- Input
267-- url: unique resource locator desired 325-- method: "GET", "PUT", "POST" etc
268-- headers: headers to send with request 326-- url: target uniform resource locator
269-- tried: is this an authentication retry? 327-- headers: request headers to send
328-- body: request message body
270-- Returns 329-- Returns
271-- body: document body, if successfull 330-- resp_body: response message body, if successfull
272-- mime: headers received with document, if sucessfull 331-- resp_hdrs: response header fields received, if sucessfull
273-- reply: server reply, if successfull 332-- line: server response status line, if successfull
274-- err: error message, if any 333-- err: error message if any
275----------------------------------------------------------------------------- 334-----------------------------------------------------------------------------
276function http_get(url, headers) 335function http_request(method, url, headers, body)
277 local sock, err, mime, body, status, reply 336 local sock, err
337 local resp_hdrs, response_body
338 local line, code
278 -- get url components 339 -- get url components
279 local parsed = %split_url(url, {port = %PORT, path ="/"}) 340 local parsed = %split_url(url, {port = %PORT, path ="/"})
280 -- fill default headers 341 -- methods are case sensitive
281 headers = %fill_headers(headers, parsed) 342 method = strupper(method)
282 -- try connection 343 -- fill default headers
344 headers = %fill_headers(headers, parsed, body)
345 -- try connection
283 sock, err = connect(parsed.host, parsed.port) 346 sock, err = connect(parsed.host, parsed.port)
284 if not sock then return nil, nil, nil, err end 347 if not sock then return nil, nil, nil, err end
285 -- set connection timeout 348 -- set connection timeout
286 sock:timeout(%TIMEOUT) 349 sock:timeout(%TIMEOUT)
287 -- send request 350 -- send request
288 err = %send_get(sock, parsed.path, headers) 351 err = %send_request(sock, method, parsed.path, headers, body)
289 if err then return nil, nil, nil, err end 352 if err then return nil, nil, nil, err end
290 -- get server message 353 -- get server message
291 status, reply, err = %get_reply(sock) 354 code, line, err = %get_status(sock)
292 if err then return nil, nil, nil, err end 355 if err then return nil, nil, nil, err end
293 -- get url accordingly 356 -- deal with reply
294 if status == 200 then -- ok, go on and get it 357 resp_hdrs, err = %get_headers(sock, {})
295 mime, err = %get_mime(sock, {}) 358 if err then return nil, nil, line, err end
296 if err then return nil, nil, reply, err end 359 -- get body if status and method allow one
297 body, err = %get_body(sock, mime) 360 if has_responsebody(method, code) then
298 if err then return nil, mime, reply, err end 361 resp_body, err = %get_body(sock, resp_hdrs)
299 sock:close() 362 if err then return nil, resp_hdrs, line, err end
300 return body, mime, reply 363 end
301 elseif status == 301 then -- moved permanently, try again 364 sock:close()
302 mime = %get_mime(sock, {}) 365 -- should we automatically retry?
303 sock:close() 366 if (code == 301 or code == 302) then
304 if mime["location"] then return http_get(mime["location"], headers) 367 if (method == "GET" or method == "HEAD") and resp_hdrs["location"] then
305 else return nil, mime, reply end 368 return http_request(method, resp_hdrs["location"], headers, body)
306 elseif status == 401 then 369 else return nil, resp_hdrs, line end
307 mime, err = %get_mime(sock, {}) 370 end
308 if err then return nil, nil, reply, err end 371 return resp_body, resp_hdrs, line
309 return nil, mime, reply 372end
310 end 373
311 return nil, nil, reply 374-----------------------------------------------------------------------------
375-- Retrieves a URL by the method "GET"
376-- Input
377-- url: target uniform resource locator
378-- headers: request headers to send
379-- Returns
380-- body: response message body, if successfull
381-- headers: response header fields, if sucessfull
382-- line: response status line, if successfull
383-- err: error message, if any
384-----------------------------------------------------------------------------
385function http_get(url, headers)
386 return http_request("GET", url, headers)
387end
388
389-----------------------------------------------------------------------------
390-- Retrieves a URL by the method "GET"
391-- Input
392-- url: target uniform resource locator
393-- body: request message body
394-- headers: request headers to send
395-- Returns
396-- body: response message body, if successfull
397-- headers: response header fields, if sucessfull
398-- line: response status line, if successfull
399-- err: error message, if any
400-----------------------------------------------------------------------------
401function http_post(url, body, headers)
402 return http_request("POST", url, headers, body)
312end 403end