@@ -25,8 +25,8 @@ local function get_content_length(header)
2525 if line == ' ' then
2626 break
2727 end
28- local key , value = line :match (' ^%s*(%S+)%s*:%s*(. +)%s*$' )
29- if key :lower () == ' content-length' then
28+ local key , value = line :match (' ^%s*(%S+)%s*:%s*(%d +)%s*$' )
29+ if key and key :lower () == ' content-length' then
3030 return tonumber (value )
3131 end
3232 end
@@ -39,66 +39,96 @@ local header_start_pattern = ('content'):gsub('%w', function(c)
3939 return ' [' .. c .. c :upper () .. ' ]'
4040end )
4141
42+ local has_strbuffer , strbuffer = pcall (require , ' string.buffer' )
43+
4244--- The actual workhorse.
43- local function request_parser_loop ()
44- local buffer = ' ' -- only for header part
45- while true do
46- -- A message can only be complete if it has a double CRLF and also the full
47- -- payload, so first let's check for the CRLFs
48- local header_end , body_start = buffer :find (' \r\n\r\n ' , 1 , true )
49- -- Start parsing the headers
50- if header_end then
51- -- This is a workaround for servers sending initial garbage before
52- -- sending headers, such as if a bash script sends stdout. It assumes
53- -- that we know all of the headers ahead of time. At this moment, the
54- -- only valid headers start with "Content-*", so that's the thing we will
55- -- be searching for.
56- -- TODO(ashkan) I'd like to remove this, but it seems permanent :(
57- local buffer_start = buffer :find (header_start_pattern )
58- if not buffer_start then
59- error (
60- string.format (
61- " Headers were expected, a different response was received. The server response was '%s'." ,
62- buffer
63- )
64- )
65- end
66- local header = buffer :sub (buffer_start , header_end + 1 )
67- local content_length = get_content_length (header )
68- -- Use table instead of just string to buffer the message. It prevents
69- -- a ton of strings allocating.
70- -- ref. http://www.lua.org/pil/11.6.html
71- --- @type string[]
72- local body_chunks = { buffer :sub (body_start + 1 ) }
73- local body_length = # body_chunks [1 ]
74- -- Keep waiting for data until we have enough.
75- while body_length < content_length do
76- --- @type string
45+ --- @type function
46+ local request_parser_loop
47+
48+ if has_strbuffer then
49+ request_parser_loop = function ()
50+ local buf = strbuffer .new ()
51+ while true do
52+ local msg = buf :tostring ()
53+ local header_end = msg :find (' \r\n\r\n ' , 1 , true )
54+ if header_end then
55+ local header = buf :get (header_end + 1 )
56+ buf :skip (2 ) -- skip past header boundary
57+ local content_length = get_content_length (header )
58+ while # buf < content_length do
59+ local chunk = coroutine.yield ()
60+ buf :put (chunk )
61+ end
62+ local body = buf :get (content_length )
63+ local chunk = coroutine.yield (body )
64+ buf :put (chunk )
65+ else
7766 local chunk = coroutine.yield ()
78- or error (' Expected more data for the body. The server may have died.' ) -- TODO hmm.
79- table.insert (body_chunks , chunk )
80- body_length = body_length + # chunk
67+ buf :put (chunk )
8168 end
82- local last_chunk = body_chunks [# body_chunks ]
69+ end
70+ end
71+ else
72+ request_parser_loop = function ()
73+ local buffer = ' ' -- only for header part
74+ while true do
75+ -- A message can only be complete if it has a double CRLF and also the full
76+ -- payload, so first let's check for the CRLFs
77+ local header_end , body_start = buffer :find (' \r\n\r\n ' , 1 , true )
78+ -- Start parsing the headers
79+ if header_end then
80+ -- This is a workaround for servers sending initial garbage before
81+ -- sending headers, such as if a bash script sends stdout. It assumes
82+ -- that we know all of the headers ahead of time. At this moment, the
83+ -- only valid headers start with "Content-*", so that's the thing we will
84+ -- be searching for.
85+ -- TODO(ashkan) I'd like to remove this, but it seems permanent :(
86+ local buffer_start = buffer :find (header_start_pattern )
87+ if not buffer_start then
88+ error (
89+ string.format (
90+ " Headers were expected, a different response was received. The server response was '%s'." ,
91+ buffer
92+ )
93+ )
94+ end
95+ local header = buffer :sub (buffer_start , header_end + 1 )
96+ local content_length = get_content_length (header )
97+ -- Use table instead of just string to buffer the message. It prevents
98+ -- a ton of strings allocating.
99+ -- ref. http://www.lua.org/pil/11.6.html
100+ --- @type string[]
101+ local body_chunks = { buffer :sub (body_start + 1 ) }
102+ local body_length = # body_chunks [1 ]
103+ -- Keep waiting for data until we have enough.
104+ while body_length < content_length do
105+ --- @type string
106+ local chunk = coroutine.yield ()
107+ or error (' Expected more data for the body. The server may have died.' ) -- TODO hmm.
108+ table.insert (body_chunks , chunk )
109+ body_length = body_length + # chunk
110+ end
111+ local last_chunk = body_chunks [# body_chunks ]
83112
84- body_chunks [# body_chunks ] = last_chunk :sub (1 , content_length - body_length - 1 )
85- local rest = ' '
86- if body_length > content_length then
87- rest = last_chunk :sub (content_length - body_length )
88- end
89- local body = table.concat (body_chunks )
90- -- Yield our data.
113+ body_chunks [# body_chunks ] = last_chunk :sub (1 , content_length - body_length - 1 )
114+ local rest = ' '
115+ if body_length > content_length then
116+ rest = last_chunk :sub (content_length - body_length )
117+ end
118+ local body = table.concat (body_chunks )
119+ -- Yield our data.
91120
92- --- @type string
93- local data = coroutine.yield (body )
94- or error (' Expected more data for the body. The server may have died.' )
95- buffer = rest .. data
96- else
97- -- Get more data since we don't have enough.
98- --- @type string
99- local data = coroutine.yield ()
100- or error (' Expected more data for the header. The server may have died.' )
101- buffer = buffer .. data
121+ --- @type string
122+ local data = coroutine.yield (body )
123+ or error (' Expected more data for the body. The server may have died.' )
124+ buffer = rest .. data
125+ else
126+ -- Get more data since we don't have enough.
127+ --- @type string
128+ local data = coroutine.yield ()
129+ or error (' Expected more data for the header. The server may have died.' )
130+ buffer = buffer .. data
131+ end
102132 end
103133 end
104134end
0 commit comments