# HG changeset patch # User jbe # Date 1434725673 -7200 # Node ID 478d6237e17adbd6c7c3028826de6c0bc91ac8f1 # Parent a79ed835b6de9f163caac6b69bc2d7842352d3c6 Fixes and code-cleanup in HTTP module diff -r a79ed835b6de -r 478d6237e17a moonbridge_http.lua --- a/moonbridge_http.lua Fri Jun 19 03:09:58 2015 +0200 +++ b/moonbridge_http.lua Fri Jun 19 16:54:33 2015 +0200 @@ -336,6 +336,11 @@ setmetatable(request, request_mt) -- callback for request body streaming: local process_body_chunk + -- function to enable draining: + local function enable_drain() + consume = drain + process_body_chunk = nil -- allow for early garbage collection + end -- local variables to track the state: local state = "init" -- one of: -- "init" (initial state) @@ -350,6 +355,7 @@ local close_requested = false -- "Connection: close" requested local close_responded = false -- "Connection: close" sent local content_length = nil -- value of Content-Length header sent + local bytes_sent = 0 -- number of bytes sent if Content-Length is set local chunk_parts = {} -- list of chunks to send local chunk_bytes = 0 -- sum of lengths of chunks to send local streamed_post_params = {} -- mapping from POST field name to stream function @@ -385,7 +391,7 @@ local function finish() if close_responded then -- discard any input: - consume = drain + enable_drain() -- close output stream: send_flush() assert_close(socket:finish()) @@ -646,6 +652,7 @@ if not terminated then request_error(true, "400 Bad Request", "Premature end of multipart/form-data request body") end + request.post_params_list, request.post_params = post_params_list, post_params request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata end if terminated then @@ -700,10 +707,13 @@ -- function to prepare body processing: local function prepare() assert_not_faulty() + if state ~= "init" then + return + end if process_body_chunk == nil then default_request_body_handling() end - if state ~= "init" then + if state ~= "init" then -- re-check if state is still "init" return end consume = coroutine.wrap(read_body) @@ -722,9 +732,8 @@ then error("All HTTP headers have already been sent") end - local old_state = state - state = "faulty" - consume = drain + local old_state, state = state, "faulty" + enable_drain() close_requested = true if old_state == "init" then state = "no_status_sent" @@ -732,15 +741,14 @@ state = old_state end end - -- -- method to send a HTTP response status (e.g. "200 OK"): function request:send_status(status) prepare() - local old_state = state - state = "faulty" + local old_state, state = state, "faulty" if old_state == "info_status_sent" then send_flush("\r\n") elseif old_state ~= "no_status_sent" then + state = old_state error("HTTP status has already been sent") end local status1 = string.sub(status, 1, 1) @@ -770,9 +778,11 @@ then error("All HTTP headers have already been sent") end + local old_state, state = state, "faulty" local key_lower = string.lower(key) if key_lower == "content-length" then - if state == "info_status_sent" then + if old_state == "info_status_sent" then + state = old_state error("Cannot set Content-Length for informational status response") end local cl = assert(tonumber(value), "Invalid content-length") @@ -786,7 +796,8 @@ elseif key_lower == "connection" then for entry in string.gmatch(string.lower(value), "[^,]+") do if string.match(entry, "^[ \t]*close[ \t]*$") then - if state == "info_status_sent" then + if old_state == "info_status_sent" then + state = old_state error("Cannot set \"Connection: close\" for informational status response") end close_responded = true @@ -795,6 +806,7 @@ end end send(socket:write(key, ": ", value, "\r\n")) + state = old_state end -- function to terminate header section in response, optionally flushing: -- (may be called multiple times unless response is finished) @@ -802,6 +814,7 @@ if state == "finished" then error("Response has already been finished") elseif state == "info_status_sent" then + state = "faulty" send_flush("\r\n") state = "no_status_sent" elseif state == "bodyless_status_sent" then @@ -839,18 +852,19 @@ -- method to send body data: function request:send_data(...) assert_not_faulty() - if output_state == "info_status_sent" then + if state == "info_status_sent" then error("No (non-informational) HTTP status has been sent yet") - elseif output_state == "bodyless_status_sent" then + elseif state == "bodyless_status_sent" then error("Cannot send response data for body-less status message") end finish_headers(false) - if output_state ~= "headers_sent" then + if state ~= "headers_sent" then error("Unexpected internal status in HTTP engine") end if request.method == "HEAD" then return end + state = "faulty" for i = 1, select("#", ...) do local str = tostring(select(i, ...)) if #str > 0 then @@ -871,6 +885,7 @@ if chunk_bytes >= output_chunk_size then send_chunk() end + state = "headers_sent" end -- method to flush output buffer: function request:flush() @@ -908,21 +923,21 @@ -- method to register POST param stream handler for a single field name: function request:stream_post_param(field_name, callback) if state ~= "init" then - error("Cannot setup request body streamer at this stage") + error("Cannot setup request body streamer at this stage anymore") end streamed_post_params[field_name] = callback end -- method to register POST param stream handler for a field name pattern: function request:stream_post_params(pattern, callback) if state ~= "init" then - error("Cannot setup request body streamer at this stage") + error("Cannot setup request body streamer at this stage anymore") end streamed_post_param_patterns[#streamed_post_param_patterns+1] = {pattern, callback} end -- method to register request body stream handler function request:set_request_body_streamer(callback) if state ~= "init" then - error("Cannot setup request body streamer at this stage") + error("Cannot setup request body streamer at this stage anymore") end local inprogress = false local buffer = {}