aboutsummaryrefslogtreecommitdiff
path: root/src
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 /src
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.
Diffstat (limited to 'src')
-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
5 files changed, 373 insertions, 310 deletions
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)