From 5b5a5b5d17f0cc0fbcb1ed9c009dbcf8a4a52ff5 Mon Sep 17 00:00:00 2001 From: SisMaker <1713699517@qq.com> Date: Mon, 17 Jan 2022 18:18:13 +0800 Subject: [PATCH] =?UTF-8?q?ft:=20=E4=BB=A3=E7=A0=81=E4=BF=AE=E6=94=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- include/eWSrv.hrl | 31 +++-- src/eWSrv.erl | 3 +- src/example/wsEgHer.erl | 4 +- src/test/elli_test.erl | 2 +- src/test/elli_tests.erl | 22 ++-- src/test/wsTest.erl | 56 +++++++++ src/wsSrv/wsConSup.erl | 12 +- src/wsSrv/wsHttp.erl | 202 +++++++++++++++++++++----------- src/wsSrv/wsHttpProtocol.erl | 216 +++++++++++++++++++++++++++++++++++ src/wsSrv/wsHttps.erl | 57 +++++---- src/wsSrv/wsReq.erl | 66 +++++------ 11 files changed, 510 insertions(+), 161 deletions(-) create mode 100644 src/test/wsTest.erl create mode 100644 src/wsSrv/wsHttpProtocol.erl diff --git a/include/eWSrv.hrl b/include/eWSrv.hrl index 0ef1d6c..bba3bce 100644 --- a/include/eWSrv.hrl +++ b/include/eWSrv.hrl @@ -1,5 +1,10 @@ -include_lib("eNet/include/eNet.hrl"). +%% @type version(). HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}'. +-type version() :: {0, 9} | {1, 0} | {1, 1}. + +-export_type([version/0]). + -define(DefWsOpts, [ binary , {packet, 4} @@ -18,8 +23,8 @@ listenOpt() | {wsMod, module()}. --record(req, { - method :: elli:http_method(), +-record(wsReq, { + method :: method(), scheme :: undefined | binary(), host :: undefined | binary(), port :: undefined | 1..65535, @@ -27,22 +32,20 @@ args :: [{binary(), any()}], raw_path :: binary(), version :: wsHttp:version(), - headers :: elli:headers(), - original_headers :: elli:headers(), - body :: elli:body(), - pid :: pid(), - socket :: undefined | wsNet:socket(), - callback :: wsHer:callback() + headers :: headers(), + original_headers :: headers(), + body :: body(), + pid :: pid() }). --export_type([req/0, http_method/0, body/0, headers/0, response_code/0]). +-export_type([req/0, method/0, body/0, headers/0, response_code/0]). %% @type req(). A record representing an HTTP request. --type req() :: #req{}. +-type req() :: #wsReq{}. %% @type http_method(). An uppercase atom representing a known HTTP verb or a %% binary for other verbs. --type http_method() :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST'| 'PUT' | 'DELETE' | 'TRACE' | binary(). +-type method() :: 'OPTIONS' | 'GET' | 'HEAD' | 'POST'| 'PUT' | 'DELETE' | 'TRACE' | binary(). %% @type body(). A binary or iolist. -type body() :: binary() | iolist(). @@ -55,6 +58,12 @@ -define(EXAMPLE_CONF, [{callback, elli_example_callback}, {callback_args, []}]). +-define(CONTENT_LENGTH_HEADER, <<"content-length">>). +-define(EXPECT_HEADER, <<"expect">>). +-define(CONNECTION_HEADER, <<"connection">>). +-define(TRANSFER_ENCODING_HEADER, <<"Transfer-Encoding">>). + + -export_type([callback/0, callback_mod/0, callback_args/0, event/0, result/0]). diff --git a/src/eWSrv.erl b/src/eWSrv.erl index 7ab12d8..421c8cb 100644 --- a/src/eWSrv.erl +++ b/src/eWSrv.erl @@ -11,6 +11,7 @@ startWSrv(WSrvName, Port, WsOpts) -> ListenName = wsUtil:lsName(WSrvName), TWsOpts = lists:keystore(conMod, 1, WsOpts, {conMod, wsHttp}), LWsOpts = lists:keystore(conArgs, 1, TWsOpts, {conArgs, WSrvName}), + WsMod = ?wsGLV(wsMod, WsOpts, wsEgHer), case ?wsGLV(sslOpts, WsOpts, false) of false -> {ok, _} = eNet:openTcp(ListenName, Port, LWsOpts); @@ -20,7 +21,7 @@ startWSrv(WSrvName, Port, WsOpts) -> ConSupSpec = #{ id => WSrvName, - start => {wsConSup, start_link, [WSrvName]}, + start => {wsConSup, start_link, [WSrvName, WsMod]}, restart => permanent, shutdown => infinity, type => supervisor, diff --git a/src/example/wsEgHer.erl b/src/example/wsEgHer.erl index a340570..62e2b40 100644 --- a/src/example/wsEgHer.erl +++ b/src/example/wsEgHer.erl @@ -27,7 +27,7 @@ Req :: elli:req(), _Args :: wsHer:callback_args(), Result :: wsHer:result(). -handle(Req, _Args) -> handle(Req#req.method, wsReq:path(Req), Req). +handle(Req, _Args) -> handle(Req#wsReq.method, wsReq:path(Req), Req). %% @doc Route `Method' and `Path' to the appropriate clause. @@ -63,7 +63,7 @@ handle(Req, _Args) -> handle(Req#req.method, wsReq:path(Req), Req). %% @see elli_request:chunk_ref/1 %% @see chunk_loop/1 -spec handle(Method, Path, Req) -> wsHer:result() when - Method :: elli:http_method(), + Method :: elli:method(), Path :: [binary()], Req :: elli:req(). handle('GET', [<<"hello">>, <<"world">>], _Req) -> diff --git a/src/test/elli_test.erl b/src/test/elli_test.erl index 8c4166f..7b726e9 100644 --- a/src/test/elli_test.erl +++ b/src/test/elli_test.erl @@ -13,7 +13,7 @@ -export([call/5]). -spec call(Method, Path, Headers, Body, Opts) -> wsHer:result() when - Method :: elli:http_method(), + Method :: elli:method(), Path :: binary(), Headers :: elli:headers(), Body :: elli:body(), diff --git a/src/test/elli_tests.erl b/src/test/elli_tests.erl index ea9238b..ad49a9d 100644 --- a/src/test/elli_tests.erl +++ b/src/test/elli_tests.erl @@ -112,13 +112,13 @@ accessors_test_() -> Method = 'POST', Body = <<"name=knut%3D">>, Name = <<"knut=">>, - Req1 = #req{raw_path = RawPath, + Req1 = #wsReq{raw_path = RawPath, original_headers = Headers, headers = Headers, method = Method, body = Body}, Args = [{<<"name">>, Name}], - Req2 = #req{original_headers = Headers, headers = Headers, args = Args, body = <<>>}, + Req2 = #wsReq{original_headers = Headers, headers = Headers, args = Args, body = <<>>}, [ %% POST /foo/bar @@ -138,7 +138,7 @@ accessors_test_() -> ?_assertMatch(Name, wsReq:get_arg_decoded(<<"name">>, Req2)), ?_assertMatch([], wsReq:post_args(Req2)), - ?_assertMatch({error, not_supported}, wsReq:chunk_ref(#req{})) + ?_assertMatch({error, not_supported}, wsReq:chunk_ref(#wsReq{})) ]. @@ -628,12 +628,12 @@ body_qs_test() -> {<<"found">>, true}], Body = <<"foo=bar&baz=bang&found">>, Headers = [{<<"content-type">>, <<"application/x-www-form-urlencoded">>}], - ?assertMatch(Expected, wsReq:body_qs(#req{body = Body, + ?assertMatch(Expected, wsReq:body_qs(#wsReq{body = Body, original_headers = Headers, headers = Headers})). to_proplist_test() -> - Req = #req{method = 'GET', + Req = #wsReq{method = 'GET', path = [<<"crash">>], args = [], version = {1, 1}, @@ -662,12 +662,12 @@ to_proplist_test() -> ?assertEqual(Prop, wsReq:to_proplist(Req)). is_request_test() -> - ?assert(wsReq:is_request(#req{})), + ?assert(wsReq:is_request(#wsReq{})), ?assertNot(wsReq:is_request({req, foobar})). query_str_test_() -> - MakeReq = fun(Path) -> #req{raw_path = Path} end, + MakeReq = fun(Path) -> #wsReq{raw_path = Path} end, [ %% For empty query strings, expect `query_str` to return an empty binary. ?_assertMatch(<<>>, wsReq:query_str(MakeReq(<<"/foo">>))), @@ -679,11 +679,11 @@ query_str_test_() -> get_range_test_() -> - Req = #req{headers = [{<<"range">>, + Req = #wsReq{headers = [{<<"range">>, <<"bytes=0-99 ,500-999 , -800">>}]}, - OffsetReq = #req{headers = [{<<"range">>, <<"bytes=200-">>}]}, - UndefReq = #req{headers = []}, - BadReq = #req{headers = [{<<"range">>, <<"bytes=--99,hallo-world">>}]}, + OffsetReq = #wsReq{headers = [{<<"range">>, <<"bytes=200-">>}]}, + UndefReq = #wsReq{headers = []}, + BadReq = #wsReq{headers = [{<<"range">>, <<"bytes=--99,hallo-world">>}]}, ByteRangeSet = [{bytes, 0, 99}, {bytes, 500, 999}, {suffix, 800}], diff --git a/src/test/wsTest.erl b/src/test/wsTest.erl new file mode 100644 index 0000000..3f81bf4 --- /dev/null +++ b/src/test/wsTest.erl @@ -0,0 +1,56 @@ +-module(wsTest). + +-compile([export_all]). + +-define(Line, <<"GET /test/tttt HTTP/1.1\r\n">>). + +test1(Rn) -> + parseRequestLine(?Line, Rn). + +test2() -> + erlang:decode_packet(http_bin, ?Line, []). + +parseRequestLine(Data, Rn) -> + case binary:split(Data, Rn) of + [Data] -> + not_enough_data; + [Line, Rest] -> + case binary:split(Line, <<" ">>, [global]) of + [Method, RawPath, V] -> + {Method, RawPath, V, Rest}; + _ -> + {error, request_line} + end + end. + + +%% <<"Content-Type: application/json; charset=utf-8">>, +-spec request(boolean(), body(), method(), host(), binary(), path(), headers()) -> iolist(). +request(true, undefined, Method, Host, _DbName, Path, Headers) -> + [ + Method, <<"/_db/_system">>, Path, <<" HTTP/1.1\r\nHost: ">>, Host, + <<"\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 0\r\n">>, + spellHeaders(Headers), <<"\r\n">> + ]; +request(false, undefined, Method, Host, DbName, Path, Headers) -> + [ + Method, DbName, Path, <<" HTTP/1.1\r\nHost: ">>, Host, + <<"\r\nContent-Type: application/json; charset=utf-8\r\nContent-Length: 0\r\n">>, + spellHeaders(Headers), <<"\r\n">> + ]; +request(false, Body, Method, Host, DbName, Path, Headers) -> + ContentLength = integer_to_binary(iolist_size(Body)), + NewHeaders = [{<<"Content-Length">>, ContentLength} | Headers], + [ + Method, DbName, Path, <<" HTTP/1.1\r\nHost: ">>, Host, + <<"\r\nContent-Type: application/json; charset=utf-8\r\n">>, + spellHeaders(NewHeaders), <<"\r\n">>, Body + ]; +request(true, Body, Method, Host, _DbName, Path, Headers) -> + ContentLength = integer_to_binary(iolist_size(Body)), + NewHeaders = [{<<"Content-Length">>, ContentLength} | Headers], + [ + Method, <<"/_db/_system">>, Path, <<" HTTP/1.1\r\nHost: ">>, Host, + <<"\r\nContent-Type: application/json; charset=utf-8\r\n">>, + spellHeaders(NewHeaders), <<"\r\n">>, Body + ]. \ No newline at end of file diff --git a/src/wsSrv/wsConSup.erl b/src/wsSrv/wsConSup.erl index 957a0f7..bfc66d7 100644 --- a/src/wsSrv/wsConSup.erl +++ b/src/wsSrv/wsConSup.erl @@ -3,23 +3,23 @@ -behaviour(supervisor). -export([ - start_link/1 + start_link/2 ]). -export([ init/1 ]). --spec(start_link(SupName :: atom()) -> {ok, pid()}). -start_link(SupName) -> - supervisor:start_link({local, SupName}, ?MODULE, undefined). +-spec(start_link(SupName :: atom(), WsMod :: module()) -> {ok, pid()}). +start_link(SupName, WsMod) -> + supervisor:start_link({local, SupName}, ?MODULE, WsMod). -init(_Args) -> +init(WsMod) -> SupFlags = #{strategy => simple_one_for_one, intensity => 100, period => 3600}, WsHttpSpec = #{ id => wsHttp, - start => {wsHttp, start_link, []}, + start => {wsHttp, start_link, [WsMod]}, restart => transient, shutdown => 3000, type => worker, diff --git a/src/wsSrv/wsHttp.erl b/src/wsSrv/wsHttp.erl index 6f0a164..46b9984 100644 --- a/src/wsSrv/wsHttp.erl +++ b/src/wsSrv/wsHttp.erl @@ -1,51 +1,128 @@ -%% @doc: Elli HTTP request implementation -%% -%% An elli_http process blocks in elli_tcp:accept/2 until a client -%% connects. It then handles requests on that connection until it's -%% closed either by the client timing out or explicitly by the user. -module(wsHttp). + +-behavior(gen_srv). + +-include_lib("eNet/include/eNet.hrl"). -include("eWSrv.hrl"). -include("wsCom.hrl"). +-export([ + start_link/1 + , send_response/4 + , send_file/5 + , mk_req/8 + , mk_req/11 + + %% Exported for looping with a fully-qualified module name + , accept/4 + , handle_request/4 + , chunk_loop/1 + , split_args/1 + , parse_path/1 + , keepalive_loop/3 + , keepalive_loop/5 + , close_or_keepalive/2 +]). + +%% eNet callback +-export([newCon/2]). + +-export([ + init/1 + , handleCall/3 + , handleCast/2 + , handleInfo/2 + , terminate/2 + , code_change/3 +]). + +-define(SERVER, ?MODULE). + +-record(state, { + stage = reqLine :: reqLine | header | body | done | error %% 接受http请求可能会有多个包 分四个阶接收 + , buffer = <<>> :: binary() + , wsReq :: undefined | #wsReq{} + , contentLength :: undefined | non_neg_integer() | chunked + , wsMod :: callback() + , socket :: undefined | wsNet:socket() + , isSsl = false :: boolean() +}). + +newCon(_Sock, SupPid) -> + supervisor:start_link(SupPid, []). + +%% ******************************************** API ******************************************************************* +start_link(WsMod) -> + gen_srv:start_link(?MODULE, WsMod, []). + +%% ******************************************** callback ************************************************************** +init(WsMod) -> + {ok, #state{wsMod = WsMod}}. + +handleCall(_Msg, _State, _FROM) -> + ?wsErr("~p call receive unexpect msg ~p ~n ", [?MODULE, _Msg]), + {reply, ok}. + +%% 默认匹配 +handleCast(_Msg, _State) -> + ?wsErr("~p cast receive unexpect msg ~p ~n ", [?MODULE, _Msg]), + kpS. + +handleInfo({tcp, Socket, Data}, #state{stage = Stage, buffer = Buffer} = State) -> + ok; + +handleInfo({tcp_closed, Socket}, _State) -> + ok; + +handleInfo({tcp_error, Socket, Reason}, _State) -> + ok; + +handleInfo({ssl, Socket, Data}, _State) -> + ok; + +handleInfo({ssl_closed, Socket}, _State) -> + ok; + +handleInfo({ssl_error, Socket, Reason}, _State) -> + ok; + + + +handleInfo({ssl, Socket, Data}, _State) -> + ok; +handleInfo({?mSockReady, Sock}, _State) -> + inet:setopts(Sock, [{packet, raw}, {active, true}]), + {ok, #state{socket = Sock}}; +handleInfo({?mSockReady, Sock, SslOpts, SslHSTet}, State) -> + case ntSslAcceptor:handshake(Sock, SslOpts, SslHSTet) of + {ok, SslSock} -> + ssl:setopts(Sock, [{packet, raw}, {active, true}]), + {ok, State#state{socket = SslSock, isSsl = true}}; + _Err -> + ?wsErr("ssl handshake error ~p~n", [_Err]), + {stop, _Err, State} + end; +handleInfo(_Msg, _State) -> + ?wsErr("~p info receive unexpect msg ~p ~n ", [?MODULE, _Msg]), + kpS. + +terminate(_Reason, _State) -> + ok. + +code_change(_OldVsn, State, _Extra) -> + {ok, State}. -%% API --export([start_link/4]). --export([send_response/4]). --export([send_file/5]). --export([mk_req/8, mk_req/11]). %% useful when testing. -%% Exported for looping with a fully-qualified module name --export([accept/4, handle_request/4, chunk_loop/1, split_args/1, - parse_path/1, keepalive_loop/3, keepalive_loop/5]). -%% Exported for correctly handling session keep-alive for handlers -%% operating in handler mode. --export([close_or_keepalive/2]). --export_type([version/0]). -%% @type version(). HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}'. --type version() :: {0, 9} | {1, 0} | {1, 1}. --define(CONTENT_LENGTH_HEADER, <<"content-length">>). --define(EXPECT_HEADER, <<"expect">>). --define(CONNECTION_HEADER, <<"connection">>). --define(TRANSFER_ENCODING_HEADER, <<"Transfer-Encoding">>). -%% TODO: use this -%% -type connection_token() :: keep_alive | close. --spec start_link(Server, ListenSocket, Options, Callback) -> pid() when - Server :: pid(), - ListenSocket :: wsNet:socket(), - Options :: proplists:proplist(), - Callback :: wsHer:callback(). -start_link(Server, ListenSocket, Options, Callback) -> - proc_lib:spawn_link(?MODULE, accept, [Server, ListenSocket, Options, Callback]). %% @doc Accept on the socket until a client connects. %% Handle the request, then loop if we're using keep alive or chunked transfer. @@ -108,7 +185,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> t(body_start), {RequestBody, B2} = get_body(S, ParsedRequestHeaders, B1, Opts, Callback), t(body_end), - Req1 = Req#req{body = RequestBody}, + Req1 = Req#wsReq{body = RequestBody}, t(user_start), Response = execute_callback(Req1), @@ -116,7 +193,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> handle_response(Req1, B2, Response); {ok, handover} -> - Req1 = Req#req{body = B1}, + Req1 = Req#wsReq{body = B1}, t(user_start), Response = Mod:handle(Req1, Args), @@ -128,7 +205,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> end. handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, Headers = [connection(Req, UserHeaders), content_length(UserHeaders, Body) @@ -145,7 +222,7 @@ handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) -> handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, ResponseHeaders = [{?TRANSFER_ENCODING_HEADER, <<"chunked">>}, connection(Req, UserHeaders) @@ -153,8 +230,8 @@ handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> send_response(Req, 200, ResponseHeaders, <<"">>), t(send_start), - Initial =:= <<"">> orelse send_chunk(Req#req.socket, Initial), - ClosingEnd = case start_chunk_loop(Req#req.socket) of + Initial =:= <<"">> orelse send_chunk(Req#wsReq.socket, Initial), + ClosingEnd = case start_chunk_loop(Req#wsReq.socket) of {error, client_closed} -> client; ok -> server end, @@ -170,15 +247,15 @@ handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, Filename, Range}) -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, ResponseHeaders = [connection(Req, UserHeaders) | UserHeaders], case wsUtil:file_size(Filename) of {error, FileError} -> handle_event(Mod, file_error, [FileError], Args), - send_server_error(Req#req.socket), - wsNet:close(Req#req.socket), + send_server_error(Req#wsReq.socket), + wsNet:close(Req#wsReq.socket), exit(normal); Size -> t(send_start), @@ -206,11 +283,7 @@ handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, t(send_end), t(request_end), - handle_event(Mod, request_complete, - [Req, ResponseCode, ResponseHeaders, <<>>, - {get_timings(), - get_sizes()}], - Args), + handle_event(Mod, request_complete, [Req, ResponseCode, ResponseHeaders, <<>>, {get_timings(), get_sizes()}], Args), {close_or_keepalive(Req, UserHeaders), Buffer} end. @@ -220,7 +293,7 @@ handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, send_response(Req, Code, Headers, UserBody) -> ResponseHeaders = assemble_response_headers(Code, Headers), - Body = case {Req#req.method, Code} of + Body = case {Req#wsReq.method, Code} of {'HEAD', _} -> <<>>; {_, 304} -> <<>>; {_, 204} -> <<>>; @@ -231,10 +304,10 @@ send_response(Req, Code, Headers, UserBody) -> Response = [ResponseHeaders, Body], - case wsNet:send(Req#req.socket, Response) of + case wsNet:send(Req#wsReq.socket, Response) of ok -> ok; {error, Closed} when Closed =:= closed orelse Closed =:= enotconn -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, handle_event(Mod, client_closed, [before_response], Args), ok end. @@ -248,23 +321,23 @@ send_response(Req, Code, Headers, UserBody) -> Headers :: elli:headers(), Filename :: file:filename(), Range :: wsUtil:range(). -send_file(#req{callback = {Mod, Args}} = Req, Code, Headers, Filename, Range) -> +send_file(#wsReq{callback = {Mod, Args}} = Req, Code, Headers, Filename, Range) -> ResponseHeaders = assemble_response_headers(Code, Headers), case file:open(Filename, [read, raw, binary]) of {ok, Fd} -> do_send_file(Fd, Range, Req, ResponseHeaders); {error, FileError} -> handle_event(Mod, file_error, [FileError], Args), - send_server_error(Req#req.socket), - wsNet:close(Req#req.socket), + send_server_error(Req#wsReq.socket), + wsNet:close(Req#wsReq.socket), exit(normal) end, ok. -do_send_file(Fd, {Offset, Length}, #req{callback = {Mod, Args}} = Req, Headers) -> - try wsNet:send(Req#req.socket, Headers) of +do_send_file(Fd, {Offset, Length}, #wsReq{callback = {Mod, Args}} = Req, Headers) -> + try wsNet:send(Req#wsReq.socket, Headers) of ok -> - case wsNet:sendfile(Fd, Req#req.socket, Offset, Length, []) of + case wsNet:sendfile(Fd, Req#wsReq.socket, Offset, Length, []) of {ok, BytesSent} -> s(file, BytesSent), ok; {error, Closed} when Closed =:= closed orelse Closed =:= enotconn -> @@ -291,7 +364,7 @@ send_rescue_response(Socket, Code, Body) -> wsNet:send(Socket, Response). %% @doc Execute the user callback, translating failure into a proper response. -execute_callback(#req{callback = {Mod, Args}} = Req) -> +execute_callback(#wsReq{callback = {Mod, Args}} = Req) -> try Mod:handle(Req, Args) of %% {ok,...{file,...}} {ok, Headers, {file, Filename}} -> @@ -588,7 +661,7 @@ do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize) do_check_max_size_x2(_, _, _, _) -> ok. -spec mk_req(Method, PathTuple, Headers, Headers, Body, V, Socket, Callback) -> Req when - Method :: elli:http_method(), + Method :: elli:method(), PathTuple :: {PathType :: atom(), RawPath :: binary()}, Headers :: elli:headers(), Body :: elli:body(), @@ -599,7 +672,7 @@ do_check_max_size_x2(_, _, _, _) -> ok. mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} = Callback) -> case parse_path(PathTuple) of {ok, {Scheme, Host, Port}, {Path, URL, URLArgs}} -> - #req{method = Method, scheme = Scheme, host = Host, + #wsReq{method = Method, scheme = Scheme, host = Host, port = Port, path = URL, args = URLArgs, version = V, raw_path = Path, original_headers = Headers, body = Body, pid = self(), socket = Socket, @@ -614,7 +687,7 @@ mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} = mk_req(Method, Scheme, Host, Port, PathTuple, Headers, ParsedHeaders, Body, V, Socket, Callback) -> Req = mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, Callback), - Req#req{scheme = Scheme, host = Host, port = Port}. + Req#wsReq{scheme = Scheme, host = Host, port = Port}. %% %% HEADERS @@ -649,18 +722,18 @@ encode_value(V) when is_integer(V) -> integer_to_binary(V); encode_value(V) when is_binary(V) -> V; encode_value(V) when is_list(V) -> list_to_binary(V). -connection_token(#req{version = {1, 1}, headers = Headers}) -> +connection_token(#wsReq{version = {1, 1}, headers = Headers}) -> case get_header(?CONNECTION_HEADER, Headers) of <<"close">> -> <<"close">>; <<"Close">> -> <<"close">>; _ -> <<"Keep-Alive">> end; -connection_token(#req{version = {1, 0}, headers = Headers}) -> +connection_token(#wsReq{version = {1, 0}, headers = Headers}) -> case get_header(?CONNECTION_HEADER, Headers) of <<"Keep-Alive">> -> <<"Keep-Alive">>; _ -> <<"close">> end; -connection_token(#req{version = {0, 9}}) -> +connection_token(#wsReq{version = {0, 9}}) -> <<"close">>. %% @doc Return the preferred session handling setting to close or keep the @@ -790,7 +863,7 @@ split_args(Qs) -> %% CALLBACK HELPERS %% -init(#req{callback = {Mod, Args}} = Req) -> +init(#wsReq{callback = {Mod, Args}} = Req) -> ?IIF(erlang:function_exported(Mod, init, 2), case Mod:init(Req, Args) of ignore -> {ok, standard}; @@ -803,8 +876,7 @@ handle_event(Mod, Name, EventArgs, ElliArgs) -> Mod:handle_event(Name, EventArgs, ElliArgs) catch EvClass:EvError:Stacktrace -> - ?wsErr("~p:handle_event/3 crashed ~p:~p~n~p", - [Mod, EvClass, EvError, Stacktrace]) + ?wsErr("~p:handle_event/3 crashed ~p:~p~n~p", [Mod, EvClass, EvError, Stacktrace]) end. %% diff --git a/src/wsSrv/wsHttpProtocol.erl b/src/wsSrv/wsHttpProtocol.erl new file mode 100644 index 0000000..1079475 --- /dev/null +++ b/src/wsSrv/wsHttpProtocol.erl @@ -0,0 +1,216 @@ +-module(wsHttpProtocol). + +-include("eWSrv.hrl"). +-include("wsCom.hrl"). + +-compile(inline). +-compile({inline_size, 128}). + +-export([ + request/2 +]). + +-spec request(undefined | recvState(), binary()) -> {ok, recvState()} | error(). +request(reqLine, Data) -> + case erlang:decode_packet(http_bin, Data, []) of + {more, _} -> + reqLine; + {ok, {http_request, Method, RawPath, Version}, Rest} -> + case byte_size(Rest) > 0 of + true -> + request(header, Rest); + _ -> + {header, Method, RawPath, Version} + end; + {ok, {http_error, ErrStr}, _} -> + {error, ErrStr}; + {ok, {http_response, _, _, _}, _} -> + {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}}; + _ -> + {ok, #recvState{stage = body, statusCode = StatusCode, headers = Headers, contentLength = ContentLength, body = Body}} + end + end; + not_enough_data -> + {ok, #recvState{stage = header, body = Data}} + 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 = <>, + case parseChunks(NewBuffer, Rn, []) of + {ok, AddBody, _Rest} -> + LastBody = <>, + {done, RecvState#recvState{stage = done, body = LastBody}}; + {not_enough_data, AddBody, Rest} -> + NewBody = <>, + {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 = <>, + 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 = <>, + 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 + true -> + {done, #recvState{stage = done, statusCode = StatusCode, headers = Headers, contentLength = 0, body = <<>>}}; + _ -> + 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}} + 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} + 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 + undefined -> + {error, invalid_chunk_size}; + 0 -> + done; + HexSize -> + parseChunkBody(Rest, HexSize) + end; + [Data] -> + not_enough_data + end. + +parseChunkBody(Data, Size) -> + case Data of + <> -> + {ok, Body, Rest}; + _ -> + not_enough_data + end. + +parseChunkSize(Bin) -> + try + binary_to_integer(Bin, 16) + catch + error:badarg -> + undefined + end. diff --git a/src/wsSrv/wsHttps.erl b/src/wsSrv/wsHttps.erl index 84b741d..bfd5dd6 100644 --- a/src/wsSrv/wsHttps.erl +++ b/src/wsSrv/wsHttps.erl @@ -25,11 +25,6 @@ %% operating in handler mode. -export([close_or_keepalive/2]). --export_type([version/0]). - -%% @type version(). HTTP version as a tuple, i.e. `{0, 9} | {1, 0} | {1, 1}'. --type version() :: {0, 9} | {1, 0} | {1, 1}. - -define(CONTENT_LENGTH_HEADER, <<"content-length">>). -define(EXPECT_HEADER, <<"expect">>). @@ -110,7 +105,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> t(body_start), {RequestBody, B2} = get_body(S, ParsedRequestHeaders, B1, Opts, Callback), t(body_end), - Req1 = Req#req{body = RequestBody}, + Req1 = Req#wsReq{body = RequestBody}, t(user_start), Response = execute_callback(Req1), @@ -118,7 +113,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> handle_response(Req1, B2, Response); {ok, handover} -> - Req1 = Req#req{body = B1}, + Req1 = Req#wsReq{body = B1}, t(user_start), Response = Mod:handle(Req1, Args), @@ -132,7 +127,7 @@ handle_request(S, PrevB, Opts, {Mod, Args} = Callback) -> end. handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, Headers = [connection(Req, UserHeaders), content_length(UserHeaders, Body) @@ -149,7 +144,7 @@ handle_response(Req, Buffer, {response, Code, UserHeaders, Body}) -> handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, ResponseHeaders = [{?TRANSFER_ENCODING_HEADER, <<"chunked">>}, connection(Req, UserHeaders) @@ -157,8 +152,8 @@ handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> send_response(Req, 200, ResponseHeaders, <<"">>), t(send_start), - Initial =:= <<"">> orelse send_chunk(Req#req.socket, Initial), - ClosingEnd = case start_chunk_loop(Req#req.socket) of + Initial =:= <<"">> orelse send_chunk(Req#wsReq.socket, Initial), + ClosingEnd = case start_chunk_loop(Req#wsReq.socket) of {error, client_closed} -> client; ok -> server end, @@ -174,15 +169,15 @@ handle_response(Req, _Buffer, {chunk, UserHeaders, Initial}) -> handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, Filename, Range}) -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, ResponseHeaders = [connection(Req, UserHeaders) | UserHeaders], case wsUtil:file_size(Filename) of {error, FileError} -> handle_event(Mod, file_error, [FileError], Args), - send_server_error(Req#req.socket), - wsNet:close(Req#req.socket), + send_server_error(Req#wsReq.socket), + wsNet:close(Req#wsReq.socket), exit(normal); Size -> t(send_start), @@ -224,7 +219,7 @@ handle_response(Req, Buffer, {file, ResponseCode, UserHeaders, send_response(Req, Code, Headers, UserBody) -> ResponseHeaders = assemble_response_headers(Code, Headers), - Body = case {Req#req.method, Code} of + Body = case {Req#wsReq.method, Code} of {'HEAD', _} -> <<>>; {_, 304} -> <<>>; {_, 204} -> <<>>; @@ -235,10 +230,10 @@ send_response(Req, Code, Headers, UserBody) -> Response = [ResponseHeaders, Body], - case wsNet:send(Req#req.socket, Response) of + case wsNet:send(Req#wsReq.socket, Response) of ok -> ok; {error, Closed} when Closed =:= closed orelse Closed =:= enotconn -> - #req{callback = {Mod, Args}} = Req, + #wsReq{callback = {Mod, Args}} = Req, handle_event(Mod, client_closed, [before_response], Args), ok end. @@ -252,23 +247,23 @@ send_response(Req, Code, Headers, UserBody) -> Headers :: elli:headers(), Filename :: file:filename(), Range :: wsUtil:range(). -send_file(#req{callback = {Mod, Args}} = Req, Code, Headers, Filename, Range) -> +send_file(#wsReq{callback = {Mod, Args}} = Req, Code, Headers, Filename, Range) -> ResponseHeaders = assemble_response_headers(Code, Headers), case file:open(Filename, [read, raw, binary]) of {ok, Fd} -> do_send_file(Fd, Range, Req, ResponseHeaders); {error, FileError} -> handle_event(Mod, file_error, [FileError], Args), - send_server_error(Req#req.socket), - wsNet:close(Req#req.socket), + send_server_error(Req#wsReq.socket), + wsNet:close(Req#wsReq.socket), exit(normal) end, ok. -do_send_file(Fd, {Offset, Length}, #req{callback = {Mod, Args}} = Req, Headers) -> - try wsNet:send(Req#req.socket, Headers) of +do_send_file(Fd, {Offset, Length}, #wsReq{callback = {Mod, Args}} = Req, Headers) -> + try wsNet:send(Req#wsReq.socket, Headers) of ok -> - case wsNet:sendfile(Fd, Req#req.socket, Offset, Length, []) of + case wsNet:sendfile(Fd, Req#wsReq.socket, Offset, Length, []) of {ok, BytesSent} -> s(file, BytesSent), ok; {error, Closed} when Closed =:= closed orelse Closed =:= enotconn -> @@ -295,7 +290,7 @@ send_rescue_response(Socket, Code, Body) -> wsNet:send(Socket, Response). %% @doc Execute the user callback, translating failure into a proper response. -execute_callback(#req{callback = {Mod, Args}} = Req) -> +execute_callback(#wsReq{callback = {Mod, Args}} = Req) -> try Mod:handle(Req, Args) of %% {ok,...{file,...}} {ok, Headers, {file, Filename}} -> @@ -593,7 +588,7 @@ do_check_max_size_x2(Socket, ContentLength, Buffer, MaxSize) do_check_max_size_x2(_, _, _, _) -> ok. -spec mk_req(Method, PathTuple, Headers, Headers, Body, V, Socket, Callback) -> Req when - Method :: elli:http_method(), + Method :: elli:method(), PathTuple :: {PathType :: atom(), RawPath :: binary()}, Headers :: elli:headers(), Body :: elli:body(), @@ -604,7 +599,7 @@ do_check_max_size_x2(_, _, _, _) -> ok. mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} = Callback) -> case parse_path(PathTuple) of {ok, {Scheme, Host, Port}, {Path, URL, URLArgs}} -> - #req{method = Method, scheme = Scheme, host = Host, + #wsReq{method = Method, scheme = Scheme, host = Host, port = Port, path = URL, args = URLArgs, version = V, raw_path = Path, original_headers = Headers, body = Body, pid = self(), socket = Socket, @@ -619,7 +614,7 @@ mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, {Mod, Args} = mk_req(Method, Scheme, Host, Port, PathTuple, Headers, ParsedHeaders, Body, V, Socket, Callback) -> Req = mk_req(Method, PathTuple, Headers, ParsedHeaders, Body, V, Socket, Callback), - Req#req{scheme = Scheme, host = Host, port = Port}. + Req#wsReq{scheme = Scheme, host = Host, port = Port}. %% %% HEADERS @@ -654,18 +649,18 @@ encode_value(V) when is_integer(V) -> integer_to_binary(V); encode_value(V) when is_binary(V) -> V; encode_value(V) when is_list(V) -> list_to_binary(V). -connection_token(#req{version = {1, 1}, headers = Headers}) -> +connection_token(#wsReq{version = {1, 1}, headers = Headers}) -> case get_header(?CONNECTION_HEADER, Headers) of <<"close">> -> <<"close">>; <<"Close">> -> <<"close">>; _ -> <<"Keep-Alive">> end; -connection_token(#req{version = {1, 0}, headers = Headers}) -> +connection_token(#wsReq{version = {1, 0}, headers = Headers}) -> case get_header(?CONNECTION_HEADER, Headers) of <<"Keep-Alive">> -> <<"Keep-Alive">>; _ -> <<"close">> end; -connection_token(#req{version = {0, 9}}) -> +connection_token(#wsReq{version = {0, 9}}) -> <<"close">>. %% @doc Return the preferred session handling setting to close or keep the @@ -795,7 +790,7 @@ split_args(Qs) -> %% CALLBACK HELPERS %% -init(#req{callback = {Mod, Args}} = Req) -> +init(#wsReq{callback = {Mod, Args}} = Req) -> ?IIF(erlang:function_exported(Mod, init, 2), case Mod:init(Req, Args) of ignore -> {ok, standard}; diff --git a/src/wsSrv/wsReq.erl b/src/wsSrv/wsReq.erl index cd4db03..7236a41 100644 --- a/src/wsSrv/wsReq.erl +++ b/src/wsSrv/wsReq.erl @@ -53,25 +53,25 @@ %% @doc Return `path' split into binary parts. -path(#req{path = Path}) -> Path. +path(#wsReq{path = Path}) -> Path. %% @doc Return the `raw_path', i.e. not split or parsed for query params. -raw_path(#req{raw_path = Path}) -> Path. +raw_path(#wsReq{raw_path = Path}) -> Path. %% @doc Return the `headers' that have had `string:casefold/1' run on each key. -headers(#req{headers = Headers}) -> Headers. +headers(#wsReq{headers = Headers}) -> Headers. %% @doc Return the original `headers'. -original_headers(#req{original_headers = Headers}) -> Headers. +original_headers(#wsReq{original_headers = Headers}) -> Headers. %% @doc Return the `method'. -method(#req{method = Method}) -> Method. +method(#wsReq{method = Method}) -> Method. %% @doc Return the `body'. -body(#req{body = Body}) -> Body. +body(#wsReq{body = Body}) -> Body. %% @doc Return the `scheme'. -scheme(#req{scheme = Scheme}) -> Scheme. +scheme(#wsReq{scheme = Scheme}) -> Scheme. %% @doc Return the `host'. -host(#req{host = Host}) -> Host. +host(#wsReq{host = Host}) -> Host. %% @doc Return the `port'. -port(#req{port = Port}) -> Port. +port(#wsReq{port = Port}) -> Port. -peer(#req{socket = Socket} = _Req) -> +peer(#wsReq{socket = Socket} = _Req) -> case wsNet:peername(Socket) of {ok, {Address, _}} -> list_to_binary(inet_parse:ntoa(Address)); @@ -79,28 +79,28 @@ peer(#req{socket = Socket} = _Req) -> undefined end. -get_header(Key, #req{headers = Headers}) -> +get_header(Key, #wsReq{headers = Headers}) -> CaseFoldedKey = string:casefold(Key), proplists:get_value(CaseFoldedKey, Headers). -get_header(Key, #req{headers = Headers}, Default) -> +get_header(Key, #wsReq{headers = Headers}, Default) -> CaseFoldedKey = string:casefold(Key), proplists:get_value(CaseFoldedKey, Headers, Default). %% @equiv get_arg(Key, Req, undefined) -get_arg(Key, #req{} = Req) -> +get_arg(Key, #wsReq{} = Req) -> get_arg(Key, Req, undefined). %% @equiv proplists:get_value(Key, Args, Default) -get_arg(Key, #req{args = Args}, Default) -> +get_arg(Key, #wsReq{args = Args}, Default) -> proplists:get_value(Key, Args, Default). %% @equiv get_arg_decoded(Key, Req, undefined) -get_arg_decoded(Key, #req{} = Req) -> +get_arg_decoded(Key, #wsReq{} = Req) -> get_arg_decoded(Key, Req, undefined). -get_arg_decoded(Key, #req{args = Args}, Default) -> +get_arg_decoded(Key, #wsReq{args = Args}, Default) -> case proplists:get_value(Key, Args) of undefined -> Default; true -> true; @@ -109,8 +109,8 @@ get_arg_decoded(Key, #req{args = Args}, Default) -> end. %% @doc Parse `application/x-www-form-urlencoded' body into a proplist. -body_qs(#req{body = <<>>}) -> []; -body_qs(#req{body = Body} = Req) -> +body_qs(#wsReq{body = <<>>}) -> []; +body_qs(#wsReq{body = Body} = Req) -> case get_header(<<"Content-Type">>, Req) of <<"application/x-www-form-urlencoded">> -> wsHttp:split_args(Body); @@ -121,17 +121,17 @@ body_qs(#req{body = Body} = Req) -> end. %% @equiv post_arg(Key, Req, undefined) -post_arg(Key, #req{} = Req) -> +post_arg(Key, #wsReq{} = Req) -> post_arg(Key, Req, undefined). -post_arg(Key, #req{} = Req, Default) -> +post_arg(Key, #wsReq{} = Req, Default) -> proplists:get_value(Key, body_qs(Req), Default). %% @equiv post_arg_decoded(Key, Req, undefined) -post_arg_decoded(Key, #req{} = Req) -> +post_arg_decoded(Key, #wsReq{} = Req) -> post_arg_decoded(Key, Req, undefined). -post_arg_decoded(Key, #req{} = Req, Default) -> +post_arg_decoded(Key, #wsReq{} = Req, Default) -> case proplists:get_value(Key, body_qs(Req)) of undefined -> Default; true -> true; @@ -144,9 +144,9 @@ post_arg_decoded(Key, #req{} = Req, Default) -> %% Both keys and values in the returned proplists will be binaries or the atom %% `true' in case no value was supplied for the query value. -spec get_args(elli:req()) -> QueryArgs :: proplists:proplist(). -get_args(#req{args = Args}) -> Args. +get_args(#wsReq{args = Args}) -> Args. -get_args_decoded(#req{args = Args}) -> +get_args_decoded(#wsReq{args = Args}) -> lists:map(fun({K, true}) -> {K, true}; ({K, V}) -> @@ -154,10 +154,10 @@ get_args_decoded(#req{args = Args}) -> end, Args). -post_args(#req{} = Req) -> +post_args(#wsReq{} = Req) -> body_qs(Req). -post_args_decoded(#req{} = Req) -> +post_args_decoded(#wsReq{} = Req) -> lists:map(fun({K, true}) -> {K, true}; ({K, V}) -> @@ -167,7 +167,7 @@ post_args_decoded(#req{} = Req) -> %% @doc Calculate the query string associated with a given `Request' %% as a binary. -spec query_str(elli:req()) -> QueryStr :: binary(). -query_str(#req{raw_path = Path}) -> +query_str(#wsReq{raw_path = Path}) -> case binary:split(Path, [<<"?">>]) of [_, Qs] -> Qs; [_] -> <<>> @@ -178,7 +178,7 @@ query_str(#req{raw_path = Path}) -> %% The result is either a `byte_range_set()' or the atom `parse_error'. %% Use {@link elli_util:normalize_range/2} to get a validated, normalized range. -spec get_range(elli:req()) -> [http_range()] | parse_error. -get_range(#req{headers = Headers}) -> +get_range(#wsReq{headers = Headers}) -> case proplists:get_value(<<"range">>, Headers) of <<"bytes=", RangeSetBin/binary>> -> parse_range_set(RangeSetBin); @@ -228,15 +228,15 @@ remove_whitespace(Bin) -> %% @doc Serialize the `Req'uest record to a proplist. %% Useful for logging. -to_proplist(#req{} = Req) -> +to_proplist(#wsReq{} = Req) -> lists:zip(record_info(fields, req), tl(tuple_to_list(Req))). %% @doc Return a reference that can be used to send chunks to the client. %% If the protocol does not support it, return `{error, not_supported}'. -chunk_ref(#req{version = {1, 1}} = Req) -> - Req#req.pid; -chunk_ref(#req{}) -> +chunk_ref(#wsReq{version = {1, 1}} = Req) -> + Req#wsReq.pid; +chunk_ref(#wsReq{}) -> {error, not_supported}. @@ -274,7 +274,7 @@ is_ref_alive(Ref) -> is_process_alive(Ref), rpc:call(node(Ref), erlang, is_process_alive, [Ref])). -is_request(#req{}) -> true; +is_request(#wsReq{}) -> true; is_request(_) -> false. uri_decode(Bin) ->