moonbridge
changeset 164:27dc025e76cc
Work on new HTTP module implementation
author | jbe |
---|---|
date | Sat Jun 13 00:55:01 2015 +0200 (2015-06-13) |
parents | 80266ee1593f |
children | 00f01d945e13 |
files | moonbridge_http.lua |
line diff
1.1 --- a/moonbridge_http.lua Fri Jun 12 01:55:50 2015 +0200 1.2 +++ b/moonbridge_http.lua Sat Jun 13 00:55:01 2015 +0200 1.3 @@ -205,6 +205,8 @@ 1.4 local close_requested = false -- "Connection: close" requested 1.5 local close_responded = false -- "Connection: close" sent 1.6 local content_length = nil -- value of Content-Length header sent 1.7 + local chunk_parts = {} -- list of chunks to send 1.8 + local chunk_bytes = 0 -- sum of lengths of chunks to send 1.9 -- functions to assert proper output/closing: 1.10 local function assert_output(...) 1.11 local retval, errmsg = ... 1.12 @@ -219,6 +221,10 @@ 1.13 state = "faulty" 1.14 error("Could not finish sending data to client: " .. errmsg) 1.15 end 1.16 + -- function to assert non-faulty handle: 1.17 + local function assert_not_faulty() 1.18 + assert(state ~= "faulty", "Tried to use faulty request handle") 1.19 + end 1.20 -- functions to send data to the browser: 1.21 local function send(...) 1.22 assert_output(socket:write_call(unblock, ...)) 1.23 @@ -243,10 +249,23 @@ 1.24 consume_all() 1.25 end 1.26 end 1.27 - -- function to prepare body processing: 1.28 + -- function that writes out buffered chunks (without flushing the socket): 1.29 + function send_chunk() 1.30 + if chunk_bytes > 0 then 1.31 + assert_output(socket:write(string.format("%x\r\n", chunk_bytes))) 1.32 + for i = 1, #chunk_parts do -- TODO: evaluated only once? 1.33 + send(chunk_parts[i]) 1.34 + chunk_parts[i] = nil 1.35 + end 1.36 + chunk_bytes = 0 1.37 + send("\r\n") 1.38 + end 1.39 + end 1.40 + -- function to prepare (or skip) body processing: 1.41 local function prepare() 1.42 + assert_not_faulty() 1.43 if state == "prepare" then 1.44 - error("Unexpected internal status in HTTP engine") 1.45 + error("Unexpected internal status in HTTP engine (recursive prepare)") 1.46 elseif state ~= "init" then 1.47 return 1.48 end 1.49 @@ -256,26 +275,32 @@ 1.50 end 1.51 -- method to ignore input and close connection after response: 1.52 function request:monologue() 1.53 - prepare() 1.54 + assert_not_faulty() 1.55 if 1.56 state == "headers_sent" or 1.57 state == "finished" 1.58 then 1.59 error("All HTTP headers have already been sent") 1.60 end 1.61 + local old_state = state 1.62 + state = "faulty" 1.63 consume = drain 1.64 close_requested = true 1.65 - if state == "init" or state == "prepare" then -- TODO: ok? 1.66 + if old_state == "init" or old_state == "prepare" then -- TODO: ok? 1.67 state = "no_status_sent" 1.68 + else 1.69 + state = old_state 1.70 end 1.71 end 1.72 -- 1.73 -- method to send a HTTP response status (e.g. "200 OK"): 1.74 function request:send_status(status) 1.75 prepare() 1.76 - if state == "info_status_sent" then 1.77 + local old_state = state 1.78 + state = "faulty" 1.79 + if old_state == "info_status_sent" then 1.80 send_flush("\r\n") 1.81 - elseif state ~= "no_status_sent" then 1.82 + elseif old_state ~= "no_status_sent" then 1.83 error("HTTP status has already been sent") 1.84 end 1.85 local status1 = string.sub(status, 1, 1) 1.86 @@ -296,13 +321,16 @@ 1.87 -- method to send a HTTP response header: 1.88 -- (key and value must be provided as separate args) 1.89 function request:send_header(key, value) 1.90 - prepare() 1.91 - if state == "no_status_sent" then 1.92 + assert_not_faulty() 1.93 + if 1.94 + state == "init" or 1.95 + state == "prepare" or 1.96 + state == "no_status_sent" 1.97 + then 1.98 error("HTTP status has not been sent yet") 1.99 elseif 1.100 - state ~= "info_status_sent" and 1.101 - state ~= "bodyless_status_sent" and 1.102 - state ~= "status_sent" 1.103 + state == "headers_sent" or 1.104 + state == "finished" 1.105 then 1.106 error("All HTTP headers have already been sent") 1.107 end 1.108 @@ -367,9 +395,45 @@ 1.109 end 1.110 -- method to finish and flush headers: 1.111 function request:finish_headers() 1.112 - prepare() 1.113 + assert_not_faulty() 1.114 finish_headers(true) 1.115 end 1.116 + -- method to send body data: 1.117 + function request:send_data(...) 1.118 + assert_not_faulty() 1.119 + if output_state == "info_status_sent" then 1.120 + error("No (non-informational) HTTP status has been sent yet") 1.121 + elseif output_state == "bodyless_status_sent" then 1.122 + error("Cannot send response data for body-less status message") 1.123 + end 1.124 + finish_headers(false) 1.125 + if output_state ~= "headers_sent" then 1.126 + error("Unexpected internal status in HTTP engine") 1.127 + end 1.128 + if request.method == "HEAD" then 1.129 + return 1.130 + end 1.131 + for i = 1, select("#", ...) do 1.132 + local str = tostring(select(i, ...)) 1.133 + if #str > 0 then 1.134 + if content_length then 1.135 + local bytes_to_send = #str 1.136 + if bytes_sent + bytes_to_send > content_length then 1.137 + error("Content length exceeded") 1.138 + else 1.139 + send(str) 1.140 + bytes_sent = bytes_sent + bytes_to_send 1.141 + end 1.142 + else 1.143 + chunk_bytes = chunk_bytes + #str 1.144 + chunk_parts[#chunk_parts+1] = str 1.145 + end 1.146 + end 1.147 + end 1.148 + if chunk_bytes >= output_chunk_size then 1.149 + send_chunk() 1.150 + end 1.151 + end 1.152 -- function to report an error: 1.153 local function request_error(throw_error, status, text) 1.154 local errmsg = "Error while reading request from client. Error response: " .. status