moonbridge
changeset 162:6c1561547f79
Work on new HTTP module implementation
author | jbe |
---|---|
date | Thu Jun 11 21:17:29 2015 +0200 (2015-06-11) |
parents | d5b8295d035e |
children | 80266ee1593f |
files | moonbridge_http.lua |
line diff
1.1 --- a/moonbridge_http.lua Thu Jun 11 00:54:18 2015 +0200 1.2 +++ b/moonbridge_http.lua Thu Jun 11 21:17:29 2015 +0200 1.3 @@ -176,20 +176,182 @@ 1.4 poll(nil, socket_set) 1.5 end 1.6 end 1.7 + local function consume_all() 1.8 + while consume do 1.9 + consume() 1.10 + end 1.11 + end 1.12 repeat 1.13 + -- create a new request object: 1.14 local request = { 1.15 socket = socket, 1.16 cookies = {} 1.17 } 1.18 + -- local variables to track the state: 1.19 + local state = "init" -- one of: 1.20 + -- "init" (initial state) 1.21 + -- "no_status_sent" (configuration complete) 1.22 + -- "info_status_sent" (1xx status code has been sent) 1.23 + -- "bodyless_status_sent" (204/304 status code has been sent) 1.24 + -- "status_sent" (regular status code has been sent) 1.25 + -- "headers_sent" (headers have been terminated) 1.26 + -- "finished" (request has been answered completely) 1.27 + local close_requested = false -- "Connection: close" requested 1.28 + local close_responded = false -- "Connection: close" sent 1.29 + local content_length = nil -- value of Content-Length header sent 1.30 + -- functions to send data to the browser: 1.31 local function send(...) 1.32 assert(socket:write_call(unblock, ...)) 1.33 end 1.34 + local function send_flush(...) 1.35 + assert(socket:flush_call(unblock, ...)) 1.36 + end 1.37 + -- TODO: 1.38 + local function prepare() 1.39 + if state == "prepare" then 1.40 + error("Unexpected internal status in HTTP engine") 1.41 + elseif state ~= "init" then 1.42 + return 1.43 + end 1.44 + state = "prepare" 1.45 + -- TODO 1.46 + state = "no_status_sent" 1.47 + end 1.48 + -- TODO: 1.49 + local function finish_response() 1.50 + if close_responded then 1.51 + -- discard any input: 1.52 + consume = drain 1.53 + -- close output stream: 1.54 + send_flush() 1.55 + assert(socket:finish()) 1.56 + -- wait for EOF of peer to avoid immediate TCP RST condition: 1.57 + consume_all() 1.58 + -- fully close socket: 1.59 + --socket_closed = true -- avoid double close on error -- TODO 1.60 + assert(socket:close()) 1.61 + else 1.62 + assert(socket:flush()) 1.63 + consume_all() 1.64 + end 1.65 + end 1.66 + -- method to send a HTTP response status (e.g. "200 OK"): 1.67 + function request:send_status(status) 1.68 + prepare() 1.69 + if state == "info_status_sent" then 1.70 + send_flush("\r\n") 1.71 + elseif state ~= "no_status_sent" then 1.72 + error("HTTP status has already been sent") 1.73 + end 1.74 + local status1 = string.sub(status, 1, 1) 1.75 + local status3 = string.sub(status, 1, 3) 1.76 + send("HTTP/1.1 ", status, "\r\n", preamble) 1.77 + local wrb = status_without_response_body[status3] 1.78 + if wrb then 1.79 + state = "bodyless_status_sent" 1.80 + if wrb == "zero_content_length" then 1.81 + request:send_header("Content-Length", 0) 1.82 + end 1.83 + elseif status1 == "1" then 1.84 + state = "info_status_sent" 1.85 + else 1.86 + state = "status_sent" 1.87 + end 1.88 + end 1.89 + -- method to send a HTTP response header: 1.90 + -- (key and value must be provided as separate args) 1.91 + function request:send_header(key, value) 1.92 + prepare() 1.93 + if state == "no_status_sent" then 1.94 + error("HTTP status has not been sent yet") 1.95 + elseif 1.96 + state ~= "info_status_sent" and 1.97 + state ~= "bodyless_status_sent" and 1.98 + state ~= "status_sent" 1.99 + then 1.100 + error("All HTTP headers have already been sent") 1.101 + end 1.102 + local key_lower = string.lower(key) 1.103 + if key_lower == "content-length" then 1.104 + if state == "info_status_sent" then 1.105 + error("Cannot set Content-Length for informational status response") 1.106 + end 1.107 + local cl = assert(tonumber(value), "Invalid content-length") 1.108 + if content_length == nil then 1.109 + content_length = cl 1.110 + elseif content_length == cl then 1.111 + return 1.112 + else 1.113 + error("Content-Length has been set multiple times with different values") 1.114 + end 1.115 + elseif key_lower == "connection" then 1.116 + for entry in string.gmatch(string.lower(value), "[^,]+") do 1.117 + if string.match(entry, "^[ \t]*close[ \t]*$") then 1.118 + if state == "info_status_sent" then 1.119 + error("Cannot set \"Connection: close\" for informational status response") 1.120 + end 1.121 + close_responded = true 1.122 + break 1.123 + end 1.124 + end 1.125 + end 1.126 + assert_output(socket:write(key, ": ", value, "\r\n")) 1.127 + end 1.128 + -- method to close connection after sending the response: 1.129 + function request:close_after_finish() 1.130 + prepare() 1.131 + if 1.132 + state == "headers_sent" or 1.133 + state == "finished" 1.134 + then 1.135 + error("All HTTP headers have already been sent") 1.136 + end 1.137 + close_requested = true 1.138 + end 1.139 + -- function to terminate header section in response, optionally flushing: 1.140 + -- (may be called multiple times unless response is finished) 1.141 + local function finish_headers(with_flush) 1.142 + if state == "finished" then 1.143 + error("Response has already been finished") 1.144 + elseif state == "info_status_sent" then 1.145 + send_flush("\r\n") 1.146 + state = "no_status_sent" 1.147 + elseif state == "bodyless_status_sent" then 1.148 + if close_requested and not close_responded then 1.149 + request:send_header("Connection", "close") 1.150 + end 1.151 + send("\r\n") 1.152 + --finish_response() -- TODO 1.153 + state = "finished" 1.154 + elseif state == "status_sent" then 1.155 + if not content_length then 1.156 + request:send_header("Transfer-Encoding", "chunked") 1.157 + end 1.158 + if close_requested and not close_responded then 1.159 + request:send_header("Connection", "close") 1.160 + end 1.161 + send("\r\n") 1.162 + if request.method == "HEAD" then 1.163 + --finish_response() -- TODO 1.164 + elseif with_flush then 1.165 + send_flush() 1.166 + end 1.167 + state = "headers_sent" 1.168 + elseif state ~= "headers_sent" then 1.169 + error("HTTP status has not been sent yet") 1.170 + end 1.171 + end 1.172 + -- method to finish and flush headers: 1.173 + function request:finish_headers() 1.174 + prepare() 1.175 + finish_headers(true) 1.176 + end 1.177 -- wait for input: 1.178 if not poll(socket_set, nil, request_idle_timeout) then 1.179 -- TODO: send error 1.180 return survive 1.181 end 1.182 - until connection_close_responded 1.183 + until close_responded 1.184 return survive 1.185 end 1.186 end