Mercurial > prosody-hg
comparison net/http/parser.lua @ 11020:7076ed654ac9
net.http.parser: Switch to util.dbuffer for buffering incoming data
This is primarily a step towards saving uploads directly to files, tho
this should hopefully be more efficient than collapsing the entire
buffer to a single string every now and then.
| author | Kim Alvefur <zash@zash.se> |
|---|---|
| date | Sat, 01 Aug 2020 18:14:09 +0200 |
| parents | b7c5d7bae4ef |
| children | 9673c95895fb |
comparison
equal
deleted
inserted
replaced
| 11019:d1604721b665 | 11020:7076ed654ac9 |
|---|---|
| 1 local tonumber = tonumber; | 1 local tonumber = tonumber; |
| 2 local assert = assert; | 2 local assert = assert; |
| 3 local t_insert, t_concat = table.insert, table.concat; | |
| 4 local url_parse = require "socket.url".parse; | 3 local url_parse = require "socket.url".parse; |
| 5 local urldecode = require "util.http".urldecode; | 4 local urldecode = require "util.http".urldecode; |
| 5 local dbuffer = require "util.dbuffer"; | |
| 6 | 6 |
| 7 local function preprocess_path(path) | 7 local function preprocess_path(path) |
| 8 path = urldecode((path:gsub("//+", "/"))); | 8 path = urldecode((path:gsub("//+", "/"))); |
| 9 if path:sub(1,1) ~= "/" then | 9 if path:sub(1,1) ~= "/" then |
| 10 path = "/"..path; | 10 path = "/"..path; |
| 26 local httpstream = {}; | 26 local httpstream = {}; |
| 27 | 27 |
| 28 function httpstream.new(success_cb, error_cb, parser_type, options_cb) | 28 function httpstream.new(success_cb, error_cb, parser_type, options_cb) |
| 29 local client = true; | 29 local client = true; |
| 30 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end | 30 if not parser_type or parser_type == "server" then client = false; else assert(parser_type == "client", "Invalid parser type"); end |
| 31 local buf, buflen, buftable = {}, 0, true; | |
| 32 local bodylimit = tonumber(options_cb and options_cb().body_size_limit) or 10*1024*1024; | 31 local bodylimit = tonumber(options_cb and options_cb().body_size_limit) or 10*1024*1024; |
| 32 -- https://stackoverflow.com/a/686243 | |
| 33 -- Indiviual headers can be up to 16k? What madness? | |
| 34 local headlimit = tonumber(options_cb and options_cb().head_size_limit) or 10*1024; | |
| 33 local buflimit = tonumber(options_cb and options_cb().buffer_size_limit) or bodylimit * 2; | 35 local buflimit = tonumber(options_cb and options_cb().buffer_size_limit) or bodylimit * 2; |
| 34 local chunked, chunk_size, chunk_start; | 36 local buffer = dbuffer.new(buflimit); |
| 37 local chunked; | |
| 35 local state = nil; | 38 local state = nil; |
| 36 local packet; | 39 local packet; |
| 37 local len; | 40 local len; |
| 38 local have_body; | 41 local have_body; |
| 39 local error; | 42 local error; |
| 40 return { | 43 return { |
| 41 feed = function(_, data) | 44 feed = function(_, data) |
| 42 if error then return nil, "parse has failed"; end | 45 if error then return nil, "parse has failed"; end |
| 43 if not data then -- EOF | 46 if not data then -- EOF |
| 44 if buftable then buf, buftable = t_concat(buf), false; end | |
| 45 if state and client and not len then -- reading client body until EOF | 47 if state and client and not len then -- reading client body until EOF |
| 46 packet.body = buf; | 48 buffer:collapse(); |
| 49 packet.body = buffer:read_chunk() or ""; | |
| 47 success_cb(packet); | 50 success_cb(packet); |
| 48 elseif buf ~= "" then -- unexpected EOF | 51 state = nil; |
| 52 elseif buffer:length() ~= 0 then -- unexpected EOF | |
| 49 error = true; return error_cb("unexpected-eof"); | 53 error = true; return error_cb("unexpected-eof"); |
| 50 end | 54 end |
| 51 return; | 55 return; |
| 52 end | 56 end |
| 53 if buftable then | 57 if not buffer:write(data) then error = true; return error_cb("max-buffer-size-exceeded"); end |
| 54 t_insert(buf, data); | 58 while buffer:length() > 0 do |
| 55 else | |
| 56 buf = { buf, data }; | |
| 57 buftable = true; | |
| 58 end | |
| 59 buflen = buflen + #data; | |
| 60 if buflen > buflimit then error = true; return error_cb("max-buffer-size-exceeded"); end | |
| 61 while buflen > 0 do | |
| 62 if state == nil then -- read request | 59 if state == nil then -- read request |
| 63 if buftable then buf, buftable = t_concat(buf), false; end | 60 local index = buffer:sub(1, headlimit):find("\r\n\r\n", nil, true); |
| 64 local index = buf:find("\r\n\r\n", nil, true); | |
| 65 if not index then return; end -- not enough data | 61 if not index then return; end -- not enough data |
| 66 -- FIXME was reason_phrase meant to be passed on somewhere? | 62 -- FIXME was reason_phrase meant to be passed on somewhere? |
| 67 local method, path, httpversion, status_code, reason_phrase; -- luacheck: ignore reason_phrase | 63 local method, path, httpversion, status_code, reason_phrase; -- luacheck: ignore reason_phrase |
| 68 local first_line; | 64 local first_line; |
| 69 local headers = {}; | 65 local headers = {}; |
| 70 for line in buf:sub(1,index+1):gmatch("([^\r\n]+)\r\n") do -- parse request | 66 for line in buffer:read(index+3):gmatch("([^\r\n]+)\r\n") do -- parse request |
| 71 if first_line then | 67 if first_line then |
| 72 local key, val = line:match("^([^%s:]+): *(.*)$"); | 68 local key, val = line:match("^([^%s:]+): *(.*)$"); |
| 73 if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers | 69 if not key then error = true; return error_cb("invalid-header-line"); end -- TODO handle multi-line and invalid headers |
| 74 key = key:lower(); | 70 key = key:lower(); |
| 75 headers[key] = headers[key] and headers[key]..","..val or val; | 71 headers[key] = headers[key] and headers[key]..","..val or val; |
| 99 if not have_body then len = 0; end | 95 if not have_body then len = 0; end |
| 100 packet = { | 96 packet = { |
| 101 code = status_code; | 97 code = status_code; |
| 102 httpversion = httpversion; | 98 httpversion = httpversion; |
| 103 headers = headers; | 99 headers = headers; |
| 104 body = have_body and "" or nil; | 100 body = false; |
| 105 -- COMPAT the properties below are deprecated | 101 -- COMPAT the properties below are deprecated |
| 106 responseversion = httpversion; | 102 responseversion = httpversion; |
| 107 responseheaders = headers; | 103 responseheaders = headers; |
| 108 }; | 104 }; |
| 109 else | 105 else |
| 124 method = method; | 120 method = method; |
| 125 url = parsed_url; | 121 url = parsed_url; |
| 126 path = path; | 122 path = path; |
| 127 httpversion = httpversion; | 123 httpversion = httpversion; |
| 128 headers = headers; | 124 headers = headers; |
| 129 body = nil; | 125 body = false; |
| 126 body_sink = nil; | |
| 130 }; | 127 }; |
| 131 end | 128 end |
| 132 buf = buf:sub(index + 4); | 129 if chunked then |
| 133 buflen = #buf; | 130 packet.body_buffer = dbuffer.new(buflimit); |
| 131 end | |
| 134 state = true; | 132 state = true; |
| 135 end | 133 end |
| 136 if state then -- read body | 134 if state then -- read body |
| 137 if client then | 135 if chunked then |
| 138 if chunked then | 136 local chunk_header = buffer:sub(1, 512); -- XXX How large do chunk headers grow? |
| 139 if chunk_start and buflen - chunk_start - 2 < chunk_size then | 137 local chunk_size, chunk_start = chunk_header:match("^(%x+)[^\r\n]*\r\n()"); |
| 140 return; | 138 if not chunk_size then return; end |
| 141 end -- not enough data | 139 chunk_size = chunk_size and tonumber(chunk_size, 16); |
| 142 if buftable then buf, buftable = t_concat(buf), false; end | 140 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end |
| 143 if not buf:find("\r\n", nil, true) then | 141 if chunk_size == 0 and chunk_header:find("\r\n\r\n", chunk_start-2, true) then |
| 144 return; | 142 local body_buffer = packet.body_buffer; |
| 145 end -- not enough data | 143 if body_buffer then |
| 146 if not chunk_size then | 144 packet.body_buffer = nil; |
| 147 chunk_size, chunk_start = buf:match("^(%x+)[^\r\n]*\r\n()"); | 145 body_buffer:collapse(); |
| 148 chunk_size = chunk_size and tonumber(chunk_size, 16); | 146 packet.body = body_buffer:read_chunk() or ""; |
| 149 if not chunk_size then error = true; return error_cb("invalid-chunk-size"); end | |
| 150 end | 147 end |
| 151 if chunk_size == 0 and buf:find("\r\n\r\n", chunk_start-2, true) then | 148 |
| 152 state, chunk_size = nil, nil; | 149 buffer:collapse(); |
| 153 buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped | 150 local buf = buffer:read_chunk(); |
| 154 success_cb(packet); | 151 buf = buf:gsub("^.-\r\n\r\n", ""); -- This ensure extensions and trailers are stripped |
| 155 elseif buflen - chunk_start - 2 >= chunk_size then -- we have a chunk | 152 buffer:write(buf); |
| 156 packet.body = packet.body..buf:sub(chunk_start, chunk_start + (chunk_size-1)); | 153 state, chunked = nil, nil; |
| 157 buf = buf:sub(chunk_start + chunk_size + 2); | 154 success_cb(packet); |
| 158 buflen = buflen - (chunk_start + chunk_size + 2 - 1); | 155 elseif buffer:length() - chunk_start - 2 >= chunk_size then -- we have a chunk |
| 159 chunk_size, chunk_start = nil, nil; | 156 buffer:discard(chunk_start - 1); -- TODO verify that it's not off-by-one |
| 160 else -- Partial chunk remaining | 157 packet.body_buffer:write(buffer:read(chunk_size)); |
| 161 break; | 158 buffer:discard(2); -- CRLF |
| 162 end | 159 else -- Partial chunk remaining |
| 163 elseif len and buflen >= len then | |
| 164 if buftable then buf, buftable = t_concat(buf), false; end | |
| 165 if packet.code == 101 then | |
| 166 packet.body, buf, buflen, buftable = buf, {}, 0, true; | |
| 167 else | |
| 168 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); | |
| 169 buflen = #buf; | |
| 170 end | |
| 171 state = nil; success_cb(packet); | |
| 172 else | |
| 173 break; | 160 break; |
| 174 end | 161 end |
| 175 elseif buflen >= len then | 162 elseif buffer:length() >= len then |
| 176 if buftable then buf, buftable = t_concat(buf), false; end | 163 assert(not chunked) |
| 177 packet.body, buf = buf:sub(1, len), buf:sub(len + 1); | 164 packet.body = buffer:read(len) or ""; |
| 178 buflen = #buf; | |
| 179 state = nil; success_cb(packet); | 165 state = nil; success_cb(packet); |
| 180 else | 166 else |
| 181 break; | 167 break; |
| 182 end | 168 end |
| 169 else | |
| 170 break; | |
| 183 end | 171 end |
| 184 end | 172 end |
| 185 end; | 173 end; |
| 186 }; | 174 }; |
| 187 end | 175 end |
