ソースを参照

ft: 代码修改

master
SisMaker 3年前
コミット
924b8f2159
3個のファイルの変更117行の追加279行の削除
  1. +109
    -18
      src/test/wsEgHer.erl
  2. +8
    -3
      src/wsSrv/wsHttpProtocol.erl
  3. +0
    -258
      src/wsSrv/wsReq.erl

+ 109
- 18
src/test/wsEgHer.erl ファイルの表示

@ -23,28 +23,28 @@ handle('GET', <<"/hello/world">>, WsReq) ->
handle('GET', <<"/hello">>, WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
%% Fetch a GET argument from the URL.
Name = wsReq:get_arg(<<"name">>, WsReq, <<"undefined">>),
Name = proplists:get_value(<<"name">>, WsReq, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>};
handle('POST', <<"hello">>, WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
%% Fetch a POST argument from the POST body.
Name = wsReq:post_arg(<<"name">>, WsReq, <<"undefined">>),
Name = proplists:get_value(<<"name">>, WsReq, <<"undefined">>),
%% Fetch and decode
City = wsReq:post_arg_decoded(<<"city">>, WsReq, <<"undefined">>),
City = proplists:get_value(<<"city">>, WsReq, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary, " of ", City/binary>>};
handle('GET', [<<"hello">>, <<"iolist">>], WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
%% Iolists will be kept as iolists all the way to the socket.
Name = wsReq:get_arg(<<"name">>, WsReq),
Name = proplists:get_value(<<"name">>, WsReq),
{ok, [], [<<"Hello ">>, Name]};
handle('GET', [<<"type">>], WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
Name = wsReq:get_arg(<<"name">>, WsReq),
Name = proplists:get_value(<<"name">>, WsReq),
%% Fetch a header.
case wsReq:get_header(<<"Accept">>, WsReq, <<"text/plain">>) of
case proplists:get_value(<<"Accept">>, WsReq, <<"text/plain">>) of
<<"text/plain">> ->
{ok, [{<<"content-type">>, <<"text/plain; charset=ISO-8859-1">>}],
<<"name: ", Name/binary>>};
@ -79,15 +79,12 @@ handle('GET', [<<"crash">>], WsReq) ->
handle('GET', [<<"decoded-hello">>], WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
%% Fetch a URI decoded GET argument from the URL.
Name = wsReq:get_arg_decoded(<<"name">>, WsReq, <<"undefined">>),
Name = proplists:get_value(<<"name">>, WsReq, <<"undefined">>),
{ok, [], <<"Hello ", Name/binary>>};
handle('GET', [<<"decoded-list">>], WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
%% Fetch a URI decoded GET argument from the URL.
[{<<"name">>, Name}, {<<"foo">>, true}] =
wsReq:get_args_decoded(WsReq),
{ok, [], <<"Hello ", Name/binary>>};
{ok, [], <<"Hello">>};
handle('GET', [<<"sendfile">>], WsReq) ->
@ -115,7 +112,7 @@ handle('GET', [<<"sendfile">>, <<"range">>], WsReq) ->
%% range with sendfile, otherwise send the entire file when
%% no range is present, or respond with a 416 if the range is invalid.
F = "README.md",
{ok, [], {file, F, wsReq:get_range(WsReq)}};
{ok, [], {file, F, get_range(WsReq)}};
handle('GET', [<<"compressed">>], WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
@ -144,8 +141,7 @@ handle('GET', [<<"chunked">>], WsReq) ->
%% close the response.
%%
%% Return immediately {chunk, Headers} to signal we want to chunk.
Ref = wsReq:chunk_ref(WsReq),
spawn(fun() -> ?MODULE:chunk_loop(Ref) end),
spawn(fun() -> ?MODULE:chunk_loop(not_support) end),
{chunk, [{<<"Content-Type">>, <<"text/event-stream">>}]};
handle('GET', [<<"shorthand">>], WsReq) ->
@ -154,7 +150,7 @@ handle('GET', [<<"shorthand">>], WsReq) ->
handle('GET', [<<"ip">>], WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
{<<"200 OK">>, wsReq:peer(WsReq)};
{<<"200 OK">>, wsNet:peername(WsReq)};
handle('GET', [<<"304">>], WsReq) ->
io:format("receive WsReq: ~p~n", [WsReq]),
@ -182,6 +178,62 @@ handle(_, _, WsReq) ->
{404, [], <<"Not Found">>}.
%% @doc Parse the `Range' header from the request.
%% 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:wsReq()) -> [http_range()] | parse_error.
get_range(#wsReq{headers = Headers}) ->
case proplists:get_value(<<"range">>, Headers) of
<<"bytes=", RangeSetBin/binary>> ->
parse_range_set(RangeSetBin);
_ -> []
end.
-spec parse_range_set(Bin :: binary()) -> [http_range()] | parse_error.
parse_range_set(<<ByteRangeSet/binary>>) ->
RangeBins = binary:split(ByteRangeSet, <<",">>, [global]),
Parsed = [parse_range(remove_whitespace(RangeBin))
|| RangeBin <- RangeBins],
case lists:member(parse_error, Parsed) of
true -> parse_error;
false -> Parsed
end.
-spec remove_whitespace(binary()) -> binary().
remove_whitespace(Bin) ->
binary:replace(Bin, <<" ">>, <<>>, [global]).
-type http_range() :: {First :: non_neg_integer(), Last :: non_neg_integer()}
| {offset, Offset :: non_neg_integer()}
| {suffix, Length :: pos_integer()}.
-spec parse_range(Bin :: binary()) -> http_range() | parse_error.
parse_range(<<$-, SuffixBin/binary>>) ->
%% suffix-byte-range
try {suffix, binary_to_integer(SuffixBin)}
catch
error:badarg -> parse_error
end;
parse_range(<<ByteRange/binary>>) ->
case binary:split(ByteRange, <<"-">>) of
%% byte-range without last-byte-pos
[FirstBytePosBin, <<>>] ->
try {offset, binary_to_integer(FirstBytePosBin)}
catch
error:badarg -> parse_error
end;
%% full byte-range
[FirstBytePosBin, LastBytePosBin] ->
try {bytes,
binary_to_integer(FirstBytePosBin),
binary_to_integer(LastBytePosBin)}
catch
error:badarg -> parse_error
end;
_ -> parse_error
end.
%% @doc Send 10 separate chunks to the client.
%% @equiv chunk_loop(Ref, 10)
chunk_loop(Ref) ->
@ -192,12 +244,51 @@ chunk_loop(Ref) ->
%% When `N == 0', call {@link elli_request:close_chunk/1.
%% elli_request:close_chunk(Ref)}.
chunk_loop(Ref, 0) ->
wsReq:close_chunk(Ref);
close_chunk(Ref);
chunk_loop(Ref, N) ->
timer:sleep(10),
case wsReq:send_chunk(Ref, [<<"chunk">>, integer_to_binary(N)]) of
case send_chunk(Ref, [<<"chunk">>, integer_to_binary(N)]) of
ok -> ok;
{error, Reason} -> ?wsErr("error in sending chunk: ~p~n", [Reason])
end,
chunk_loop(Ref, N - 1).
chunk_loop(Ref, N - 1).
%% @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(#wsReq{}) ->
% {error, not_supported}.
%% @doc Explicitly close the chunked connection.
%% Return `{error, closed}' if the client already closed the connection.
%% @equiv send_chunk(Ref, close)
close_chunk(Ref) ->
send_chunk(Ref, close).
% %% @doc Send a chunk asynchronously.
% async_send_chunk(Ref, Data) ->
% Ref ! {chunk, Data}.
%% @doc Send a chunk synchronously.
%% If the referenced process is dead, return early with `{error, closed}',
%% instead of timing out.
send_chunk(Ref, Data) ->
?IIF(is_ref_alive(Ref),
send_chunk(Ref, Data, 5000),
{error, closed}).
is_ref_alive(Ref) ->
?IIF(node(Ref) =:= node(),
is_process_alive(Ref),
rpc:call(node(Ref), erlang, is_process_alive, [Ref])).
send_chunk(Ref, Data, Timeout) ->
Ref ! {chunk, Data, self()},
receive
{Ref, ok} ->
ok;
{Ref, {error, Reason}} ->
{error, Reason}
after Timeout ->
{error, timeout}
end.

+ 8
- 3
src/wsSrv/wsHttpProtocol.erl ファイルの表示

@ -5,9 +5,7 @@
-compile(inline).
-compile({inline_size, 128}).
-export([
request/3
]).
-export([request/3]).
-spec request(Stage :: stage(), Data :: binary(), State :: #wsState{}) -> {ok, NewState :: #wsState{}} | {done, NewState :: #wsState{}} | {error, term()}.
request(reqLine, Data, State) ->
@ -64,6 +62,13 @@ request(header, Data, State) ->
_ ->
request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked})
end;
<<"Chunked">> ->
case Rn of
undefined ->
request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked, rn = binary:compile_pattern(<<"\r\n">>)});
_ ->
request(header, Rest, State#wsState{buffer = Rest, headerCnt = NewHeaderCnt, temHeader = NewTemHeader, contentLength = chunked})
end;
_ ->
{error, 'Transfer-Encoding'}
end;

+ 0
- 258
src/wsSrv/wsReq.erl ファイルの表示

@ -1,258 +0,0 @@
-module(wsReq).
-include("wsCom.hrl").
-export([
send_chunk/2
, async_send_chunk/2
, chunk_ref/1
, close_chunk/1
, query_str/1
, get_header/2
, get_header/3
, get_arg_decoded/2
, get_arg_decoded/3
, get_arg/2
, get_arg/3
, get_args/1
, get_args_decoded/1
, post_arg/2
, post_arg/3
, post_arg_decoded/2
, post_arg_decoded/3
, post_args/1
, post_args_decoded/1
, body_qs/1
, get_range/1
, to_proplist/1
, is_request/1
, uri_decode/1
]).
-export_type([http_range/0]).
-type http_range() :: {First :: non_neg_integer(), Last :: non_neg_integer()}
| {offset, Offset :: non_neg_integer()}
| {suffix, Length :: pos_integer()}.
%%
%% Helpers for working with a #req{}
%%
get_header(Key, #wsReq{headers = Headers}) ->
CaseFoldedKey = string:casefold(Key),
proplists:get_value(CaseFoldedKey, Headers).
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, #wsReq{} = Req) ->
get_arg(Key, Req, undefined).
%% @equiv proplists:get_value(Key, 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, #wsReq{} = Req) ->
get_arg_decoded(Key, Req, undefined).
get_arg_decoded(Key, #wsReq{args = Args}, Default) ->
case proplists:get_value(Key, Args) of
undefined -> Default;
true -> true;
EncodedValue ->
uri_decode(EncodedValue)
end.
%% @doc Parse `application/x-www-form-urlencoded' body into a proplist.
body_qs(#wsReq{body = <<>>}) -> [];
body_qs(#wsReq{body = Body} = Req) ->
case get_header(<<"Content-Type">>, Req) of
<<"application/x-www-form-urlencoded">> ->
wsHttp:splitArgs(Body);
<<"application/x-www-form-urlencoded;", _/binary>> -> % ; charset=...
wsHttp:splitArgs(Body);
_ ->
erlang:error(badarg)
end.
%% @equiv post_arg(Key, Req, undefined)
post_arg(Key, #wsReq{} = Req) ->
post_arg(Key, Req, undefined).
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, #wsReq{} = Req) ->
post_arg_decoded(Key, Req, undefined).
post_arg_decoded(Key, #wsReq{} = Req, Default) ->
case proplists:get_value(Key, body_qs(Req)) of
undefined -> Default;
true -> true;
EncodedValue ->
uri_decode(EncodedValue)
end.
%% @doc Return a proplist of keys and values of the original query string.
%% 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:wsReq()) -> QueryArgs :: proplists:proplist().
get_args(#wsReq{args = Args}) -> Args.
get_args_decoded(#wsReq{args = Args}) ->
lists:map(fun({K, true}) ->
{K, true};
({K, V}) ->
{K, uri_decode(V)}
end, Args).
post_args(#wsReq{} = Req) ->
body_qs(Req).
post_args_decoded(#wsReq{} = Req) ->
lists:map(fun({K, true}) ->
{K, true};
({K, V}) ->
{K, uri_decode(V)}
end, body_qs(Req)).
%% @doc Calculate the query string associated with a given `Request'
%% as a binary.
-spec query_str(elli:wsReq()) -> QueryStr :: binary().
query_str(#wsReq{path = Path}) ->
case binary:split(Path, [<<"?">>]) of
[_, Qs] -> Qs;
[_] -> <<>>
end.
%% @doc Parse the `Range' header from the request.
%% 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:wsReq()) -> [http_range()] | parse_error.
get_range(#wsReq{headers = Headers}) ->
case proplists:get_value(<<"range">>, Headers) of
<<"bytes=", RangeSetBin/binary>> ->
parse_range_set(RangeSetBin);
_ -> []
end.
-spec parse_range_set(Bin :: binary()) -> [http_range()] | parse_error.
parse_range_set(<<ByteRangeSet/binary>>) ->
RangeBins = binary:split(ByteRangeSet, <<",">>, [global]),
Parsed = [parse_range(remove_whitespace(RangeBin))
|| RangeBin <- RangeBins],
case lists:member(parse_error, Parsed) of
true -> parse_error;
false -> Parsed
end.
-spec parse_range(Bin :: binary()) -> http_range() | parse_error.
parse_range(<<$-, SuffixBin/binary>>) ->
%% suffix-byte-range
try {suffix, binary_to_integer(SuffixBin)}
catch
error:badarg -> parse_error
end;
parse_range(<<ByteRange/binary>>) ->
case binary:split(ByteRange, <<"-">>) of
%% byte-range without last-byte-pos
[FirstBytePosBin, <<>>] ->
try {offset, binary_to_integer(FirstBytePosBin)}
catch
error:badarg -> parse_error
end;
%% full byte-range
[FirstBytePosBin, LastBytePosBin] ->
try {bytes,
binary_to_integer(FirstBytePosBin),
binary_to_integer(LastBytePosBin)}
catch
error:badarg -> parse_error
end;
_ -> parse_error
end.
-spec remove_whitespace(binary()) -> binary().
remove_whitespace(Bin) ->
binary:replace(Bin, <<" ">>, <<>>, [global]).
%% @doc Serialize the `Req'uest record to a proplist.
%% Useful for logging.
to_proplist(#wsReq{} = Req) ->
lists:zip(record_info(fields, wsReq), 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(#wsReq{}) ->
{error, not_supported}.
%% @doc Explicitly close the chunked connection.
%% Return `{error, closed}' if the client already closed the connection.
%% @equiv send_chunk(Ref, close)
close_chunk(Ref) ->
send_chunk(Ref, close).
%% @doc Send a chunk asynchronously.
async_send_chunk(Ref, Data) ->
Ref ! {chunk, Data}.
%% @doc Send a chunk synchronously.
%% If the referenced process is dead, return early with `{error, closed}',
%% instead of timing out.
send_chunk(Ref, Data) ->
?IIF(is_ref_alive(Ref),
send_chunk(Ref, Data, 5000),
{error, closed}).
send_chunk(Ref, Data, Timeout) ->
Ref ! {chunk, Data, self()},
receive
{Ref, ok} ->
ok;
{Ref, {error, Reason}} ->
{error, Reason}
after Timeout ->
{error, timeout}
end.
is_ref_alive(Ref) ->
?IIF(node(Ref) =:= node(),
is_process_alive(Ref),
rpc:call(node(Ref), erlang, is_process_alive, [Ref])).
is_request(#wsReq{}) -> true;
is_request(_) -> false.
uri_decode(Bin) ->
case binary:match(Bin, [<<"+">>, <<"%">>]) of
nomatch -> Bin;
{Pos, _} ->
<<Prefix:Pos/binary, Rest/binary>> = Bin,
uri_decode(Rest, Prefix)
end.
uri_decode(<<>>, Acc) -> Acc;
uri_decode(<<$+, Rest/binary>>, Acc) ->
uri_decode(Rest, <<Acc/binary, $\s>>);
uri_decode(<<$%, H, L, Rest/binary>>, Acc) ->
uri_decode(Rest, <<Acc/binary, (hex_to_int(H)):4, (hex_to_int(L)):4>>);
uri_decode(<<C, Rest/binary>>, Acc) ->
uri_decode(Rest, <<Acc/binary, C>>).
-compile({inline, [hex_to_int/1]}).
hex_to_int(X) when X >= $0, X =< $9 -> X - $0;
hex_to_int(X) when X >= $a, X =< $f -> X - ($a - 10);
hex_to_int(X) when X >= $A, X =< $F -> X - ($A - 10).

読み込み中…
キャンセル
保存