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

Impressum / About Us