Comparison

net/http/parser.lua @ 4631:fc5d3b053454

net.http.{server|codes|parser}: Initial commit.
author Waqas Hussain <waqas20@gmail.com>
date Sun, 08 Apr 2012 04:09:33 +0500
child 4712:4fc99f1b7570
comparison
equal deleted inserted replaced
4630:9502c0224caf 4631:fc5d3b053454
1
2 local tonumber = tonumber;
3 local assert = assert;
4
5 local httpstream = {};
6
7 function httpstream.new(success_cb, error_cb, parser_type, options_cb)
8 local client = true;
9 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end
10 local buf = "";
11 local chunked;
12 local state = nil;
13 local packet;
14 local len;
15 local have_body;
16 local error;
17 return {
18 feed = function(self, data)
19 if error then return nil, "parse has failed"; end
20 if not data then -- EOF
21 if state and client and not len then -- reading client body until EOF
22 packet.body = buf;
23 success_cb(packet);
24 elseif buf ~= "" then -- unexpected EOF
25 error = true; return error_cb();
26 end
27 return;
28 end
29 buf = buf..data;
30 while #buf > 0 do
31 if state == nil then -- read request
32 local index = buf:find("\r\n\r\n", nil, true);
33 if not index then return; end -- not enough data
34 local method, path, httpversion, status_code, reason_phrase;
35 local first_line;
36 local headers = {};
37 for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request
38 if first_line then
39 local key, val = line:match("^([^%s:]+): *(.*)$");
40 if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers
41 key = key:lower();
42 headers[key] = headers[key] and headers[key]..","..val or val;
43 else
44 first_line = line;
45 if client then
46 httpversion, status_code, reason_phrase = line:match("^HTTP/(1%.[01]) (%d%d%d) (.*)$");
47 if not status_code then error = true; return error_cb("invalid-status-line"); end
48 have_body = not
49 ( (options_cb and options_cb().method == "HEAD")
50 or (status_code == 204 or status_code == 304 or status_code == 301)
51 or (status_code >= 100 and status_code < 200) );
52 chunked = have_body and headers["transfer-encoding"] == "chunked";
53 else
54 method, path, httpversion = line:match("^(%w+) (%S+) HTTP/(1%.[01])$");
55 if not method then error = true; return error_cb("invalid-status-line"); end
56 path = path:gsub("^//+", "/"); -- TODO parse url more
57 end
58 end
59 end
60 len = tonumber(headers["content-length"]); -- TODO check for invalid len
61 if client then
62 -- FIXME handle '100 Continue' response (by skipping it)
63 if not have_body then len = 0; end
64 packet = {
65 code = status_code;
66 httpversion = httpversion;
67 headers = headers;
68 body = have_body and "" or nil;
69 -- COMPAT the properties below are deprecated
70 responseversion = httpversion;
71 responseheaders = headers;
72 };
73 else
74 len = len or 0;
75 packet = {
76 method = method;
77 path = path;
78 httpversion = httpversion;
79 headers = headers;
80 body = nil;
81 };
82 end
83 buf = buf:sub(index + 4);
84 state = true;
85 end
86 if state then -- read body
87 if client then
88 if chunked then
89 local index = buf:find("\r\n", nil, true);
90 if not index then return; end -- not enough data
91 local chunk_size = buf:match("^%x+");
92 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end
93 chunk_size = tonumber(chunk_size, 16);
94 index = index + 2;
95 if chunk_size == 0 then
96 state = nil; success_cb(packet);
97 elseif #buf - index + 1 >= chunk_size then -- we have a chunk
98 packet.body = packet.body..buf:sub(index, index + chunk_size - 1);
99 buf = buf:sub(index + chunk_size);
100 end
101 error("trailers"); -- FIXME MUST read trailers
102 elseif len and #buf >= len then
103 packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
104 state = nil; success_cb(packet);
105 end
106 elseif #buf >= len then
107 packet.body, buf = buf:sub(1, len), buf:sub(len + 1);
108 state = nil; success_cb(packet);
109 end
110 end
111 end
112 end;
113 };
114 end
115
116 return httpstream;