Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 

520 rindas
18 KiB

-module(wsHttp).
-include_lib("eNet/include/eNet.hrl").
-include("wsCom.hrl").
-export([
start_link/1
, sendResponse/5
, sendFile/5
%% Exported for looping with a fully-qualified module name
, chunkLoop/1
, spellHeaders/1
, splitArgs/1
, closeOrKeepAlive/2
, maybeSendContinue/2
]).
%% eNet callback
-export([newCon/2]).
-export([
init_it/2
, system_code_change/4
, system_continue/3
, system_get_state/1
, system_terminate/4
]).
newCon(_Sock, WsMod) ->
?MODULE:start_link(WsMod).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% genActor start %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-spec(start_link(atom()) -> {ok, pid()} | ignore | {error, term()}).
start_link(WsMod) ->
proc_lib:start_link(?MODULE, init_it, [self(), WsMod], infinity, []).
init_it(Parent, Args) ->
process_flag(trap_exit, true),
modInit(Parent, Args).
-spec system_code_change(term(), module(), undefined | term(), term()) -> {ok, term()}.
system_code_change(State, _Module, _OldVsn, _Extra) ->
{ok, State}.
-spec system_continue(pid(), [], {module(), atom(), pid(), term()}) -> ok.
system_continue(_Parent, _Debug, {Parent, State}) ->
loop(Parent, State).
-spec system_get_state(term()) -> {ok, term()}.
system_get_state(State) ->
{ok, State}.
-spec system_terminate(term(), pid(), [], term()) -> none().
system_terminate(Reason, _Parent, _Debug, State) ->
terminate(Reason, State).
modInit(Parent, Args) ->
case init(Args) of
{ok, State} ->
proc_lib:init_ack(Parent, {ok, self()}),
loop(Parent, State);
{stop, Reason} ->
proc_lib:init_ack(Parent, {error, Reason}),
exit(Reason)
end.
loop(Parent, State) ->
receive
{system, From, Request} ->
sys:handle_system_msg(Request, From, Parent, ?MODULE, [], {Parent, State});
{'EXIT', Parent, Reason} ->
terminate(Reason, State);
Msg ->
case handleMsg(Msg, State) of
kpS ->
loop(Parent, State);
{ok, NewState} ->
loop(Parent, NewState);
{stop, Reason} ->
terminate(Reason, State),
exit(Reason)
end
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% genActor end %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% ************************************************ API ***************************************************************
init(WsMod) ->
{ok, #wsState{wsMod = WsMod}}.
handleMsg({tcp, _Socket, Data}, State) ->
#wsState{stage = Stage, buffer = Buffer, socket = Socket} = State,
case wsHttpProtocol:request(Stage, <<Buffer/binary, Data/binary>>, State) of
{ok, _NewState} = LRet ->
LRet;
{done, NewState} ->
Response = doHandle(NewState),
#wsState{buffer = Buffer, socket = Socket, temHeader = TemHeader, method = Method} = NewState,
case doResponse(Response, Socket, TemHeader, Method) of
keep_alive ->
handleMsg({tcp, _Socket, Buffer}, newWsState(NewState));
close ->
{stop, close}
end;
Err ->
?wsErr("recv the http data error ~p~n", [Err]),
sendBadRequest(Socket),
{stop, close}
end;
handleMsg({tcp_closed, _Socket}, _State) ->
{stop, tcp_closed};
handleMsg({tcp_error, _Socket, Reason}, _State) ->
?wsErr("the http tcp socket error ~p~n", [Reason]),
{stop, tcp_error};
handleMsg({ssl, _Socket, Data}, State) ->
#wsState{stage = Stage, buffer = Buffer, socket = Socket} = State,
case wsHttpProtocol:request(Stage, <<Buffer/binary, Data/binary>>, State) of
{ok, _NewState} = LRet ->
LRet;
{done, NewState} ->
Response = doHandle(NewState),
#wsState{buffer = Buffer, temHeader = TemHeader, method = Method} = NewState,
case doResponse(Response, Socket, TemHeader, Method) of
keep_alive ->
handleMsg({tcp, Socket, Buffer}, newWsState(NewState));
close ->
{stop, close}
end;
Err ->
?wsErr("recv the http data error ~p~n", [Err]),
sendBadRequest(Socket),
{stop, close}
end;
handleMsg({ssl_closed, _Socket}, _State) ->
{stop, ssl_closed};
handleMsg({ssl_error, _Socket, Reason}, _State) ->
?wsErr("the http ssl socket error ~p~n", [Reason]),
{stop, ssl_error};
handleMsg({?mSockReady, Sock}, _State) ->
inet:setopts(Sock, [{packet, raw}, {active, true}]),
{ok, #wsState{socket = Sock}};
handleMsg({?mSockReady, Sock, SslOpts, SslHSTet}, State) ->
case wsSslAcceptor:handshake(Sock, SslOpts, SslHSTet) of
{ok, SslSock} ->
ssl:setopts(Sock, [{packet, raw}, {active, true}]),
{ok, State#wsState{socket = SslSock, isSsl = true}};
_Err ->
?wsErr("ssl handshake error ~p~n", [_Err]),
{stop, handshake_error}
end;
handleMsg(_Msg, _State) ->
?wsErr("~p info receive unexpect msg ~p ~n ", [?MODULE, _Msg]),
kpS.
terminate(_Reason, #wsState{socket = Socket} = _State) ->
wsNet:close(Socket),
ok.
newWsState(WsState) ->
WsState#wsState{
stage = reqLine
, buffer = <<>>
, wsReq = undefined
, headerCnt = 0
, temHeader = []
, contentLength = undefined
, temChunked = <<>>
}.
%% @doc Execute the user callback, translating failure into a proper response.
doHandle(State) ->
#wsState{wsMod = WsMod, method = Method, path = Path, wsReq = WsReq} = State,
try WsMod:handle(Method, Path, WsReq) of
%% {ok,...{file,...}}
{ok, Headers, {file, Filename}} ->
{file, 200, Headers, Filename, []};
{ok, Headers, {file, Filename, Range}} ->
{file, 200, Headers, Filename, Range};
%% ok simple
{ok, Headers, Body} -> {response, 200, Headers, Body};
{ok, Body} -> {response, 200, [], Body};
%% Chunk
{chunk, Headers} -> {chunk, Headers, <<"">>};
{chunk, Headers, Initial} -> {chunk, Headers, Initial};
%% File
{HttpCode, Headers, {file, Filename}} ->
{file, HttpCode, Headers, Filename, {0, 0}};
{HttpCode, Headers, {file, Filename, Range}} ->
{file, HttpCode, Headers, Filename, Range};
%% Simple
{HttpCode, Headers, Body} -> {response, HttpCode, Headers, Body};
{HttpCode, Body} -> {response, HttpCode, [], Body};
%% Unexpected
Unexpected ->
?wsErr("handle return error ~p ~p~n", [WsReq, Unexpected]),
{response, 500, [], <<"Internal server error">>}
catch
throw:{ResponseCode, Headers, Body} when is_integer(ResponseCode) ->
{response, ResponseCode, Headers, Body};
throw:Exc:Stacktrace ->
?wsErr("handle catch throw ~p ~p ~p~n", [WsReq, Exc, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
error:Error:Stacktrace ->
?wsErr("handle catch error ~p ~p ~p~n", [WsReq, Error, Stacktrace]),
{response, 500, [], <<"Internal server error">>};
exit:Exit:Stacktrace ->
?wsErr("handle catch exit ~p ~p ~p~n", [WsReq, Exit, Stacktrace]),
{response, 500, [], <<"Internal server error">>}
end.
doResponse({response, Code, UserHeaders, Body}, Socket, TemHeader, Method) ->
Headers = [connection(UserHeaders, TemHeader), contentLength(UserHeaders, Body) | UserHeaders],
sendResponse(Socket, Method, Code, Headers, Body),
closeOrKeepAlive(UserHeaders, TemHeader);
doResponse({chunk, UserHeaders, Initial}, Socket, TemHeader, Method) ->
ResponseHeaders = [transferEncoding(UserHeaders), connection(UserHeaders, TemHeader) | UserHeaders],
sendResponse(Socket, Method, 200, ResponseHeaders, <<>>),
Initial =:= <<"">> orelse sendChunk(Socket, Initial),
case startChunkLoop(Socket) of
{error, client_closed} -> client;
ok -> server
end,
close;
doResponse({file, ResponseCode, UserHeaders, Filename, Range}, Socket, TemHeader, Method) ->
ResponseHeaders = [connection(UserHeaders, TemHeader) | UserHeaders],
case wsUtil:fileSize(Filename) of
{error, _FileError} ->
sendSrvError(Socket),
{stop, file_error};
Size ->
Ret =
case wsUtil:normalizeRange(Range, Size) of
undefined ->
sendFile(Socket, ResponseCode, [{?CONTENT_LENGTH_HEADER, Size} | ResponseHeaders], Filename, {0, 0});
{Offset, Length} ->
ERange = wsUtil:encodeRange({Offset, Length}, Size),
sendFile(Socket, 206, lists:append(ResponseHeaders, [{?CONTENT_LENGTH_HEADER, Length}, {<<"Content-Range">>, ERange}]), Filename, {Offset, Length});
invalid_range ->
ERange = wsUtil:encodeRange(invalid_range, Size),
sendResponse(Socket, Method, 416, lists:append(ResponseHeaders, [{<<"Content-Length">>, 0}, {<<"Content-Range">>, ERange}]), <<>>),
{error, range}
end,
case Ret of
ok ->
closeOrKeepAlive(UserHeaders, TemHeader);
{error, Reason} ->
{stop, Reason}
end
end.
%% @doc Generate a HTTP response and send it to the client.
sendResponse(Socket, Method, Code, Headers, UserBody) ->
Body =
case Method of
'HEAD' ->
<<>>;
_ ->
case Code of
304 ->
<<>>;
204 ->
<<>>;
_ ->
UserBody
end
end,
Response = httpResponse(Code, Headers, Body),
case wsNet:send(Socket, Response) of
ok ->
ok;
_Err ->
?wsErr("send_response error ~p~n", [_Err])
end.
%% @doc Send a HTTP response to the client where the body is the
%% contents of the given file. Assumes correctly set response code
%% and headers.
-spec sendFile(Req, Code, Headers, Filename, Range) -> ok when
Req :: elli:wsReq(),
Code :: elli:httpCode(),
Headers :: elli:headers(),
Filename :: file:filename(),
Range :: wsUtil:range().
sendFile(Socket, Code, Headers, Filename, Range) ->
ResponseHeaders = httpResponse(Code, Headers, <<>>),
case file:open(Filename, [read, raw, binary]) of
{ok, Fd} -> doSendFile(Fd, Range, Socket, ResponseHeaders);
{error, _FileError} = Err ->
sendSrvError(Socket),
Err
end.
doSendFile(Fd, {Offset, Length}, Socket, Headers) ->
try wsNet:send(Socket, Headers) of
ok ->
case wsNet:sendfile(Fd, Socket, Offset, Length, []) of
{ok, _BytesSent} -> ok;
{error, Closed} = LErr when Closed =:= closed orelse Closed =:= enotconn ->
?wsErr("send file error"),
LErr
end;
{error, Closed} = LErr when Closed =:= closed orelse Closed =:= enotconn ->
?wsErr("send file error"),
LErr
after
file:close(Fd)
end.
%% @doc To send a response, we must first have received everything the
%% client is sending. If this is not the case, {@link send_bad_request/1}
%% might reset the client connection.
sendBadRequest(Socket) ->
sendRescueResponse(Socket, 400, <<"Bad Request">>).
sendSrvError(Socket) ->
sendRescueResponse(Socket, 500, <<"Server Error">>).
sendRescueResponse(Socket, Code, Body) ->
Response = httpResponse(Code, Body),
wsNet:send(Socket, Response).
%% CHUNKED-TRANSFER
%% @doc The chunk loop is an intermediary between the socket and the
%% user. We forward anything the user sends until the user sends an
%% empty response, which signals that the connection should be
%% closed. When the client closes the socket, the loop exits.
startChunkLoop(Socket) ->
%% Set the socket to active so we receive the tcp_closed message
%% if the client closes the connection
wsNet:setopts(Socket, [{active, once}]),
?MODULE:chunkLoop(Socket).
chunkLoop(Socket) ->
receive
{tcp_closed, Socket} ->
{error, client_closed};
{chunk, close} ->
case wsNet:send(Socket, <<"0\r\n\r\n">>) of
ok ->
ok;
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
{error, client_closed}
end;
{chunk, close, From} ->
case wsNet:send(Socket, <<"0\r\n\r\n">>) of
ok ->
From ! {self(), ok},
ok;
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
From ! {self(), {error, closed}},
ok
end;
{chunk, Data} ->
sendChunk(Socket, Data),
?MODULE:chunkLoop(Socket);
{chunk, Data, From} ->
case sendChunk(Socket, Data) of
ok ->
From ! {self(), ok};
{error, Closed} when Closed =:= closed orelse Closed =:= enotconn ->
From ! {self(), {error, closed}}
end,
?MODULE:chunkLoop(Socket)
after 10000 ->
?MODULE:chunkLoop(Socket)
end.
sendChunk(Socket, Data) ->
case iolist_size(Data) of
0 -> ok;
Size ->
Response = [integer_to_binary(Size, 16), <<"\r\n">>, Data, <<"\r\n">>],
wsNet:send(Socket, Response)
end.
maybeSendContinue(Socket, Headers) ->
% According to RFC2616 section 8.2.3 an origin server must respond with
% either a "100 Continue" or a final response code when the client
% headers contains "Expect:100-continue"
case lists:keyfind(?EXPECT_HEADER, 1, Headers) of
<<"100-continue">> ->
Response = httpResponse(100),
wsNet:send(Socket, Response);
_Other ->
ok
end.
httpResponse(Code) ->
httpResponse(Code, <<>>).
httpResponse(Code, Body) ->
httpResponse(Code, [{?CONTENT_LENGTH_HEADER, size(Body)}], Body).
httpResponse(Code, Headers, Body) ->
[<<"HTTP/1.1 ">>, status(Code), <<"\r\n">>, spellHeaders(Headers), <<"\r\n">>, Body].
spellHeaders(Headers) ->
<<<<(toBinStr(Key))/binary, ": ", (toBinStr(Value))/binary, "\r\n">> || {Key, Value} <- Headers>>.
-spec splitArgs(binary()) -> list({binary(), binary() | true}).
splitArgs(<<>>) -> [];
splitArgs(Qs) ->
Tokens = binary:split(Qs, <<"&">>, [global, trim]),
[case binary:split(Token, <<"=">>) of [Token] -> {Token, true}; [Name, Value] ->
{Name, Value} end || Token <- Tokens].
toBinStr(V) when is_integer(V) -> integer_to_binary(V);
toBinStr(V) when is_binary(V) -> V;
toBinStr(V) when is_list(V) -> list_to_binary(V);
toBinStr(V) when is_atom(V) -> atom_to_binary(V).
closeOrKeepAlive(UserHeaders, ReqHeader) ->
case lists:keyfind(?CONNECTION_HEADER, 1, UserHeaders) of
{_, <<"Close">>} ->
close;
{_, <<"close">>} ->
close;
_ ->
case lists:keyfind(?CONNECTION_HEADER, 1, ReqHeader) of
{_, <<"Close">>} ->
close;
{_, <<"close">>} ->
close;
_ ->
keep_alive
end
end.
connection(UserHeaders, ReqHeader) ->
case lists:keyfind(?CONNECTION_HEADER, 1, UserHeaders) of
false ->
case lists:keyfind(?CONNECTION_HEADER, 1, ReqHeader) of
false ->
{?CONNECTION_HEADER, <<"Keep-Alive">>};
Tuple ->
Tuple
end;
_ ->
[]
end.
contentLength(Headers, Body) ->
case lists:keyfind(?CONTENT_LENGTH_HEADER, Headers) of
false ->
{?CONTENT_LENGTH_HEADER, iolist_size(Body)};
_ ->
[]
end.
transferEncoding(Headers) ->
case lists:keyfind(?TRANSFER_ENCODING_HEADER, Headers) of
false ->
{?TRANSFER_ENCODING_HEADER, <<"chunked">>};
_ ->
[]
end.
%% HTTP STATUS CODES
status(100) -> <<"100 Continue">>;
status(101) -> <<"101 Switching Protocols">>;
status(102) -> <<"102 Processing">>;
status(200) -> <<"200 OK">>;
status(201) -> <<"201 Created">>;
status(202) -> <<"202 Accepted">>;
status(203) -> <<"203 Non-Authoritative Information">>;
status(204) -> <<"204 No Content">>;
status(205) -> <<"205 Reset Content">>;
status(206) -> <<"206 Partial Content">>;
status(207) -> <<"207 Multi-Status">>;
status(226) -> <<"226 IM Used">>;
status(300) -> <<"300 Multiple Choices">>;
status(301) -> <<"301 Moved Permanently">>;
status(302) -> <<"302 Found">>;
status(303) -> <<"303 See Other">>;
status(304) -> <<"304 Not Modified">>;
status(305) -> <<"305 Use Proxy">>;
status(306) -> <<"306 Switch Proxy">>;
status(307) -> <<"307 Temporary Redirect">>;
status(400) -> <<"400 Bad Request">>;
status(401) -> <<"401 Unauthorized">>;
status(402) -> <<"402 Payment Required">>;
status(403) -> <<"403 Forbidden">>;
status(404) -> <<"404 Not Found">>;
status(405) -> <<"405 Method Not Allowed">>;
status(406) -> <<"406 Not Acceptable">>;
status(407) -> <<"407 Proxy Authentication Required">>;
status(408) -> <<"408 Request Timeout">>;
status(409) -> <<"409 Conflict">>;
status(410) -> <<"410 Gone">>;
status(411) -> <<"411 Length Required">>;
status(412) -> <<"412 Precondition Failed">>;
status(413) -> <<"413 Request Entity Too Large">>;
status(414) -> <<"414 Request-URI Too Long">>;
status(415) -> <<"415 Unsupported Media Type">>;
status(416) -> <<"416 Requested Range Not Satisfiable">>;
status(417) -> <<"417 Expectation Failed">>;
status(418) -> <<"418 I'm a teapot">>;
status(422) -> <<"422 Unprocessable Entity">>;
status(423) -> <<"423 Locked">>;
status(424) -> <<"424 Failed Dependency">>;
status(425) -> <<"425 Unordered Collection">>;
status(426) -> <<"426 Upgrade Required">>;
status(428) -> <<"428 Precondition Required">>;
status(429) -> <<"429 Too Many Requests">>;
status(431) -> <<"431 Request Header Fields Too Large">>;
status(500) -> <<"500 Internal Server Error">>;
status(501) -> <<"501 Not Implemented">>;
status(502) -> <<"502 Bad Gateway">>;
status(503) -> <<"503 Service Unavailable">>;
status(504) -> <<"504 Gateway Timeout">>;
status(505) -> <<"505 HTTP Version Not Supported">>;
status(506) -> <<"506 Variant Also Negotiates">>;
status(507) -> <<"507 Insufficient Storage">>;
status(510) -> <<"510 Not Extended">>;
status(511) -> <<"511 Network Authentication Required">>;
status(I) when is_integer(I), I >= 100, I < 1000 -> <<(integer_to_binary(I))/binary, "Status">>;
status(B) when is_binary(B) -> B.