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

Impressum / About Us