Permalink
5130 lines (4576 sloc) 185 KB
%%%----------------------------------------------------------------------
%%% File : yaws_server.erl
%%% Author : Claes Wikstrom <klacke@hyber.org>
%%% Purpose :
%%% Created : 16 Jan 2002 by Claes Wikstrom <klacke@hyber.org>
%%%----------------------------------------------------------------------
-module(yaws_server).
-author('klacke@hyber.org').
-behaviour(gen_server).
-include("../include/yaws.hrl").
-include("../include/yaws_api.hrl").
-include("yaws_debug.hrl").
-include_lib("kernel/include/file.hrl").
-export([mappath/3, vdirpath/3]).
%% External exports
-export([start_link/1]).
-export([safe_path/1]).
%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2,
code_change/3]).
-export([status/0,
getconf/0,
stats/0,
gs_status/0,
listen_port/1,
ssi/3,ssi/5,ssi/6
]).
%% internal exports
-export([gserv/3,acceptor0/2, load_and_run/2, done_or_continue/0,
accumulate_content/1, deliver_accumulated/4, deliver_accumulated/1,
setup_dirs/1,
deliver_dyn_part/8, finish_up_dyn_file/2, gserv_loop/4
]).
%% exports for eunit usage
-export([comp_sname/2, wildcomp_salias/2]).
-export(['GET'/4,
'POST'/4,
'HEAD'/4,
'TRACE'/4,
'OPTIONS'/4,
'PUT'/4,
'DELETE'/4,
'PATCH'/4]).
-import(lists, [member/2, foreach/2, map/2,
flatten/1, reverse/1]).
-import(yaws_api, [ehtml_expand/1]).
-record(gs, {gconf,
group, %% list of #sconf{} s
ssl, %% ssl | nossl
certinfo, %% undefined | [{string(), #certinfo{}}]
l, %% listen socket
connections = 0, %% number of TCP connections opened now
sessions = 0, %% number of active HTTP sessions
reqs = 0}). %% total number of processed HTTP requests
-record(state, {gc, %% Global conf #gc{} record
pairs, %% [{GservPid, ScList}]
embedded %% true if in embedded mode, false otherwise
}).
%% undefined | mtime from #file_info
-record(certinfo, {keyfile,
certfile,
cacertfile
}).
-define(elog(X,Y), error_logger:info_msg("*elog ~p:~p: " X,
[?MODULE, ?LINE | Y])).
start_link(A) ->
gen_server:start_link({local, yaws_server}, yaws_server, A, []).
status() ->
gen_server:call(?MODULE, status, 10000).
gs_status() ->
[_|Pids] = gen_server:call(?MODULE, pids, 10000),
lists:map(
fun(P) ->
P ! {self(), status},
receive {P, Stat} -> Stat end
end, Pids).
getconf() ->
gen_server:call(?MODULE,getconf, infinity).
%% Return the configured port number from the sconf or, if the port number
%% is 0 indicating an ephemeral port, retrieve the actual port via sockname
listen_port(#sconf{}=SC) ->
try
lists:foldl(fun(#gs{group=SCs, l=Sock}, Acc) ->
case lists:member(SC, SCs) of
true ->
{ok, {_, Port}} =
case SC#sconf.ssl of
undefined ->
inet:sockname(Sock);
_ ->
ssl:sockname(Sock)
end,
%% throw the result to end the fold early
throw(Port);
false ->
Acc
end
end, [], gs_status()),
{error, not_found}
catch
throw:Port ->
Port
end.
stats() ->
{_S, Time} = status(),
Diff = calendar:time_difference(Time, calendar:local_time()),
L = [begin
SC = hd(GS#gs.group),
{SC#sconf.listen, SC#sconf.port,
GS#gs.connections, GS#gs.sessions, GS#gs.reqs}
end || GS <- gs_status()],
{Diff, L}.
l2a(L) when is_list(L) -> list_to_atom(L);
l2a(A) when is_atom(A) -> A.
%%----------------------------------------------------------------------
%% Func: init/1
%% Returns: {ok, State} |
%% {ok, State, Timeout} |
%% ignore |
%% {stop, Reason}
%%----------------------------------------------------------------------
init(Env) -> %% #env{Trace, TraceOut, Conf, RunMod, Embedded, Id}) ->
process_flag(trap_exit, true),
put(start_time, calendar:local_time()), %% for uptime
case Env#env.embedded of
false ->
Config = (catch yaws_config:load(Env)),
case Config of
{ok, Gconf, Sconfs} ->
erase(logdir),
?Debug("GC = ~s~n", [?format_record(Gconf, gconf)]),
lists:foreach(
fun(Group) ->
lists:foreach(
fun(_SC) ->
?Debug("SC = ~s~n",
[?format_record(_SC, sconf)])
end, Group)
end, Sconfs),
init2(Gconf, Sconfs, Env#env.runmod, Env#env.embedded, true);
{error, E} ->
case erase(logdir) of
undefined ->
error_logger:error_msg("Yaws: Bad conf: ~p~n",[E]),
init:stop(),
{stop, E};
Dir ->
GC = yaws_config:make_default_gconf(true,
Env#env.id),
yaws_log:setup(GC#gconf{logdir = Dir}, []),
error_logger:error_msg("Yaws: bad conf: ~s "
"terminating~n",[E]),
init:stop(),
{stop, E}
end;
EXIT ->
error_logger:format("FATAL ~p~n", [EXIT]),
erlang:error(badconf)
end;
true ->
{ok, #state{gc = undefined,
embedded = Env#env.embedded,
pairs = []}}
end.
init2(GC, Sconfs, RunMod, Embedded, FirstTime) ->
put(gc, GC),
yaws_sendfile:check_gc_flags(GC),
case GC#gconf.mnesia_dir of
MD when length(MD) > 0 ->
yaws_debug:format("loading mnesia ~p~n", [MD]),
application:set_env(mnesia,dir,MD),
mnesia:start();
_ ->
ok
end,
foreach(fun(D) ->
yaws_debug:format("Add path ~p~n", [D]),
code:add_pathz(D)
end, GC#gconf.ebin_dir),
yaws_debug:format("Running with id=~p ~n"
"~s"
"Logging to directory ~p~n",
[GC#gconf.id,
if ?gc_has_debug(GC) ->
"Running with debug checks "
"turned on (slower server) \n";
true ->
""
end,
GC#gconf.logdir]),
case Embedded of
false ->
setup_dirs(GC),
case yaws_ctl:start(GC, FirstTime) of
ok ->
ok;
{error, RSN} ->
%% Must call init stop here otherwise heart
%% will restart us
error_logger:format("Failed to start: ~s~n", [RSN]),
init:stop(),
receive nothing -> ok end
end;
true ->
ok
end,
runmod(RunMod, GC),
yaws_config:compile_and_load_src_dir(GC),
yaws_dynopts:generate(GC),
yaws_dynopts:purge_old_code(),
yaws_log:setup(GC, Sconfs),
yaws_trace:setup(GC),
L2 = lists:zf(fun(Group) -> start_group(GC, Group) end,
yaws_config:load_mime_types_module(GC, Sconfs)),
{ok, #state{gc = GC,
pairs = L2,
embedded = Embedded}}.
start_group(GC, Group) ->
FailOnBind = ?gc_fail_on_bind_err(GC),
case proc_lib:start_link(?MODULE, gserv, [self(), GC, Group]) of
{error, F, A} when FailOnBind == false ->
error_logger:error_msg(F, A),
false;
{error, F, A} ->
error_logger:error_msg(F, A),
erlang:error(badbind);
{error, Reason} when FailOnBind == false ->
error_logger:error_msg("FATAL: ~p~n", [Reason]),
false;
{error, Reason} ->
error_logger:error_msg("FATAL: ~p~n", [Reason]),
erlang:error(badbind);
{Pid, SCs} ->
{true, {Pid, SCs}};
none ->
false
end.
%%----------------------------------------------------------------------
%% Func: handle_call/3
%% Returns: {reply, Reply, State} |
%% {reply, Reply, State, Timeout} |
%% {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, Reply, State} | (terminate/2 is called)
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_call(status, _From, State) ->
Reply = {State, get(start_time)},
{reply, Reply, State};
handle_call(id, _From, State) ->
{reply, (State#state.gc)#gconf.id, State};
handle_call(pids, _From, State) -> %% for gprof
L = map(fun(X) ->element(1, X) end, State#state.pairs),
{reply, [self() | L], State};
%% This is a brutal restart of everything
handle_call({setconf, GC, Groups}, _From, State) ->
%% First off, terminate all currently running processes
Curr = map(fun(X) ->element(1, X) end, State#state.pairs),
foreach(fun(Pid) ->
gserv_stop(Pid)
end, Curr),
{ok, State2} = init2(GC, Groups, undef, State#state.embedded, false),
{reply, ok, State2};
handle_call(getconf, _From, State) ->
Groups = map(fun({_Pid, SCs}) -> SCs end, State#state.pairs),
{reply, {ok, State#state.gc, Groups}, State};
%% If cert has changed, server will stop implicitly
handle_call(check_certs, _From, State) ->
L = lists:map(fun({Pid, _SCs}) ->
Pid ! {check_cert_changed, self()},
receive {Pid, YesNo} ->
YesNo
end
end, State#state.pairs),
{reply, L, State};
handle_call({update_sconf, Pos, NewSc}, From, State) ->
case yaws_config:search_sconf(State#state.gc, NewSc, State#state.pairs) of
{Pid, OldSc, Group} ->
OldPos = string:str(Group, [OldSc]),
case (yaws_config:eq_sconfs(OldSc,NewSc) andalso OldPos == Pos) of
true ->
error_logger:info_msg("Keeping conf for ~s intact\n",
[yaws:sconf_to_srvstr(OldSc)]),
{reply, ok, State};
false ->
Pid ! {update_sconf, Pos, NewSc, OldSc, From, self()},
receive
{updated_sconf, Pid, NewSc2} ->
P2 = yaws_config:update_sconf(State#state.gc,
NewSc2, Pos,
State#state.pairs),
{noreply, State#state{pairs = P2}}
after 2000 ->
{reply, {error, "Failed to update new conf"}, State}
end
end;
false ->
{reply, {error, "No matching group"}, State}
end;
handle_call({delete_sconf, Sc}, From, State) ->
case yaws_config:search_sconf(State#state.gc, Sc, State#state.pairs) of
{Pid, OldSc, Group} when length(Group) == 1 ->
error_logger:info_msg("Terminate whole ~s virt server group \n",
[yaws:sconf_to_srvstr(OldSc)]),
gserv_stop(Pid),
NewPairs = lists:keydelete(Pid, 1, State#state.pairs),
{reply, ok, State#state{pairs = NewPairs}};
{Pid, OldSc, _Group} ->
Pid ! {delete_sconf, OldSc, From},
P2 = yaws_config:delete_sconf(State#state.gc, OldSc,
State#state.pairs),
{noreply, State#state{pairs = P2}};
false ->
{reply, {error, "No matching group"}, State}
end;
handle_call({add_sconf, Pos, Sc}, From, State) ->
case yaws_config:search_group(State#state.gc, Sc, State#state.pairs) of
[{Pid, _Group}] ->
Pid ! {add_sconf, From, Pos, Sc, self()},
receive
{added_sconf, Pid, Sc2} ->
P2 = yaws_config:update_sconf(State#state.gc,
Sc2, Pos,
State#state.pairs),
{noreply, State#state{pairs = P2}}
after 2000 ->
{reply, {error, "Failed to add new conf"}, State}
end;
[] ->
%% Need to create a new group
error_logger:info_msg("Creating new virt server ~s\n",
[yaws:sconf_to_srvstr(Sc)]),
GC = State#state.gc,
case start_group(GC, [Sc]) of
false ->
{reply, {ok, Sc}, State};
{true, {_,[Sc2]}=Pair} ->
P2 = [Pair | State#state.pairs],
{reply, {ok, Sc2}, State#state{pairs = P2}}
end
end;
handle_call({update_gconf, GC}, _From, State) ->
lists:foreach(fun({Pid, _Group}) ->
Pid ! {update_gconf, GC}
end, State#state.pairs),
%% no need to tell yaws_log, new vals must be compatible
put(gc, GC),
{reply, ok, State#state{gc = GC}}.
%%----------------------------------------------------------------------
%% Func: handle_cast/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_cast(_Msg, State) ->
{noreply, State}.
%%----------------------------------------------------------------------
%% Func: handle_info/2
%% Returns: {noreply, State} |
%% {noreply, State, Timeout} |
%% {stop, Reason, State} (terminate/2 is called)
%%----------------------------------------------------------------------
handle_info({Pid, certchanged}, State) ->
{noreply, State#state{pairs = lists:keydelete(Pid, 1, State#state.pairs)}};
handle_info({'EXIT', Pid, Reason}, State) ->
case lists:keysearch(Pid, 1, State#state.pairs) of
{value, _} ->
%% one of our gservs died
error_logger:format("yaws: FATAL gserv died ~p~n", [Reason]),
erlang:error(restartme);
false ->
ignore
end,
{noreply, State};
handle_info(_Msg, State) ->
?Debug("GOT ~p~n", [_Msg]),
{noreply, State}.
%%----------------------------------------------------------------------
%% Func: terminate/2
%% Purpose: Shutdown the server
%% Returns: any (ignored by gen_server)
%%----------------------------------------------------------------------
terminate(_Reason, State) ->
foreach(fun({Pid, _GP}) -> gserv_stop(Pid) end, State#state.pairs),
ok.
do_listen(GC, [SC0|_]=Group) ->
case SC0#sconf.ssl of
undefined ->
{nossl, undefined,
gen_tcp_listen(SC0#sconf.port, tcp_listen_opts(SC0))};
_ ->
CertInfo = lists:foldl(fun(#sconf{servername=SN, ssl=SSL}, Acc) ->
[{SN, certinfo(SSL)}|Acc]
end, [], Group),
{ssl, lists:reverse(CertInfo),
ssl_listen(SC0#sconf.port, ssl_listen_opts(GC, Group))}
end.
certinfo(SSL=#ssl{}) ->
#certinfo{
keyfile = if SSL#ssl.keyfile /= undefined ->
case file:read_file_info(SSL#ssl.keyfile) of
{ok, FI} ->
FI#file_info.mtime;
_ ->
undefined
end;
true ->
undefined
end,
certfile = if SSL#ssl.certfile /= undefined ->
case file:read_file_info(SSL#ssl.certfile) of
{ok, FI} ->
FI#file_info.mtime;
_ ->
undefined
end;
true ->
undefined
end,
cacertfile = if SSL#ssl.cacertfile /= undefined ->
case file:read_file_info(SSL#ssl.cacertfile) of
{ok, FI} ->
FI#file_info.mtime;
_ ->
undefined
end;
true ->
undefined
end
}.
gen_tcp_listen(Port, Opts) ->
?Debug("TCP Listen ~p:~p~n", [Port, Opts]),
gen_tcp:listen(Port, Opts).
ssl_listen(Port, Opts) ->
?Debug("SSL Listen ~p:~p~n", [Port, Opts]),
ssl:listen(Port, Opts).
gserv(_Top, _, []) ->
proc_lib:init_ack(none);
%% One server per IP we listen to
gserv(Top, GC, Group0) ->
process_flag(trap_exit, true),
?TC([{record, GC, gconf}]),
put(gc, GC),
put(top, Top),
Group1 = map(fun(SC) -> setup_ets(SC) end, Group0),
Group = map(fun(SC) -> start_stats(SC) end, Group1),
case do_listen(GC, Group) of
{SSLBOOL, CertInfo, {ok, Listen}} ->
lists:foreach(fun(SC) -> call_start_mod(SC) end, Group),
error_logger:info_msg(
"Yaws: Listening to ~s:~w for <~p> virtual servers:~s~n",
[inet_parse:ntoa((hd(Group))#sconf.listen),
(hd(Group))#sconf.port,
length(Group),
catch map(
fun(S) ->
io_lib:format("~n - ~s under ~s",
[yaws:sconf_to_srvstr(S),
S#sconf.docroot])
end, Group)
]),
proc_lib:init_ack({self(), Group}),
GS = #gs{gconf = GC,
group = Group,
ssl = SSLBOOL,
certinfo = CertInfo,
l = Listen},
Last = initial_acceptor(GS),
gserv_loop(GS#gs{sessions = 1}, [], 0, Last);
{_,_,Err} ->
error_logger:format("Yaws: Failed to listen ~s:~w : ~p~n",
[inet_parse:ntoa((hd(Group))#sconf.listen),
(hd(Group))#sconf.port, Err]),
proc_lib:init_ack({error, "Can't listen to socket: ~p ",[Err]}),
exit(normal)
end.
setup_dirs(GC) ->
Dir = yaws:id_dir(GC#gconf.id),
Ctl = yaws:ctl_file(GC#gconf.id),
ok = filelib:ensure_dir(Ctl),
case file:list_dir(Dir) of
{ok, LL} ->
foreach(
fun(F) ->
file:delete(filename:join([Dir, F]))
end, LL -- ["CTL"]);
{error, RSN} ->
error_logger:format("Failed to list ~p probably "
"due to permission errs: ~p",
[Dir, RSN]),
erlang:error(RSN)
end.
setup_ets(SC) ->
E = ets:new(yaws_code, [public, set]),
ets:insert(E, {num_files, 0}),
ets:insert(E, {num_bytes, 0}),
SC#sconf{ets = E}.
clear_ets_complete(SC) ->
case SC#sconf.ets of
undefined ->
setup_ets(SC);
E ->
ets:match_delete(E,'_'),
ets:insert(E, {num_files, 0}),
ets:insert(E, {num_bytes, 0}),
SC
end.
start_stats(SC) ->
case ?sc_has_statistics(SC) of
true ->
{ok, Pid} = yaws_stats:start_link(),
SC#sconf{stats = Pid};
false ->
SC
end.
stop_stats(SC) ->
case SC#sconf.stats of
undefined ->
SC;
Pid when is_pid(Pid) ->
%% Unlink the stats process before stopping it to be sure to not
%% receive the {'EXIT..} message in gserv_loop.
unlink(Pid),
yaws_stats:stop(Pid),
SC#sconf{stats = undefined}
end.
gserv_loop(GS, Ready, Rnum, Last) ->
receive
{From , status} ->
From ! {self(), GS},
?MODULE:gserv_loop(GS, Ready, Rnum, Last);
{_From, next, Accepted} when Ready == [] ->
close_accepted_if_max(GS,Accepted),
New = acceptor(GS),
GS2 = GS#gs{sessions = GS#gs.sessions + 1,
connections = GS#gs.connections + 1},
?MODULE:gserv_loop(GS2, Ready, Rnum, New);
{_From, next, Accepted} ->
close_accepted_if_max(GS,Accepted),
[{_Then, R}|RS] = Ready,
R ! {self(), accept},
GS2 = GS#gs{connections=GS#gs.connections + 1},
?MODULE:gserv_loop(GS2, RS, Rnum-1, R);
{_From, decrement} ->
GS2 = GS#gs{connections=GS#gs.connections - 1},
?MODULE:gserv_loop(GS2, Ready, Rnum, Last);
{From, done_client, Int} ->
GS2 = if
Int == 0 -> GS#gs{connections = GS#gs.connections - 1};
Int > 0 -> GS#gs{reqs = GS#gs.reqs+Int,
connections = GS#gs.connections - 1}
end,
PoolSize = (GS#gs.gconf)#gconf.acceptor_pool_size,
if
Rnum == PoolSize ->
From ! {self(), stop},
?MODULE:gserv_loop(GS2, Ready, Rnum, Last);
Rnum < PoolSize ->
%% cache this process for 10 secs
?MODULE:gserv_loop(GS2,
[{yaws:get_time_tuple(), From} | Ready],
Rnum+1, Last)
end;
{'EXIT', Pid, Reason} ->
case get(top) of
Pid when Reason /= shutdown ->
error_logger:format("Top proc died, terminate gserv",[]),
{links, Ls} = process_info(self(), links),
foreach(fun(X) -> unlink(X), exit(X, shutdown) end, Ls),
exit(noserver);
Pid ->
{links, Ls} = process_info(self(), links),
foreach(fun(X) -> unlink(X), exit(X, shutdown) end, Ls),
exit(normal);
Top when Reason == failaccept ->
error_logger:format(
"Accept proc died, terminate gserv",[]),
{links, Ls} = process_info(self(), links),
%% do not send exit signal to yaws_server process
Ls1 = Ls -- [Top],
foreach(fun(X) -> unlink(X), exit(X, shutdown) end, Ls1),
exit(noserver);
_ ->
GS2 = if (Reason == normal) orelse (Reason == shutdown) ->
%% connections already decremented
GS#gs{sessions = GS#gs.sessions - 1};
true ->
GS#gs{sessions = GS#gs.sessions - 1,
connections = GS#gs.connections - 1}
end,
if
Pid == Last ->
%% probably died due to new code loaded; if we
%% don't start a new acceptor here we end up with
%% no active acceptor
error_logger:format("Last acceptor died (~p), "
"restart it", [Reason]),
New = acceptor(GS),
?MODULE:gserv_loop(GS2, Ready, Rnum, New);
true ->
case lists:keysearch(Pid, 2, Ready) of
{value, _} ->
Ready1 = lists:keydelete(Pid, 2, Ready),
?MODULE:gserv_loop(GS2, Ready1,
Rnum-1, Last);
false ->
?MODULE:gserv_loop(GS2, Ready, Rnum, Last)
end
end
end;
{From, stop} ->
unlink(From),
%% Close the socket and stop acceptors
stop_ready(Ready, Last),
if
GS#gs.ssl == nossl -> gen_tcp:close(GS#gs.l);
GS#gs.ssl == ssl -> ssl:close(GS#gs.l)
end,
%% Stop yaws_stats processes, if needed
foreach(fun(SC) -> stop_stats(SC) end, GS#gs.group),
%% Close softly all opened connections
{links, Ls1} = process_info(self(), links),
foreach(fun(X) when is_pid(X) ->
unlink(X),
X ! {self(), suspend},
exit(X, shutdown);
(_) -> ok
end, Ls1),
WaitFun = fun(_, 0, Pids) -> Pids;
(_, _, []) -> [];
(F, Secs, Pids0) ->
timer:sleep(1000),
Pids1 = lists:filter(fun(X) when is_pid(X) ->
is_process_alive(X);
(_) ->
false
end, Pids0),
F(F, Secs-1, Pids1)
end,
Ls2 = WaitFun(WaitFun, 60, Ls1),
%% Kill all remaining connections
foreach(fun(X) -> exit(X, kill) end, Ls2),
From ! {self(), ok},
exit(normal);
%% This code will shutdown all ready procs as well as the
%% acceptor()
{update_sconf, Pos, NewSc, OldSc, From, Updater} ->
case lists:member(OldSc, GS#gs.group) of
false ->
error_logger:error_msg("gserv: No found SC ~p/~p~n",
[OldSc, GS#gs.group]),
erlang:error(nosc);
true ->
stop_stats(OldSc),
NewSc1 = start_stats(NewSc),
stop_ready(Ready, Last),
NewSc2 = clear_ets_complete(
NewSc1#sconf{ets = OldSc#sconf.ets}
),
GS2 = GS#gs{group = yaws:insert_at(
NewSc2, Pos,
lists:delete(OldSc, GS#gs.group)
)},
Ready2 = [],
Updater ! {updated_sconf, self(), NewSc2},
gen_server:reply(From, {ok, NewSc2}),
error_logger:info_msg("Updating sconf for server ~s~n",
[yaws:sconf_to_srvstr(NewSc2)]),
New = acceptor(GS2),
?MODULE:gserv_loop(GS2, Ready2, 0, New)
end;
{delete_sconf, OldSc, From} ->
case lists:member(OldSc, GS#gs.group) of
false ->
error_logger:error_msg("gserv: No found SC ~n",[]),
erlang:error(nosc);
true ->
stop_ready(Ready, Last),
GS2 = GS#gs{group = lists:delete(OldSc,GS#gs.group)},
Ready2 = [],
stop_stats(OldSc),
ets:delete(OldSc#sconf.ets),
gen_server:reply(From, ok),
error_logger:info_msg("Deleting sconf for server ~s~n",
[yaws:sconf_to_srvstr(OldSc)]),
New = acceptor(GS2),
?MODULE:gserv_loop(GS2, Ready2, 0, New)
end;
{add_sconf, From, Pos, SC0, Adder} ->
SC = start_stats(SC0),
stop_ready(Ready, Last),
SC2 = setup_ets(SC),
GS2 = GS#gs{group = yaws:insert_at(SC2, Pos, GS#gs.group)},
Ready2 = [],
Adder ! {added_sconf, self(), SC2},
gen_server:reply(From, {ok, SC2}),
error_logger:info_msg("Adding sconf for server ~s~n",
[yaws:sconf_to_srvstr(SC)]),
New = acceptor(GS2),
?MODULE:gserv_loop(GS2, Ready2, 0, New);
{check_cert_changed, From} ->
Changed =
case GS#gs.ssl of
ssl ->
CertInfo = GS#gs.certinfo,
lists:any(
fun(#sconf{servername=SN}=SC) ->
certinfo(SC#sconf.ssl) /=
proplists:get_value(SN, CertInfo)
end,
GS#gs.group
);
nossl ->
false
end,
if
Changed == false ->
From ! {self(), no},
?MODULE:gserv_loop(GS, Ready, Rnum, Last);
Changed == true ->
error_logger:info_msg(
"Stopping ~s due to cert change\n",
[yaws:sconf_to_srvstr(hd(GS#gs.group))]),
{links, Ls0} = process_info(self(), links),
Ls = Ls0 -- [get(top)],
foreach(fun(X) -> unlink(X), exit(X, shutdown) end, Ls),
From ! {self(), yes},
unlink(get(top)),
get(top) ! {self(), certchanged},
exit(normal)
end;
{update_gconf, GC} ->
stop_ready(Ready, Last),
GS2 = GS#gs{gconf = GC},
Ready2 = [],
put(gc, GC),
error_logger:info_msg("Updating gconf \n",[]),
New = acceptor(GS2),
?MODULE:gserv_loop(GS2, Ready2, 0, New)
after (10 * 1000) ->
%% collect old procs, to save memory
{NowMega, NowSecs, _} = yaws:get_time_tuple(),
R2 = lists:filter(fun({{ThenMega, ThenSecs, _}, Pid}) ->
if
NowMega > ThenMega;
(NowSecs > (ThenSecs + 8)) ->
Pid ! {self(), stop},
false;
true ->
true
end
end, Ready),
?MODULE:gserv_loop(GS, R2, length(R2), Last)
end.
stop_ready(Ready, Last) ->
error_logger:info_msg("stop_ready(~p, ~p)~n", [Ready, Last]),
unlink(Last),
exit(Last, shutdown),
lists:foreach(
fun({_,Pid}) ->
Pid ! {self(), stop}
end, Ready).
gserv_stop(Gpid) ->
case is_process_alive(Gpid) of
true ->
Gpid ! {self(), stop},
receive
{Gpid, ok} -> ok
end;
false ->
ok
end.
call_start_mod(SC) ->
case SC#sconf.start_mod of
undefined ->
ok;
Mod0 ->
Mod = l2a(Mod0),
case code:ensure_loaded(Mod) of
{module, Mod} ->
error_logger:info_msg(
"Yaws: calling start_mod: ~p:start/1~n", [Mod]),
spawn(Mod, start, [SC]);
Err ->
error_logger:format("Cannot load module ~p: ~p~n",
[Mod,Err])
end
end.
listen_opts(SC) ->
InetType = if
is_tuple( SC#sconf.listen), size( SC#sconf.listen) == 8 ->
[inet6];
true ->
[]
end,
[binary,
{ip, SC#sconf.listen},
{packet, http},
{packet_size, 16#4000},
{reuseaddr, true},
{active, false}
| proplists:get_value(listen_opts, SC#sconf.soptions, [])
] ++ InetType.
tcp_listen_opts(SC) ->
Opts = listen_opts(SC),
?Debug("tcp listen options: ~p", [Opts]),
Opts.
check_sni_servername(_, [], Default) ->
Default;
check_sni_servername(SN, [{SC,Opts}|Rest], Default) ->
case comp_sname(SN, SC#sconf.servername) of
true ->
Opts;
false ->
Res = lists:any(fun(Alias) -> wildcomp_salias(SN, Alias) end,
SC#sconf.serveralias),
case Res of
true -> Opts;
false -> check_sni_servername(SN, Rest, Default)
end
end.
ssl_sni_opts(Group, DefaultSSLOpts) ->
SniOpts = lists:foldl(fun(SC, Acc) ->
[{SC, ssl_listen_opts(SC)}|Acc]
end, [], Group),
SniFun = fun(SN) ->
check_sni_servername(SN, lists:reverse(SniOpts),
DefaultSSLOpts)
end,
[{sni_fun, SniFun}].
ssl_listen_opts(GC, [SC0|_]=Group) ->
DefaultSSLOpts = ssl_listen_opts(SC0),
Opts0 = listen_opts(SC0) ++ DefaultSSLOpts,
Opts = case GC#gconf.sni of
disable -> Opts0;
_ -> Opts0 ++ ssl_sni_opts(Group, DefaultSSLOpts)
end,
?Debug("ssl listen options: ~p", [Opts]),
Opts.
ssl_listen_opts(#sconf{ssl=SSL}) ->
L = [if SSL#ssl.keyfile /= undefined ->
{keyfile, SSL#ssl.keyfile};
true ->
false
end,
if SSL#ssl.certfile /= undefined ->
{certfile, SSL#ssl.certfile};
true ->
false
end,
if SSL#ssl.cacertfile /= undefined ->
{cacertfile, SSL#ssl.cacertfile};
true ->
false
end,
if SSL#ssl.dhfile /= undefined ->
{dhfile, SSL#ssl.dhfile};
true ->
false
end,
if SSL#ssl.verify /= undefined ->
{verify, SSL#ssl.verify};
true ->
false
end,
if SSL#ssl.fail_if_no_peer_cert /= undefined ->
{fail_if_no_peer_cert, SSL#ssl.fail_if_no_peer_cert};
true ->
false
end,
if SSL#ssl.password /= undefined ->
{password, SSL#ssl.password};
true ->
false
end,
if SSL#ssl.ciphers /= undefined ->
{ciphers, SSL#ssl.ciphers};
true ->
false
end,
if SSL#ssl.protocol_version /= undefined ->
{versions, SSL#ssl.protocol_version};
true ->
false
end,
if SSL#ssl.depth /= undefined ->
{depth, SSL#ssl.depth};
true ->
false
end,
if SSL#ssl.secure_renegotiate /= undefined ->
{secure_renegotiate, SSL#ssl.secure_renegotiate};
true ->
false
end,
if SSL#ssl.client_renegotiation /= undefined ->
{client_renegotiation, SSL#ssl.client_renegotiation};
true ->
false
end,
if SSL#ssl.honor_cipher_order /= undefined ->
{honor_cipher_order, SSL#ssl.honor_cipher_order};
true ->
false
end,
case yaws_dynopts:have_ssl_log_alert() of
true -> {log_alert, false};
false -> false
end
],
[X || X <- L, X /= false].
do_accept(GS) when GS#gs.ssl == nossl ->
?Debug("wait in accept ... ~n",[]),
gen_tcp:accept(GS#gs.l);
do_accept(GS) when GS#gs.ssl == ssl ->
ssl:transport_accept(GS#gs.l).
initial_acceptor(GS) ->
acceptor(GS).
acceptor(GS) ->
case (GS#gs.gconf)#gconf.process_options of
[] ->
proc_lib:spawn_link(?MODULE, acceptor0, [GS, self()]);
Opts ->
%% as we tightly controlled what is set in options, we can
%% blindly add "link" to get a linked process as per default
%% case and use the provided options.
proc_lib:spawn_opt(?MODULE, acceptor0, [GS, self()], [link | Opts])
end.
acceptor0(GS, Top) ->
?TC([{record, GS, gs}]),
put(gserv_pid, Top),
put(gc, GS#gs.gconf),
yaws_trace:open(),
X = do_accept(GS),
Top ! {self(), next, X},
case X of
{ok, Client} ->
if
GS#gs.ssl == ssl ->
case ssl:ssl_accept(
Client, (GS#gs.gconf)#gconf.keepalive_timeout
) of
ok ->
ok;
{error, closed} ->
Top ! {self(), decrement},
exit(normal);
{error, esslaccept} ->
%% Don't log SSL esslaccept to error log since it
%% seems this is what we get on portscans and
%% similar
?Debug("SSL accept failed: ~p~n", [esslaccept]),
Top ! {self(), decrement},
exit(normal);
{error, Reason} ->
error_logger:format("SSL accept failed: ~p~n",
[Reason]),
Top ! {self(), decrement},
exit(normal)
end;
true ->
ok
end,
{IP,Port} = peername(Client, GS#gs.ssl),
put(trace_filter, yaws_trace:get_filter()),
Res = (catch aloop(Client, {IP,Port}, GS, 0)),
%% Skip closing the socket, as required by web sockets & stream
%% processes.
CloseSocket = (get(outh) =:= undefined) orelse
(done_or_continue() =:= done),
case CloseSocket of
false -> ok;
true ->
if
GS#gs.ssl == nossl ->
gen_tcp:close(Client);
GS#gs.ssl == ssl ->
ssl:close(Client)
end
end,
case Res of
{ok, Int} when is_integer(Int) ->
Top ! {self(), done_client, Int};
{'EXIT', normal} ->
Top ! {self(), decrement},
exit(normal);
{'EXIT', shutdown} ->
exit(shutdown);
{'EXIT', {error, einval}} ->
%% Typically clients that close their end of the socket
%% don't log. Happens all the time.
Top ! {self(), decrement},
exit(normal);
{'EXIT', {{error, einval}, _}} ->
Top ! {self(), decrement},
exit(normal);
{'EXIT', {{badmatch, {error, einval}}, _}} ->
Top ! {self(), decrement},
exit(normal);
{'EXIT', {error, closed}} ->
Top ! {self(), decrement},
exit(normal);
{'EXIT', {{error, closed}, _}} ->
Top ! {self(), decrement},
exit(normal);
{'EXIT', {{error, econnreset},_}} ->
Top ! {self(), decrement},
exit(normal);
{'EXIT', Reason2} ->
error_logger:error_msg("Yaws process died: ~p~n",
[Reason2]),
Top ! {self(), decrement},
exit(shutdown)
end,
%% we cache processes
receive
{'EXIT', Top, Error} ->
exit(Error);
{Top, stop} ->
exit(normal);
{Top, accept} ->
erase_transients(),
acceptor0(GS, Top)
end;
{error, Reason} when ((Reason == timeout) or
(Reason == einval) or
(Reason == normal) or
(Reason == econnaborted)) ->
%% The econnaborted is
%% caused by recieving a RST when a SYN or SYN+ACK was expected.
%% einval is reported to happen, it could be accept attempts
%% on just recently reconfigured servers through --hup ... ???
Top ! {self(), done_client, 0},
receive
{Top, stop} ->
exit(normal);
{Top, accept} ->
acceptor0(GS, Top)
end;
{error, closed} ->
%% This is what happens when we call yaws --stop
Top ! {self(), decrement},
exit(normal);
{error, Reason} when ((Reason == emfile) or
(Reason == enfile)) ->
error_logger:format("yaws: Failed to accept - no more "
"file descriptors - terminating: ~p~n",
[Reason]),
exit(failaccept);
ERR ->
%% When we fail to accept, the correct thing to do
%% is to terminate yaws as an application, if we're running
%% yaws as a standalone webserver, we want to restart the
%% entire webserver, preferably through heart
%% typical errors here are shortage of fds or memory
error_logger:format("yaws: Failed to accept - terminating: ~p~n",
[ERR]),
exit(failaccept)
end.
%%%----------------------------------------------------------------------
%%% Internal functions
%%%----------------------------------------------------------------------
aloop(CliSock, {IP,Port}=IPPort, GS, Num) ->
case yaws_trace:get_type(GS#gs.gconf) of
undefined ->
ok;
_ when Num =:= 0 ->
yaws_trace:write(from_client,
?F("New (~p) connection from ~s:~w~n",
[GS#gs.ssl,inet_parse:ntoa(IP),Port]));
_ ->
ok
end,
process_flag(trap_exit, false),
init_db(),
SSL = GS#gs.ssl,
Head = yaws:http_get_headers(CliSock, SSL),
process_flag(trap_exit, true),
?Debug("Head = ~p~n", [Head]),
case Head of
{error, {too_many_headers, ReqTooMany}} ->
%% RFC 6585 status code 431
?Debug("Request headers too large~n", []),
case pick_sconf(GS#gs.gconf, #headers{}, GS#gs.group) of
undefined ->
deliver_400(CliSock, ReqTooMany);
SC ->
put(sc, SC),
put(outh, #outh{}),
deliver_431(CliSock, ReqTooMany)
end,
{ok, Num+1};
{Req0, H0} when Req0#http_request.method /= bad_request ->
{Req, H} = fix_abs_uri(Req0, H0),
?Debug("{Req, H} = ~p~n", [{Req, H}]),
case pick_sconf({GS#gs.ssl, CliSock}, GS#gs.gconf, H, GS#gs.group) of
undefined ->
deliver_400(CliSock, Req),
{ok, Num+1};
SC ->
put(outh, #outh{}),
put(sc, SC),
DispatchResult = case SC#sconf.dispatch_mod of
undefined ->
continue;
DispatchMod ->
Arg = make_arg(SC, CliSock, IPPort,
H, Req, undefined),
ok = yaws:setopts(CliSock,
[{packet, raw},
{active, false}],
yaws:is_ssl(SC)),
DispatchMod:dispatch(Arg)
end,
case DispatchResult of
done ->
erase_transients(),
case exceed_keepalive_maxuses(GS, Num) of
true -> {ok, Num+1};
false -> aloop(CliSock, IPPort, GS, Num+1)
end;
closed ->
%% Dispatcher closed the socket
erase_transients(),
{ok, Num+1};
continue ->
?Debug("SC: ~s", [?format_record(SC, sconf)]),
?TC([{record, SC, sconf}]),
?Debug("Headers = ~s~n", [?format_record(H, headers)]),
?Debug("Request = ~s~n",
[?format_record(Req, http_request)]),
run_trace_filter(GS, IP, Req, H),
yaws_stats:hit(),
check_keepalive_maxuses(GS, Num),
Call = case yaws_shaper:check(SC, IP) of
allow ->
call_method(Req#http_request.method,CliSock,
IPPort,Req,H);
{deny, Status, Msg} ->
deliver_xxx(CliSock, Req, Status, Msg)
end,
Call2 = fix_keepalive_maxuses(Call),
handle_method_result(Call2, CliSock, IPPort,
GS, Req, H, Num)
end
end;
closed ->
case yaws_trace:get_type(GS#gs.gconf) of
undefined -> ok;
_ -> yaws_trace:write(from_client, "closed\n")
end,
{ok, Num};
_ ->
% not even HTTP traffic
exit(normal)
end.
run_trace_filter(GS, IP, Req, H) ->
case {yaws_trace:get_type(GS#gs.gconf), get(trace_filter)} of
{undefined, _} ->
ok;
{_, undefined} ->
RStr = yaws_api:reformat_request(Req),
HStr = yaws:headers_to_str(H),
yaws_trace:write(from_client, ?F("~s~n~s~n", [RStr, HStr])),
ok;
{_, FilterFun} ->
case FilterFun(inet_parse:ntoa(IP),Req,H) of
true ->
RStr = yaws_api:reformat_request(Req),
HStr = yaws:headers_to_str(H),
yaws_trace:write(from_client, ?F("~s~n~s~n", [RStr, HStr])),
ok;
false ->
put(gc, (GS#gs.gconf)#gconf{trace = false})
end
end.
%% Checks how many times keepalive has been used and updates the
%% process dictionary outh variable if required to say that the
%% connection has exceeded its maxuses.
check_keepalive_maxuses(GS, Num) ->
Flag = exceed_keepalive_maxuses(GS, Num),
put(outh, (get(outh))#outh{exceedmaxuses=Flag}).
exceed_keepalive_maxuses(GS, Num) ->
case (GS#gs.gconf)#gconf.keepalive_maxuses of
nolimit -> false;
0 -> false;
N when Num+1 < N -> false;
_N -> true
end.
%% Change to Res to 'done' if we've exceeded our maxuses.
fix_keepalive_maxuses(Res) ->
case Res of
continue ->
case (get(outh))#outh.exceedmaxuses of
true ->
done; % no keepalive this time!
_ ->
Res
end;
_ ->
Res
end.
%% keep original dictionary but filter out eventual previous init_db
%% in erase_transients/0
init_db() ->
put(init_db, lists:keydelete(init_db, 1, get())).
erase_transients() ->
%% flush all messages.
%% If exit signal is received from the gserv process, rethrow it
Top = get(gserv_pid),
Fun = fun(G) -> receive
{'EXIT', Top, Reason} -> exit(Reason);
_X -> G(G)
after 0 -> ok
end
end,
Fun(Fun),
I = get(init_db),
if I == undefined ->
ok;
is_list(I) ->
erase(),
%% Need to keep init_db in case we do not enter aloop (i.e. init:db)
%% again as R12B-5 requires proc_lib keys in dict while exiting...
put(init_db, I),
lists:foreach(fun({K,V}) -> put(K,V) end, I)
end.
handle_method_result(Res, CliSock, {IP,Port}, GS, Req, H, Num) ->
case Res of
continue ->
yaws_shaper:update(get(sc), IP, Req),
maybe_access_log(IP, Req, H),
erase_transients(),
aloop(CliSock, {IP,Port}, GS, Num+1);
done ->
yaws_shaper:update(get(sc), IP, Req),
maybe_access_log(IP, Req, H),
erase_transients(),
{ok, Num+1};
{page, P} ->
%% Because the request is rewritten but the body is the same, we
%% keep post_parse and erase query_parse.
erase(query_parse),
put(outh, #outh{}),
case P of
{Options, Page} ->
%% We got additional headers for the page to deliver.
%%
%% Might be useful for `Vary' or `Content-Location'.
%%
%% These headers are stored to be set later to preserve
%% it during the next loop.
put(page_options, Options);
Page ->
ok
end,
%% `is_reentrant_request' flag is used to correctly identify the url
%% type
put(is_reentrant_request, true),
%% Renew #sconf{} to restore docroot/xtra_docroots fields
OldSC = get(sc),
NewSC = pick_sconf(GS#gs.gconf, H, GS#gs.group),
put(sc, NewSC#sconf{appmods = OldSC#sconf.appmods}),
%% Rewrite the request
NextReq = Req#http_request{path = {abs_path, Page}},
%% Renew #arg{}: keep clidata, state and cont
Arg0 = case get(yaws_arg) of
undefined -> #arg{};
A -> A
end,
Arg1 = make_arg(CliSock, {IP,Port}, H, NextReq, Arg0#arg.clidata),
Arg2 = Arg1#arg{orig_req = Arg0#arg.orig_req,
cont = Arg0#arg.cont,
state = Arg0#arg.state},
%% Get the number of bytes already read and do the reentrant call
CliDataPos = case get(client_data_pos) of
undefined -> 0;
Pos -> Pos
end,
Call = handle_request(CliSock, Arg2, CliDataPos),
Call2 = fix_keepalive_maxuses(Call),
handle_method_result(Call2, CliSock, {IP,Port}, GS, NextReq, H, Num)
end.
peername(CliSock, ssl) ->
case ssl:peername(CliSock) of
{ok, Res} -> Res;
_ -> {unknown, unknown}
end;
peername(CliSock, nossl) ->
case inet:peername(CliSock) of
{ok, Res} -> Res;
_ -> {unknown, unknown}
end.
deepforeach(_F, []) ->
ok;
deepforeach(F, [H|T]) ->
deepforeach(F, H),
deepforeach(F, T);
deepforeach(F, X) ->
F(X).
fix_abs_uri(Req, H) ->
case Req#http_request.path of
{absoluteURI, _Scheme, Host0, Port, RawPath} ->
Host = case Port of
P when is_integer(P) ->
Host0 ++ [$: | integer_to_list(P)];
% Is this ok?
_ ->
Host0
end,
{Req#http_request{path={abs_path, RawPath}},
H#headers{host=Host}};
_ -> {Req, H}
end.
%% Case-insensitive compare servername and ignore any optional :Port
%% postfix. This is performance-sensitive code, so if you change it,
%% measure it.
comp_sname([], []) ->
true;
comp_sname([$:|_], [$:|_]) ->
true;
comp_sname([$:|_], []) ->
true;
comp_sname([], [$:|_]) ->
true;
comp_sname([$:|_], _) ->
false;
comp_sname(_, [$:|_]) ->
false;
comp_sname([], _) ->
false;
comp_sname(_, []) ->
false;
comp_sname([C1|T1], [C2|T2]) ->
case string:to_lower(C1) == string:to_lower(C2) of
true -> comp_sname(T1, T2);
false -> false
end.
%% Same thing than comp_sname but here we compare a pattern containing
%% wildcards:
%% - '*' matches any sequence of zero or more characters
%% - '?' matches one character unless that character is a period ('.')
wildcomp_salias([], []) ->
true;
wildcomp_salias([$:|_], [$:|_]) ->
true;
wildcomp_salias([$:|_], []) ->
true;
wildcomp_salias([], [$:|_]) ->
true;
wildcomp_salias([$:|_], _) ->
false;
wildcomp_salias(_, [$:|_]) ->
false;
wildcomp_salias([], _) ->
false;
wildcomp_salias(_, []) ->
false;
wildcomp_salias([$.|_], [$?|_]) ->
false;
wildcomp_salias([_|T1], [$?|T2]) ->
wildcomp_salias(T1, T2);
wildcomp_salias(_, [$*]) ->
true;
wildcomp_salias([_|T1]=Str, [$*|T2]=Pattern) ->
case wildcomp_salias(Str, T2) of
true -> true;
false -> wildcomp_salias(T1, Pattern)
end;
wildcomp_salias([C1|T1], [C2|T2]) ->
case string:to_lower(C1) == string:to_lower(C2) of
true -> wildcomp_salias(T1, T2);
false -> false
end.
comp_sni(SniHost, Host) ->
case Host of
undefined -> false;
{_} -> false;
_ -> comp_sname(SniHost, Host)
end.
pick_sconf({nossl, _}, GC, H, Group) ->
pick_sconf(GC, H, Group);
pick_sconf({ssl, _}, #gconf{sni=disable}=GC, H, Group) ->
pick_sconf(GC, H, Group);
pick_sconf({ssl, Sock}, GC, H, Group) ->
SniHost = case yaws_dynopts:connection_information(Sock, [sni_hostname]) of
{ok, [{sni_hostname, SN}]} -> SN;
_ -> undefined
end,
if
SniHost == undefined andalso GC#gconf.sni == strict ->
error_logger:format(
"SSL Error: No Hostname was provided via SNI~n", []
),
undefined;
SniHost /= undefined ->
%% Host header must be defined to SniHost. Multiple Host headers are
%% not allowed here.
case comp_sni(SniHost, H#headers.host) of
true ->
pick_sni_sconf(SniHost, GC, H, Group);
false ->
error_logger:format(
"SSL Error: Hostname ~p provided via SNI and hostname ~p"
" provided via HTTP are different~n",
[SniHost, H#headers.host]
),
undefined
end;
true ->
pick_sni_sconf(SniHost, GC, H, Group)
end.
%% Check is the server is visible without SNI or if the default server is
%% visible when SNI hostname does not match.
pick_sni_sconf(SniHost, GC, H, Group) ->
SC = pick_sconf(GC, H, Group),
Flag = (SniHost == undefined orelse get(nomatch_virthost)),
if
Flag andalso SC /= undefined andalso
SC#sconf.ssl /= undefined andalso (SC#sconf.ssl)#ssl.require_sni ->
error_logger:format("server ~p require a (matching) SNI hostname~n",
[SC#sconf.servername]),
undefined;
true ->
SC
end.
pick_sconf(GC, H, Group) ->
case H#headers.host of
undefined when ?gc_pick_first_virthost_on_nomatch(GC) ->
put(nomatch_virthost, true),
hd(Group);
{[Host|_]} when ?gc_pick_first_virthost_on_nomatch(GC) ->
pick_host(GC, Host, Group, Group);
{_} ->
%% HTTP spec does not allow multiple Host headers
undefined;
Host ->
pick_host(GC, Host, Group, Group)
end.
%% Compare Host against [] in case caller sends an empty Host header
pick_host(GC, Host, SCs, Group)
when Host == []; Host == undefined; SCs == [] ->
if
?gc_pick_first_virthost_on_nomatch(GC) ->
put(nomatch_virthost, true),
hd(Group);
true ->
yaws_debug:format("Drop req since ~p doesn't match any "
"servername \n", [Host]),
undefined
end;
pick_host(GC, Host, [SC|T], Group) ->
case comp_sname(Host, SC#sconf.servername) of
true ->
SC;
false ->
Res = lists:any(fun(Alias) -> wildcomp_salias(Host, Alias) end,
SC#sconf.serveralias),
case Res of
true -> SC;
false -> pick_host(GC, Host, T, Group)
end
end.
maybe_auth_log(Item, ARG) ->
SC=get(sc),
case ?sc_has_auth_log(SC) of
false ->
ok;
true ->
Req = ARG#arg.req,
{IP,_} = ARG#arg.client_ip_port,
Path = safe_path(Req#http_request.path),
yaws_log:authlog(SC, IP, Path, Item)
end.
maybe_access_log(Ip, Req, H) ->
SC=get(sc),
case ?sc_has_access_log(SC) of
true ->
Time = timer:now_diff(yaws:get_time_tuple(), get(request_start_time)),
yaws_log:accesslog(SC, Ip, Req, H, get(outh), Time);
false ->
ignore
end.
safe_path({abs_path, Path}) -> Path;
safe_path(_) -> "/undecodable_path".
%% ret: continue | done
'GET'(CliSock, IPPort, Req, Head) ->
no_body_method(CliSock, IPPort, Req, Head).
'POST'(CliSock, IPPort, Req, Head) ->
?Debug("POST Req=~s~n H=~s~n", [?format_record(Req, http_request),
?format_record(Head, headers)]),
body_method(CliSock, IPPort, Req, Head).
un_partial({partial, Bin}) ->
Bin;
un_partial(Bin) ->
Bin.
call_method(Method, CliSock, IPPort, Req, H) ->
case Method of
F when is_atom(F) ->
?MODULE:F(CliSock, IPPort, Req, H);
L when is_list(L) ->
handle_extension_method(L, CliSock, IPPort, Req, H)
end.
'HEAD'(CliSock, IPPort, Req, Head) ->
put(acc_content, discard),
no_body_method(CliSock, IPPort, Req, Head).
not_implemented(CliSock, _IPPort, Req, Head) ->
SC=get(sc),
ok = yaws:setopts(CliSock, [{packet, raw}, binary], yaws:is_ssl(SC)),
flush(CliSock, Head#headers.content_length,
yaws:to_lower(Head#headers.transfer_encoding)),
deliver_501(CliSock, Req).
'TRACE'(CliSock, IPPort, Req, Head) ->
not_implemented(CliSock, IPPort, Req, Head).
'OPTIONS'(CliSock, IPPort, Req, Head) ->
case Req#http_request.path of
'*' ->
% Handle "*" as per RFC2616 section 5.1.2
deliver_options(CliSock, Req, ['GET', 'HEAD', 'OPTIONS',
'PUT', 'POST', 'DELETE']);
_ ->
no_body_method(CliSock, IPPort, Req, Head)
end.
'PUT'(CliSock, IPPort, Req, Head) ->
?Debug("PUT Req=~p~n H=~p~n", [?format_record(Req, http_request),
?format_record(Head, headers)]),
SC=get(sc),
case ?sc_has_dav(SC) of
true ->
%% body is handled by yaws_dav:put/1
ok = yaws:setopts(CliSock, [{packet, raw}, binary],
yaws:is_ssl(SC)),
ARG = make_arg(CliSock, IPPort, Head, Req, undefined),
handle_request(CliSock, ARG, 0);
false ->
body_method(CliSock, IPPort, Req, Head)
end.
'DELETE'(CliSock, IPPort, Req, Head) ->
body_method(CliSock, IPPort, Req, Head).
'PATCH'(CliSock, IPPort, Req, Head) ->
?Debug("PATCH Req=~p~n H=~p~n", [?format_record(Req, http_request),
?format_record(Head, headers)]),
body_method(CliSock, IPPort, Req, Head).
body_method(CliSock, IPPort, Req, Head) ->
SC=get(sc),
ok = yaws:setopts(CliSock, [{packet, raw}, binary], yaws:is_ssl(SC)),
PPS = SC#sconf.partial_post_size,
case yaws_api:get_header(Head, {lower, "expect"}) of
undefined ->
ok;
Value ->
case yaws:to_lower(Value) of
"100-continue" -> deliver_100(CliSock);
_ -> ok
end
end,
Res = case {yaws:to_lower(Head#headers.transfer_encoding),
Head#headers.content_length} of
{"chunked", _} ->
get_chunked_client_data(CliSock, yaws:is_ssl(SC));
{_, undefined} ->
<<>>;
{_, Len} ->
Int_len = list_to_integer(Len),
if
Int_len < 0 ->
{error, content_length_overflow};
Int_len == 0 ->
<<>>;
PPS /= nolimit andalso PPS < Int_len ->
{partial, get_client_data(CliSock, PPS, yaws:is_ssl(SC))};
true ->
get_client_data(CliSock, Int_len, yaws:is_ssl(SC))
end
end,
case Res of
{error, Reason} ->
error_logger:format("Invalid Request: ~p~n", [Reason]),
deliver_400(CliSock, Req);
Bin ->
?Debug("Request data = ~s~n", [binary_to_list(un_partial(Bin))]),
ARG = make_arg(CliSock, IPPort, Head, Req, Bin),
handle_request(CliSock, ARG, size(un_partial(Bin)))
end.
no_body_method(CliSock, IPPort, Req, Head) ->
SC=get(sc),
ok = yaws:setopts(CliSock, [{packet, raw}, binary], yaws:is_ssl(SC)),
flush(CliSock, Head#headers.content_length,
yaws:to_lower(Head#headers.transfer_encoding)),
Head1 = Head#headers{content_length=undefined, transfer_encoding=undefined},
ARG = make_arg(CliSock, IPPort, Head1, Req, undefined),
handle_request(CliSock, ARG, 0).
make_arg(CliSock0, IPPort, Head, Req, Bin) ->
SC = get(sc),
make_arg(SC, CliSock0, IPPort, Head, Req, Bin).
make_arg(SC, CliSock0, IPPort, Head, Req, Bin) ->
CliSock = case yaws:is_ssl(SC) of
nossl ->
CliSock0;
ssl ->
{ssl, CliSock0}
end,
ARG = #arg{clisock = CliSock,
client_ip_port = IPPort,
headers = Head,
req = Req,
orig_req = Req,
opaque = SC#sconf.opaque,
pid = self(),
docroot = SC#sconf.docroot,
docroot_mount = "/",
clidata = Bin
},
apply(SC#sconf.arg_rewrite_mod, arg_rewrite, [ARG]).
%% PATCH is not an extension method, but at this time the Erlang HTTP
%% request line parser doesn't know about it so it comes back to us as a
%% string rather than an atom, which causes call_method to call
%% handle_extension_method. If and when the parser is updated to accept
%% PATCH, we'll get it back as an atom and this following clause will be
%% unnecessary.
handle_extension_method("PATCH", CliSock, IPPort, Req, Head) ->
'PATCH'(CliSock, IPPort, Req#http_request{method = 'PATCH'}, Head);
handle_extension_method(_Method, CliSock, IPPort, Req, Head) ->
body_method(CliSock, IPPort, Req, Head).
%% Return values:
%% continue, done, {page, Page}
handle_request(CliSock, ARG, _N)
when is_record(ARG#arg.state, rewrite_response) ->
State = ARG#arg.state,
?Debug("SrvReq=~s - RwResp=~s~n",[?format_record(ARG#arg.req, http_request),
?format_record(State, rewrite_response)]),
OutH = #outh{status = State#rewrite_response.status,
chunked = false,
date = yaws:make_date_header(),
server = yaws:make_server_header()},
put(outh, OutH),
deepforeach(fun(X) ->
case X of
{header, H} -> yaws:accumulate_header(H);
_ -> ok
end
end, State#rewrite_response.headers),
case State#rewrite_response.content of
<<>> ->
deliver_accumulated(CliSock);
_ ->
%% Define a default content type if needed
case yaws:outh_get_content_type() of
undefined ->
Mime = mime_types:default_type(get(sc)),
yaws:outh_set_content_type(Mime);
_ ->
ok
end,
accumulate_content(State#rewrite_response.content),
deliver_accumulated(ARG, CliSock, undefined, final)
end,
done_or_continue();
handle_request(CliSock, ARG, N) ->
Req = ARG#arg.req,
?Debug("SrvReq=~s~n",[?format_record(Req, http_request)]),
case Req#http_request.path of
{abs_path, RawPath} ->
case (catch yaws_api:url_decode_q_split(RawPath)) of
{'EXIT', _} -> %% weird broken cracker requests
deliver_400(CliSock, Req);
{DecPath, QueryPart} ->
%% http://<server><port><DecPath>?<QueryPart>
%% DecPath is stored in arg.server_path and is equiv to
%%SCRIPT_PATH + PATH_INFO (where PATH_INFO may be empty)
QueryString = case QueryPart of
[] ->
undefined;
_ ->
QueryPart
end,
%% by this stage, ARG#arg.docroot_mount is either "/" ,
%% or has been set by a rewrite module.
%%!todo - retrieve 'vdir' definitions from main part of
%% config file rather than
%% rely on rewrite module to dig them out of opaque.
ARGvdir = ARG#arg.docroot_mount,
%% here we make sure that the conf file, or any rewrite mod
%% wrote nothing, or something sensible into
%% arg.docroot_mount
%% It must be empty, or of the form "/path/" where path
%% may be further slash-separated.
%%
%%!todo - review - is handle_request
%% (which is presumably performance
%%sensitive) really the place for sanity checks?
%% Presumably this sort of check is trivial enough
%% that it'll have negligible impact.
VdirSanity = case ARGvdir of
"/" ->
sane;
[$/|_] ->
case string:right(ARGvdir,1) of
"/" when length(ARGvdir) > 2 ->
sane;
_ ->
loopy
end;
_ ->
loopy
end,
case VdirSanity of
loopy ->
%%!todo - log somewhere?
error_logger:format(
"BAD arg.docroot_mount data: '~p'\n",[ARGvdir]),
deliver_xxx(CliSock, Req, 500),
exit(normal);
_ ->
ok
end,
SC = get(sc),
IsRev = is_revproxy(ARG, DecPath, SC),
IsRedirect = is_redirect_map(DecPath,
SC#sconf.redirect_map),
case {IsRev, IsRedirect} of
{_, {true, Redir}} ->
ARG1 = ARG#arg{server_path = DecPath,
querydata = QueryString},
deliver_redirect_map(CliSock, Req, ARG1, Redir, N);
{false, _} ->
%%'main' branch so to speak. Most requests
%% pass through here.
UT = url_type(DecPath, ARG#arg.docroot,
ARG#arg.docroot_mount),
ARG1 = ARG#arg{server_path = DecPath,
querydata = QueryString,
fullpath = UT#urltype.fullpath,
prepath = UT#urltype.dir,
pathinfo = UT#urltype.pathinfo},
handle_normal_request(CliSock, ARG1, UT,
SC#sconf.authdirs, N);
{{true, PP}, _} ->
UT = #urltype{type = appmod,
data = {yaws_revproxy, []}},
ARG1 = ARG#arg{server_path = DecPath,
querydata = QueryString,
state = PP},
handle_normal_request(CliSock, ARG1, UT,
SC#sconf.authdirs, N)
end
end;
{scheme, _Scheme, _RequestString} ->
deliver_501(CliSock, Req);
_ -> % for completeness
deliver_400(CliSock, Req)
end.
handle_normal_request(CliSock, ARG, UT = #urltype{type=error}, _, N) ->
handle_ut(CliSock, ARG, UT, N);
handle_normal_request(CliSock, ARG, UT, Authdirs, N) ->
{IsAuth, ARG1} = case is_auth(ARG, Authdirs) of
{true, User} -> {true, set_auth_user(ARG, User)};
E -> {E, ARG}
end,
case IsAuth of
true ->
%%!todo - remove special treatment of appmod here. (after suitable
%% deprecation period) - prepath & pathinfo are applicable to other
%% types of dynamic url too replace: appmoddata with pathinfo &
%% appmod_prepath with prepath.
case UT#urltype.type of
appmod ->
{_Mod, PathInfo} = UT#urltype.data,
Appmoddata = case PathInfo of
undefined ->
undefined;
"/" ->
"/";
_ ->
lists:dropwhile(fun(C) -> C == $/ end,
PathInfo)
end,
ARG2 = ARG1#arg{appmoddata = Appmoddata,
appmod_prepath = UT#urltype.dir};
_ ->
ARG2 = ARG1
end,
handle_ut(CliSock, ARG2, UT, N);
false_403 ->
deliver_403(CliSock, ARG1#arg.orig_req);
{false, AuthMethods, Realm} ->
UT1 = #urltype{type = {unauthorized, AuthMethods, Realm},
path = ARG1#arg.server_path},
handle_ut(CliSock, ARG1, UT1, N)
end.
set_auth_user(ARG, User) ->
H = ARG#arg.headers,
Auth =
case H#headers.authorization of
{undefined, _, _} ->
{User, undefined, undefined};
{_User, Pass, Orig} ->
{User, Pass, Orig};
undefined ->
{User, undefined, undefined};
E ->
E
end,
H2 = H#headers{authorization = Auth},
ARG#arg{headers = H2}.
filter_auths(Auths, Req_dir) ->
case filter_auths(Auths, Req_dir, []) of
[] when Req_dir =:= "/" orelse Req_dir =:= "." ->
[];
[] ->
filter_auths(Auths, filename:dirname(Req_dir));
As ->
As
end.
filter_auths([], _, Auths) ->
lists:reverse(Auths);
filter_auths([A=#auth{dir=Req_dir}|T], Req_dir, Auths) ->
filter_auths(T, Req_dir, [A|Auths]);
filter_auths([_|T], Req_dir, Auths) ->
filter_auths(T, Req_dir, Auths).
%% Call is_auth(...)/5 with a default value.
is_auth(#arg{req=Req, orig_req=Req}=ARG, L) ->
case lists:keyfind(ARG#arg.docroot, 1, L) of
{_, Auths} -> is_req_auth(ARG, Auths, true);
false -> true
end;
is_auth(ARG, L) ->
case lists:keyfind(ARG#arg.docroot, 1, L) of
{_, Auths} -> is_req_auth(ARG, Auths, is_orig_req_auth(ARG,Auths,true));
false -> true
end.
is_orig_req_auth(#arg{orig_req=OrigReq, headers=H}=ARG, Auths, Ret) ->
case OrigReq#http_request.path of
{abs_path, RawPath} ->
case (catch yaws_api:url_decode_q_split(RawPath)) of
{'EXIT', _} ->
Ret;
{DecPath, _} ->
is_auth(ARG, DecPath, H, filter_auths(Auths, DecPath),
{true, []})
end;
_ ->
Ret
end.
is_req_auth(#arg{server_path=Req_dir, headers=H}=ARG, Auths, Ret) ->
case is_auth(ARG, Req_dir, H, filter_auths(Auths, Req_dir), {true, []}) of
true -> Ret;
Else -> Else
end.
%% Either no authentication was done or all methods returned false
is_auth(_ARG, _Req_dir, _H, [], {Ret, Auth_headers}) ->
yaws:outh_set_auth(Auth_headers),
Ret;
is_auth(ARG, Req_dir, H, [Auth_methods|T], {_Ret, Auth_headers}) ->
Auth_H = H#headers.authorization,
case handle_auth(ARG, Auth_H, Auth_methods, false) of
%% If we auth using an authmod we need to return User
%% so that we can set it in ARG.
{false, A} ->
L = A#auth.headers,
Auth_methods1 = Auth_methods#auth{realm = A#auth.realm,
outmod = A#auth.outmod},
is_auth(ARG, Req_dir, H, T,
{{false, Auth_methods1, A#auth.realm}, L ++ Auth_headers});
Is_auth -> %% true, {true, User} or false_403
Is_auth
end.
handle_auth(#arg{client_ip_port={IP,_}}=ARG, Auth_H,
#auth{acl={AllowIPs, DenyIPs, Order}}=Auth_methods, Ret) ->
Fun = fun(IpMask) -> yaws:match_ipmask(IP, IpMask) end,
Ret1 = case Auth_methods of
#auth{users=[],pam=false,mod=[]} -> true;
_ -> Ret
end,
case {AllowIPs, DenyIPs, Order} of
{_, all, deny_allow} ->
case lists:any(Fun, AllowIPs) of
true ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1);
false ->
false_403
end;
{all, _, deny_allow} ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1);
{_, _, deny_allow} ->
case lists:any(Fun, DenyIPs) of
true ->
case lists:any(Fun, AllowIPs) of
true ->
handle_auth(ARG, Auth_H,
Auth_methods#auth{acl=none},
Ret1);
false ->
false_403
end;
false ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1)
end;
{_, all, allow_deny} ->
false_403;
{all, _, allow_deny} ->
case lists:any(Fun, DenyIPs) of
true ->
false_403;
false ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1)
end;
{_, _, allow_deny} ->
case lists:any(Fun, AllowIPs) of
true ->
case lists:any(Fun, DenyIPs) of
true ->
false_403;
false ->
handle_auth(ARG, Auth_H,
Auth_methods#auth{acl=none},
Ret1)
end;
false ->
false_403
end
end;
handle_auth(_ARG, _Auth_H, #auth{users=[],pam=false,mod=[]}, true) ->
true;
handle_auth(ARG, _Auth_H, Auth_methods=#auth{users=[],pam=false,mod=[]}, Ret) ->
maybe_auth_log({401, Auth_methods#auth.realm}, ARG),
{Ret, Auth_methods};
handle_auth(ARG, Auth_H, Auth_methods = #auth{mod = Mod}, Ret) when Mod /= [] ->
case catch Mod:auth(ARG, Auth_methods) of
{'EXIT', Reason} ->
L = ?F("authmod crashed ~n~p:auth(~p, ~n ~p) \n"
"Reason: ~p~n"
"Stack: ~p~n",
[Mod, ARG, Auth_methods, Reason,
erlang:get_stacktrace()]),
handle_crash(ARG, L),
CliSock = case yaws_api:get_sslsocket(ARG#arg.clisock) of
{ok, SslSock} -> SslSock;
undefined -> ARG#arg.clisock
end,
deliver_accumulated(CliSock),
exit(normal);
%% appmod means the auth headers are undefined, i.e. false.
%% TODO: change so that authmods simply return true/false
{true, User} ->
{true, User};
true ->
true;
false ->
handle_auth(ARG, Auth_H, Auth_methods#auth{mod = []},
Ret);
{false, Realm} ->
handle_auth(ARG, Auth_H, Auth_methods#auth{mod=[], realm=Realm},
Ret);
{appmod, Module} ->
handle_auth(ARG, Auth_H, Auth_methods#auth{mod=[], outmod=Module},
Ret);
_ ->
maybe_auth_log(403, ARG),
false_403
end;
%% if the headers are undefined we do not need to check Pam or Users
handle_auth(ARG, undefined, Auth_methods, Ret) ->
handle_auth(ARG, undefined, Auth_methods#auth{pam = false, users= []}, Ret);
handle_auth(ARG, {User, Password, OrigString},
Auth_methods = #auth{pam = Pam}, Ret) when Pam /= false ->
case yaws_pam:auth(User, Password) of
{yes, _} ->
maybe_auth_log({ok, User}, ARG),
true;
{no, _Rsn} ->
handle_auth(ARG, {User, Password, OrigString},
Auth_methods#auth{pam = false}, Ret)
end;
handle_auth(ARG, {User, Password, OrigString},
Auth_methods = #auth{users = Users}, Ret) when Users /= [] ->
F = fun({U, A, S, H}) ->
(U == User andalso H == crypto:hash(A, [S,Password]))
end,
case lists:any(F, Users) of
true ->
maybe_auth_log({ok, User}, ARG),
true;
false ->
handle_auth(ARG, {User, Password, OrigString},
Auth_methods#auth{users = []}, Ret)
end.
is_revproxy(ARG, Path, SC = #sconf{revproxy = RevConf}) ->
IsFwd = ?sc_forward_proxy(SC),
%% Note: these are mututally exclusive.
case {IsFwd, RevConf} of
{false, []} ->
false;
{false, _} ->
is_revproxy1(Path, RevConf);
{true, _} ->
{true, #proxy_cfg{prefix="/", url=fwdproxy_url(ARG)}}
end.
is_revproxy1(_,[]) ->
false;
is_revproxy1(Path, RevConf) ->
case lists:keyfind(Path, #proxy_cfg.prefix, RevConf) of
#proxy_cfg{}=R ->
{true, R};
false when Path =:= "/" orelse Path =:= "." ->
false;
false ->
is_revproxy1(filename:dirname(Path), RevConf)
end.
is_redirect_map(_, []) ->
false;
is_redirect_map(Path, RedirMap) ->
case lists:keyfind(Path, 1, RedirMap) of
{Path, _Code, _Url, _AppendMod}=E ->
{true, E};
false when Path =:= "/" orelse Path =:= "." ->
false;
false ->
is_redirect_map(filename:dirname(Path), RedirMap)
end.
%% Find out what which module to call when urltype is unauthorized
%% Precedence is:
%% 1. SC#errormod401 if it's not default
%% 2. outmod if defined
%% 3. yaws_outmod
get_unauthorized_outmod(_Req_dir, _Auth, Errormod401)
when Errormod401 /= yaws_outmod ->
Errormod401;
get_unauthorized_outmod(_Req_dir, Auth, Errormod401) ->
case Auth#auth.outmod /= [] of
true -> Auth#auth.outmod;
false -> Errormod401
end.
%% Return values:
%% continue, done, {page, Page}
handle_ut(CliSock, ARG, UT = #urltype{type = regular}, _N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
Regular_allowed = ['GET', 'HEAD', 'OPTIONS'],
IsReentrantRequest = erase(is_reentrant_request),
if
%% Do not check http method for reentrant requests
IsReentrantRequest == true;
Req#http_request.method == 'GET';
Req#http_request.method == 'HEAD' ->
ETag = yaws:make_etag(UT#urltype.finfo),
Range = case H#headers.if_range of
[34|_] = Range_etag when Range_etag /= ETag ->
all;
_ ->
requested_range(
H#headers.range,
(UT#urltype.finfo)#file_info.size)
end,
case Range of
error -> deliver_416(
CliSock, Req,
(UT#urltype.finfo)#file_info.size);
_ ->
Do_deliver =
case Req#http_request.method of
'HEAD' -> fun() -> deliver_accumulated(CliSock),
done end;
_ -> fun() -> deliver_file(CliSock, Req,
UT, Range) end
end,
case H#headers.if_none_match of
undefined ->
case H#headers.if_match of
undefined ->
case H#headers.if_modified_since of
undefined ->
yaws:outh_set_static_headers
(Req, UT, H, Range),
maybe_set_page_options(),
Do_deliver();
UTC_string ->
case yaws:is_modified_p(
UT#urltype.finfo,
UTC_string) of
true ->
yaws:outh_set_static_headers
(Req, UT, H, Range),
maybe_set_page_options(),
Do_deliver();
false ->
yaws:outh_set_304_headers(
Req, UT, H),
maybe_set_page_options(),
deliver_accumulated(
CliSock),
done_or_continue()
end
end;
Line ->
case member(ETag,
yaws:split_sep(Line, $,)) of
true ->
yaws:outh_set_static_headers(
Req, UT, H, Range),
maybe_set_page_options(),
Do_deliver();
false ->
deliver_xxx(CliSock, Req, 412)
end
end;
Line ->
case member(ETag,yaws:split_sep(Line, $,)) of
true ->
yaws:outh_set_304_headers(Req, UT, H),
maybe_set_page_options(),
deliver_accumulated(CliSock),
done_or_continue();
false ->
yaws:outh_set_static_headers
(Req, UT, H, Range),
Do_deliver()
end
end
end;
Req#http_request.method == 'OPTIONS' ->
deliver_options(CliSock, Req, Regular_allowed);
true ->
deliver_405(CliSock, Req, Regular_allowed)
end;
handle_ut(CliSock, ARG, UT = #urltype{type = yaws}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
?Debug("UT = ~s~n", [?format_record(UT, urltype)]),
Yaws_allowed = ['GET', 'POST', 'HEAD', 'OPTIONS'],
if
Req#http_request.method == 'GET';
Req#http_request.method == 'POST';
Req#http_request.method == 'HEAD' ->
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
do_yaws(CliSock, ARG, UT, N);
Req#http_request.method == 'OPTIONS' ->
deliver_options(CliSock, Req, Yaws_allowed);
true ->
deliver_405(CliSock, Req, Yaws_allowed)
end;
handle_ut(CliSock, ARG, UT = #urltype{type = {unauthorized, Auth, Realm}}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
SC = get(sc),
yaws:outh_set_dyn_headers(Req, H, UT),
%% outh_set_dyn headers sets status to 200 by default
%% so we need to set it 401
yaws:outh_set_status_code(401),
Outmod = get_unauthorized_outmod(UT#urltype.path, Auth,
SC#sconf.errormod_401),
OutFun = fun (A) ->
case catch Outmod:out401(A, Auth, Realm) of
{'EXIT', {undef, _}} ->
%% Possibly a deprecated warning
Outmod:out(A);
{'EXIT', Reason} ->
exit(Reason);
Result ->
Result
end
end,
DeliverFun = fun (A) -> finish_up_dyn_file(A, CliSock) end,
deliver_dyn_part(CliSock, 0, "appmod", N, ARG, UT, OutFun, DeliverFun);
handle_ut(CliSock, ARG, UT = #urltype{type = error}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
SC=get(sc),GC=get(gc),
case UT#urltype.type of
error when SC#sconf.xtra_docroots == [] ->
yaws:outh_set_dyn_headers(Req, H, UT),
deliver_dyn_part(CliSock,
0, "404",
N,
ARG,UT,
fun(A)->(SC#sconf.errormod_404):
out404(A,GC,SC)
end,
fun(A)->finish_up_dyn_file(A, CliSock)
end
);
error ->
SC2 = SC#sconf{docroot = hd(SC#sconf.xtra_docroots),
xtra_docroots = tl(SC#sconf.xtra_docroots)},
put(sc, SC2),
%%!todo - review & change. rewriting the docroot and xtra_docroots
%% is not a good way to handle the xtra_docroot feature because
%% it makes less information available to the subsequent calls -
%% this is especially an issue for a nested ssi.
ARG2 = ARG#arg{docroot = SC2#sconf.docroot},
handle_request(CliSock, ARG2, N)
end;
handle_ut(CliSock, ARG, UT = #urltype{type = directory}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
SC=get(sc),
if (?sc_has_dir_listings(SC)) ->
Directory_allowed = ['GET', 'HEAD', 'OPTIONS'],
IsReentrantRequest = erase(is_reentrant_request),
if
%% Do not check http method for reentrant requests
IsReentrantRequest == true;
Req#http_request.method == 'GET';
Req#http_request.method == 'HEAD' ->
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
P = UT#urltype.fullpath,
yaws_ls:list_directory(ARG, CliSock, UT#urltype.data,
P, Req,
?sc_has_dir_all_zip(SC));
Req#http_request.method == 'OPTIONS' ->
deliver_options(CliSock, Req, Directory_allowed);
true ->
deliver_405(CliSock, Req, Directory_allowed)
end;
true ->
handle_ut(CliSock, ARG, #urltype{type = error}, N)
end;
handle_ut(CliSock, ARG, UT = #urltype{type = redir}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
yaws:outh_set_dyn_headers(Req, H, UT),
case yaws:outh_get_doclose() of
true -> ok;
_ -> flush(CliSock, N, H#headers.content_length,
yaws:to_lower(H#headers.transfer_encoding))
end,
deliver_302(CliSock, Req, ARG, UT#urltype.path);
handle_ut(CliSock, ARG, UT = #urltype{type = appmod}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
{Mod,_} = UT#urltype.data,
deliver_dyn_part(CliSock,
0, "appmod",
N,
ARG,UT,
fun(A)->Mod:out(A) end,
fun(A)->finish_up_dyn_file(A, CliSock) end
);
handle_ut(CliSock, ARG, UT = #urltype{type = cgi}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
deliver_dyn_part(CliSock,
0, "cgi",
N,
ARG,UT,
fun(A)->yaws_cgi:call_cgi(
A,flatten(UT#urltype.fullpath))
end,
fun(A)->finish_up_dyn_file(A, CliSock) end
);
handle_ut(CliSock, ARG, UT = #urltype{type = fcgi}, N) ->
error_logger:error_msg("*** handle_ut: type=fcgi~n"), %%@@@
Req = ARG#arg.req,
H = ARG#arg.headers,
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
deliver_dyn_part(CliSock,
0, "fcgi",
N,
ARG,UT,
fun(A)->yaws_cgi:call_fcgi_responder(A)
end,
fun(A)->finish_up_dyn_file(A, CliSock)
end
);
handle_ut(CliSock, ARG, UT = #urltype{type = dav}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
Next =
case Req#http_request.method of
'OPTIONS' ->
options;
_ when Req#http_request.method == 'GET';
Req#http_request.method == 'HEAD' ->
case prim_file:read_file_info(UT#urltype.fullpath) of
{ok, FI} when FI#file_info.type == regular ->
{regular, FI};
_ ->
error
end;
_Dav when Req#http_request.method == 'PUT';
Req#http_request.method == 'DELETE';
Req#http_request.method == "PROPFIND";
Req#http_request.method == "PROPPATCH";
Req#http_request.method == "LOCK";
Req#http_request.method == "UNLOCK";
Req#http_request.method == "MOVE";
Req#http_request.method == "COPY";
Req#http_request.method == "MKCOL" ->
dav;
_ ->
error
end,
case Next of
error ->
handle_ut(CliSock, ARG, #urltype{type = error}, N);
options ->
deliver_options(CliSock, Req, []);
{regular, Finfo} ->
handle_ut(CliSock, ARG, UT#urltype{type = regular,
finfo = Finfo}, N);
dav ->
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
deliver_dyn_part(CliSock,
0, "dav",
N,
ARG,UT,
Next,
fun(A) -> finish_up_dyn_file(A, CliSock) end
)
end;
handle_ut(CliSock, ARG, UT = #urltype{type = php}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
SC=get(sc),
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
Fun = case SC#sconf.php_handler of
{cgi, Exe} ->
fun(A)->
yaws_cgi:call_cgi(
A,Exe,flatten(UT#urltype.fullpath)
)
end;
{fcgi, {PhpFcgiHost, PhpFcgiPort}} ->
fun(A)->
yaws_cgi:call_fcgi_responder(
A, [{app_server_host, PhpFcgiHost},
{app_server_port, PhpFcgiPort}]
)
end;
{extern, {PhpMod, PhpFun}} ->
fun(A) ->
PhpMod:PhpFun(A)
end;
{extern, {PhpNode,PhpMod,PhpFun}} ->
fun(A) ->
%% Mod:Fun must return
rpc:call(PhpNode, PhpMod, PhpFun, [A], infinity)
end
end,
deliver_dyn_part(CliSock,
0, "php",
N,
ARG,UT,
Fun,
fun(A)->finish_up_dyn_file(A, CliSock) end
).
done_or_continue() ->
case yaws:outh_get_doclose() of
true -> done;
false -> continue;
keep_alive -> continue;
undefined -> continue
end.
%% we may have content,
new_redir_h(OH, Loc) ->
new_redir_h(OH, Loc, 302).
new_redir_h(OH, Loc, Status) ->
OH2 = OH#outh{status = Status,
location = Loc},
put(outh, OH2).
%% we must deliver a 302 if the browser asks for a dir
%% without a trailing / in the HTTP req
%% otherwise the relative urls in /dir/index.html will be broken.
%% Note: Here Path is always decoded, so we must encode it
deliver_302(CliSock, _Req, Arg, Path) ->
?Debug("in redir 302 ",[]),
H = get(outh),
SC=get(sc),
Scheme = yaws:redirect_scheme(SC),
Headers = Arg#arg.headers,
EncPath = yaws_api:url_encode(Path),
RedirHost = yaws:redirect_host(SC, Headers#headers.host),
%% QueryString must be added
Loc = case Arg#arg.querydata of
undefined -> ["Location: ", Scheme, RedirHost, EncPath, "\r\n"];
[] -> ["Location: ", Scheme, RedirHost, EncPath, "\r\n"];
Q -> ["Location: ", Scheme, RedirHost, EncPath, "?", Q, "\r\n"]
end,
new_redir_h(H, Loc),
deliver_accumulated(CliSock),
done_or_continue().
deliver_redirect_map(CliSock, Req, _Arg,
{_Prefix, Code, undefined, _Mode}, _N) ->
%% Here Code is 1xx, 2xx, 4xx or 5xx
?Debug("in redir ~p", [Code]),
deliver_xxx(CliSock, Req, Code);
deliver_redirect_map(_CliSock, _Req, Arg,
{_Prefix, Code, Path, Mode}, N) when is_list(Path) ->
%% Here Code is 1xx, 2xx, 4xx or 5xx
?Debug("in redir ~p", [Code]),
Path1 = if
Mode == append ->
EncPath = yaws_api:url_encode(Arg#arg.server_path),
filename:join([Path ++ EncPath]);
true -> %% noappend
Path
end,
Page = case Arg#arg.querydata of
undefined -> Path1;
[] -> Path1;
Q -> Path1 ++ "?" ++ Q
end,
%% Set variables used in handle_method_result/7
put(yaws_arg, Arg),
put(client_data_pos, N),
{page, {[{status, Code}], Page}};
deliver_redirect_map(CliSock, Req, Arg,
{_Prefix, Code, URL, Mode}, N) when is_record(URL, url) ->
%% Here Code is 3xx
?Debug("in redir ~p", [Code]),
H = get(outh),
QueryData = case Arg#arg.querydata of
undefined -> [];
Q -> Q
end,
LocPath = if
Mode == append ->
EncPath = yaws_api:url_encode(Arg#arg.server_path),
Path1 = filename:join([URL#url.path ++ EncPath]),
yaws_api:format_partial_url(
URL#url{path=Path1,querypart=QueryData}, get(sc)
);
true -> %% noappend
yaws_api:format_partial_url(URL#url{querypart=QueryData},
get(sc))
end,
Loc = ["Location: ", LocPath, "\r\n"],
{DoClose, _Chunked} = yaws:dcc(Req, Arg#arg.headers),
case DoClose of
true -> ok;
_ -> flush(CliSock, N, (Arg#arg.headers)#headers.content_length,
yaws:to_lower((Arg#arg.headers)#headers.transfer_encoding))
end,
new_redir_h(H#outh{
connection = yaws:make_connection_close_header(DoClose),
doclose = DoClose,
server = yaws:make_server_header(),
chunked = false,
date = yaws:make_date_header()
}, Loc, Code),
deliver_accumulated(CliSock),
done_or_continue().
deliver_options(CliSock, _Req, Options) ->
H = #outh{status = 200,
doclose = false,
chunked = false,
server = yaws:make_server_header(),
date = yaws:make_date_header(),
allow = yaws:make_allow_header(Options)},
put(outh, H),
deliver_accumulated(CliSock),
continue.
deliver_100(CliSock) ->
H = #outh{status = 100,
doclose = false,
chunked = false,
server = yaws:make_server_header(),
allow = yaws:make_allow_header()},
put(outh, H),
deliver_accumulated(CliSock),
continue.
deliver_xxx(CliSock, _Req, Code) ->
deliver_xxx(CliSock, _Req, Code, "").
deliver_xxx(CliSock, _Req, Code, ExtraHtml) ->
B = ["<html><h1>", integer_to_list(Code), $\ ,
yaws_api:code_to_phrase(Code), "</h1></html>", ExtraHtml],
Sz = iolist_size(B),
Server = case get(sc) of
undefined -> undefined;
_ -> yaws:make_server_header()
end,
H = #outh{status = Code,
doclose = true,
chunked = false,
server = Server,
connection = yaws:make_connection_close_header(true),
content_length = yaws:make_content_length_header(Sz),
contlen = Sz,
content_type = yaws:make_content_type_header("text/html")},
put(outh, H),
accumulate_content(B),
deliver_accumulated(CliSock),
done.
deliver_400(CliSock, Req) ->
deliver_xxx(CliSock, Req, 400).% Bad Request
deliver_403(CliSock, Req) ->
deliver_xxx(CliSock, Req, 403). % Forbidden
deliver_405(CliSock, Req, Methods) ->
Methods_msg = lists:flatten(
["<p>This resource allows ",
yaws:join_sep([atom_to_list(M) || M <- Methods], ", "),
"</p>"]),
deliver_xxx(CliSock, Req, 405, Methods_msg).
deliver_416(CliSock, _Req, Tot) ->
B = ["<html><h1>416 ", yaws_api:code_to_phrase(416), "</h1></html>"],
Sz = iolist_size(B),
H = #outh{status = 416,
doclose = true,
chunked = false,
server = yaws:make_server_header(),
connection = yaws:make_connection_close_header(true),
content_range = ["Content-Range: */",
integer_to_list(Tot), $\r, $\n],
content_length = yaws:make_content_length_header(Sz),
contlen = Sz,
content_type = yaws:make_content_type_header("text/html")},
put(outh, H),
accumulate_content(B),
deliver_accumulated(CliSock),
done.
deliver_431(CliSock, Req) ->
deliver_xxx(CliSock, Req, 431).
deliver_501(CliSock, Req) ->
deliver_xxx(CliSock, Req, 501). % Not implemented
do_yaws(CliSock, ARG, UT, N) ->
Key = UT#urltype.getpath, %% always flat
Mtime = mtime(UT#urltype.finfo),
SC=get(sc),
case ets:lookup(SC#sconf.ets, Key) of
[{_Key, spec, Mtime1, Spec, Es}] when Mtime1 == Mtime,
Es == 0 ->
deliver_dyn_file(CliSock, Spec, ARG, UT, N);
Other ->
purge_old_mods(get(gc),Other),
{ok, NbErrs, Spec} = yaws_compile:compile_file(UT#urltype.fullpath),
?Debug("Spec for file ~s is:~n~p~n",[UT#urltype.fullpath, Spec]),
ets:insert(SC#sconf.ets, {Key, spec, Mtime, Spec, NbErrs}),
deliver_dyn_file(CliSock, Spec, ARG, UT, N)
end.
purge_old_mods(_, []) ->
ok;
purge_old_mods(_GC, [{_FileAtom, spec, _Mtime1, Spec, _}]) ->
foreach(
fun({mod, _, _, _, Mod, _Func}) ->
code:purge(Mod),
code:purge(Mod);
(_) ->
ok
end, Spec).
get_client_data(CliSock, Len, SSlBool) ->
get_client_data(CliSock, Len, [], SSlBool).
get_client_data(_CliSock, 0, Bs, _SSlBool) ->
list_to_binary(Bs);
get_client_data(CliSock, Len, Bs, SSlBool) ->
case yaws:cli_recv(CliSock, Len, SSlBool) of
{ok, B} ->
get_client_data(CliSock, Len-size(B), [Bs,B], SSlBool);
_Other ->
error_logger:format("get_client_data: ~p~n", [_Other]),
exit(normal)
end.
%% not nice to support this for ssl sockets
get_chunked_client_data(CliSock,SSL) ->
SC = get(sc),
Val = erase(current_chunk_size),
Len = if
Val =:= 0 ->
%% Last chunk was already read.
undefined;
Val =:= undefined ->
yaws:setopts(CliSock, [binary, {packet, line}],SSL),
%% Ignore chunk extentions
{N, _Exts} = yaws:get_chunk_header(CliSock, SSL),
yaws:setopts(CliSock, [binary, {packet, raw}],SSL),
N;
true ->
Val
end,
if
Len =:= undefined ->
%% Do nothing
put(current_chunk_size, 0),
<<>>;
Len == 0 ->
put(current_chunk_size, 0),
%% Ignore chunk trailer
yaws:get_chunk_trailer(CliSock, SSL),
<<>>;
Len =< SC#sconf.partial_post_size ->
B = yaws:get_chunk(CliSock, Len, 0, SSL),
yaws:eat_crnl(CliSock,SSL),
{partial, list_to_binary(B)};
true ->
B = yaws:get_chunk(CliSock, SC#sconf.partial_post_size, 0, SSL),
put(current_chunk_size, Len - SC#sconf.partial_post_size),
{partial, list_to_binary(B)}
end.
%% Return values:
%% continue, done, {page, Page}
deliver_dyn_part(CliSock, % essential params
LineNo, YawsFile, % for diagnostic output
CliDataPos0, % for `get_more' and `flush'
Arg,UT,
YawsFun, % call YawsFun(Arg)
DeliverCont % call DeliverCont(Arg)
% to continue normally
) ->
%% Note: yaws_arg and client_data_pos are also used in
%% handle_method_result/7 when `{page, Page}' is returned
put(yaws_ut, UT),
put(yaws_arg, Arg),
put(client_data_pos, CliDataPos0),
OutReply = try
Res = YawsFun(Arg),
handle_out_reply(Res, LineNo, YawsFile, UT, Arg)
catch
Class:Exc ->
handle_out_reply({throw, Class, Exc}, LineNo,
YawsFile, UT, Arg)
end,
case OutReply of
{get_more, Cont, State} when element(1, Arg#arg.clidata) == partial ->
CliDataPos1 = get(client_data_pos),
More = get_more_post_data(CliSock, CliDataPos1, Arg),
A2 = Arg#arg{clidata=More, cont=Cont, state=State},
deliver_dyn_part(
CliSock, LineNo, YawsFile, CliDataPos1+size(un_partial(More)),
A2, UT, YawsFun, DeliverCont
);
break ->
finish_up_dyn_file(Arg, CliSock);
{page, Page} ->
{page, Page};
Arg2 = #arg{} ->
DeliverCont(Arg2);
{streamcontent, _, _} ->
Priv = deliver_accumulated(Arg, CliSock, undefined, stream),
stream_loop_send(Priv, CliSock, 30000);
%% For other timeout values (other than 30 second)
{streamcontent_with_timeout, _, _, TimeOut} ->
Priv = deliver_accumulated(Arg, CliSock, undefined, stream),
stream_loop_send(Priv, CliSock, TimeOut);
{streamcontent_with_size, Sz, _, _} ->
Priv = deliver_accumulated(Arg, CliSock, Sz, stream),
stream_loop_send(Priv, CliSock, 30000);
{streamcontent_from_pid, _, Pid} ->
case yaws:outh_get_content_encoding() of
decide -> yaws:outh_set_content_encoding(identity);
_ -> ok
end,
Priv = deliver_accumulated(Arg, CliSock, undefined, stream),
wait_for_streamcontent_pid(Priv, CliSock, Pid);
{websocket, CallbackMod, Opts} ->
%% The handshake passes control over the socket to OwnerPid
%% and terminates the Yaws worker!
yaws_websockets:start(Arg, CallbackMod, Opts);
_ ->
DeliverCont(Arg)
end.
finish_up_dyn_file(Arg, CliSock) ->
deliver_accumulated(Arg, CliSock, undefined, final),
done_or_continue().
%% do the header and continue
deliver_dyn_file(CliSock, Specs, ARG, UT, N) ->
Bin = ut_read(UT),
deliver_dyn_file(CliSock, Bin, Specs, ARG, UT, N).
deliver_dyn_file(CliSock, Bin, [H|T], Arg, UT, N) ->
?Debug("deliver_dyn_file: ~p~n", [H]),
case H of
{mod, LineNo, YawsFile, NumChars, Mod, out} ->
{_, Bin2} = skip_data(Bin, NumChars),
deliver_dyn_part(CliSock, LineNo, YawsFile,
N, Arg, UT,
fun(A)->Mod:out(A) end,
fun(A)->deliver_dyn_file(CliSock,Bin2,T,A,UT,0)
end);
{data, 0} ->
deliver_dyn_file(CliSock, Bin, T, Arg, UT, N);
{data, NumChars} ->
{Send, Bin2} = skip_data(Bin, NumChars),
accumulate_content(Send),
deliver_dyn_file(CliSock, Bin2, T, Arg, UT, N);
{skip, 0} ->
deliver_dyn_file(CliSock, Bin, T, Arg, UT, N);
{skip, NumChars} ->
{_, Bin2} = skip_data(Bin, NumChars),
deliver_dyn_file(CliSock, Bin2, T, Arg, UT, N);
{binding, NumChars} ->
{Send, Bin2} = skip_data(Bin, NumChars),
"%%"++Key = binary_to_list(Send),
Chunk =
case get({binding, Key--"%%"}) of
undefined -> Send;
Value -> Value
end,
accumulate_content(Chunk),
deliver_dyn_file(CliSock, Bin2, T, Arg, UT, N);
{error, NumChars, Str} ->
{_, Bin2} = skip_data(Bin, NumChars),
accumulate_content(Str),
deliver_dyn_file(CliSock, Bin2, T, Arg, UT, N);
{verbatim, NumChars, Data} ->
{_Send, Bin2} = skip_data(Bin, NumChars),
accumulate_content(Data),
deliver_dyn_file(CliSock, Bin2, T, Arg, UT, N);
yssi ->
ok
end;
deliver_dyn_file(CliSock, _Bin, [], ARG,_UT,_N) ->
?Debug("deliver_dyn: done~n", []),
finish_up_dyn_file(ARG, CliSock).
-define(unflushed_timeout, 300).
stream_loop_send(Priv, CliSock, IdleTimeout) ->
stream_loop_send(Priv, CliSock, unflushed, ?unflushed_timeout, IdleTimeout).
stream_loop_send(Priv, CliSock, FlushStatus, CurTimeout, IdleTimeout) ->
receive
{streamcontent, Cont} ->
P = send_streamcontent_chunk(Priv, CliSock, Cont),
stream_loop_send(P, CliSock, unflushed,
?unflushed_timeout, IdleTimeout);
{streamcontent_with_ack, From, Cont} -> % acknowledge after send
P = send_streamcontent_chunk(Priv, CliSock, Cont),
From ! {self(), streamcontent_ack},
stream_loop_send(P, CliSock, unflushed,
?unflushed_timeout, IdleTimeout);
endofstreamcontent ->
end_streaming(Priv, CliSock)
after CurTimeout ->
case FlushStatus of
flushed ->
erlang:error(stream_timeout);
unflushed ->
P = sync_streamcontent(Priv, CliSock),
stream_loop_send(P, CliSock, flushed,
IdleTimeout, IdleTimeout)
end
end.
make_chunk(Data) ->
case yaws:outh_get_chunked() of
true ->
case iolist_size(Data) of
0 ->
empty;
S ->
CRNL = crnl(),
{S, [yaws:integer_to_hex(S), CRNL, Data, CRNL]}
end;
false ->
{iolist_size(Data), Data}
end.
make_final_chunk(Data) ->
case yaws:outh_get_chunked() of
true ->
CRNL = crnl(),
case iolist_size(Data) of
0 ->
{0, ["0",CRNL,CRNL]};
S ->
{S, [yaws:integer_to_hex(S), CRNL, Data, CRNL,
"0", CRNL, CRNL]}
end;
false ->
{iolist_size(Data), Data}
end.
send_streamcontent_chunk(discard, _, _) ->
discard;
send_streamcontent_chunk(undefined, CliSock, Data) ->
case make_chunk(Data) of
empty -> ok;
{Size, Chunk} ->
?Debug("send ~p bytes to ~p ~n",
[Size, CliSock]),
yaws:outh_inc_act_contlen(Size),
yaws:gen_tcp_send(CliSock, Chunk)
end,
undefined;
send_streamcontent_chunk({Z, Priv}, CliSock, Data) ->
?Debug("send ~p bytes to ~p ~n",
[iolist_size(Data), CliSock]),
{ok, P, D} = yaws_zlib:gzipDeflate(Z, Priv, iolist_to_binary(Data), none),
case make_chunk(D) of
empty -> ok;
{Size, Chunk} ->
yaws:outh_inc_act_contlen(Size),
yaws:gen_tcp_send(CliSock, Chunk)
end,
{Z, P}.
sync_streamcontent(discard, _CliSock) ->
discard;
sync_streamcontent(undefined, _CliSock) ->
undefined;
sync_streamcontent({Z, Priv}, CliSock) ->
?Debug("syncing~n", []),
{ok, P, D} = yaws_zlib:gzipDeflate(Z, Priv, <<>>, sync),
case make_chunk(D) of
empty -> ok;
{Size, Chunk} ->
yaws:outh_inc_act_contlen(Size),
yaws:gen_tcp_send(CliSock, Chunk)
end,
{Z, P}.
end_streaming(discard, _) ->
done_or_continue();
end_streaming(undefined, CliSock) ->
?Debug("end_streaming~n", []),
{_, Chunk} = make_final_chunk(<<>>),
yaws:gen_tcp_send(CliSock, Chunk),
done_or_continue();
end_streaming({Z, Priv}, CliSock) ->
?Debug("end_streaming~n", []),
{ok, _P, Data} = yaws_zlib:gzipDeflate(Z, Priv, <<>>, finish),
{Size, Chunk} = make_final_chunk(Data),
yaws:outh_inc_act_contlen(Size),
yaws:gen_tcp_send(CliSock, Chunk),
yaws_zlib:gzipEnd(Z),
zlib:close(Z),
done_or_continue().
%% what about trailers ??
%% vinoski -- I think trailers should be added as an optional argument to
%% yaws_api:stream_chunk_end(). The end_streaming() function above could
%% then easily deal with sending them.
wait_for_streamcontent_pid(Priv, CliSock, ContentPid) ->
Ref = erlang:monitor(process, ContentPid),
case Priv of
discard ->
ContentPid ! {discard, self()};
_ ->
SC = get(sc),
case SC#sconf.ssl of
undefined ->
gen_tcp:controlling_process(CliSock, ContentPid);
_ ->
ssl:controlling_process(CliSock, ContentPid)
end,
ContentPid ! {ok, self()}
end,
receive
endofstreamcontent ->
demonitor_streamcontent_pid(Ref);
{endofstreamcontent, closed} ->
H = get(outh),
put(outh, H#outh{doclose = true}),
demonitor_streamcontent_pid(Ref);
{'DOWN', Ref, _, _, _} ->
ok
end,
done_or_continue().
demonitor_streamcontent_pid(Ref) ->
erlang:demonitor(Ref),
%% should just use demonitor [flush] option instead?
receive
{'DOWN', Ref, _, _, _} ->
ok
after 0 ->
ok
end.
skip_data(Bin, Sz) ->
?Debug("Skip data ~p bytes from", [Sz]),
<<Head:Sz/binary, Tail/binary>> = Bin,
{Head, Tail}.
accumulate_content(Data) ->
case get(acc_content) of
undefined ->
put(acc_content, [Data]);
discard ->
discard;
List ->
put(acc_content, [List, Data])
end.
%% handle_out_reply(R, ...)
%%
%% R is a reply or a deep list of replies. The special return values
%% `streamcontent', `get_more_data' etc, which are not handled here
%% completely but returned, have to be the last element of the list.
handle_out_reply(L, LineNo, YawsFile, UT, ARG) when is_list (L) ->
handle_out_reply_l(L, LineNo, YawsFile, UT, ARG, undefined);
%% yssi, yaws include
handle_out_reply({yssi, Yfile}, LineNo, YawsFile, UT, ARG) ->
SC = get(sc),
%% special case for abs paths
UT2=case Yfile of