# HG changeset patch # User jbe # Date 1434149701 -7200 # Node ID 27dc025e76cc0a1d9e01a86026afe8f36b7a92c6 # Parent 80266ee1593f8822f78c248e683d8aa129ea8cf7 Work on new HTTP module implementation diff -r 80266ee1593f -r 27dc025e76cc moonbridge_http.lua --- a/moonbridge_http.lua Fri Jun 12 01:55:50 2015 +0200 +++ b/moonbridge_http.lua Sat Jun 13 00:55:01 2015 +0200 @@ -205,6 +205,8 @@ 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 chunk_parts = {} -- list of chunks to send + local chunk_bytes = 0 -- sum of lengths of chunks to send -- functions to assert proper output/closing: local function assert_output(...) local retval, errmsg = ... @@ -219,6 +221,10 @@ state = "faulty" error("Could not finish sending data to client: " .. errmsg) end + -- function to assert non-faulty handle: + local function assert_not_faulty() + assert(state ~= "faulty", "Tried to use faulty request handle") + end -- functions to send data to the browser: local function send(...) assert_output(socket:write_call(unblock, ...)) @@ -243,10 +249,23 @@ consume_all() end end - -- function to prepare body processing: + -- function that writes out buffered chunks (without flushing the socket): + function send_chunk() + if chunk_bytes > 0 then + assert_output(socket:write(string.format("%x\r\n", chunk_bytes))) + for i = 1, #chunk_parts do -- TODO: evaluated only once? + send(chunk_parts[i]) + chunk_parts[i] = nil + end + chunk_bytes = 0 + send("\r\n") + end + end + -- function to prepare (or skip) body processing: local function prepare() + assert_not_faulty() if state == "prepare" then - error("Unexpected internal status in HTTP engine") + error("Unexpected internal status in HTTP engine (recursive prepare)") elseif state ~= "init" then return end @@ -256,26 +275,32 @@ end -- method to ignore input and close connection after response: function request:monologue() - prepare() + assert_not_faulty() if state == "headers_sent" or state == "finished" then error("All HTTP headers have already been sent") end + local old_state = state + state = "faulty" consume = drain close_requested = true - if state == "init" or state == "prepare" then -- TODO: ok? + if old_state == "init" or old_state == "prepare" then -- TODO: ok? state = "no_status_sent" + else + state = old_state end end -- -- method to send a HTTP response status (e.g. "200 OK"): function request:send_status(status) prepare() - if state == "info_status_sent" then + local old_state = state + state = "faulty" + if old_state == "info_status_sent" then send_flush("\r\n") - elseif state ~= "no_status_sent" then + elseif old_state ~= "no_status_sent" then error("HTTP status has already been sent") end local status1 = string.sub(status, 1, 1) @@ -296,13 +321,16 @@ -- method to send a HTTP response header: -- (key and value must be provided as separate args) function request:send_header(key, value) - prepare() - if state == "no_status_sent" then + assert_not_faulty() + if + state == "init" or + state == "prepare" or + state == "no_status_sent" + then error("HTTP status has not been sent yet") elseif - state ~= "info_status_sent" and - state ~= "bodyless_status_sent" and - state ~= "status_sent" + state == "headers_sent" or + state == "finished" then error("All HTTP headers have already been sent") end @@ -367,9 +395,45 @@ end -- method to finish and flush headers: function request:finish_headers() - prepare() + assert_not_faulty() finish_headers(true) end + -- method to send body data: + function request:send_data(...) + assert_not_faulty() + if output_state == "info_status_sent" then + error("No (non-informational) HTTP status has been sent yet") + elseif output_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 + error("Unexpected internal status in HTTP engine") + end + if request.method == "HEAD" then + return + end + for i = 1, select("#", ...) do + local str = tostring(select(i, ...)) + if #str > 0 then + if content_length then + local bytes_to_send = #str + if bytes_sent + bytes_to_send > content_length then + error("Content length exceeded") + else + send(str) + bytes_sent = bytes_sent + bytes_to_send + end + else + chunk_bytes = chunk_bytes + #str + chunk_parts[#chunk_parts+1] = str + end + end + end + if chunk_bytes >= output_chunk_size then + send_chunk() + end + end -- function to report an error: local function request_error(throw_error, status, text) local errmsg = "Error while reading request from client. Error response: " .. status