@@ -4,7 +4,7 @@ defmodule Bandit.HTTP1Request do
44 @ behaviour Plug.Conn.Adapter
55 @ behaviour Bandit.HTTPRequest
66
7- defstruct state: :new , socket: nil , buffer: << >> , version: nil
7+ defstruct state: :new , socket: nil , buffer: << >> , body_size: nil , body_encoding: nil , connection: nil , version: nil
88
99 defmodule UnreadHeadersError do
1010 defexception message: "Headers have not been read yet"
@@ -24,42 +24,50 @@ defmodule Bandit.HTTP1Request do
2424 def request ( % Socket { } = socket ) , do: { :ok , __MODULE__ , % __MODULE__ { socket: socket } }
2525
2626 @ impl Bandit.HTTPRequest
27- def read_headers ( req , type \\ :http , headers \\ [ ] , method \\ nil , path \\ nil )
27+ def read_headers ( req ) do
28+ case do_read_headers ( req ) do
29+ { :ok , headers , method , path , req } ->
30+ body_size =
31+ case get_header ( headers , "content-length" ) do
32+ nil -> nil
33+ size -> String . to_integer ( size )
34+ end
2835
29- def read_headers ( % __MODULE__ { state: :new , socket: socket , buffer: buffer } = req , type , headers , method , path ) do
30- case :erlang . decode_packet ( type , buffer , [ ] ) do
31- { :more , _len } ->
32- case Socket . recv ( socket ) do
33- { :ok , more_data } -> read_headers ( % { req | buffer: buffer <> more_data } , type , headers , method , path )
34- { :error , reason } -> { :error , reason }
35- end
36+ body_encoding = get_header ( headers , "content-encoding" )
37+ connection = get_header ( headers , "connection" )
3638
37- { :ok , { :http_request , method , { :abs_path , path } , version } , rest } ->
38- read_headers ( % { req | buffer: rest , version: version ( version ) } , :httph , headers , method , path )
39-
40- { :ok , { :http_header , _ , header , _ , value } , rest } ->
41- read_headers (
42- % { req | buffer: rest } ,
43- :httph ,
44- [ { header |> to_string ( ) |> String . downcase ( ) , to_string ( value ) } | headers ] ,
45- to_string ( method ) ,
46- to_string ( path )
47- )
48-
49- { :ok , :http_eoh , rest } ->
50- { :ok , headers , to_string ( method ) , to_string ( path ) , % { req | state: :headers_read , buffer: rest } }
39+ { :ok , headers , method , path ,
40+ % { req | body_size: body_size , body_encoding: body_encoding , connection: connection } }
5141
5242 { :error , reason } ->
5343 { :error , reason }
5444 end
5545 end
5646
57- def read_headers ( % __MODULE__ { } , _ , _ , _ , _ ) , do: raise ( AlreadyReadError )
58-
5947 @ impl Plug.Conn.Adapter
60- def read_req_body ( % __MODULE__ { state: :headers_read } = req , _opts ) do
61- # TODO
62- { :ok , << >> , % { req | state: :body_read } }
48+ def read_req_body ( % __MODULE__ { state: :headers_read , body_size: nil , body_encoding: nil } = req , _opts ) do
49+ { :ok , nil , req }
50+ end
51+
52+ # TODO handle chunked encoding as a thing
53+
54+ def read_req_body ( % __MODULE__ { state: :headers_read , buffer: buffer , body_size: body_size } = req , opts )
55+ when is_number ( body_size ) do
56+ to_read = min ( body_size , Keyword . get ( opts , :length , 8_000_000 ) ) - byte_size ( buffer )
57+
58+ case do_read_req_body_by_size ( req , to_read , opts ) do
59+ { :ok , % __MODULE__ { buffer: buffer } = req } ->
60+ remaining_bytes = body_size - byte_size ( buffer )
61+
62+ if remaining_bytes > 0 do
63+ { :more , buffer , % { req | buffer: << >> , body_size: remaining_bytes } }
64+ else
65+ { :ok , buffer , % { req | state: :body_read , buffer: << >> , body_size: 0 } }
66+ end
67+
68+ { :error , reason } ->
69+ { :error , reason }
70+ end
6371 end
6472
6573 def read_req_body ( % __MODULE__ { state: :new } , _opts ) , do: raise ( UnreadHeadersError )
@@ -119,7 +127,46 @@ defmodule Bandit.HTTP1Request do
119127 def get_http_protocol ( % __MODULE__ { version: version } ) , do: version
120128
121129 @ impl Bandit.HTTPRequest
122- def keepalive? ( % __MODULE__ { version: version } ) , do: version == "HTTP/1.1"
130+ def keepalive? ( % __MODULE__ { version: version } ) , do: version == :"HTTP/1.1"
131+
132+ defp do_read_headers ( req , type \\ :http , headers \\ [ ] , method \\ nil , path \\ nil )
133+
134+ defp do_read_headers ( % __MODULE__ { state: :new , socket: socket , buffer: buffer } = req , type , headers , method , path ) do
135+ case :erlang . decode_packet ( type , buffer , [ ] ) do
136+ { :more , _len } ->
137+ case Socket . recv ( socket ) do
138+ { :ok , more_data } -> do_read_headers ( % { req | buffer: buffer <> more_data } , type , headers , method , path )
139+ { :error , reason } -> { :error , reason }
140+ end
141+
142+ { :ok , { :http_request , method , { :abs_path , path } , version } , rest } ->
143+ do_read_headers ( % { req | buffer: rest , version: version ( version ) } , :httph , headers , method , path )
144+
145+ { :ok , { :http_header , _ , header , _ , value } , rest } ->
146+ do_read_headers (
147+ % { req | buffer: rest } ,
148+ :httph ,
149+ [ { header |> to_string ( ) |> String . downcase ( ) , to_string ( value ) } | headers ] ,
150+ to_string ( method ) ,
151+ to_string ( path )
152+ )
153+
154+ { :ok , :http_eoh , rest } ->
155+ { :ok , headers , method , path , % { req | state: :headers_read , buffer: rest } }
156+
157+ { :error , reason } ->
158+ { :error , reason }
159+ end
160+ end
161+
162+ defp do_read_headers ( % __MODULE__ { } , _ , _ , _ , _ ) , do: raise ( AlreadyReadError )
163+
164+ defp get_header ( headers , header , default \\ nil ) do
165+ case List . keyfind ( headers , header , 0 ) do
166+ { _ , value } -> value
167+ nil -> default
168+ end
169+ end
123170
124171 defp format_headers ( headers , body ) do
125172 [ { "content-length" , body |> byte_size ( ) |> to_string ( ) } | headers ]
@@ -128,4 +175,25 @@ defmodule Bandit.HTTP1Request do
128175
129176 defp version ( { 1 , 1 } ) , do: :"HTTP/1.1"
130177 defp version ( { 1 , 0 } ) , do: :"HTTP/1.0"
178+
179+ defp do_read_req_body_by_size ( % __MODULE__ { } = req , 0 , _opts ) , do: { :ok , req }
180+
181+ defp do_read_req_body_by_size ( % __MODULE__ { socket: socket , buffer: buffer } = req , to_read , opts ) do
182+ read_size = min ( to_read , Keyword . get ( opts , :read_length , 1_000_000 ) )
183+ read_timeout = Keyword . get ( opts , :read_timeout , 15_000 )
184+
185+ case Socket . recv ( socket , read_size , read_timeout ) do
186+ { :ok , chunk } ->
187+ remaining_bytes = to_read - byte_size ( chunk )
188+
189+ if remaining_bytes > 0 do
190+ do_read_req_body_by_size ( % { req | buffer: buffer <> chunk } , remaining_bytes , opts )
191+ else
192+ { :ok , % { req | buffer: buffer <> chunk } }
193+ end
194+
195+ { :error , reason } ->
196+ { :error , reason }
197+ end
198+ end
131199end
0 commit comments