aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDiego Nehab <diego@tecgraf.puc-rio.br>2004-01-16 07:06:31 +0000
committerDiego Nehab <diego@tecgraf.puc-rio.br>2004-01-16 07:06:31 +0000
commit89f3ecf7820857f91c4039536d2bbe3cf12d5f95 (patch)
tree76e6cc9ff403a8c5ee52448d7ae9311bdf499039
parent3febb302ad28fd25de51cbc686739469b92d8921 (diff)
downloadluasocket-89f3ecf7820857f91c4039536d2bbe3cf12d5f95.tar.gz
luasocket-89f3ecf7820857f91c4039536d2bbe3cf12d5f95.tar.bz2
luasocket-89f3ecf7820857f91c4039536d2bbe3cf12d5f95.zip
http.lua updated. still needs proxy support.
code.lua updated. looks neat.
-rw-r--r--TODO15
-rw-r--r--etc/get.lua2
-rw-r--r--src/http.lua526
-rw-r--r--src/socket.h1
-rw-r--r--src/tcp.c38
-rw-r--r--src/usocket.c110
-rw-r--r--src/wsocket.c8
-rw-r--r--test/auth/.htaccess2
-rw-r--r--test/httptest.lua89
-rw-r--r--test/testclnt.lua6
10 files changed, 442 insertions, 355 deletions
diff --git a/TODO b/TODO
index 9fdb1e8..bd8a950 100644
--- a/TODO
+++ b/TODO
@@ -1,7 +1,17 @@
1replace times by getrusage 1change send/recv to avoid using select
2 2
3make sure modules know if their dependencies are there. 3add gethostname and use it in HTTP, SMTP etc, and add manual entry.
4add local connect, and manual entry
5add shutdown, and manual entry
6
7only allocate in case of success
8only call select if io fails...
9Proxy support pro http
4 10
11make REUSEADDR an option...
12
13make sure modules know if their dependencies are there.
14_
5one thing i noticed in usocket.c is that it doesn't check for EINTR 15one thing i noticed in usocket.c is that it doesn't check for EINTR
6after write(), sendto(), read(), recvfrom() etc. ? the usual trick is 16after write(), sendto(), read(), recvfrom() etc. ? the usual trick is
7to loop while you get EINTR: 17to loop while you get EINTR:
@@ -68,7 +78,6 @@ Ajeitar o protocolo da luaopen_socket()... sei lá qual é.
68 - proteger ou atomizar o conjunto (timedout, receive), (timedout, send) 78 - proteger ou atomizar o conjunto (timedout, receive), (timedout, send)
69 - inet_ntoa também é uma merda. 79 - inet_ntoa também é uma merda.
70- SSL 80- SSL
71- Proxy support pro http
72 81
73- checar operações em closed sockets 82- checar operações em closed sockets
74- checar teste de writable socket com select 83- checar teste de writable socket com select
diff --git a/etc/get.lua b/etc/get.lua
index a093e24..2d804a0 100644
--- a/etc/get.lua
+++ b/etc/get.lua
@@ -99,7 +99,7 @@ end
99function getbyhttp(url, file, size) 99function getbyhttp(url, file, size)
100 local response = socket.http.request_cb( 100 local response = socket.http.request_cb(
101 {url = url}, 101 {url = url},
102 {body_cb = receive2disk(file, size)} 102 {body_cb = receive2disk(file, size)}
103 ) 103 )
104 print() 104 print()
105 if response.code ~= 200 then print(response.status or response.error) end 105 if response.code ~= 200 then print(response.status or response.error) end
diff --git a/src/http.lua b/src/http.lua
index 18a44b6..1925c68 100644
--- a/src/http.lua
+++ b/src/http.lua
@@ -5,22 +5,29 @@
5-- Conforming to: RFC 2616, LTN7 5-- Conforming to: RFC 2616, LTN7
6-- RCS ID: $Id$ 6-- RCS ID: $Id$
7----------------------------------------------------------------------------- 7-----------------------------------------------------------------------------
8 8-- make sure LuaSocket is loaded
9local Public, Private = {}, {} 9if not LUASOCKET_LIBNAME then error('module requires LuaSocket') end
10local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace 10-- get LuaSocket namespace
11socket.http = Public -- create http sub namespace 11local socket = _G[LUASOCKET_LIBNAME]
12if not socket then error('module requires LuaSocket') end
13-- create smtp namespace inside LuaSocket namespace
14local http = {}
15socket.http = http
16-- make all module globals fall into smtp namespace
17setmetatable(http, { __index = _G })
18setfenv(1, http)
12 19
13----------------------------------------------------------------------------- 20-----------------------------------------------------------------------------
14-- Program constants 21-- Program constants
15----------------------------------------------------------------------------- 22-----------------------------------------------------------------------------
16-- connection timeout in seconds 23-- connection timeout in seconds
17Public.TIMEOUT = 60 24TIMEOUT = 60
18-- default port for document retrieval 25-- default port for document retrieval
19Public.PORT = 80 26PORT = 80
20-- user agent field sent in request 27-- user agent field sent in request
21Public.USERAGENT = "LuaSocket 2.0" 28USERAGENT = "LuaSocket 2.0"
22-- block size used in transfers 29-- block size used in transfers
23Public.BLOCKSIZE = 8192 30BLOCKSIZE = 8192
24 31
25----------------------------------------------------------------------------- 32-----------------------------------------------------------------------------
26-- Tries to get a pattern from the server and closes socket on error 33-- Tries to get a pattern from the server and closes socket on error
@@ -30,7 +37,7 @@ Public.BLOCKSIZE = 8192
30-- received pattern on success 37-- received pattern on success
31-- nil followed by error message on error 38-- nil followed by error message on error
32----------------------------------------------------------------------------- 39-----------------------------------------------------------------------------
33function Private.try_receive(sock, pattern) 40local function try_receiving(sock, pattern)
34 local data, err = sock:receive(pattern) 41 local data, err = sock:receive(pattern)
35 if not data then sock:close() end 42 if not data then sock:close() end
36 return data, err 43 return data, err
@@ -43,26 +50,13 @@ end
43-- Returns 50-- Returns
44-- err: error message if any, nil if successfull 51-- err: error message if any, nil if successfull
45----------------------------------------------------------------------------- 52-----------------------------------------------------------------------------
46function Private.try_send(sock, data) 53local function try_sending(sock, ...)
47 local sent, err = sock:send(data) 54 local sent, err = sock:send(unpack(arg))
48 if not sent then sock:close() end 55 if not sent then sock:close() end
49 return err 56 return err
50end 57end
51 58
52----------------------------------------------------------------------------- 59-----------------------------------------------------------------------------
53-- Computes status code from HTTP status line
54-- Input
55-- line: HTTP status line
56-- Returns
57-- code: integer with status code, or nil if malformed line
58-----------------------------------------------------------------------------
59function Private.get_statuscode(line)
60 local code, _
61 _, _, code = string.find(line, "HTTP/%d*%.%d* (%d%d%d)")
62 return tonumber(code)
63end
64
65-----------------------------------------------------------------------------
66-- Receive server reply messages, parsing for status code 60-- Receive server reply messages, parsing for status code
67-- Input 61-- Input
68-- sock: socket connected to the server 62-- sock: socket connected to the server
@@ -71,10 +65,13 @@ end
71-- line: full HTTP status line 65-- line: full HTTP status line
72-- err: error message if any 66-- err: error message if any
73----------------------------------------------------------------------------- 67-----------------------------------------------------------------------------
74function Private.receive_status(sock) 68local function receive_status(sock)
75 local line, err 69 local line, err
76 line, err = Private.try_receive(sock) 70 line, err = try_receiving(sock)
77 if not err then return Private.get_statuscode(line), line 71 if not err then
72 local code, _
73 _, _, code = string.find(line, "HTTP/%d*%.%d* (%d%d%d)")
74 return tonumber(code), line
78 else return nil, nil, err end 75 else return nil, nil, err end
79end 76end
80 77
@@ -89,11 +86,12 @@ end
89-- all name_i are lowercase 86-- all name_i are lowercase
90-- nil and error message in case of error 87-- nil and error message in case of error
91----------------------------------------------------------------------------- 88-----------------------------------------------------------------------------
92function Private.receive_headers(sock, headers) 89local function receive_headers(sock, headers)
93 local line, err 90 local line, err
94 local name, value, _ 91 local name, value, _
92 headers = headers or {}
95 -- get first line 93 -- get first line
96 line, err = Private.try_receive(sock) 94 line, err = try_receiving(sock)
97 if err then return nil, err end 95 if err then return nil, err end
98 -- headers go until a blank line is found 96 -- headers go until a blank line is found
99 while line ~= "" do 97 while line ~= "" do
@@ -105,12 +103,12 @@ function Private.receive_headers(sock, headers)
105 end 103 end
106 name = string.lower(name) 104 name = string.lower(name)
107 -- get next line (value might be folded) 105 -- get next line (value might be folded)
108 line, err = Private.try_receive(sock) 106 line, err = try_receiving(sock)
109 if err then return nil, err end 107 if err then return nil, err end
110 -- unfold any folded values 108 -- unfold any folded values
111 while not err and string.find(line, "^%s") do 109 while not err and string.find(line, "^%s") do
112 value = value .. line 110 value = value .. line
113 line, err = Private.try_receive(sock) 111 line, err = try_receiving(sock)
114 if err then return nil, err end 112 if err then return nil, err end
115 end 113 end
116 -- save pair in table 114 -- save pair in table
@@ -121,6 +119,19 @@ function Private.receive_headers(sock, headers)
121end 119end
122 120
123----------------------------------------------------------------------------- 121-----------------------------------------------------------------------------
122-- Aborts a receive callback
123-- Input
124-- cb: callback function
125-- err: error message to pass to callback
126-- Returns
127-- callback return or if nil err
128-----------------------------------------------------------------------------
129local function abort(cb, err)
130 local go, err_or_f = cb(nil, err)
131 return err_or_f or err
132end
133
134-----------------------------------------------------------------------------
124-- Receives a chunked message body 135-- Receives a chunked message body
125-- Input 136-- Input
126-- sock: socket connected to the server 137-- sock: socket connected to the server
@@ -129,54 +140,37 @@ end
129-- Returns 140-- Returns
130-- nil if successfull or an error message in case of error 141-- nil if successfull or an error message in case of error
131----------------------------------------------------------------------------- 142-----------------------------------------------------------------------------
132function Private.receivebody_bychunks(sock, headers, receive_cb) 143local function receive_body_bychunks(sock, headers, receive_cb)
133 local chunk, size, line, err, go, uerr, _ 144 local chunk, size, line, err, go, err_or_f, _
134 while 1 do 145 while 1 do
135 -- get chunk size, skip extention 146 -- get chunk size, skip extention
136 line, err = Private.try_receive(sock) 147 line, err = try_receiving(sock)
137 if err then 148 if err then return abort(receive_cb, err) end
138 local go, uerr = receive_cb(nil, err)
139 return uerr or err
140 end
141 size = tonumber(string.gsub(line, ";.*", ""), 16) 149 size = tonumber(string.gsub(line, ";.*", ""), 16)
142 if not size then 150 if not size then return abort(receive_cb, "invalid chunk size") end
143 err = "invalid chunk size"
144 sock:close()
145 go, uerr = receive_cb(nil, err)
146 return uerr or err
147 end
148 -- was it the last chunk? 151 -- was it the last chunk?
149 if size <= 0 then break end 152 if size <= 0 then break end
150 -- get chunk 153 -- get chunk
151 chunk, err = Private.try_receive(sock, size) 154 chunk, err = try_receiving(sock, size)
152 if err then 155 if err then return abort(receive_cb, err) end
153 go, uerr = receive_cb(nil, err)
154 return uerr or err
155 end
156 -- pass chunk to callback 156 -- pass chunk to callback
157 go, uerr = receive_cb(chunk) 157 go, err_or_f = receive_cb(chunk)
158 if not go then 158 -- see if callback needs to be replaced
159 sock:close() 159 receive_cb = err_or_f or receive_cb
160 return uerr or "aborted by callback" 160 -- see if callback aborted
161 end 161 if not go then return err_or_f or "aborted by callback" end
162 -- skip CRLF on end of chunk 162 -- skip CRLF on end of chunk
163 _, err = Private.try_receive(sock) 163 _, err = try_receiving(sock)
164 if err then 164 if err then return abort(receive_cb, err) end
165 go, uerr = receive_cb(nil, err)
166 return uerr or err
167 end
168 end 165 end
169 -- the server should not send trailer headers because we didn't send a 166 -- the server should not send trailer headers because we didn't send a
170 -- header informing it we know how to deal with them. we do not risk 167 -- header informing it we know how to deal with them. we do not risk
171 -- being caught unprepaired. 168 -- being caught unprepaired.
172 headers, err = Private.receive_headers(sock, headers) 169 _, err = receive_headers(sock, headers)
173 if err then 170 if err then return abort(receive_cb, err) end
174 go, uerr = receive_cb(nil, err)
175 return uerr or err
176 end
177 -- let callback know we are done 171 -- let callback know we are done
178 go, uerr = receive_cb("") 172 _, err_or_f = receive_cb("")
179 return uerr 173 return err_or_f
180end 174end
181 175
182----------------------------------------------------------------------------- 176-----------------------------------------------------------------------------
@@ -188,25 +182,21 @@ end
188-- Returns 182-- Returns
189-- nil if successfull or an error message in case of error 183-- nil if successfull or an error message in case of error
190----------------------------------------------------------------------------- 184-----------------------------------------------------------------------------
191function Private.receivebody_bylength(sock, length, receive_cb) 185local function receive_body_bylength(sock, length, receive_cb)
192 local uerr, go
193 while length > 0 do 186 while length > 0 do
194 local size = math.min(Public.BLOCKSIZE, length) 187 local size = math.min(BLOCKSIZE, length)
195 local chunk, err = sock:receive(size) 188 local chunk, err = sock:receive(size)
196 -- if there was an error before we got all the data 189 local go, err_or_f = receive_cb(chunk)
197 if err and string.len(chunk) ~= length then 190 length = length - string.len(chunk)
198 go, uerr = receive_cb(nil, err) 191 -- see if callback aborted
199 return uerr or err 192 if not go then return err_or_f or "aborted by callback" end
200 end 193 -- see if callback needs to be replaced
201 go, uerr = receive_cb(chunk) 194 receive_cb = err_or_f or receive_cb
202 if not go then 195 -- see if there was an error
203 sock:close() 196 if err and length > 0 then return abort(receive_cb, err) end
204 return uerr or "aborted by callback"
205 end
206 length = length - size
207 end 197 end
208 go, uerr = receive_cb("") 198 local _, err_or_f = receive_cb("")
209 return uerr 199 return err_or_f
210end 200end
211 201
212----------------------------------------------------------------------------- 202-----------------------------------------------------------------------------
@@ -217,24 +207,24 @@ end
217-- Returns 207-- Returns
218-- nil if successfull or an error message in case of error 208-- nil if successfull or an error message in case of error
219----------------------------------------------------------------------------- 209-----------------------------------------------------------------------------
220function Private.receivebody_untilclosed(sock, receive_cb) 210local function receive_body_untilclosed(sock, receive_cb)
221 local err, go, uerr
222 while 1 do 211 while 1 do
223 local chunk, err = sock:receive(Public.BLOCKSIZE) 212 local chunk, err = sock:receive(BLOCKSIZE)
224 if err == "closed" or not err then 213 local go, err_or_f = receive_cb(chunk)
225 go, uerr = receive_cb(chunk) 214 -- see if callback aborted
226 if not go then 215 if not go then return err_or_f or "aborted by callback" end
227 sock:close() 216 -- see if callback needs to be replaced
228 return uerr or "aborted by callback" 217 receive_cb = err_or_f or receive_cb
218 -- see if we are done
219 if err == "closed" then
220 if chunk ~= "" then
221 go, err_or_f = receive_cb("")
222 return err_or_f
229 end 223 end
230 if err == "closed" then break end
231 else
232 go, uerr = callback(nil, err)
233 return uerr or err
234 end 224 end
225 -- see if there was an error
226 if err then return abort(receive_cb, err) end
235 end 227 end
236 go, uerr = receive_cb("")
237 return uerr
238end 228end
239 229
240----------------------------------------------------------------------------- 230-----------------------------------------------------------------------------
@@ -246,59 +236,68 @@ end
246-- Returns 236-- Returns
247-- nil if successfull or an error message in case of error 237-- nil if successfull or an error message in case of error
248----------------------------------------------------------------------------- 238-----------------------------------------------------------------------------
249function Private.receive_body(sock, headers, receive_cb) 239local function receive_body(sock, headers, receive_cb)
250 local te = headers["transfer-encoding"] 240 local te = headers["transfer-encoding"]
251 if te and te ~= "identity" then 241 if te and te ~= "identity" then
252 -- get by chunked transfer-coding of message body 242 -- get by chunked transfer-coding of message body
253 return Private.receivebody_bychunks(sock, headers, receive_cb) 243 return receive_body_bychunks(sock, headers, receive_cb)
254 elseif tonumber(headers["content-length"]) then 244 elseif tonumber(headers["content-length"]) then
255 -- get by content-length 245 -- get by content-length
256 local length = tonumber(headers["content-length"]) 246 local length = tonumber(headers["content-length"])
257 return Private.receivebody_bylength(sock, length, receive_cb) 247 return receive_body_bylength(sock, length, receive_cb)
258 else 248 else
259 -- get it all until connection closes 249 -- get it all until connection closes
260 return Private.receivebody_untilclosed(sock, receive_cb) 250 return receive_body_untilclosed(sock, receive_cb)
261 end 251 end
262end 252end
263 253
264----------------------------------------------------------------------------- 254-----------------------------------------------------------------------------
265-- Drop HTTP response body 255-- Sends data comming from a callback
266-- Input 256-- Input
267-- sock: socket connected to the server 257-- data: data connection
268-- headers: response header fields 258-- send_cb: callback to produce file contents
269-- Returns 259-- Returns
270-- nil if successfull or an error message in case of error 260-- nil if successfull, or an error message in case of error
271----------------------------------------------------------------------------- 261-----------------------------------------------------------------------------
272function Private.drop_body(sock, headers) 262local function send_body_bychunks(data, send_cb)
273 return Private.receive_body(sock, headers, function (c, e) return 1 end) 263 while 1 do
264 local chunk, err_or_f = send_cb()
265 -- check if callback aborted
266 if not chunk then return err_or_f or "aborted by callback" end
267 -- check if callback should be replaced
268 send_cb = err_or_f or send_cb
269 -- if we are done, send last-chunk
270 if chunk == "" then return try_sending(data, "0\r\n\r\n") end
271 -- else send middle chunk
272 local err = try_sending(data,
273 string.format("%X\r\n", string.len(chunk)),
274 chunk,
275 "\r\n"
276 )
277 if err then return err end
278 end
274end 279end
275 280
276----------------------------------------------------------------------------- 281-----------------------------------------------------------------------------
277-- Sends data comming from a callback 282-- Sends data comming from a callback
278-- Input 283-- Input
279-- data: data connection 284-- data: data connection
280-- send_cb: callback to produce file contents 285-- send_cb: callback to produce body contents
281-- chunk, size: first callback return values
282-- Returns 286-- Returns
283-- nil if successfull, or an error message in case of error 287-- nil if successfull, or an error message in case of error
284----------------------------------------------------------------------------- 288-----------------------------------------------------------------------------
285function Private.send_indirect(data, send_cb, chunk, size) 289local function send_body_bylength(data, send_cb)
286 local total, sent, err
287 total = 0
288 while 1 do 290 while 1 do
289 if type(chunk) ~= "string" or type(size) ~= "number" then 291 local chunk, err_or_f = send_cb()
290 data:close() 292 -- check if callback aborted
291 if not chunk and type(size) == "string" then return size 293 if not chunk then return err_or_f or "aborted by callback" end
292 else return "invalid callback return" end 294 -- check if callback should be replaced
293 end 295 send_cb = err_or_f or send_cb
294 sent, err = data:send(chunk) 296 -- check if callback is done
295 if err then 297 if chunk == "" then return end
296 data:close() 298 -- send data
297 return err 299 local err = try_sending(data, chunk)
298 end 300 if err then return err end
299 total = total + sent
300 if total >= size then break end
301 chunk, size = send_cb()
302 end 301 end
303end 302end
304 303
@@ -310,16 +309,16 @@ end
310-- Returns 309-- Returns
311-- err: error message if any 310-- err: error message if any
312----------------------------------------------------------------------------- 311-----------------------------------------------------------------------------
313function Private.send_headers(sock, headers) 312local function send_headers(sock, headers)
314 local err 313 local err
315 headers = headers or {} 314 headers = headers or {}
316 -- send request headers 315 -- send request headers
317 for i, v in headers do 316 for i, v in headers do
318 err = Private.try_send(sock, i .. ": " .. v .. "\r\n") 317 err = try_sending(sock, i .. ": " .. v .. "\r\n")
319 if err then return err end 318 if err then return err end
320 end 319 end
321 -- mark end of request headers 320 -- mark end of request headers
322 return Private.try_send(sock, "\r\n") 321 return try_sending(sock, "\r\n")
323end 322end
324 323
325----------------------------------------------------------------------------- 324-----------------------------------------------------------------------------
@@ -333,43 +332,39 @@ end
333-- Returns 332-- Returns
334-- err: nil in case of success, error message otherwise 333-- err: nil in case of success, error message otherwise
335----------------------------------------------------------------------------- 334-----------------------------------------------------------------------------
336function Private.send_request(sock, method, uri, headers, body_cb) 335local function send_request(sock, method, uri, headers, body_cb)
337 local chunk, size, done, err 336 local chunk, size, done, err
338 -- send request line 337 -- send request line
339 err = Private.try_send(sock, method .. " " .. uri .. " HTTP/1.1\r\n") 338 err = try_sending(sock, method .. " " .. uri .. " HTTP/1.1\r\n")
340 if err then return err end 339 if err then return err end
341 -- if there is a request message body, add content-length header 340 if body_cb and not headers["content-length"] then
342 chunk, size = body_cb() 341 headers["transfer-encoding"] = "chunked"
343 if type(chunk) == "string" and type(size) == "number" then
344 if size > 0 then
345 headers["content-length"] = tostring(size)
346 end
347 else
348 sock:close()
349 if not chunk and type(size) == "string" then return size
350 else return "invalid callback return" end
351 end 342 end
352 -- send request headers 343 -- send request headers
353 err = Private.send_headers(sock, headers) 344 err = send_headers(sock, headers)
354 if err then return err end 345 if err then return err end
355 -- send request message body, if any 346 -- send request message body, if any
356 if body_cb then 347 if body_cb then
357 return Private.send_indirect(sock, body_cb, chunk, size) 348 if not headers["content-length"] then
349 return send_body_bychunks(sock, body_cb)
350 else
351 return send_body_bylength(sock, body_cb)
352 end
358 end 353 end
359end 354end
360 355
361----------------------------------------------------------------------------- 356-----------------------------------------------------------------------------
362-- Determines if we should read a message body from the server response 357-- Determines if we should read a message body from the server response
363-- Input 358-- Input
364-- request: a table with the original request information 359-- reqt: a table with the original request information
365-- response: a table with the server response information 360-- respt: a table with the server response information
366-- Returns 361-- Returns
367-- 1 if a message body should be processed, nil otherwise 362-- 1 if a message body should be processed, nil otherwise
368----------------------------------------------------------------------------- 363-----------------------------------------------------------------------------
369function Private.has_body(request, response) 364local function should_receive_body(reqt, respt)
370 if request.method == "HEAD" then return nil end 365 if reqt.method == "HEAD" then return nil end
371 if response.code == 204 or response.code == 304 then return nil end 366 if respt.code == 204 or respt.code == 304 then return nil end
372 if response.code >= 100 and response.code < 200 then return nil end 367 if respt.code >= 100 and respt.code < 200 then return nil end
373 return 1 368 return 1
374end 369end
375 370
@@ -381,11 +376,11 @@ end
381-- Returns 376-- Returns
382-- lower: a table with the same headers, but with lowercase field names 377-- lower: a table with the same headers, but with lowercase field names
383----------------------------------------------------------------------------- 378-----------------------------------------------------------------------------
384function Private.fill_headers(headers, parsed) 379local function fill_headers(headers, parsed)
385 local lower = {} 380 local lower = {}
386 headers = headers or {} 381 headers = headers or {}
387 -- set default headers 382 -- set default headers
388 lower["user-agent"] = Public.USERAGENT 383 lower["user-agent"] = USERAGENT
389 -- override with user values 384 -- override with user values
390 for i,v in headers do 385 for i,v in headers do
391 lower[string.lower(i)] = v 386 lower[string.lower(i)] = v
@@ -399,15 +394,15 @@ end
399----------------------------------------------------------------------------- 394-----------------------------------------------------------------------------
400-- Decides wether we should follow retry with authorization formation 395-- Decides wether we should follow retry with authorization formation
401-- Input 396-- Input
402-- request: a table with the original request information 397-- reqt: a table with the original request information
403-- parsed: parsed request URL 398-- parsed: parsed request URL
404-- response: a table with the server response information 399-- respt: a table with the server response information
405-- Returns 400-- Returns
406-- 1 if we should retry, nil otherwise 401-- 1 if we should retry, nil otherwise
407----------------------------------------------------------------------------- 402-----------------------------------------------------------------------------
408function Private.should_authorize(request, parsed, response) 403local function should_authorize(reqt, parsed, respt)
409 -- if there has been an authorization attempt, it must have failed 404 -- if there has been an authorization attempt, it must have failed
410 if request.headers["authorization"] then return nil end 405 if reqt.headers["authorization"] then return nil end
411 -- if we don't have authorization information, we can't retry 406 -- if we don't have authorization information, we can't retry
412 if parsed.user and parsed.password then return 1 407 if parsed.user and parsed.password then return 1
413 else return nil end 408 else return nil end
@@ -416,66 +411,66 @@ end
416----------------------------------------------------------------------------- 411-----------------------------------------------------------------------------
417-- Returns the result of retrying a request with authorization information 412-- Returns the result of retrying a request with authorization information
418-- Input 413-- Input
419-- request: a table with the original request information 414-- reqt: a table with the original request information
420-- parsed: parsed request URL 415-- parsed: parsed request URL
421-- response: a table with the server response information 416-- respt: a table with the server response information
422-- Returns 417-- Returns
423-- response: result of target redirection 418-- respt: result of target authorization
424----------------------------------------------------------------------------- 419-----------------------------------------------------------------------------
425function Private.authorize(request, parsed, response) 420local function authorize(reqt, parsed, respt)
426 request.headers["authorization"] = "Basic " .. 421 reqt.headers["authorization"] = "Basic " ..
427 socket.code.base64(parsed.user .. ":" .. parsed.password) 422 socket.code.base64.encode(parsed.user .. ":" .. parsed.password)
428 local authorize = { 423 local autht = {
429 redirects = request.redirects, 424 nredirects = reqt.nredirects,
430 method = request.method, 425 method = reqt.method,
431 url = request.url, 426 url = reqt.url,
432 body_cb = request.body_cb, 427 body_cb = reqt.body_cb,
433 headers = request.headers 428 headers = reqt.headers
434 } 429 }
435 return Public.request_cb(authorize, response) 430 return request_cb(autht, respt)
436end 431end
437 432
438----------------------------------------------------------------------------- 433-----------------------------------------------------------------------------
439-- Decides wether we should follow a server redirect message 434-- Decides wether we should follow a server redirect message
440-- Input 435-- Input
441-- request: a table with the original request information 436-- reqt: a table with the original request information
442-- response: a table with the server response information 437-- respt: a table with the server response information
443-- Returns 438-- Returns
444-- 1 if we should redirect, nil otherwise 439-- 1 if we should redirect, nil otherwise
445----------------------------------------------------------------------------- 440-----------------------------------------------------------------------------
446function Private.should_redirect(request, response) 441local function should_redirect(reqt, respt)
447 local follow = not request.stay 442 local follow = not reqt.stay
448 follow = follow and (response.code == 301 or response.code == 302) 443 follow = follow and (respt.code == 301 or respt.code == 302)
449 follow = follow and (request.method == "GET" or request.method == "HEAD") 444 follow = follow and (reqt.method == "GET" or reqt.method == "HEAD")
450 follow = follow and not (request.redirects and request.redirects >= 5) 445 follow = follow and not (reqt.nredirects and reqt.nredirects >= 5)
451 return follow 446 return follow
452end 447end
453 448
454----------------------------------------------------------------------------- 449-----------------------------------------------------------------------------
455-- Returns the result of a request following a server redirect message. 450-- Returns the result of a request following a server redirect message.
456-- Input 451-- Input
457-- request: a table with the original request information 452-- reqt: a table with the original request information
458-- response: a table with the following fields: 453-- respt: a table with the following fields:
459-- body_cb: response method body receive-callback 454-- body_cb: response method body receive-callback
460-- Returns 455-- Returns
461-- response: result of target redirection 456-- respt: result of target redirection
462----------------------------------------------------------------------------- 457-----------------------------------------------------------------------------
463function Private.redirect(request, response) 458local function redirect(reqt, respt)
464 local redirects = request.redirects or 0 459 local nredirects = reqt.nredirects or 0
465 redirects = redirects + 1 460 nredirects = nredirects + 1
466 local redirect = { 461 local redirt = {
467 redirects = redirects, 462 nredirects = nredirects,
468 method = request.method, 463 method = reqt.method,
469 -- the RFC says the redirect URL has to be absolute, but some 464 -- the RFC says the redirect URL has to be absolute, but some
470 -- servers do not respect that 465 -- servers do not respect that
471 url = socket.url.absolute(request.url, response.headers["location"]), 466 url = socket.url.absolute(reqt.url, respt.headers["location"]),
472 body_cb = request.body_cb, 467 body_cb = reqt.body_cb,
473 headers = request.headers 468 headers = reqt.headers
474 } 469 }
475 local response = Public.request_cb(redirect, response) 470 respt = request_cb(redirt, respt)
476 -- we pass the location header as a clue we tried to redirect 471 -- we pass the location header as a clue we tried to redirect
477 if response.headers then response.headers.location = redirect.url end 472 if respt.headers then respt.headers.location = redirt.url end
478 return response 473 return respt
479end 474end
480 475
481----------------------------------------------------------------------------- 476-----------------------------------------------------------------------------
@@ -485,7 +480,7 @@ end
485-- Returns 480-- Returns
486-- uri: request URI for parsed URL 481-- uri: request URI for parsed URL
487----------------------------------------------------------------------------- 482-----------------------------------------------------------------------------
488function Private.request_uri(parsed) 483local function request_uri(parsed)
489 local uri = "" 484 local uri = ""
490 if parsed.path then uri = uri .. parsed.path end 485 if parsed.path then uri = uri .. parsed.path end
491 if parsed.params then uri = uri .. ";" .. parsed.params end 486 if parsed.params then uri = uri .. ";" .. parsed.params end
@@ -502,105 +497,110 @@ end
502-- user: account user name 497-- user: account user name
503-- password: account password) 498-- password: account password)
504-- Returns 499-- Returns
505-- request: request table 500-- reqt: request table
506----------------------------------------------------------------------------- 501-----------------------------------------------------------------------------
507function Private.build_request(data) 502local function build_request(data)
508 local request = {} 503 local reqt = {}
509 if type(data) == "table" then 504 if type(data) == "table" then
510 for i, v in data 505 for i, v in data
511 do request[i] = v 506 do reqt[i] = v
512 end 507 end
513 else request.url = data end 508 else reqt.url = data end
514 return request 509 return reqt
515end 510end
516 511
517----------------------------------------------------------------------------- 512-----------------------------------------------------------------------------
518-- Sends a HTTP request and retrieves the server reply using callbacks to 513-- Sends a HTTP request and retrieves the server reply using callbacks to
519-- send the request body and receive the response body 514-- send the request body and receive the response body
520-- Input 515-- Input
521-- request: a table with the following fields 516-- reqt: a table with the following fields
522-- method: "GET", "PUT", "POST" etc (defaults to "GET") 517-- method: "GET", "PUT", "POST" etc (defaults to "GET")
523-- url: target uniform resource locator 518-- url: target uniform resource locator
524-- user, password: authentication information 519-- user, password: authentication information
525-- headers: request headers to send, or nil if none 520-- headers: request headers to send, or nil if none
526-- body_cb: request message body send-callback, or nil if none 521-- body_cb: request message body send-callback, or nil if none
527-- stay: should we refrain from following a server redirect message? 522-- stay: should we refrain from following a server redirect message?
528-- response: a table with the following fields: 523-- respt: a table with the following fields:
529-- body_cb: response method body receive-callback 524-- body_cb: response method body receive-callback
530-- Returns 525-- Returns
531-- response: a table with the following fields: 526-- respt: a table with the following fields:
532-- headers: response header fields received, or nil if failed 527-- headers: response header fields received, or nil if failed
533-- status: server response status line, or nil if failed 528-- status: server response status line, or nil if failed
534-- code: server status code, or nil if failed 529-- code: server status code, or nil if failed
535-- error: error message, or nil if successfull 530-- error: error message, or nil if successfull
536----------------------------------------------------------------------------- 531-----------------------------------------------------------------------------
537function Public.request_cb(request, response) 532function request_cb(reqt, respt)
538 local parsed = socket.url.parse(request.url, { 533 local parsed = socket.url.parse(reqt.url, {
539 host = "", 534 host = "",
540 port = Public.PORT, 535 port = PORT,
541 path ="/", 536 path ="/",
542 scheme = "http" 537 scheme = "http"
543 }) 538 })
544 if parsed.scheme ~= "http" then 539 if parsed.scheme ~= "http" then
545 response.error = string.format("unknown scheme '%s'", parsed.scheme) 540 respt.error = string.format("unknown scheme '%s'", parsed.scheme)
546 return response 541 return respt
547 end 542 end
548 -- explicit authentication info overrides that given by the URL 543 -- explicit authentication info overrides that given by the URL
549 parsed.user = request.user or parsed.user 544 parsed.user = reqt.user or parsed.user
550 parsed.password = request.password or parsed.password 545 parsed.password = reqt.password or parsed.password
551 -- default method 546 -- default method
552 request.method = request.method or "GET" 547 reqt.method = reqt.method or "GET"
553 -- fill default headers 548 -- fill default headers
554 request.headers = Private.fill_headers(request.headers, parsed) 549 reqt.headers = fill_headers(reqt.headers, parsed)
555 -- try to connect to server 550 -- try to connect to server
556 local sock 551 local sock
557 sock, response.error = socket.connect(parsed.host, parsed.port) 552 sock, respt.error = socket.connect(parsed.host, parsed.port)
558 if not sock then return response end 553 if not sock then return respt end
559 -- set connection timeout so that we do not hang forever 554 -- set connection timeout so that we do not hang forever
560 sock:settimeout(Public.TIMEOUT) 555 sock:settimeout(TIMEOUT)
561 -- send request message 556 -- send request message
562 response.error = Private.send_request(sock, request.method, 557 respt.error = send_request(sock, reqt.method,
563 Private.request_uri(parsed), request.headers, request.body_cb) 558 request_uri(parsed), reqt.headers, reqt.body_cb)
564 if response.error then return response end 559 if respt.error then
560 sock:close()
561 return respt
562 end
565 -- get server response message 563 -- get server response message
566 response.code, response.status, response.error = 564 respt.code, respt.status, respt.error = receive_status(sock)
567 Private.receive_status(sock) 565 if respt.error then return respt end
568 if response.error then return response end 566 -- deal with continue 100
569 -- deal with 1xx status 567 -- servers should not send them, but they might
570 if response.code == 100 then 568 if respt.code == 100 then
571 response.headers, response.error = Private.receive_headers(sock, {}) 569 respt.headers, respt.error = receive_headers(sock, {})
572 if response.error then return response end 570 if respt.error then return respt end
573 response.code, response.status, response.error = 571 respt.code, respt.status, respt.error = receive_status(sock)
574 Private.receive_status(sock) 572 if respt.error then return respt end
575 if response.error then return response end
576 end 573 end
577 -- receive all headers 574 -- receive all headers
578 response.headers, response.error = Private.receive_headers(sock, {}) 575 respt.headers, respt.error = receive_headers(sock, {})
579 if response.error then return response end 576 if respt.error then return respt end
580 -- decide what to do based on request and response parameters 577 -- decide what to do based on request and response parameters
581 if Private.should_redirect(request, response) then 578 if should_redirect(reqt, respt) then
582 Private.drop_body(sock, response.headers) 579 -- drop the body
580 receive_body(sock, respt.headers, function (c, e) return 1 end)
581 -- we are done with this connection
583 sock:close() 582 sock:close()
584 return Private.redirect(request, response) 583 return redirect(reqt, respt)
585 elseif Private.should_authorize(request, parsed, response) then 584 elseif should_authorize(reqt, parsed, respt) then
586 Private.drop_body(sock, response.headers) 585 -- drop the body
586 receive_body(sock, respt.headers, function (c, e) return 1 end)
587 -- we are done with this connection
587 sock:close() 588 sock:close()
588 return Private.authorize(request, parsed, response) 589 return authorize(reqt, parsed, respt)
589 elseif Private.has_body(request, response) then 590 elseif should_receive_body(reqt, respt) then
590 response.error = Private.receive_body(sock, response.headers, 591 respt.error = receive_body(sock, respt.headers, respt.body_cb)
591 response.body_cb) 592 if respt.error then return respt end
592 if response.error then return response end
593 sock:close() 593 sock:close()
594 return response 594 return respt
595 end 595 end
596 sock:close() 596 sock:close()
597 return response 597 return respt
598end 598end
599 599
600----------------------------------------------------------------------------- 600-----------------------------------------------------------------------------
601-- Sends a HTTP request and retrieves the server reply 601-- Sends a HTTP request and retrieves the server reply
602-- Input 602-- Input
603-- request: a table with the following fields 603-- reqt: a table with the following fields
604-- method: "GET", "PUT", "POST" etc (defaults to "GET") 604-- method: "GET", "PUT", "POST" etc (defaults to "GET")
605-- url: request URL, i.e. the document to be retrieved 605-- url: request URL, i.e. the document to be retrieved
606-- user, password: authentication information 606-- user, password: authentication information
@@ -608,22 +608,22 @@ end
608-- body: request message body as a string, or nil if none 608-- body: request message body as a string, or nil if none
609-- stay: should we refrain from following a server redirect message? 609-- stay: should we refrain from following a server redirect message?
610-- Returns 610-- Returns
611-- response: a table with the following fields: 611-- respt: a table with the following fields:
612-- body: response message body, or nil if failed 612-- body: response message body, or nil if failed
613-- headers: response header fields, or nil if failed 613-- headers: response header fields, or nil if failed
614-- status: server response status line, or nil if failed 614-- status: server response status line, or nil if failed
615-- code: server response status code, or nil if failed 615-- code: server response status code, or nil if failed
616-- error: error message if any 616-- error: error message if any
617----------------------------------------------------------------------------- 617-----------------------------------------------------------------------------
618function Public.request(request) 618function request(reqt)
619 local response = {} 619 local respt = {}
620 request.body_cb = socket.callback.send_string(request.body) 620 reqt.body_cb = socket.callback.send_string(reqt.body)
621 local concat = socket.concat.create() 621 local concat = socket.concat.create()
622 response.body_cb = socket.callback.receive_concat(concat) 622 respt.body_cb = socket.callback.receive_concat(concat)
623 response = Public.request_cb(request, response) 623 respt = request_cb(reqt, respt)
624 response.body = concat:getresult() 624 respt.body = concat:getresult()
625 response.body_cb = nil 625 respt.body_cb = nil
626 return response 626 return respt
627end 627end
628 628
629----------------------------------------------------------------------------- 629-----------------------------------------------------------------------------
@@ -639,12 +639,11 @@ end
639-- code: server response status code, or nil if failed 639-- code: server response status code, or nil if failed
640-- error: error message if any 640-- error: error message if any
641----------------------------------------------------------------------------- 641-----------------------------------------------------------------------------
642function Public.get(url_or_request) 642function get(url_or_request)
643 local request = Private.build_request(url_or_request) 643 local reqt = build_request(url_or_request)
644 request.method = "GET" 644 reqt.method = "GET"
645 local response = Public.request(request) 645 local respt = request(reqt)
646 return response.body, response.headers, 646 return respt.body, respt.headers, respt.code, respt.error
647 response.code, response.error
648end 647end
649 648
650----------------------------------------------------------------------------- 649-----------------------------------------------------------------------------
@@ -662,11 +661,14 @@ end
662-- code: server response status code, or nil if failed 661-- code: server response status code, or nil if failed
663-- error: error message, or nil if successfull 662-- error: error message, or nil if successfull
664----------------------------------------------------------------------------- 663-----------------------------------------------------------------------------
665function Public.post(url_or_request, body) 664function post(url_or_request, body)
666 local request = Private.build_request(url_or_request) 665 local reqt = build_request(url_or_request)
667 request.method = "POST" 666 reqt.method = "POST"
668 request.body = request.body or body 667 reqt.body = reqt.body or body
669 local response = Public.request(request) 668 reqt.headers = reqt.headers or
670 return response.body, response.headers, 669 { ["content-length"] = string.len(reqt.body) }
671 response.code, response.error 670 local respt = request(reqt)
671 return respt.body, respt.headers, respt.code, respt.error
672end 672end
673
674return http
diff --git a/src/socket.h b/src/socket.h
index c7db5f2..cea9e0d 100644
--- a/src/socket.h
+++ b/src/socket.h
@@ -37,6 +37,7 @@ int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len,
37const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len); 37const char *sock_connect(p_sock ps, SA *addr, socklen_t addr_len);
38const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len); 38const char *sock_bind(p_sock ps, SA *addr, socklen_t addr_len);
39void sock_listen(p_sock ps, int backlog); 39void sock_listen(p_sock ps, int backlog);
40void sock_shutdown(p_sock ps, int how);
40int sock_send(p_sock ps, const char *data, size_t count, 41int sock_send(p_sock ps, const char *data, size_t count,
41 size_t *sent, int timeout); 42 size_t *sent, int timeout);
42int sock_recv(p_sock ps, char *data, size_t count, 43int sock_recv(p_sock ps, char *data, size_t count,
diff --git a/src/tcp.c b/src/tcp.c
index d68db08..afa0477 100644
--- a/src/tcp.c
+++ b/src/tcp.c
@@ -25,6 +25,7 @@ static int meth_bind(lua_State *L);
25static int meth_send(lua_State *L); 25static int meth_send(lua_State *L);
26static int meth_getsockname(lua_State *L); 26static int meth_getsockname(lua_State *L);
27static int meth_getpeername(lua_State *L); 27static int meth_getpeername(lua_State *L);
28static int meth_shutdown(lua_State *L);
28static int meth_receive(lua_State *L); 29static int meth_receive(lua_State *L);
29static int meth_accept(lua_State *L); 30static int meth_accept(lua_State *L);
30static int meth_close(lua_State *L); 31static int meth_close(lua_State *L);
@@ -49,6 +50,7 @@ static luaL_reg tcp[] = {
49 {"getsockname", meth_getsockname}, 50 {"getsockname", meth_getsockname},
50 {"settimeout", meth_settimeout}, 51 {"settimeout", meth_settimeout},
51 {"close", meth_close}, 52 {"close", meth_close},
53 {"shutdown", meth_shutdown},
52 {"setoption", meth_setoption}, 54 {"setoption", meth_setoption},
53 {"__gc", meth_close}, 55 {"__gc", meth_close},
54 {"fd", meth_fd}, 56 {"fd", meth_fd},
@@ -201,12 +203,12 @@ static int meth_accept(lua_State *L)
201 int err = IO_ERROR; 203 int err = IO_ERROR;
202 p_tcp server = (p_tcp) aux_checkclass(L, "tcp{server}", 1); 204 p_tcp server = (p_tcp) aux_checkclass(L, "tcp{server}", 1);
203 p_tm tm = &server->tm; 205 p_tm tm = &server->tm;
204 p_tcp client = lua_newuserdata(L, sizeof(t_tcp)); 206 p_tcp client;
205 aux_setclass(L, "tcp{client}", -1); 207 t_sock sock;
206 tm_markstart(tm); 208 tm_markstart(tm);
207 /* loop until connection accepted or timeout happens */ 209 /* loop until connection accepted or timeout happens */
208 while (err != IO_DONE) { 210 while (err != IO_DONE) {
209 err = sock_accept(&server->sock, &client->sock, 211 err = sock_accept(&server->sock, &sock,
210 (SA *) &addr, &addr_len, tm_getfailure(tm)); 212 (SA *) &addr, &addr_len, tm_getfailure(tm));
211 if (err == IO_CLOSED || (err == IO_TIMEOUT && !tm_getfailure(tm))) { 213 if (err == IO_CLOSED || (err == IO_TIMEOUT && !tm_getfailure(tm))) {
212 lua_pushnil(L); 214 lua_pushnil(L);
@@ -214,6 +216,9 @@ static int meth_accept(lua_State *L)
214 return 2; 216 return 2;
215 } 217 }
216 } 218 }
219 client = lua_newuserdata(L, sizeof(t_tcp));
220 aux_setclass(L, "tcp{client}", -1);
221 client->sock = sock;
217 /* initialize remaining structure fields */ 222 /* initialize remaining structure fields */
218 io_init(&client->io, (p_send) sock_send, (p_recv) sock_recv, &client->sock); 223 io_init(&client->io, (p_send) sock_send, (p_recv) sock_recv, &client->sock);
219 tm_init(&client->tm, -1, -1); 224 tm_init(&client->tm, -1, -1);
@@ -273,6 +278,33 @@ static int meth_close(lua_State *L)
273} 278}
274 279
275/*-------------------------------------------------------------------------*\ 280/*-------------------------------------------------------------------------*\
281* Shuts the connection down
282\*-------------------------------------------------------------------------*/
283static int meth_shutdown(lua_State *L)
284{
285 p_tcp tcp = (p_tcp) aux_checkgroup(L, "tcp{any}", 1);
286 const char *how = luaL_optstring(L, 2, "both");
287 switch (how[0]) {
288 case 'b':
289 if (strcmp(how, "both")) goto error;
290 sock_shutdown(&tcp->sock, 2);
291 break;
292 case 's':
293 if (strcmp(how, "send")) goto error;
294 sock_shutdown(&tcp->sock, 1);
295 break;
296 case 'r':
297 if (strcmp(how, "receive")) goto error;
298 sock_shutdown(&tcp->sock, 0);
299 break;
300 }
301 return 0;
302error:
303 luaL_argerror(L, 2, "invalid shutdown method");
304 return 0;
305}
306
307/*-------------------------------------------------------------------------*\
276* Just call inet methods 308* Just call inet methods
277\*-------------------------------------------------------------------------*/ 309\*-------------------------------------------------------------------------*/
278static int meth_getpeername(lua_State *L) 310static int meth_getpeername(lua_State *L)
diff --git a/src/usocket.c b/src/usocket.c
index b120d7b..f2d9f01 100644
--- a/src/usocket.c
+++ b/src/usocket.c
@@ -73,6 +73,14 @@ void sock_listen(p_sock ps, int backlog)
73} 73}
74 74
75/*-------------------------------------------------------------------------*\ 75/*-------------------------------------------------------------------------*\
76*
77\*-------------------------------------------------------------------------*/
78void sock_shutdown(p_sock ps, int how)
79{
80 shutdown(*ps, how);
81}
82
83/*-------------------------------------------------------------------------*\
76* Accept with timeout 84* Accept with timeout
77\*-------------------------------------------------------------------------*/ 85\*-------------------------------------------------------------------------*/
78int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len, 86int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len,
@@ -100,39 +108,47 @@ int sock_accept(p_sock ps, p_sock pa, SA *addr, socklen_t *addr_len,
100 108
101/*-------------------------------------------------------------------------*\ 109/*-------------------------------------------------------------------------*\
102* Send with timeout 110* Send with timeout
111* Here we exchanged the order of the calls to write and select
112* The idea is that the outer loop (whoever is calling sock_send)
113* will call the function again if we didn't time out, so we can
114* call write and then select only if it fails.
115* Should speed things up!
116* We are also treating EINTR and EPIPE errors.
103\*-------------------------------------------------------------------------*/ 117\*-------------------------------------------------------------------------*/
104int sock_send(p_sock ps, const char *data, size_t count, size_t *sent, 118int sock_send(p_sock ps, const char *data, size_t count, size_t *sent,
105 int timeout) 119 int timeout)
106{ 120{
107 t_sock sock = *ps; 121 t_sock sock = *ps;
108 struct timeval tv; 122 ssize_t put;
109 fd_set fds;
110 ssize_t put = 0;
111 int err;
112 int ret; 123 int ret;
124 /* avoid making system calls on closed sockets */
113 if (sock == SOCK_INVALID) return IO_CLOSED; 125 if (sock == SOCK_INVALID) return IO_CLOSED;
114 tv.tv_sec = timeout / 1000; 126 /* make sure we repeat in case the call was interrupted */
115 tv.tv_usec = (timeout % 1000) * 1000; 127 do put = write(sock, data, count);
116 FD_ZERO(&fds); 128 while (put <= 0 && errno == EINTR);
117 FD_SET(sock, &fds); 129 /* deal with failure */
118 ret = select(sock+1, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL); 130 if (put <= 0) {
119 if (ret > 0) { 131 /* in any case, nothing has been sent */
120 put = write(sock, data, count);
121 if (put <= 0) {
122 err = IO_CLOSED;
123#ifdef __CYGWIN__
124 /* this is for CYGWIN, which is like Unix but has Win32 bugs */
125 if (errno == EWOULDBLOCK) err = IO_DONE;
126#endif
127 *sent = 0;
128 } else {
129 *sent = put;
130 err = IO_DONE;
131 }
132 return err;
133 } else {
134 *sent = 0; 132 *sent = 0;
135 return IO_TIMEOUT; 133 /* run select to avoid busy wait */
134 if (errno != EPIPE) {
135 struct timeval tv;
136 fd_set fds;
137 tv.tv_sec = timeout / 1000;
138 tv.tv_usec = (timeout % 1000) * 1000;
139 FD_ZERO(&fds);
140 FD_SET(sock, &fds);
141 ret = select(sock+1, NULL, &fds, NULL, timeout >= 0 ? &tv : NULL);
142 /* tell the caller to call us again because there is more data */
143 if (ret > 0) return IO_DONE;
144 /* tell the caller there was no data before timeout */
145 else return IO_TIMEOUT;
146 /* here we know the connection has been closed */
147 } else return IO_CLOSED;
148 /* here we sent successfully sent something */
149 } else {
150 *sent = put;
151 return IO_DONE;
136 } 152 }
137} 153}
138 154
@@ -176,32 +192,36 @@ int sock_sendto(p_sock ps, const char *data, size_t count, size_t *sent,
176 192
177/*-------------------------------------------------------------------------*\ 193/*-------------------------------------------------------------------------*\
178* Receive with timeout 194* Receive with timeout
195* Here we exchanged the order of the calls to write and select
196* The idea is that the outer loop (whoever is calling sock_send)
197* will call the function again if we didn't time out, so we can
198* call write and then select only if it fails.
199* Should speed things up!
200* We are also treating EINTR errors.
179\*-------------------------------------------------------------------------*/ 201\*-------------------------------------------------------------------------*/
180int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout) 202int sock_recv(p_sock ps, char *data, size_t count, size_t *got, int timeout)
181{ 203{
182 t_sock sock = *ps; 204 t_sock sock = *ps;
183 struct timeval tv; 205 ssize_t taken;
184 fd_set fds;
185 int ret;
186 ssize_t taken = 0;
187 if (sock == SOCK_INVALID) return IO_CLOSED; 206 if (sock == SOCK_INVALID) return IO_CLOSED;
188 tv.tv_sec = timeout / 1000; 207 do taken = read(sock, data, count);
189 tv.tv_usec = (timeout % 1000) * 1000; 208 while (taken <= 0 && errno == EINTR);
190 FD_ZERO(&fds); 209 if (taken <= 0) {
191 FD_SET(sock, &fds); 210 struct timeval tv;
192 ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL); 211 fd_set fds;
193 if (ret > 0) { 212 int ret;
194 taken = read(sock, data, count);
195 if (taken <= 0) {
196 *got = 0;
197 return IO_CLOSED;
198 } else {
199 *got = taken;
200 return IO_DONE;
201 }
202 } else {
203 *got = 0; 213 *got = 0;
204 return IO_TIMEOUT; 214 if (taken == 0) return IO_CLOSED;
215 tv.tv_sec = timeout / 1000;
216 tv.tv_usec = (timeout % 1000) * 1000;
217 FD_ZERO(&fds);
218 FD_SET(sock, &fds);
219 ret = select(sock+1, &fds, NULL, NULL, timeout >= 0 ? &tv : NULL);
220 if (ret > 0) return IO_DONE;
221 else return IO_TIMEOUT;
222 } else {
223 *got = taken;
224 return IO_DONE;
205 } 225 }
206} 226}
207 227
diff --git a/src/wsocket.c b/src/wsocket.c
index 1ba28b6..59d88df 100644
--- a/src/wsocket.c
+++ b/src/wsocket.c
@@ -36,6 +36,14 @@ void sock_destroy(p_sock ps)
36} 36}
37 37
38/*-------------------------------------------------------------------------*\ 38/*-------------------------------------------------------------------------*\
39*
40\*-------------------------------------------------------------------------*/
41void sock_shutdown(p_sock ps, int how)
42{
43 shutdown(*ps, how);
44}
45
46/*-------------------------------------------------------------------------*\
39* Creates and sets up a socket 47* Creates and sets up a socket
40\*-------------------------------------------------------------------------*/ 48\*-------------------------------------------------------------------------*/
41const char *sock_create(p_sock ps, int domain, int type, int protocol) 49const char *sock_create(p_sock ps, int domain, int type, int protocol)
diff --git a/test/auth/.htaccess b/test/auth/.htaccess
index b9f100e..31e1123 100644
--- a/test/auth/.htaccess
+++ b/test/auth/.htaccess
@@ -1,4 +1,4 @@
1AuthName "Test Realm" 1AuthName "Test Realm"
2AuthType Basic 2AuthType Basic
3AuthUserFile /home/diego/tec/luasocket/test/auth/.htpasswd 3AuthUserFile /Users/diego/tec/luasocket/test/auth/.htpasswd
4require valid-user 4require valid-user
diff --git a/test/httptest.lua b/test/httptest.lua
index 030974c..9d9fa25 100644
--- a/test/httptest.lua
+++ b/test/httptest.lua
@@ -1,8 +1,8 @@
1-- needs Alias from /home/c/diego/tec/luasocket/test to 1-- needs Alias from /home/c/diego/tec/luasocket/test to
2-- /luasocket-test 2-- "/luasocket-test" and "/luasocket-test/"
3-- needs ScriptAlias from /home/c/diego/tec/luasocket/test/cgi 3-- needs ScriptAlias from /home/c/diego/tec/luasocket/test/cgi
4-- to /luasocket-test-cgi 4-- to "/luasocket-test-cgi" and "/luasocket-test-cgi/"
5-- needs AllowOverride AuthConfig on /home/c/diego/tec/luasocket/test/auth 5-- needs "AllowOverride AuthConfig" on /home/c/diego/tec/luasocket/test/auth
6local similar = function(s1, s2) 6local similar = function(s1, s2)
7 return string.lower(string.gsub(s1 or "", "%s", "")) == 7 return string.lower(string.gsub(s1 or "", "%s", "")) ==
8 string.lower(string.gsub(s2 or "", "%s", "")) 8 string.lower(string.gsub(s2 or "", "%s", ""))
@@ -31,12 +31,18 @@ local check_request = function(request, expect, ignore)
31 local response = socket.http.request(request) 31 local response = socket.http.request(request)
32 for i,v in response do 32 for i,v in response do
33 if not ignore[i] then 33 if not ignore[i] then
34 if v ~= expect[i] then fail(i .. " differs!") end 34 if v ~= expect[i] then
35 if string.len(v) < 80 then print(v) end
36 fail(i .. " differs!")
37 end
35 end 38 end
36 end 39 end
37 for i,v in expect do 40 for i,v in expect do
38 if not ignore[i] then 41 if not ignore[i] then
39 if v ~= response[i] then fail(i .. " differs!") end 42 if v ~= response[i] then
43 if string.len(v) < 80 then print(v) end
44 fail(i .. " differs!")
45 end
40 end 46 end
41 end 47 end
42 print("ok") 48 print("ok")
@@ -47,15 +53,18 @@ local host, request, response, ignore, expect, index, prefix, cgiprefix
47local t = socket.time() 53local t = socket.time()
48 54
49host = host or "localhost" 55host = host or "localhost"
50prefix = prefix or "/luasocket" 56prefix = prefix or "/luasocket-test"
51cgiprefix = cgiprefix or "/luasocket/cgi" 57cgiprefix = cgiprefix or "/luasocket-test-cgi"
52index = readfile("test/index.html") 58index = readfile("test/index.html")
53 59
54io.write("testing request uri correctness: ") 60io.write("testing request uri correctness: ")
55local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string" 61local forth = cgiprefix .. "/request-uri?" .. "this+is+the+query+string"
56local back = socket.http.get("http://" .. host .. forth) 62local back, h, c, e = socket.http.get("http://" .. host .. forth)
57if similar(back, forth) then print("ok") 63if similar(back, forth) then print("ok")
58else fail("failed!") end 64else
65print(h, c, e)
66fail()
67end
59 68
60io.write("testing query string correctness: ") 69io.write("testing query string correctness: ")
61forth = "this+is+the+query+string" 70forth = "this+is+the+query+string"
@@ -77,6 +86,38 @@ ignore = {
77} 86}
78check_request(request, expect, ignore) 87check_request(request, expect, ignore)
79 88
89socket.http.get("http://" .. host .. prefix .. "/lixo.html")
90
91io.write("testing post method: ")
92-- wanted to test chunked post, but apache doesn't support it...
93request = {
94 url = "http://" .. host .. cgiprefix .. "/cat",
95 method = "POST",
96 body = index,
97 -- remove content-length header to send chunked body
98 headers = { ["content-length"] = string.len(index) }
99}
100expect = {
101 body = index,
102 code = 200
103}
104ignore = {
105 status = 1,
106 headers = 1
107}
108check_request(request, expect, ignore)
109
110io.write("testing simple post function: ")
111body = socket.http.post("http://" .. host .. cgiprefix .. "/cat", index)
112check(body == index)
113
114io.write("testing simple post function with table args: ")
115body = socket.http.post {
116 url = "http://" .. host .. cgiprefix .. "/cat",
117 body = index
118}
119check(body == index)
120
80io.write("testing http redirection: ") 121io.write("testing http redirection: ")
81request = { 122request = {
82 url = "http://" .. host .. prefix 123 url = "http://" .. host .. prefix
@@ -175,7 +216,8 @@ io.write("testing manual basic auth: ")
175request = { 216request = {
176 url = "http://" .. host .. prefix .. "/auth/index.html", 217 url = "http://" .. host .. prefix .. "/auth/index.html",
177 headers = { 218 headers = {
178 authorization = "Basic " .. socket.code.base64("luasocket:password") 219 authorization = "Basic " ..
220 socket.code.base64.encode("luasocket:password")
179 } 221 }
180} 222}
181expect = { 223expect = {
@@ -246,22 +288,6 @@ ignore = {
246} 288}
247check_request(request, expect, ignore) 289check_request(request, expect, ignore)
248 290
249io.write("testing post method: ")
250request = {
251 url = "http://" .. host .. cgiprefix .. "/cat",
252 method = "POST",
253 body = index
254}
255expect = {
256 body = index,
257 code = 200
258}
259ignore = {
260 status = 1,
261 headers = 1
262}
263check_request(request, expect, ignore)
264
265io.write("testing wrong scheme: ") 291io.write("testing wrong scheme: ")
266request = { 292request = {
267 url = "wrong://" .. host .. cgiprefix .. "/cat", 293 url = "wrong://" .. host .. cgiprefix .. "/cat",
@@ -287,17 +313,6 @@ body = socket.http.get {
287} 313}
288check(body == index) 314check(body == index)
289 315
290io.write("testing simple post function: ")
291body = socket.http.post("http://" .. host .. cgiprefix .. "/cat", index)
292check(body == index)
293
294io.write("testing simple post function with table args: ")
295body = socket.http.post {
296 url = "http://" .. host .. cgiprefix .. "/cat",
297 body = index
298}
299check(body == index)
300
301io.write("testing HEAD method: ") 316io.write("testing HEAD method: ")
302response = socket.http.request { 317response = socket.http.request {
303 method = "HEAD", 318 method = "HEAD",
diff --git a/test/testclnt.lua b/test/testclnt.lua
index 3f217bd..2420711 100644
--- a/test/testclnt.lua
+++ b/test/testclnt.lua
@@ -84,19 +84,19 @@ function reconnect()
84 remote [[ 84 remote [[
85 if data then data:close() data = nil end 85 if data then data:close() data = nil end
86 data = server:accept() 86 data = server:accept()
87 -- data:setoption("nodelay", true) 87 data:setoption("tcp-nodelay", true)
88 ]] 88 ]]
89 data, err = socket.connect(host, port) 89 data, err = socket.connect(host, port)
90 if not data then fail(err) 90 if not data then fail(err)
91 else pass("connected!") end 91 else pass("connected!") end
92 -- data:setoption("nodelay", true) 92 data:setoption("tcp-nodelay", true)
93end 93end
94 94
95pass("attempting control connection...") 95pass("attempting control connection...")
96control, err = socket.connect(host, port) 96control, err = socket.connect(host, port)
97if err then fail(err) 97if err then fail(err)
98else pass("connected!") end 98else pass("connected!") end
99-- control:setoption("nodelay", true) 99control:setoption("tcp-nodelay", true)
100 100
101------------------------------------------------------------------------ 101------------------------------------------------------------------------
102test("method registration") 102test("method registration")