moonbridge

view moonbridge_http.lua @ 174:d6db92e0f231

Ignore nil values in pairs(...) on GET and POST params
author jbe
date Wed Jun 17 23:50:28 2015 +0200 (2015-06-17)
parents 6e80bcf89bd5
children 4cf337821a52
line source
1 #!/usr/bin/env lua
3 -- module preamble
4 local _G, _M = _ENV, {}
5 _ENV = setmetatable({}, {
6 __index = function(self, key)
7 local value = _M[key]; if value ~= nil then return value end
8 return _G[key]
9 end,
10 __newindex = _M
11 })
13 -- function that encodes certain HTML entities:
14 -- (not used by the library itself)
15 function encode_html(text)
16 return (
17 string.gsub(
18 text, '[<>&"]',
19 function(char)
20 if char == '<' then
21 return "&lt;"
22 elseif char == '>' then
23 return "&gt;"
24 elseif char == '&' then
25 return "&amp;"
26 elseif char == '"' then
27 return "&quot;"
28 end
29 end
30 )
31 )
33 end
35 -- function that encodes special characters for URIs:
36 -- (not used by the library itself)
37 function encode_uri(text)
38 return (
39 string.gsub(text, "[^0-9A-Za-z_%.~-]",
40 function (char)
41 return string.format("%%%02x", string.byte(char))
42 end
43 )
44 )
45 end
47 -- function undoing URL encoding:
48 do
49 local b0 = string.byte("0")
50 local b9 = string.byte("9")
51 local bA = string.byte("A")
52 local bF = string.byte("F")
53 local ba = string.byte("a")
54 local bf = string.byte("f")
55 function decode_uri(str)
56 return (
57 string.gsub(
58 string.gsub(str, "%+", " "),
59 "%%([0-9A-Fa-f][0-9A-Fa-f])",
60 function(hex)
61 local n1, n2 = string.byte(hex, 1, 2)
62 if n1 >= b0 and n1 <= b9 then n1 = n1 - b0
63 elseif n1 >= bA and n1 <= bF then n1 = n1 - bA + 10
64 elseif n1 >= ba and n1 <= bf then n1 = n1 - ba + 10
65 else error("Assertion failed") end
66 if n2 >= b0 and n2 <= b9 then n2 = n2 - b0
67 elseif n2 >= bA and n2 <= bF then n2 = n2 - bA + 10
68 elseif n2 >= ba and n2 <= bf then n2 = n2 - ba + 10
69 else error("Assertion failed") end
70 return string.char(n1 * 16 + n2)
71 end
72 )
73 )
74 end
75 end
77 -- status codes that carry no response body (in addition to 1xx):
78 -- (set to "zero_content_length" if Content-Length header is required)
79 status_without_response_body = {
80 ["101"] = true, -- list 101 to allow protocol switch
81 ["204"] = true,
82 ["205"] = "zero_content_length",
83 ["304"] = true
84 }
86 -- handling of GET/POST param tables:
87 local new_params_list -- defined later
88 do
89 local params_list_mapping = setmetatable({}, {__mode="k"})
90 local function nextnonempty(tbl, key)
91 while true do
92 key = next(tbl, key)
93 if key == nil then
94 return nil
95 end
96 local value = tbl[key]
97 if #value > 0 then
98 return key, value
99 end
100 end
101 end
102 local function nextvalue(tbl, key)
103 while true do
104 key = next(tbl, key)
105 if key == nil then
106 return nil
107 end
108 local value = tbl[key][1]
109 if value ~= nil then
110 return key, value
111 end
112 end
113 end
114 local params_list_metatable = {
115 __index = function(self, key)
116 local tbl = {}
117 self[key] = tbl
118 return tbl
119 end,
120 __pairs = function(self)
121 return nextnonempty, self, nil
122 end
123 }
124 local params_metatable = {
125 __index = function(self, key)
126 return params_list_mapping[self][key][1]
127 end,
128 __newindex = function(self, key, value)
129 params_list_mapping[self][key] = {value}
130 end,
131 __pairs = function(self)
132 return nextvalue, params_list_mapping[self], nil
133 end
134 }
135 -- returns a table to store key value-list pairs (i.e. multiple values),
136 -- and a second table automatically mapping keys to the first value
137 -- using the key value-list pairs in the first table:
138 new_params_list = function()
139 local params_list = setmetatable({}, params_list_metatable)
140 local params = setmetatable({}, params_metatable)
141 params_list_mapping[params] = params_list
142 return params_list, params
143 end
144 end
146 -- parses URL encoded form data and stores it in
147 -- a key value-list pairs structure that has to be
148 -- previously obtained by calling by new_params_list():
149 local function read_urlencoded_form(tbl, data)
150 for rawkey, rawvalue in string.gmatch(data, "([^?=&]*)=([^?=&]*)") do
151 local subtbl = tbl[decode_uri(rawkey)]
152 subtbl[#subtbl+1] = decode_uri(rawvalue)
153 end
154 end
156 -- extracts first value from each subtable:
157 local function get_first_values(tbl)
158 local newtbl = {}
159 for key, subtbl in pairs(tbl) do
160 newtbl[key] = subtbl[1]
161 end
162 return newtbl
163 end
165 function generate_handler(handler, options)
166 -- swap arguments if necessary (for convenience):
167 if type(handler) ~= "function" and type(options) == "function" then
168 handler, options = options, handler
169 end
170 -- helper function to process options:
171 local function default(name, default_value)
172 local value = options[name]
173 if value == nil then
174 return default_value
175 else
176 return value or nil
177 end
178 end
179 -- process options:
180 options = options or {}
181 local preamble = "" -- preamble sent with every(!) HTTP response
182 do
183 -- named arg "static_headers" is used to create the preamble:
184 local s = options.static_headers
185 local t = {}
186 if s then
187 if type(s) == "string" then
188 for line in string.gmatch(s, "[^\r\n]+") do
189 t[#t+1] = line
190 end
191 else
192 for i, kv in ipairs(options.static_headers) do
193 if type(kv) == "string" then
194 t[#t+1] = kv
195 else
196 t[#t+1] = kv[1] .. ": " .. kv[2]
197 end
198 end
199 end
200 end
201 t[#t+1] = ""
202 preamble = table.concat(t, "\r\n")
203 end
204 local input_chunk_size = options.maximum_input_chunk_size or options.chunk_size or 16384
205 local output_chunk_size = options.minimum_output_chunk_size or options.chunk_size or 1024
206 local header_size_limit = options.header_size_limit or 1024*1024
207 local body_size_limit = options.body_size_limit or 64*1024*1024
208 local request_idle_timeout = default("request_idle_timeout", 330)
209 local request_header_timeout = default("request_header_timeout", 30)
210 local request_body_timeout = default("request_body_timeout", 60)
211 local response_timeout = default("response_timeout", 1800)
212 local poll = options.poll_function or moonbridge_io.poll
213 -- return socket handler:
214 return function(socket)
215 local socket_set = {[socket] = true} -- used for poll function
216 local survive = true -- set to false if process shall be terminated later
217 local consume -- function that reads some input if possible
218 -- function that drains some input if possible:
219 local function drain()
220 local bytes, status = socket:drain_nb(input_chunk_size)
221 if not bytes or status == "eof" then
222 consume = nil
223 end
224 end
225 -- function trying to unblock socket by reading:
226 local function unblock()
227 if consume then
228 poll(socket_set, socket_set)
229 consume()
230 else
231 poll(nil, socket_set)
232 end
233 end
234 -- function that enforces consumption of all input:
235 local function consume_all()
236 while consume do
237 poll(socket_set, nil)
238 consume()
239 end
240 end
241 -- handle requests in a loop:
242 repeat
243 -- copy limits:
244 local remaining_header_size_limit = header_size_limit
245 local remaining_body_size_limit = body_size_limit
246 -- table for caching nil values:
247 local headers_value_nil = {}
248 -- create a new request object with metatable:
249 local request -- allow references to local variable
250 request = {
251 -- allow access to underlying socket:
252 socket = socket,
253 -- cookies are simply stored in a table:
254 cookies = {},
255 -- table mapping header field names to value-lists
256 -- (raw access, but case-insensitive):
257 headers = setmetatable({}, {
258 __index = function(self, key)
259 local lowerkey = string.lower(key)
260 if lowerkey == key then
261 return
262 end
263 local result = rawget(self, lowerkey)
264 if result == nil then
265 result = {}
266 end
267 self[lowerkey] = result
268 self[key] = result
269 return result
270 end
271 }),
272 -- table mapping header field names to value-lists
273 -- (for headers with comma separated values):
274 headers_csv_table = setmetatable({}, {
275 __index = function(self, key)
276 local result = {}
277 for i, line in ipairs(request.headers[key]) do
278 for entry in string.gmatch(line, "[^,]+") do
279 local value = string.match(entry, "^[ \t]*(..-)[ \t]*$")
280 if value then
281 result[#result+1] = value
282 end
283 end
284 end
285 self[key] = result
286 return result
287 end
288 }),
289 -- table mapping header field names to a comma separated string
290 -- (for headers with comma separated values):
291 headers_csv_string = setmetatable({}, {
292 __index = function(self, key)
293 local result = {}
294 for i, line in ipairs(request.headers[key]) do
295 result[#result+1] = line
296 end
297 result = table.concat(result, ", ")
298 self[key] = result
299 return result
300 end
301 }),
302 -- table mapping header field names to a single string value
303 -- (or false if header has been sent multiple times):
304 headers_value = setmetatable({}, {
305 __index = function(self, key)
306 if headers_value_nil[key] then
307 return nil
308 end
309 local result = nil
310 local values = request.headers_csv_table[key]
311 if #values == 0 then
312 headers_value_nil[key] = true
313 elseif #values == 1 then
314 result = values[1]
315 else
316 result = false
317 end
318 self[key] = result
319 return result
320 end
321 }),
322 -- table mapping header field names to a flag table,
323 -- indicating if the comma separated value contains certain entries:
324 headers_flags = setmetatable({}, {
325 __index = function(self, key)
326 local result = setmetatable({}, {
327 __index = function(self, key)
328 local lowerkey = string.lower(key)
329 local result = rawget(self, lowerkey) or false
330 self[lowerkey] = result
331 self[key] = result
332 return result
333 end
334 })
335 for i, value in ipairs(request.headers_csv_table[key]) do
336 result[string.lower(value)] = true
337 end
338 self[key] = result
339 return result
340 end
341 })
342 }
343 -- create metatable for request object:
344 local request_mt = {}
345 setmetatable(request, request_mt)
346 -- callback for request body streaming:
347 local process_body_chunk
348 -- local variables to track the state:
349 local state = "init" -- one of:
350 -- "init" (initial state)
351 -- "no_status_sent" (configuration complete)
352 -- "info_status_sent" (1xx status code has been sent)
353 -- "bodyless_status_sent" (204/304 status code has been sent)
354 -- "status_sent" (regular status code has been sent)
355 -- "headers_sent" (headers have been terminated)
356 -- "finished" (request has been answered completely)
357 -- "faulty" (I/O or protocaol error)
358 local close_requested = false -- "Connection: close" requested
359 local close_responded = false -- "Connection: close" sent
360 local content_length = nil -- value of Content-Length header sent
361 local chunk_parts = {} -- list of chunks to send
362 local chunk_bytes = 0 -- sum of lengths of chunks to send
363 local streamed_post_params = {} -- mapping from POST field name to stream function
364 local streamed_post_param_patterns = {} -- list of POST field pattern and stream function pairs
365 -- functions to assert proper output/closing:
366 local function assert_output(...)
367 local retval, errmsg = ...
368 if retval then return ... end
369 state = "faulty"
370 socket:reset()
371 error("Could not send data to client: " .. errmsg)
372 end
373 local function assert_close(...)
374 local retval, errmsg = ...
375 if retval then return ... end
376 state = "faulty"
377 error("Could not finish sending data to client: " .. errmsg)
378 end
379 -- function to assert non-faulty handle:
380 local function assert_not_faulty()
381 assert(state ~= "faulty", "Tried to use faulty request handle")
382 end
383 -- functions to send data to the browser:
384 local function send(...)
385 assert_output(socket:write_call(unblock, ...))
386 end
387 local function send_flush(...)
388 assert_output(socket:flush_call(unblock, ...))
389 end
390 -- function to finish request:
391 local function finish()
392 if close_responded then
393 -- discard any input:
394 consume = drain
395 -- close output stream:
396 send_flush()
397 assert_close(socket:finish())
398 -- wait for EOF of peer to avoid immediate TCP RST condition:
399 consume_all()
400 -- fully close socket:
401 assert_close(socket:close())
402 else
403 send_flush()
404 process_body_chunk = nil
405 consume_all()
406 end
407 end
408 -- function that writes out buffered chunks (without flushing the socket):
409 function send_chunk()
410 if chunk_bytes > 0 then
411 assert_output(socket:write(string.format("%x\r\n", chunk_bytes)))
412 for i = 1, #chunk_parts do -- TODO: evaluated only once?
413 send(chunk_parts[i])
414 chunk_parts[i] = nil
415 end
416 chunk_bytes = 0
417 send("\r\n")
418 end
419 end
420 -- function to report an error:
421 local function request_error(throw_error, status, text)
422 local errmsg = "Error while reading request from client. Error response: " .. status
423 if text then
424 errmsg = errmsg .. " (" .. text .. ")"
425 end
426 if
427 state == "init" or
428 state == "no_status_sent" or
429 state == "info_status_sent"
430 then
431 local error_response_status, errmsg2 = pcall(function()
432 request:monologue()
433 request:send_status(status)
434 request:send_header("Content-Type", "text/plain")
435 request:send_data(status, "\n")
436 if text then
437 request:send_data("\n", text, "\n")
438 end
439 request:finish()
440 end)
441 if not error_response_status then
442 error("Unexpected error while sending error response: " .. errmsg2)
443 end
444 elseif state ~= "faulty" then
445 state = "faulty"
446 assert_close(socket:reset())
447 end
448 if throw_error then
449 error(errmsg)
450 else
451 return survive
452 end
453 end
454 -- read functions
455 local function read(...)
456 local data, status = socket:read_yield(...)
457 if data == nil then
458 request_error(true, "400 Bad Request", "Read error")
459 end
460 if status == "eof" then
461 request_error(true, "400 Bad Request", "Unexpected EOF")
462 end
463 return data
464 end
465 local function read_eof(...)
466 local data, status = socket:read_yield(...)
467 if data == nil then
468 request_error(true, "400 Bad Request", "Read error")
469 end
470 if status == "eof" then
471 if data == "" then
472 return nil
473 else
474 request_error(true, "400 Bad Request", "Unexpected EOF")
475 end
476 end
477 return data
478 end
479 -- reads a number of bytes from the socket,
480 -- optionally feeding these bytes chunk-wise
481 -- into a callback function:
482 local function read_body_bytes(remaining)
483 while remaining > 0 do
484 local limit
485 if remaining > input_chunk_size then
486 limit = input_chunk_size
487 else
488 limit = remaining
489 end
490 local chunk = read(limit)
491 remaining = remaining - limit
492 if process_body_chunk then
493 process_body_chunk(chunk)
494 end
495 end
496 end
497 -- coroutine for request body processing:
498 local function read_body()
499 if request.headers_flags["Transfer-Encoding"]["chunked"] then
500 while true do
501 local line = read(32 + remaining_body_size_limit, "\n")
502 local zeros, lenstr = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)\r?\n$")
503 local chunkext
504 if lenstr then
505 chunkext = ""
506 else
507 zeros, lenstr, chunkext = string.match(line, "^(0*)([1-9A-Fa-f]+[0-9A-Fa-f]*)([ \t;].-)\r?\n$")
508 end
509 if not lenstr or #lenstr > 13 then
510 request_error(true, "400 Bad Request", "Encoding error while reading chunk of request body")
511 end
512 local len = tonumber("0x" .. lenstr)
513 remaining_body_size_limit = remaining_body_size_limit - (#zeros + #chunkext + len)
514 if remaining_body_size_limit < 0 then
515 request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded")
516 end
517 if len == 0 then break end
518 read_body_bytes(len)
519 local term = read(2, "\n")
520 if term ~= "\r\n" and term ~= "\n" then
521 request_error(true, "400 Bad Request", "Encoding error while reading chunk of request body")
522 end
523 end
524 while true do
525 local line = read(2 + remaining_body_size_limit, "\n")
526 if line == "\r\n" or line == "\n" then break end
527 remaining_body_size_limit = remaining_body_size_limit - #line
528 if remaining_body_size_limit < 0 then
529 request_error(true, "413 Request Entity Too Large", "Request body size limit exceeded while reading trailer section of chunked request body")
530 end
531 end
532 elseif request_body_content_length then
533 read_body_bytes(request_body_content_length)
534 end
535 end
536 -- function to setup default request body handling:
537 local function default_request_body_handling()
538 local post_params_list, post_params = new_params_list()
539 local content_type = request.headers_value["Content-Type"]
540 if content_type then
541 if
542 content_type == "application/x-www-form-urlencoded" or
543 string.match(content_type, "^application/x%-www%-form%-urlencoded *;")
544 then
545 read_urlencoded_form(post_params_list, request.body)
546 request.post_params_list, request.post_params = post_params_list, post_params
547 else
548 local boundary = string.match(
549 content_type,
550 '^multipart/form%-data[ \t]*[;,][ \t]*boundary="([^"]+)"$'
551 ) or string.match(
552 content_type,
553 '^multipart/form%-data[ \t]*[;,][ \t]*boundary=([^"; \t]+)$'
554 )
555 if boundary then
556 local post_metadata_list, post_metadata = new_params_list()
557 boundary = "--" .. boundary
558 local headerdata = ""
559 local streamer
560 local field_name
561 local metadata = {}
562 local value_parts
563 local function default_streamer(chunk)
564 value_parts[#value_parts+1] = chunk
565 end
566 local function stream_part_finish()
567 if streamer == default_streamer then
568 local value = table.concat(value_parts)
569 value_parts = nil
570 if field_name then
571 local values = post_params_list[field_name]
572 values[#values+1] = value
573 local metadata_entries = post_metadata_list[field_name]
574 metadata_entries[#metadata_entries+1] = metadata
575 end
576 else
577 streamer()
578 end
579 headerdata = ""
580 streamer = nil
581 field_name = nil
582 metadata = {}
583 end
584 local function stream_part_chunk(chunk)
585 if streamer then
586 streamer(chunk)
587 else
588 headerdata = headerdata .. chunk
589 while true do
590 local line, remaining = string.match(headerdata, "^(.-)\r?\n(.*)$")
591 if not line then
592 break
593 end
594 if line == "" then
595 streamer = streamed_post_params[field_name]
596 if not streamer then
597 for i, rule in ipairs(streamed_post_param_patterns) do
598 if string.match(field_name, rule[1]) then
599 streamer = rule[2]
600 break
601 end
602 end
603 end
604 if not streamer then
605 value_parts = {}
606 streamer = default_streamer
607 end
608 streamer(remaining, field_name, metadata)
609 return
610 end
611 headerdata = remaining
612 local header_key, header_value = string.match(line, "^([^:]*):[ \t]*(.-)[ \t]*$")
613 if not header_key then
614 request_error(true, "400 Bad Request", "Invalid header in multipart/form-data part")
615 end
616 header_key = string.lower(header_key)
617 if header_key == "content-disposition" then
618 local escaped_header_value = string.gsub(header_value, '"[^"]*"', function(str)
619 return string.gsub(str, "=", "==")
620 end)
621 field_name = string.match(escaped_header_value, ';[ \t]*name="([^"]*)"')
622 if field_name then
623 field_name = string.gsub(field_name, "==", "=")
624 else
625 field_name = string.match(header_value, ';[ \t]*name=([^"; \t]+)')
626 end
627 metadata.file_name = string.match(escaped_header_value, ';[ \t]*filename="([^"]*)"')
628 if metadata.file_name then
629 metadata.file_name = string.gsub(metadata.file_name, "==", "=")
630 else
631 string.match(header_value, ';[ \t]*filename=([^"; \t]+)')
632 end
633 elseif header_key == "content-type" then
634 metadata.content_type = header_value
635 elseif header_key == "content-transfer-encoding" then
636 request_error(true, "400 Bad Request", "Content-transfer-encoding not supported by multipart/form-data parser")
637 end
638 end
639 end
640 end
641 local skippart = true -- ignore data until first boundary
642 local afterbound = false -- interpret 2 bytes after boundary ("\r\n" or "--")
643 local terminated = false -- final boundary read
644 local bigchunk = ""
645 request:set_request_body_streamer(function(chunk)
646 if chunk == nil then
647 if not terminated then
648 request_error(true, "400 Bad Request", "Premature end of multipart/form-data request body")
649 end
650 request.post_metadata_list, request.post_metadata = post_metadata_list, post_metadata
651 end
652 if terminated then
653 return
654 end
655 bigchunk = bigchunk .. chunk
656 while true do
657 if afterbound then
658 if #bigchunk <= 2 then
659 return
660 end
661 local terminator = string.sub(bigchunk, 1, 2)
662 if terminator == "\r\n" then
663 afterbound = false
664 bigchunk = string.sub(bigchunk, 3)
665 elseif terminator == "--" then
666 terminated = true
667 bigchunk = nil
668 return
669 else
670 request_error(true, "400 Bad Request", "Error while parsing multipart body (expected CRLF or double minus)")
671 end
672 end
673 local pos1, pos2 = string.find(bigchunk, boundary, 1, true)
674 if not pos1 then
675 if not skippart then
676 local safe = #bigchunk-#boundary
677 if safe > 0 then
678 stream_part_chunk(string.sub(bigchunk, 1, safe))
679 bigchunk = string.sub(bigchunk, safe+1)
680 end
681 end
682 return
683 end
684 if not skippart then
685 stream_part_chunk(string.sub(bigchunk, 1, pos1 - 1))
686 stream_part_finish()
687 else
688 boundary = "\r\n" .. boundary
689 skippart = false
690 end
691 bigchunk = string.sub(bigchunk, pos2 + 1)
692 afterbound = true
693 end
694 end)
695 else
696 request_error(true, "415 Unsupported Media Type", "Unknown Content-Type of request body")
697 end
698 end
699 end
700 end
701 -- function to prepare body processing:
702 local function prepare()
703 assert_not_faulty()
704 if process_body_chunk == nil then
705 default_request_body_handling()
706 end
707 if state ~= "init" then
708 return
709 end
710 consume = coroutine.wrap(read_body)
711 state = "no_status_sent"
712 if request.headers_flags["Expect"]["100-continue"] then
713 request:send_status("100 Continue")
714 request:finish_headers()
715 end
716 end
717 -- method to ignore input and close connection after response:
718 function request:monologue()
719 assert_not_faulty()
720 if
721 state == "headers_sent" or
722 state == "finished"
723 then
724 error("All HTTP headers have already been sent")
725 end
726 local old_state = state
727 state = "faulty"
728 consume = drain
729 close_requested = true
730 if old_state == "init" then
731 state = "no_status_sent"
732 else
733 state = old_state
734 end
735 end
736 --
737 -- method to send a HTTP response status (e.g. "200 OK"):
738 function request:send_status(status)
739 prepare()
740 local old_state = state
741 state = "faulty"
742 if old_state == "info_status_sent" then
743 send_flush("\r\n")
744 elseif old_state ~= "no_status_sent" then
745 error("HTTP status has already been sent")
746 end
747 local status1 = string.sub(status, 1, 1)
748 local status3 = string.sub(status, 1, 3)
749 send("HTTP/1.1 ", status, "\r\n", preamble)
750 local wrb = status_without_response_body[status3]
751 if wrb then
752 state = "bodyless_status_sent"
753 if wrb == "zero_content_length" then
754 request:send_header("Content-Length", 0)
755 end
756 elseif status1 == "1" then
757 state = "info_status_sent"
758 else
759 state = "status_sent"
760 end
761 end
762 -- method to send a HTTP response header:
763 -- (key and value must be provided as separate args)
764 function request:send_header(key, value)
765 assert_not_faulty()
766 if state == "init" or state == "no_status_sent" then
767 error("HTTP status has not been sent yet")
768 elseif
769 state == "headers_sent" or
770 state == "finished"
771 then
772 error("All HTTP headers have already been sent")
773 end
774 local key_lower = string.lower(key)
775 if key_lower == "content-length" then
776 if state == "info_status_sent" then
777 error("Cannot set Content-Length for informational status response")
778 end
779 local cl = assert(tonumber(value), "Invalid content-length")
780 if content_length == nil then
781 content_length = cl
782 elseif content_length == cl then
783 return
784 else
785 error("Content-Length has been set multiple times with different values")
786 end
787 elseif key_lower == "connection" then
788 for entry in string.gmatch(string.lower(value), "[^,]+") do
789 if string.match(entry, "^[ \t]*close[ \t]*$") then
790 if state == "info_status_sent" then
791 error("Cannot set \"Connection: close\" for informational status response")
792 end
793 close_responded = true
794 break
795 end
796 end
797 end
798 assert_output(socket:write(key, ": ", value, "\r\n"))
799 end
800 -- function to terminate header section in response, optionally flushing:
801 -- (may be called multiple times unless response is finished)
802 local function finish_headers(with_flush)
803 if state == "finished" then
804 error("Response has already been finished")
805 elseif state == "info_status_sent" then
806 send_flush("\r\n")
807 state = "no_status_sent"
808 elseif state == "bodyless_status_sent" then
809 if close_requested and not close_responded then
810 request:send_header("Connection", "close")
811 end
812 send("\r\n")
813 finish()
814 state = "finished"
815 elseif state == "status_sent" then
816 if not content_length then
817 request:send_header("Transfer-Encoding", "chunked")
818 end
819 if close_requested and not close_responded then
820 request:send_header("Connection", "close")
821 end
822 send("\r\n")
823 if request.method == "HEAD" then
824 finish()
825 elseif with_flush then
826 send_flush()
827 end
828 state = "headers_sent"
829 elseif state ~= "headers_sent" then
830 error("HTTP status has not been sent yet")
831 end
832 end
833 -- method to finish and flush headers:
834 function request:finish_headers()
835 assert_not_faulty()
836 finish_headers(true)
837 end
838 -- method to send body data:
839 function request:send_data(...)
840 assert_not_faulty()
841 if output_state == "info_status_sent" then
842 error("No (non-informational) HTTP status has been sent yet")
843 elseif output_state == "bodyless_status_sent" then
844 error("Cannot send response data for body-less status message")
845 end
846 finish_headers(false)
847 if output_state ~= "headers_sent" then
848 error("Unexpected internal status in HTTP engine")
849 end
850 if request.method == "HEAD" then
851 return
852 end
853 for i = 1, select("#", ...) do
854 local str = tostring(select(i, ...))
855 if #str > 0 then
856 if content_length then
857 local bytes_to_send = #str
858 if bytes_sent + bytes_to_send > content_length then
859 error("Content length exceeded")
860 else
861 send(str)
862 bytes_sent = bytes_sent + bytes_to_send
863 end
864 else
865 chunk_bytes = chunk_bytes + #str
866 chunk_parts[#chunk_parts+1] = str
867 end
868 end
869 end
870 if chunk_bytes >= output_chunk_size then
871 send_chunk()
872 end
873 end
874 -- method to flush output buffer:
875 function request:flush()
876 assert_not_faulty()
877 send_chunk()
878 send_flush()
879 end
880 -- method to finish response:
881 function request:finish()
882 assert_not_faulty()
883 if state == "finished" then
884 return
885 elseif state == "info_status_sent" then
886 error("Informational HTTP response can be finished with :finish_headers() method")
887 end
888 finish_headers(false)
889 if state == "headers_sent" then
890 if request.method ~= "HEAD" then
891 state = "faulty"
892 if content_length then
893 if bytes_sent ~= content_length then
894 error("Content length not used")
895 end
896 else
897 send_chunk()
898 send("0\r\n\r\n")
899 end
900 finish()
901 end
902 state = "finished"
903 elseif state ~= "finished" then
904 error("Unexpected internal status in HTTP engine")
905 end
906 end
907 -- method to register POST param stream handler for a single field name:
908 function request:stream_post_param(field_name, callback)
909 if state ~= "init" then
910 error("Cannot setup request body streamer at this stage")
911 end
912 streamed_post_params[field_name] = callback
913 end
914 -- method to register POST param stream handler for a field name pattern:
915 function request:stream_post_params(pattern, callback)
916 if state ~= "init" then
917 error("Cannot setup request body streamer at this stage")
918 end
919 streamed_post_param_patterns[#streamed_post_param_patterns+1] = {pattern, callback}
920 end
921 -- method to register request body stream handler
922 function request:set_request_body_streamer(callback)
923 if state ~= "init" then
924 error("Cannot setup request body streamer at this stage")
925 end
926 local inprogress = false
927 local buffer = {}
928 process_body_chunk = function(chunk)
929 if inprogress then
930 buffer[#buffer+1] = chunk
931 else
932 inprogress = true
933 callback(chunk)
934 while #buffer > 0 do
935 chunk = table.concat(buffer)
936 buffer = {}
937 callback(chunk)
938 end
939 inprogress = false
940 end
941 end
942 end
943 -- method to start reading request body
944 function request:consume_input()
945 prepare()
946 consume_all()
947 end
948 -- method to stream request body
949 function request:stream_request_body(callback)
950 request:set_request_body_streamer(function(chunk)
951 if chunk ~= nil then
952 callback(chunk)
953 end
954 end)
955 request:consume_input()
956 end
957 -- metamethod to read special attibutes of request object:
958 function request_mt:__index(key, value)
959 if key == "body" then
960 local chunks = {}
961 request:stream_request_body(function(chunk)
962 chunks[#chunks+1] = chunk
963 end)
964 self.body = table.concat(chunks)
965 return self.body
966 elseif
967 key == "post_params_list" or key == "post_params" or
968 key == "post_metadata_list" or key == "post_metadata"
969 then
970 prepare()
971 consume_all()
972 return self[key]
973 end
974 end
975 -- coroutine for reading headers:
976 local function read_headers()
977 -- read and parse request line:
978 local line = read_eof(remaining_header_size_limit, "\n")
979 if not line then
980 return false, survive
981 end
982 remaining_header_size_limit = remaining_header_size_limit - #line
983 if remaining_header_size_limit == 0 then
984 return false, request_error(false, "414 Request-URI Too Long")
985 end
986 local target, proto
987 request.method, target, proto =
988 line:match("^([^ \t\r]+)[ \t]+([^ \t\r]+)[ \t]*([^ \t\r]*)[ \t]*\r?\n$")
989 if not request.method then
990 return false, request_error(false, "400 Bad Request")
991 elseif proto ~= "HTTP/1.1" then
992 return false, request_error(false, "505 HTTP Version Not Supported")
993 end
994 -- read and parse headers:
995 while true do
996 local line = read(remaining_header_size_limit, "\n");
997 remaining_header_size_limit = remaining_header_size_limit - #line
998 if line == "\r\n" or line == "\n" then
999 break
1000 end
1001 if remaining_header_size_limit == 0 then
1002 return false, request_error(false, "431 Request Header Fields Too Large")
1003 end
1004 local key, value = string.match(line, "^([^ \t\r]+):[ \t]*(.-)[ \t]*\r?\n$")
1005 if not key then
1006 return false, request_error(false, "400 Bad Request")
1007 end
1008 local values = request.headers[key]
1009 values[#values+1] = value
1010 end
1011 return true -- success
1012 end
1013 -- wait for input:
1014 if not poll(socket_set, nil, request_idle_timeout) then
1015 return request_error(false, "408 Request Timeout", "Idle connection timed out")
1016 end
1017 -- read headers (with timeout):
1018 do
1019 local coro = coroutine.wrap(read_headers)
1020 local starttime = request_header_timeout and moonbridge_io.timeref()
1021 while true do
1022 local status, retval = coro()
1023 if status == nil then
1024 local remaining
1025 if request_header_timeout then
1026 remaining = request_header_timeout - moonbridge_io.timeref(starttime)
1027 end
1028 if not poll(socket_set, nil, remaining) then
1029 return request_error(false, "408 Request Timeout", "Timeout while receiving headers")
1030 end
1031 elseif status == false then
1032 return retval
1033 elseif status == true then
1034 break
1035 else
1036 error("Unexpected yield value")
1037 end
1038 end
1039 end
1040 -- process "Connection: close" header if existent:
1041 connection_close_requested = request.headers_flags["Connection"]["close"]
1042 -- process "Content-Length" header if existent:
1043 do
1044 local values = request.headers_csv_table["Content-Length"]
1045 if #values > 0 then
1046 request_body_content_length = tonumber(values[1])
1047 local proper_value = tostring(request_body_content_length)
1048 for i, value in ipairs(values) do
1049 value = string.match(value, "^0*(.*)")
1050 if value ~= proper_value then
1051 return request_error(false, "400 Bad Request", "Content-Length header(s) invalid")
1052 end
1053 end
1054 if request_body_content_length > remaining_body_size_limit then
1055 return request_error(false, "413 Request Entity Too Large", "Announced request body size is too big")
1056 end
1057 end
1058 end
1059 -- process "Transfer-Encoding" header if existent:
1060 do
1061 local flag = request.headers_flags["Transfer-Encoding"]["chunked"]
1062 local list = request.headers_csv_table["Transfer-Encoding"]
1063 if (flag and #list ~= 1) or (not flag and #list ~= 0) then
1064 return request_error(false, "400 Bad Request", "Unexpected Transfer-Encoding")
1065 end
1066 end
1067 -- process "Expect" header if existent:
1068 for i, value in ipairs(request.headers_csv_table["Expect"]) do
1069 if string.lower(value) ~= "100-continue" then
1070 return request_error(false, "417 Expectation Failed", "Unexpected Expect header")
1071 end
1072 end
1073 -- get mandatory Host header according to RFC 7230:
1074 request.host = request.headers_value["Host"]
1075 if not request.host then
1076 return request_error(false, "400 Bad Request", "No valid host header")
1077 end
1078 -- parse request target:
1079 request.path, request.query = string.match(target, "^/([^?]*)(.*)$")
1080 if not request.path then
1081 local host2
1082 host2, request.path, request.query = string.match(target, "^[Hh][Tt][Tt][Pp]://([^/?]+)/?([^?]*)(.*)$")
1083 if host2 then
1084 if request.host ~= host2 then
1085 return request_error(false, "400 Bad Request", "No valid host header")
1086 end
1087 elseif not (target == "*" and request.method == "OPTIONS") then
1088 return request_error(false, "400 Bad Request", "Invalid request target")
1089 end
1090 end
1091 -- parse GET params:
1092 if request.query then
1093 read_urlencoded_form(request.get_params_list, request.query)
1094 end
1095 -- parse cookies:
1096 for i, line in ipairs(request.headers["Cookie"]) do
1097 for rawkey, rawvalue in
1098 string.gmatch(line, "([^=; ]*)=([^=; ]*)")
1099 do
1100 request.cookies[decode_uri(rawkey)] = decode_uri(rawvalue)
1101 end
1102 end
1103 -- (re)set timeout for handler:
1104 timeout(response_timeout or 0)
1105 -- call underlying handler and remember boolean result:
1106 if handler(request) ~= true then survive = false end
1107 -- finish request (unless already done by underlying handler):
1108 request:finish()
1109 -- stop timeout timer:
1110 timeout(0)
1111 until close_responded
1112 return survive
1113 end
1114 end
1116 return _M

Impressum / About Us