|
|
@ -7,20 +7,20 @@ |
|
|
|
-compile({inline_size, 128}). |
|
|
|
|
|
|
|
-export([ |
|
|
|
request/2 |
|
|
|
request/3 |
|
|
|
]). |
|
|
|
|
|
|
|
-spec request(undefined | recvState(), binary()) -> {ok, recvState()} | error(). |
|
|
|
request(reqLine, Data) -> |
|
|
|
-spec request(Stage :: stage(), Data :: binary(), State :: #state{}) -> {ok, NewState :: #state{}} | {done, NewState :: #state{}} | {error, term()}. |
|
|
|
request(reqLine, Data, State) -> |
|
|
|
case erlang:decode_packet(http_bin, Data, []) of |
|
|
|
{more, _} -> |
|
|
|
reqLine; |
|
|
|
{ok, State#state{buffer = Data}}; |
|
|
|
{ok, {http_request, Method, RawPath, Version}, Rest} -> |
|
|
|
case byte_size(Rest) > 0 of |
|
|
|
true -> |
|
|
|
request(header, Rest); |
|
|
|
request(header, Rest, State#state{stage = header, buffer = Rest, wsReq = #wsReq{method = Method, raw_path = RawPath, version = Version}, headerCnt = 0}); |
|
|
|
_ -> |
|
|
|
{header, Method, RawPath, Version} |
|
|
|
{ok, State#state{stage = header, buffer = <<>>, wsReq = #wsReq{method = Method, raw_path = RawPath, version = Version}, headerCnt = 0}} |
|
|
|
end; |
|
|
|
{ok, {http_error, ErrStr}, _} -> |
|
|
|
{error, ErrStr}; |
|
|
@ -28,178 +28,103 @@ request(reqLine, Data) -> |
|
|
|
{error, http_response}; |
|
|
|
{error, _Reason} = Ret -> |
|
|
|
Ret |
|
|
|
end. |
|
|
|
|
|
|
|
|
|
|
|
case parseRequestLine(Data) of |
|
|
|
{StatusCode, Rest} -> |
|
|
|
case splitHeaders(Rest, Rn, RnRn) of |
|
|
|
{undefined, Headers, Body} -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = undefined, body = Body}}; |
|
|
|
{0, Headers, Rest} -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = Rest}}; |
|
|
|
{chunked, Headers, Body} -> |
|
|
|
case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of |
|
|
|
true -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = Body}}; |
|
|
|
_ -> |
|
|
|
RecvState = #recvState{stage = body, contentLength = chunked, statusCode = StatusCode, headers = Headers}, |
|
|
|
request(RecvState, Rn, RnRn, Body, IsHeadMethod) |
|
|
|
end; |
|
|
|
{ContentLength, Headers, Body} -> |
|
|
|
BodySize = erlang:size(Body), |
|
|
|
if |
|
|
|
BodySize == ContentLength -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; |
|
|
|
BodySize > ContentLength -> |
|
|
|
?wsWarn("11 contentLength get to long data why? more: ~p ~n", [BodySize - ContentLength]), |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; |
|
|
|
true -> |
|
|
|
case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of |
|
|
|
true -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; |
|
|
|
end; |
|
|
|
request(header, Data, State) -> |
|
|
|
case erlang:decode_packet(httph_bin, Data, []) of |
|
|
|
{more, _} -> |
|
|
|
{ok, State#state{buffer = Data}}; |
|
|
|
{ok, {http_header, _, Key, _, Value}, Rest} -> |
|
|
|
#state{headerCnt = HeaderCnt, temHeader = TemHeader, rn = Rn} = State, |
|
|
|
NewTemHeader = [{Key, Value} | TemHeader], |
|
|
|
NewHeaderCnt = HeaderCnt + 1, |
|
|
|
case NewHeaderCnt >= 100 of |
|
|
|
true -> |
|
|
|
{error, too_many_headers}; |
|
|
|
_ -> |
|
|
|
case Key of |
|
|
|
'Content-Length' -> |
|
|
|
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = binary_to_integer(Value)}); |
|
|
|
'Transfer-Encoding' -> |
|
|
|
case Value of |
|
|
|
<<"chunked">> -> |
|
|
|
case Rn of |
|
|
|
undefined -> |
|
|
|
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked, rn = binary:compile_pattern(<<"\r\n">>)}); |
|
|
|
_ -> |
|
|
|
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked}) |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
{ok, #recvState{stage = body, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}} |
|
|
|
end |
|
|
|
end; |
|
|
|
not_enough_data -> |
|
|
|
{ok
, #recvStaten>{stage = header, body = Data}} |
|
|
|
{error, 'Transfer-Encoding'} |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
request(header, Rest, State#state{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader}) |
|
|
|
end |
|
|
|
end; |
|
|
|
not_enough_data -> |
|
|
|
{ok, #recvState{stage = header, body = Data}}; |
|
|
|
{error, Reason} -> |
|
|
|
{error, Reason} |
|
|
|
end; |
|
|
|
request(#recvState{stage = body, contentLength = chunked, body = Body, buffer = Buffer} = RecvState, Rn, _RnRn, Data, _IsHeadMethod) -> |
|
|
|
NewBuffer = <<Buffer/binary, Data/binary>>, |
|
|
|
case parseChunks(NewBuffer, Rn, []) of |
|
|
|
{ok, AddBody, _Rest} -> |
|
|
|
LastBody = <<Body/binary, AddBody/binary>>, |
|
|
|
{done, RecvState#recvState{stage = done, body = LastBody}}; |
|
|
|
{not_enough_data, AddBody, Rest} -> |
|
|
|
NewBody = <<Body/binary, AddBody/binary>>, |
|
|
|
{ok, RecvState#recvState{body = NewBody, buffer = Rest}}; |
|
|
|
{error, Reason} -> |
|
|
|
{error, Reason} |
|
|
|
end; |
|
|
|
request(#recvState{stage = body, contentLength = ContentLength, body = Body} = RecvState, _Rn, _RnRn, Data, _IsHeadMethod) -> |
|
|
|
CurData = <<Body/binary, Data/binary>>, |
|
|
|
BodySize = erlang:size(CurData), |
|
|
|
if |
|
|
|
BodySize == ContentLength -> |
|
|
|
{done, RecvState#recvState{stage = done, body = CurData}}; |
|
|
|
BodySize > ContentLength -> |
|
|
|
?wsWarn("22 contentLength get to long data why? more: ~p ~n", [BodySize - ContentLength]), |
|
|
|
{done, #recvState{stage = done, body = CurData}}; |
|
|
|
true -> |
|
|
|
{ok, RecvState#recvState{body = CurData}} |
|
|
|
end; |
|
|
|
request(#recvState{stage = header, body = OldBody}, Rn, RnRn, Data, IsHeadMethod) -> |
|
|
|
CurBody = <<OldBody/binary, Data/binary>>, |
|
|
|
case parseRequestLine(CurBody, Rn) of |
|
|
|
{StatusCode, Rest} -> |
|
|
|
case splitHeaders(Rest, Rn, RnRn) of |
|
|
|
{undefined, Headers, Body} -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = undefined, body = Body}}; |
|
|
|
{0, Headers, Body} -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = Body}}; |
|
|
|
{chunked, Headers, Rest} -> |
|
|
|
case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of |
|
|
|
{ok, http_eoh, Rest} -> |
|
|
|
#state{temHeader = TemHeader, contentLength = CLen, wsReq = WsReq} = State, |
|
|
|
NewWsReq = WsReq#wsReq{headers = TemHeader}, |
|
|
|
case CLen of |
|
|
|
undefined -> |
|
|
|
{error, content_length}; |
|
|
|
0 -> |
|
|
|
{done, State#state{buffer = Rest, wsReq = NewWsReq, temHeader = []}}; |
|
|
|
_ -> |
|
|
|
case byte_size(Rest) > 0 of |
|
|
|
true -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = <<>>}}; |
|
|
|
request(body, Rest, State#state{stage = body, buffer = Rest, wsReq = NewWsReq, temHeader = []}); |
|
|
|
_ -> |
|
|
|
RecvState = #recvState{stage = body, contentLength = chunked, statusCode = StatusCode, headers = Headers}, |
|
|
|
request(RecvState, Rn, RnRn, Rest, IsHeadMethod) |
|
|
|
end; |
|
|
|
{ContentLength, Headers, Body} -> |
|
|
|
BodySize = erlang:size(Body), |
|
|
|
if |
|
|
|
BodySize == ContentLength -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; |
|
|
|
BodySize > ContentLength -> |
|
|
|
?wsWarn("33 contentLength get to long data why? more: ~p ~n", [BodySize - ContentLength]), |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; |
|
|
|
true -> |
|
|
|
case IsHeadMethod orelse StatusCode == 204 orelse StatusCode == 304 orelse (StatusCode < 200 andalso StatusCode >= 100) of |
|
|
|
true -> |
|
|
|
{done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}}; |
|
|
|
_ -> |
|
|
|
{ok, #recvState{stage = body, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}} |
|
|
|
end |
|
|
|
end; |
|
|
|
not_enough_data -> |
|
|
|
{ok, #recvState{stage = header, body = CurBody}} |
|
|
|
{ok, State#state{stage = body, buffer = <<>>, wsReq = NewWsReq, temHeader = []}} |
|
|
|
end |
|
|
|
end; |
|
|
|
not_enough_data -> |
|
|
|
{ok, #recvState{stage = header, body = CurBody}}; |
|
|
|
{error, Reason} -> |
|
|
|
{error, Reason} |
|
|
|
end. |
|
|
|
|
|
|
|
splitHeaders(Data, Rn, RnRn) -> |
|
|
|
case binary:split(Data, RnRn) of |
|
|
|
[Data] -> |
|
|
|
not_enough_data; |
|
|
|
[Headers, Body] -> |
|
|
|
HeadersList = binary:split(Headers, Rn, [global]), |
|
|
|
ContentLength = contentLength(HeadersList), |
|
|
|
{ContentLength, Headers, Body} |
|
|
|
end. |
|
|
|
|
|
|
|
contentLength([]) -> |
|
|
|
undefined; |
|
|
|
contentLength([<<"Content-Length: ", Rest/binary>> | _T]) -> |
|
|
|
binary_to_integer(Rest); |
|
|
|
contentLength([<<"content-length: ", Rest/binary>> | _T]) -> |
|
|
|
binary_to_integer(Rest); |
|
|
|
contentLength([<<"Transfer-Encoding: chunked">> | _T]) -> |
|
|
|
chunked; |
|
|
|
contentLength([<<"transfer-encoding: chunked">> | _T]) -> |
|
|
|
chunked; |
|
|
|
contentLength([_ | T]) -> |
|
|
|
contentLength(T). |
|
|
|
|
|
|
|
parseRequestLine(Data, Rn) -> |
|
|
|
case binary:split(Data, Rn) of |
|
|
|
[Data] -> |
|
|
|
not_enough_data; |
|
|
|
[Line, Rest] -> |
|
|
|
case binary:split(Line, <<" ">>) of |
|
|
|
[Method, RawPath, V] -> |
|
|
|
{Method, RawPath, V, Rest}; |
|
|
|
_ -> |
|
|
|
{error, request_line} |
|
|
|
{ok, {http_error, ErrStr}, _Rest} -> |
|
|
|
{error, ErrStr}; |
|
|
|
{error, _Reason} = Ret -> |
|
|
|
Ret |
|
|
|
end; |
|
|
|
request(body, Data, State) -> |
|
|
|
#state{contentLength = CLen, wsReq = WsReq, temChunked = TemChunked, rn = Rn} = State, |
|
|
|
case CLen of |
|
|
|
chunked -> |
|
|
|
case parseChunks(Data, Rn, TemChunked) of |
|
|
|
{ok, NewTemChunked, Rest} -> |
|
|
|
{ok, State#state{buffer = Rest, temChunked = NewTemChunked}}; |
|
|
|
{over, LastTemChunked, Rest} -> |
|
|
|
{done, State#state{buffer = Rest, temChunked = <<>>, wsReq = WsReq#wsReq{body = LastTemChunked}}}; |
|
|
|
{error, _Reason} = Ret -> |
|
|
|
Ret |
|
|
|
end; |
|
|
|
_ -> |
|
|
|
BodySize = erlang:size(Data), |
|
|
|
if |
|
|
|
BodySize == CLen -> |
|
|
|
{done, State#state{buffer = <<>>, wsReq = WsReq#wsReq{body = Data}}}; |
|
|
|
BodySize > CLen -> |
|
|
|
<<Body:CLen/binary, Rest/binary>> = Data, |
|
|
|
{done, State#state{buffer = Rest, wsReq = WsReq#wsReq{body = Body}}}; |
|
|
|
true -> |
|
|
|
{ok, State#state{buffer = Data}} |
|
|
|
end |
|
|
|
end. |
|
|
|
|
|
|
|
parseChunks(Data, Rn, Acc) -> |
|
|
|
case parseChunk(Data, Rn) of |
|
|
|
done -> |
|
|
|
{ok, iolist_to_binary(lists:reverse(Acc)), <<>>}; |
|
|
|
{ok, Body, Rest} -> |
|
|
|
parseChunks(Rest, Rn, [Body | Acc]); |
|
|
|
not_enough_data -> |
|
|
|
{not_enough_data, iolist_to_binary(lists:reverse(Acc)), Data}; |
|
|
|
{error, Reason} -> |
|
|
|
{error, Reason} |
|
|
|
end. |
|
|
|
|
|
|
|
parseChunk(Data, Rn) -> |
|
|
|
case binary:split(Data, Rn) of |
|
|
|
[Size, Rest] -> |
|
|
|
case parseChunkSize(Size) of |
|
|
|
case chunkSize(Size) of |
|
|
|
undefined -> |
|
|
|
{error, invalid_chunk_size}; |
|
|
|
0 -> |
|
|
|
done; |
|
|
|
{over, Acc, Rest}; |
|
|
|
HexSize -> |
|
|
|
parseChunkBody(Rest, HexSize) |
|
|
|
end; |
|
|
|
[Data] -> |
|
|
|
not_enough_data |
|
|
|
case chunkBody(Rest, HexSize) of |
|
|
|
not_enough_data -> |
|
|
|
{ok, Acc, Data}; |
|
|
|
{ok, Body, Rest} -> |
|
|
|
parseChunks(Rest, Rn, <<Acc/binary, Body/binary>>) |
|
|
|
end |
|
|
|
end |
|
|
|
end. |
|
|
|
|
|
|
|
parseChunkBody(Data, Size) -> |
|
|
|
chunkBody(Data, Size) -> |
|
|
|
case Data of |
|
|
|
<<Body:Size/binary, "\r\n", Rest/binary>> -> |
|
|
|
{ok, Body, Rest}; |
|
|
@ -207,10 +132,10 @@ parseChunkBody(Data, Size) -> |
|
|
|
not_enough_data |
|
|
|
end. |
|
|
|
|
|
|
|
parseChunkSize(Bin) -> |
|
|
|
chunkSize(Bin) -> |
|
|
|
try |
|
|
|
binary_to_integer(Bin, 16) |
|
|
|
catch |
|
|
|
error:badarg -> |
|
|
|
undefined |
|
|
|
end. |
|
|
|
end. |