Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

4834 lines (4276 sloc) 171.587 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").
-include_lib("kernel/include/inet.hrl").
-export([mappath/3, vdirpath/3]).
%% External exports
-export([start_link/1]).
-export([safe_decode_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,
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/5, setup_dirs/1,
deliver_dyn_part/8, finish_up_dyn_file/2, gserv_loop/4
]).
-export(['GET'/3,
'POST'/3,
'HEAD'/3,
'TRACE'/3,
'OPTIONS'/3,
'PUT'/3,
'DELETE'/3]).
-import(lists, [member/2, foreach/2, map/2,
flatten/1, flatmap/2, reverse/1]).
-import(yaws_api, [ehtml_expand/1]).
-record(gs, {gconf,
group, %% list of #sconf{} s
ssl, %% ssl | nossl
certinfo, %% undefined | #certinfo{}
l, %% listen socket
mnum = 0,
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}]
mnum = 0, %% dyn compiled erl module number
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).
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),
yaws_log:setup(Gconf, Sconfs),
case Gconf#gconf.trace of
{true, What} ->
yaws_log:open_trace(What),
yaws_api:set_tty_trace(?gc_has_tty_trace(Gconf));
_ ->
ok
end,
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 = [],
mnum = 0}}
end.
init2(GC, Sconfs, RunMod, Embedded, FirstTime) ->
put(gc, 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 (localinstall=~p) ~n"
"~s"
"Logging to directory ~p~n",
[GC#gconf.id, yaws_generated:is_local_install(),
if ?gc_has_debug(GC) ->
"Running with debug checks "
"turned on (slower server) \n";
true ->
""
end,
GC#gconf.logdir]),
setup_dirs(GC),
case Embedded of
false ->
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),
L2 = lists:zf(
fun(Group) -> start_group(GC, Group) end,
Sconfs),
{ok, #state{gc = GC,
pairs = L2,
mnum = 0,
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};
handle_call(mnum, _From, State) ->
Mnum = State#state.mnum +1,
{reply, Mnum, State#state{mnum = Mnum}};
%% 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, NewSc}, From, State) ->
case yaws_config:search_sconf(NewSc, State#state.pairs) of
{Pid, OldSc, _Group} ->
case yaws_config:eq_sconfs(OldSc,NewSc) of
true ->
error_logger:info_msg("Keeping conf for ~s intact\n",
[yaws:sconf_to_srvstr(OldSc)]),
{reply, ok, State};
false ->
Pid ! {update_sconf, NewSc, OldSc, From, self()},
receive
{updated_sconf, Pid, NewSc2} ->
P2 = yaws_config:update_sconf(NewSc2,
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(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(OldSc, State#state.pairs),
{noreply, State#state{pairs = P2}};
false ->
{reply, {error, "No matching group"}, State}
end;
handle_call({add_sconf, SC}, From, State) ->
case yaws_config:search_group(SC, State#state.pairs) of
[{Pid, Group}] ->
Pid ! {add_sconf, From, SC, self()},
receive
{added_sconf, Pid, SC2} ->
P2 = lists:keyreplace(Pid, 1, State#state.pairs,
{Pid, Group ++ [SC2]}),
{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, State};
{true, Pair} ->
P2 = [Pair | State#state.pairs],
{reply, ok, 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, SC) ->
case SC#sconf.ssl of
undefined ->
{nossl, undefined, gen_tcp_listen(SC#sconf.port, listen_opts(SC))};
SSL ->
{ssl, certinfo(SSL),
ssl:listen(SC#sconf.port, ssl_listen_opts(GC, SC, SSL))}
end.
certinfo(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("Listen ~p:~p~n", [Port, Opts]),
gen_tcp: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) ->
case ?sc_has_statistics(SC) of
true ->
start_stats(SC);
false ->
SC
end
end, Group1),
SC = hd(Group),
case do_listen(GC, SC) of
{SSLBOOL, CertInfo, {ok, Listen}} ->
lists:foreach(fun(XSC) -> call_start_mod(XSC) end, Group),
error_logger:info_msg(
"Yaws: Listening to ~s:~w for <~p> virtual servers:~s~n",
[inet_parse:ntoa(SC#sconf.listen),
SC#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(SC#sconf.listen),
SC#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),
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) ->
{ok, Pid} = yaws_stats:start_link(),
SC#sconf{stats = Pid}.
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, [{now(), 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);
_ when Reason == failaccept ->
error_logger:format(
"Accept proc died, terminate gserv",[]),
{links, Ls} = process_info(self(), links),
foreach(fun(X) -> unlink(X), exit(X, shutdown) end, Ls),
exit(noserver);
_ ->
GS2 = GS#gs{sessions = GS#gs.sessions - 1},
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(#sconf{stats=Pid}) when is_pid(Pid) ->
yaws_stats:stop(Pid);
(_) ->
ok
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, 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 ->
Pid = OldSc#sconf.stats,
error_logger:info_msg("update_sconf: Stats pid ~p~n",[Pid]),
case Pid of
undefined ->
ok;
Pid when is_pid(Pid) ->
yaws_stats:stop(Pid)
end,
NewSc1 = case ?sc_has_statistics(NewSc) of
true ->
start_stats(NewSc);
false ->
NewSc
end,
stop_ready(Ready, Last),
NewSc2 = clear_ets_complete(NewSc1),
%% Need to insert the sconf at the same position
%% it previously was
NewG = lists:map(fun(Sc) when Sc == OldSc->
NewSc2;
(Other) ->
Other
end, GS#gs.group),
GS2 = GS#gs{group = NewG},
Ready2 = [],
Updater ! {updated_sconf, self(), NewSc2},
gen_server:reply(From, ok),
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 ->
Pid = OldSc#sconf.stats,
stop_ready(Ready, Last),
GS2 = GS#gs{group = lists:delete(OldSc,GS#gs.group)},
Ready2 = [],
case ?sc_has_statistics(OldSc) of
true ->
error_logger:info_msg("delete_sconf: Pid= ~p~n",
[Pid]),
yaws_stats:stop(Pid);
false ->
ok
end,
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, SC0, Adder} ->
SC = case ?sc_has_statistics(SC0) of
true ->
{ok, Pid} = yaws_stats:start_link(),
error_logger:info_msg("add_sconf: Pid= ~p~n", [Pid]),
SC0#sconf{stats=Pid};
false ->
SC0
end,
stop_ready(Ready, Last),
SC2 = setup_ets(SC),
GS2 = GS#gs{group = GS#gs.group ++ [SC2]},
Ready2 = [],
Adder ! {added_sconf, self(), SC2},
gen_server:reply(From, ok),
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,
case lists:any(
fun(SC) ->
certinfo(SC#sconf.ssl) =/= CertInfo end,
GS#gs.group) of
true ->
yes;
false ->
no
end;
nossl ->
no
end,
if
Changed == no ->
From ! {self(), no},
?MODULE:gserv_loop(GS, Ready, Rnum, Last);
Changed == yes ->
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, _} = now(),
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) ->
Gpid ! {self(), stop},
receive
{Gpid, ok} ->
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},
{recbuf, 8192},
{reuseaddr, true},
{active, false}
| proplists:get_value(listen_opts, SC#sconf.soptions, [])
] ++ InetType.
ssl_listen_opts(GC, SC, SSL) ->
InetType = if
is_tuple( SC#sconf.listen), size( SC#sconf.listen) == 8 ->
[inet6];
true ->
[]
end,
[binary,
{ip, SC#sconf.listen},
{packet, http},
{recbuf, 8192},
{reuseaddr, true},
{active, false} | ssl_listen_opts(GC, SSL)] ++ InetType.
ssl_listen_opts(GC, 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.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.depth /= undefined ->
{depth, SSL#ssl.depth};
true ->
false
end,
if ?gc_use_old_ssl(GC) ->
{ssl_imp, old};
true ->
{ssl_imp, new}
end
],
filter_false(L).
filter_false(L) ->
[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),
X = do_accept(GS),
Top ! {self(), next, X},
case X of
{ok, Client} ->
if
GS#gs.ssl == ssl ->
case ssl:ssl_accept(Client) 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", [Reason]),
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,
case (GS#gs.gconf)#gconf.trace of %% traffic trace
{true, _} ->
case peername(Client, GS#gs.ssl) of
{ok, {IP, Port}} ->
Str = ?F("New (~p) connection from ~s:~w~n",
[GS#gs.ssl, inet_parse:ntoa(IP),Port]),
yaws_log:trace_traffic(from_client, Str);
_ ->
ignore
end;
_ ->
ok
end,
Res = (catch aloop(Client, 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', {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]),
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, GS, Num) ->
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
{Req0, H0} when Req0#http_request.method /= bad_request ->
{Req, H} = fix_abs_uri(Req0, H0),
?Debug("{Req, H} = ~p~n", [{Req, H}]),
SC = pick_sconf(GS#gs.gconf, H, GS#gs.group),
?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)]),
IP = case ?sc_has_access_log(SC) of
true ->
case peername(CliSock, SSL) of
{ok, {Ip, _Port}} ->
case ?gc_log_has_resolve_hostname(
(GS#gs.gconf)) of
true ->
case inet:gethostbyaddr(Ip) of
{ok, HE} ->
HE#hostent.h_name;
_ ->
Ip
end;
false ->
Ip
end;
_ ->
undefined
end;
_ ->
undefined
end,
put(outh, #outh{}),
put(sc, SC),
yaws_stats:hit(),
check_keepalive_maxuses(GS, Num),
Call = case yaws_shaper:check(SC, IP) of
allow ->
call_method(Req#http_request.method,CliSock,Req,H);
{deny, Status, Msg} ->
deliver_xxx(CliSock, Req, Status, Msg)
end,
Call2 = fix_keepalive_maxuses(Call),
handle_method_result(Call2, CliSock, IP, GS, Req, H, Num);
closed ->
{ok, Num};
_ ->
% not even HTTP traffic
exit(normal)
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) ->
case (GS#gs.gconf)#gconf.keepalive_maxuses of
nolimit ->
ok;
0 ->
ok;
N when Num+1 < N ->
ok;
_N ->
put(outh, (get(outh))#outh{exceedmaxuses=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, 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, GS, Num+1);
done ->
yaws_shaper:update(get(sc), IP, Req),
maybe_access_log(IP, Req, H),
erase_transients(),
{ok, Num+1};
{page, P} ->
%% keep post_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_delayed_redirect' flag is used to correctly identify the url
%% type
put(is_delayed_redirect, true),
SC = pick_sconf(GS#gs.gconf, H, GS#gs.group),
put(sc, SC#sconf{appmods = []}),
check_keepalive_maxuses(GS, Num),
Call = call_method(Req#http_request.method,
CliSock,
Req#http_request{path = {abs_path, Page}},
H#headers{content_length = undefined}),
Call2 = fix_keepalive_maxuses(Call),
handle_method_result(Call2, CliSock, IP, GS, Req, H, Num)
end.
peername(CliSock, ssl) ->
ssl:peername(CliSock);
peername(CliSock, nossl) ->
inet:peername(CliSock).
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.
pick_sconf(GC, H, Group) ->
case H#headers.host of
undefined when ?gc_pick_first_virthost_on_nomatch(GC) ->
hd(Group);
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 == []; SCs == [] ->
if
?gc_pick_first_virthost_on_nomatch(GC) ->
hd(Group);
true ->
yaws_debug:format("Drop req since ~p doesn't match any "
"servername \n", [Host]),
exit(normal)
end;
pick_host(GC, Host, [SC|T], Group) ->
case comp_sname(Host, SC#sconf.servername) of
true -> SC;
false -> pick_host(GC, Host, T, Group)
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_decode_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(now(), get(request_start_time)),
yaws_log:accesslog(SC, Ip, Req, H, get(outh), Time);
false ->
ignore
end.
safe_decode_path(Path) ->
case (catch decode_path(Path)) of
{'EXIT', _} ->
"/undecodable_path";
Val ->
Val
end.
decode_path({abs_path, Path}) ->
yaws_api:url_decode(Path).
%% ret: continue | done
'GET'(CliSock, Req, Head) ->
no_body_method(CliSock, Req, Head).
'POST'(CliSock, Req, Head) ->
?Debug("POST Req=~s~n H=~s~n", [?format_record(Req, http_request),
?format_record(Head, headers)]),
OtherHeaders = Head#headers.other,
Continue =
case lists:keysearch("Expect", 3, OtherHeaders) of
{value, {_,_,"Expect",_,Value}} ->
Value;
_ ->
""
end,
case yaws:to_lower(Continue) of
"100-continue" ->
deliver_100(CliSock),
body_method(CliSock, Req, Head);
_ ->
body_method(CliSock, Req, Head)
end.
un_partial({partial, Bin}) ->
Bin;
un_partial(Bin) ->
Bin.
call_method(Method, CliSock, Req, H) ->
case Method of
F when is_atom(F) ->
?MODULE:F(CliSock, Req, H);
L when is_list(L) ->
handle_extension_method(L, CliSock, Req, H)
end.
'HEAD'(CliSock, Req, Head) ->
put(acc_content, discard),
no_body_method(CliSock, Req, Head).
not_implemented(CliSock, Req, Head) ->
SC=get(sc),
ok = yaws:setopts(CliSock, [{packet, raw}, binary], yaws:is_ssl(SC)),
flush(CliSock, Head#headers.content_length),
deliver_501(CliSock, Req).
'TRACE'(CliSock, Req, Head) ->
not_implemented(CliSock, Req, Head).
'OPTIONS'(CliSock, 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, Req, Head)
end.
'PUT'(CliSock, Req, Head) ->
?Debug("PUT Req=~p~n H=~p~n", [?format_record(Req, http_request),
?format_record(Head, headers)]),
body_method(CliSock, Req, Head).
'DELETE'(CliSock, Req, Head) ->
no_body_method(CliSock, Req, Head).
%%%
%%% WebDav specifics: PROPFIND, MKCOL,....
%%%
'PROPFIND'(CliSock, Req, Head) ->
%%?elog("PROPFIND Req=~p H=~p~n",
%% [?format_record(Req, http_request),
%% ?format_record(Head, headers)]),
body_method(CliSock, Req, Head).
'MOVE'(CliSock, Req, Head) ->
no_body_method(CliSock, Req, Head).
'COPY'(CliSock, Req, Head) ->
no_body_method(CliSock, Req, Head).
body_method(CliSock, Req, Head) ->
SC=get(sc),
ok = yaws:setopts(CliSock, [{packet, raw}, binary], yaws:is_ssl(SC)),
PPS = SC#sconf.partial_post_size,
Bin = case Head#headers.content_length of
undefined ->
case Head#headers.transfer_encoding of
"chunked" ->
get_chunked_client_data(CliSock, yaws:is_ssl(SC));
_ ->
<<>>
end;
Len when is_integer(PPS) ->
Int_len = list_to_integer(Len),
if
Int_len == 0 ->
<<>>;
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;
Len when PPS == nolimit ->
Int_len = list_to_integer(Len),
if
Int_len == 0 ->
<<>>;
true ->
get_client_data(CliSock, Int_len,
yaws:is_ssl(SC))
end
end,
?Debug("Request data = ~s~n", [binary_to_list(un_partial(Bin))]),
ARG = make_arg(CliSock, Head, Req, Bin),
handle_request(CliSock, ARG, size(un_partial(Bin))).
'MKCOL'(CliSock, Req, Head) ->
no_body_method(CliSock, Req, Head).
no_body_method(CliSock, Req, Head) ->
SC=get(sc),
ok = yaws:setopts(CliSock, [{packet, raw}, binary], yaws:is_ssl(SC)),
flush(CliSock, Head#headers.content_length),
ARG = make_arg(CliSock, Head, Req, undefined),
handle_request(CliSock, ARG, 0).
make_arg(CliSock, Head, Req, Bin) ->
SC = get(sc),
IP = if
is_port(CliSock) ->
case inet:peername(CliSock) of
{ok, IpPort} ->
IpPort;
_ ->
{unknown, unknown}
end;
true ->
case ssl:peername(CliSock) of
{ok, IpPort} ->
IpPort;
_ ->
{unknown, unknown}
end
end,
ARG = #arg{clisock = CliSock,
client_ip_port = IP,
headers = Head,
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]).
handle_extension_method("PROPFIND", CliSock, Req, Head) ->
'PROPFIND'(CliSock, Req, Head);
handle_extension_method("MKCOL", CliSock, Req, Head) ->
'MKCOL'(CliSock, Req, Head);
handle_extension_method("MOVE", CliSock, Req, Head) ->
'MOVE'(CliSock, Req, Head);
handle_extension_method("COPY", CliSock, Req, Head) ->
'COPY'(CliSock, Req, Head);
handle_extension_method(_Method, CliSock, Req, Head) ->
not_implemented(CliSock, 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)]),
yaws:outh_set_status_code(State#rewrite_response.status),
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);
_ ->
accumulate_content(State#rewrite_response.content),
deliver_accumulated(ARG, CliSock, decide, 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}} ->
deliver_302_map(CliSock, Req, ARG, Redir);
{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}, _} ->
ARG1 = ARG#arg{server_path = DecPath,
querydata = QueryString},
handle_revproxy_request(CliSock, ARG1, PP,
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,
%% In case of delayed redirect, we must handle the
%% request as a dynamic one.
UT2 = case erase(is_delayed_redirect) of
true when UT#urltype.type =:= regular ->
UT#urltype{type=delayed_regular};
true when UT#urltype.type =:= directory ->
UT#urltype{type=delayed_directory};
_ ->
UT
end,
handle_ut(CliSock, ARG2, UT2, N);
false_403 ->
deliver_403(CliSock, ARG1#arg.req);
{false, AuthMethods, Realm} ->
UT1 = #urltype{type = {unauthorized, AuthMethods, Realm},
path = ARG1#arg.server_path},
handle_ut(CliSock, ARG1, UT1, N)
end.
handle_revproxy_request(CliSock, ARG, PP, 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 ->
yaws_revproxy:init(CliSock, ARG1, ARG1#arg.server_path,
ARG1#arg.querydata, PP, N);
false_403 ->
deliver_403(CliSock, ARG1#arg.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 =:= "/" ->
[];
[] ->
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, L) ->
Req_dir = ARG#arg.server_path,
H = ARG#arg.headers,
case lists:keyfind(ARG#arg.docroot, 1, L) of
{_, Auths} ->
is_auth(ARG, Req_dir, H, filter_auths(Auths, Req_dir), {true, []});
false ->
{true, []}
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) ->
%% Transform Client IP address in integer
IntIP =
case IP of
undefined ->
0;
{0,0,0,0,0,16#FFFF,N1,N2} ->
(N1 bsl 16) bor N2;
{N1,N2,N3,N4} ->
(N1 bsl 24) bor (N2 bsl 16) bor (N3 bsl 8) bor N4;
{N1,N2,N3,N4,N5,N6,N7,N8} ->
(N1 bsl 112) bor (N2 bsl 96) bor (N3 bsl 80) bor (N4 bsl 64) bor
(N5 bsl 48) bor (N6 bsl 32) bor (N7 bsl 16) bor N8
end,
%% Build the match_spec to test ACLs
MHead = {'$1', '$2'},
Guard = {'andalso',
{'=<', '$1', {const, IntIP}},
{'>=', '$2', {const, IntIP}}},
MSpec = [{MHead, [Guard], [true]}],
CMSpec = ets:match_spec_compile(MSpec),
Ret1 = case Auth_methods of
#auth{users=[],pam=false,mod=[]} -> true;
_ -> Ret
end,
case {AllowIPs, DenyIPs, Order} of
{_, all, deny_allow} ->
case ets:match_spec_run(AllowIPs, CMSpec) of
[true|_] ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1);
_ ->
false_403
end;
{all, _, deny_allow} ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1);
{_, _, deny_allow} ->
case ets:match_spec_run(DenyIPs, CMSpec) of
[true|_] ->
case ets:match_spec_run(AllowIPs, CMSpec) of
[true|_] ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none},
Ret1);
_ ->
false_403
end;
_ ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1)
end;
{_, all, allow_deny} ->
false_403;
{all, _, allow_deny} ->
case ets:match_spec_run(DenyIPs, CMSpec) of
[true|_] ->
false_403;
_ ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none}, Ret1)
end;
{_, _, allow_deny} ->
case ets:match_spec_run(AllowIPs, CMSpec) of
[true|_] ->
case ets:match_spec_run(DenyIPs, CMSpec) of
[true|_] ->
false_403;
_ ->
handle_auth(ARG, Auth_H, Auth_methods#auth{acl=none},
Ret1)
end;
_ ->
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),
deliver_accumulated(ARG#arg.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, Mod} ->
handle_auth(ARG, Auth_H, Auth_methods#auth{mod=[], outmod=Mod},
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 /= [] ->
case member({User, Password}, 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, {"/", fwdproxy_url(ARG)}}
end.
is_revproxy1(_,[]) ->
false;
is_revproxy1(Path, [{Prefix, URL} | Tail]) ->
case yaws:is_prefix(Prefix, Path) of
{true,_} ->
{true, {Prefix,URL}};
false ->
is_revproxy1(Path, Tail)
end.
is_redirect_map(_, []) ->
false;
is_redirect_map(Path, [E={Prefix, _URL, _AppendMode}|Tail]) ->
case yaws:is_prefix(Prefix, Path) of
{true, _} ->
{true, E};
false ->
is_redirect_map(Path, Tail)
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'],
if
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
'GET' -> fun() -> deliver_file(CliSock, Req,
UT, Range) end;
'HEAD' -> fun() -> deliver_accumulated(CliSock),
done 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),
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),
Do_deliver();
false ->
yaws:outh_set_304_headers(
Req, UT, H),
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),
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),
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 = delayed_regular}, _N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
Do_deliver =
case Req#http_request.method of
'HEAD' -> fun() -> deliver_accumulated(CliSock), done end;
_ -> fun() -> deliver_file(CliSock, Req, UT, all) end
end,
yaws:outh_set_dyn_headers(Req, H, UT),
maybe_set_page_options(),
Do_deliver();
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'],
if
Req#http_request.method == 'GET';
Req#http_request.method == 'HEAD' ->
yaws:outh_set_dyn_headers(Req, H, UT),
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 = delayed_directory}, N) ->
Req = ARG#arg.req,
H = ARG#arg.headers,
SC = get(sc),
if (?sc_has_dir_listings(SC)) ->
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));
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),
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),
{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,
SC=get(sc),
Next =
if
Req#http_request.method == 'PUT' ->
fun(A) -> yaws_dav:put(SC, A) end;
Req#http_request.method == 'DELETE' ->
fun(A) -> yaws_dav:delete(A) end;
Req#http_request.method == "PROPFIND" ->
fun(A)-> yaws_dav:propfind(A) end;
Req#http_request.method == "MOVE" ->
fun(A)-> yaws_dav:move(A) end;
Req#http_request.method == "COPY" ->
fun(A)-> yaws_dav:copy(A) end;
Req#http_request.method == "MKCOL" ->
fun(A)-> yaws_dav:mkcol(A) end;
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;
true ->
error
end,
case Next of
error ->
handle_ut(CliSock, ARG, #urltype{type = error}, N);
{regular, Finfo} ->
handle_ut(CliSock, ARG, UT#urltype{type = regular,
finfo = Finfo}, N);
_ ->
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.
%%!todo - review.
%% Why is DecPath being tokenized around "?" - only to reassemble??
%% What happens when Path is not flat?
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,
DecPath = yaws_api:url_decode(Path),
RedirHost = yaws:redirect_host(SC, Headers#headers.host),
Loc = case string:tokens(DecPath, "?") of
[P] ->
["Location: ", Scheme, RedirHost, P, "\r\n"];
[P, Q] ->
["Location: ", Scheme, RedirHost, P, "?", Q, "\r\n"]
end,
new_redir_h(H, Loc),
deliver_accumulated(CliSock),
done_or_continue().
deliver_302_map(CliSock, Req, Arg,
{_Prefix,URL,Mode}) when is_record(URL,url) ->
?Debug("in redir 302 ",[]),
H = get(outh),
DecPath = safe_decode_path(Req#http_request.path),
{P, Q} = yaws:split_at(DecPath, $?),
LocPath = yaws_api:format_partial_url(URL, get(sc)),
Loc = if
Mode == append ->
Newpath = filename:join([URL#url.path ++ P]),
NLocPath = yaws_api:format_partial_url(
URL#url{path = Newpath}, get(sc)),
case Q of
[] ->
["Location: ", NLocPath, "\r\n"];
_Q ->
["Location: ", NLocPath, "?", Q, "\r\n"]
end;
Mode == noappend,Q == [] ->
["Location: ", LocPath, "\r\n"];
Mode == noappend,Q /= [] ->
["Location: ", LocPath, "?", Q, "\r\n"]
end,
Headers = Arg#arg.headers,
{DoClose, _Chunked} = yaws:dcc(Req, Headers),
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),
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 = list_to_binary(["<html><h1>",
integer_to_list(Code), $\ ,
yaws_api:code_to_phrase(Code),
"</h1></html>",
ExtraHtml]),
H = #outh{status = Code,
doclose = true,
chunked = false,
server = yaws:make_server_header(),
connection = yaws:make_connection_close_header(true),
content_length = yaws:make_content_length_header(size(B)),
contlen = size(B),
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 = list_to_binary(["<html><h1>416 ",
yaws_api:code_to_phrase(416),
"</h1></html>"]),
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(size(B)),
contlen = size(B),
content_type = yaws:make_content_type_header("text/html")},
put(outh, H),
accumulate_content(B),
deliver_accumulated(CliSock),
done.
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 ->
del_old_files(get(gc),Other),
{ok, [{errors, Errs}| 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, Errs}),
deliver_dyn_file(CliSock, Spec, ARG, UT, N)
end.
del_old_files(_, []) ->
ok;
del_old_files(_GC, [{_FileAtom, spec, _Mtime1, Spec, _}]) ->
foreach(
fun({mod, _, _, _, Mod, _Func}) ->
F=filename:join([yaws:tmpdir(), "yaws",
yaws:to_list(Mod) ++ ".erl"]),
code:purge(Mod),
code:purge(Mod),
file:delete(F);
(_) ->
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 =:= undefined orelse Val =:= 0) ->
yaws:setopts(CliSock, [binary, {packet, line}],SSL),
N = yaws:get_chunk_num(CliSock,SSL),
yaws:setopts(CliSock, [binary, {packet, raw}],SSL),
N;
true ->
Val
end,
if
Len == 0 ->
_Tmp=yaws:do_recv(CliSock, 2, SSL),%% flush last crnl
<<>>;
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
CliDataPos, % for `get_more'
Arg,UT,
YawsFun, % call YawsFun(Arg)
DeliverCont % call DeliverCont(Arg)
% to continue normally
) ->
put(yaws_ut, UT),
put(yaws_arg, Arg),
Res = (catch YawsFun(Arg)),
case handle_out_reply(Res, LineNo, YawsFile, UT, Arg) of
{get_more, Cont, State} when
element(1, Arg#arg.clidata) == partial ->
More = get_more_post_data(CliDataPos, Arg),
A2 = Arg#arg{clidata = More,
cont = Cont,
state = State},
deliver_dyn_part(
CliSock, LineNo, YawsFile, CliDataPos+size(un_partial(More)),
A2, UT, YawsFun, DeliverCont
);
break ->
finish_up_dyn_file(Arg, CliSock);
{page, Page} ->
{page, Page};
Arg2 = #arg{} ->
DeliverCont(Arg2);
{streamcontent, MimeType, FirstChunk} ->
yaws:outh_set_content_type(MimeType),
accumulate_content(FirstChunk),
Priv = deliver_accumulated(Arg, CliSock,
decide, undefined, stream),
stream_loop_send(Priv, CliSock, 30000);
%% For other timeout values (other than 30 second)
{streamcontent_with_timeout, MimeType, FirstChunk, TimeOut} ->
yaws:outh_set_content_type(MimeType),
accumulate_content(FirstChunk),
Priv = deliver_accumulated(Arg, CliSock,
decide, undefined, stream),
stream_loop_send(Priv, CliSock, TimeOut);
{streamcontent_with_size, Sz, MimeType, FirstChunk} ->
yaws:outh_set_content_type(MimeType),
accumulate_content(FirstChunk),
Priv = deliver_accumulated(Arg, CliSock,
decide, Sz, stream),
stream_loop_send(Priv, CliSock, 30000);
{streamcontent_from_pid, MimeType, Pid} ->
yaws:outh_set_content_type(MimeType),
Priv = deliver_accumulated(Arg, CliSock,
no, undefined, stream),
wait_for_streamcontent_pid(Priv, CliSock, Pid);
{websocket, OwnerPid, SocketMode} ->
%% The handshake passes control over the socket to OwnerPid
%% and terminates the Yaws worker!
yaws_websockets:handshake(Arg, OwnerPid, SocketMode);
_ ->
DeliverCont(Arg)
end.
finish_up_dyn_file(Arg, CliSock) ->
deliver_accumulated(Arg, CliSock, decide, 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).
stream_loop_send(Priv, CliSock, Timeout) ->
{ok, FlushTimer} = timer:send_after(300, flush_timer),
case Timeout of
infinity ->
untimed_stream_loop_send(Priv, CliSock, FlushTimer);
_ ->
{ok, TimeoutTimer} = timer:send_after(Timeout, timeout_timer),
stream_loop_send(Priv, CliSock, Timeout,
FlushTimer, TimeoutTimer)
end.
untimed_stream_loop_send(Priv, CliSock, FlushTimer) ->
receive
{streamcontent, Cont} ->
P = send_streamcontent_chunk(Priv, CliSock, Cont),
untimed_stream_loop_send(P, CliSock, FlushTimer) ;
{streamcontent_with_ack, From, Cont} -> % acknowledge after send
P = send_streamcontent_chunk(Priv, CliSock, Cont),
From ! {self(), streamcontent_ack},
untimed_stream_loop_send(P, CliSock, FlushTimer) ;
endofstreamcontent ->
cancel_t(FlushTimer, flush_timer),
end_streaming(Priv, CliSock);
timeout_timer ->
cancel_t(FlushTimer, flush_timer),
erlang:error(stream_timeout);
flush_timer ->
P = sync_streamcontent(Priv, CliSock),
untimed_stream_loop_send(P, CliSock, FlushTimer)
end.
cancel_t(T, Msg) ->
timer:cancel(T),
receive
Msg -> ok
after 0 -> ok
end.
stream_loop_send(Priv, CliSock, Timeout,
FlushTimer, TimeoutTimer) ->
receive
{streamcontent, Cont} ->
P = send_streamcontent_chunk(Priv, CliSock, Cont),
cancel_t(TimeoutTimer, timeout_timer),
{ok, TimeoutTimer2} = timer:send_after(Timeout, timeout_timer),
stream_loop_send(P, CliSock, Timeout,
FlushTimer, TimeoutTimer2) ;
{streamcontent_with_ack, From, Cont} -> % acknowledge after send
P = send_streamcontent_chunk(Priv, CliSock, Cont),
From ! {self(), streamcontent_ack},
cancel_t(TimeoutTimer, timeout_timer),
{ok, TimeoutTimer2} = timer:send_after(Timeout, timeout_timer),
stream_loop_send(P, CliSock, Timeout,
FlushTimer, TimeoutTimer2) ;
endofstreamcontent ->
cancel_t(TimeoutTimer, timeout_timer),
cancel_t(FlushTimer, flush_timer),
end_streaming(Priv, CliSock);
timeout_timer ->
cancel_t(TimeoutTimer, timeout_timer),
cancel_t(FlushTimer, flush_timer),
erlang:error(stream_timeout);
flush_timer ->
P = sync_streamcontent(Priv, CliSock),
stream_loop_send(P, CliSock, Timeout,
FlushTimer, TimeoutTimer)
end.
make_chunk(Data) ->
case yaws:outh_get_chunked() of
true ->
case binary_size(Data) of
0 ->
empty;
S ->
CRNL = crnl(),
{S, [yaws:integer_to_hex(S), CRNL, Data, CRNL]}
end;
false ->
{binary_size(Data), Data}
end.
make_final_chunk(Data) ->
case yaws:outh_get_chunked() of
true ->
CRNL = crnl(),
case binary_size(Data) of
0 ->
{0, ["0",CRNL,CRNL]};
S ->
{S, [yaws:integer_to_hex(S), CRNL, Data, CRNL,
"0", CRNL, CRNL]}
end;
false ->
{binary_size(Data), Data}
end.
send_streamcontent_chunk(discard, _, _) ->
discard;
send_streamcontent_chunk(undeflated, 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,
undeflated;
send_streamcontent_chunk({Z, Priv}, CliSock, Data) ->
?Debug("send ~p bytes to ~p ~n",
[binary_size(Data), CliSock]),
{ok, P, D} = yaws_zlib:gzipDeflate(Z, Priv, 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(undeflated, _CliSock) ->
undeflated;
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(undeflated, 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}.
to_binary(B) when is_binary(B) ->
B;
to_binary(L) when is_list(L) ->
list_to_binary(L).
%% binary_size(X) -> size(to_binary(X)).
binary_size(X) -> binary_size(0,X).
binary_size(I, []) ->
I;
binary_size(I, [H|T]) ->
J = binary_size(I, H),
binary_size(J, T);
binary_size(I, B) when is_binary(B) ->
I + size(B);
binary_size(I, _Int) when is_integer(_Int) ->
I+1.
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
[$/|_] ->
url_type( Yfile, ARG#arg.docroot, ARG#arg.docroot_mount);
_Else ->
%%why lists:flatten? is urltype.dir ever nested more than
%% 1 level deep?
%%!todo - replace with conc_path if 1 level - or specify that
%% urltype fields should be written flat!
%% All this deep listing of relatively *short* strings
%seems unwieldy. just how much performance can it gain if we
%% end up using slower funcs like lists:flatten anyway?
%% review!.
url_type(lists:flatten(UT#urltype.dir) ++ [$/|Yfile],
ARG#arg.docroot, ARG#arg.docroot_mount)
end,
case UT2#urltype.type of
yaws ->
Mtime = mtime(UT2#urltype.finfo),
Key = UT2#urltype.getpath,
CliSock = ARG#arg.clisock,
N = 0,
case ets:lookup(SC#sconf.ets, Key) of
[{_Key, spec, Mtime1, Spec, Es}] when Mtime1 == Mtime,
Es == 0 ->
deliver_dyn_file(CliSock, Spec ++ [yssi], ARG, UT2, N);
Other ->
del_old_files(get(gc), Other),
{ok, [{errors, Errs}| Spec]} =
yaws_compile:compile_file(UT2#urltype.fullpath),
?Debug("Spec for file ~s is:~n~p~n",
[UT2#urltype.fullpath, Spec]),
ets:insert(SC#sconf.ets, {Key, spec, Mtime, Spec, Errs}),
deliver_dyn_file(CliSock, Spec ++ [yssi], ARG, UT2, N)
end;
error when SC#sconf.xtra_docroots /= [] ->
SC2 = SC#sconf{docroot = hd(SC#sconf.xtra_docroots),
xtra_docroots = tl(SC#sconf.xtra_docroots)},
put(sc, SC2), ARG2 = ARG#arg{docroot = SC2#sconf.docroot},
Ret = handle_out_reply({yssi, Yfile}, LineNo, YawsFile, UT, ARG2),
put(sc, SC),
Ret;
_ ->
error_logger:format("Failed to yssi ~p~n", [Yfile]),
ok
end;
handle_out_reply({html, Html}, _LineNo, _YawsFile, _UT, _ARG) ->
accumulate_content(Html);
handle_out_reply({ehtml, E}, _LineNo, _YawsFile, _UT, ARG) ->
Res = case safe_ehtml_expand(E) of
{ok, Val} ->
accumulate_content(Val);
{error, ErrStr} ->
handle_crash(ARG, ErrStr)
end,
Res;
handle_out_reply({exhtml, E}, _LineNo, _YawsFile, _UT, A) ->
N = count_trailing_spaces(),
Res = case yaws_exhtml:format(E, N) of
{ok, Val} ->
accumulate_content(Val);
{error, ErrStr} ->
handle_crash(A,ErrStr)
end,
Res;
handle_out_reply({exhtml, Value2StringF, E}, _LineNo, _YawsFile, _UT, A) ->
N = count_trailing_spaces(),
Res = case yaws_exhtml:format(E, N, Value2StringF) of
{ok, Val} ->
accumulate_content(Val);
{error, ErrStr} ->
handle_crash(A,ErrStr)
end,
Res;
handle_out_reply({sexhtml, E}, _LineNo, _YawsFile, _UT, A) ->
Res = case yaws_exhtml:sformat(E) of
{ok, Val} ->
accumulate_content(Val);
{error, ErrStr} ->
handle_crash(A,ErrStr)
end,
Res;
handle_out_reply({sexhtml, Value2StringF, E},
_LineNo, _YawsFile, _UT, A) ->
Res = case yaws_exhtml:sformat(E, Value2StringF) of
{ok, Val} ->
accumulate_content(Val);
{error, ErrStr} ->
handle_crash(A,ErrStr)
end,
Res;
handle_out_reply({content, MimeType, Cont}, _LineNo,_YawsFile, _UT, _ARG) ->
yaws:outh_set_content_type(MimeType),
accumulate_content(Cont);
handle_out_reply({streamcontent, MimeType, First},
_LineNo,_YawsFile, _UT, _ARG) ->
yaws:outh_set_content_type(MimeType),
{streamcontent, MimeType, First};
handle_out_reply({streamcontent_with_timeout, MimeType, First, Timeout},
_LineNo,_YawsFile, _UT, _ARG) ->
yaws:outh_set_content_type(MimeType),
{streamcontent_with_timeout, MimeType, First, Timeout};
handle_out_reply(Res = {page, _Page},
_LineNo,_YawsFile, _UT, _ARG) ->
Res;
handle_out_reply({streamcontent_with_size, Sz, MimeType, First},
_LineNo,_YawsFile, _UT, _ARG) ->
yaws:outh_set_content_type(MimeType),
{streamcontent_with_size, Sz, MimeType, First};
handle_out_reply({streamcontent_from_pid, MimeType, Pid},
_LineNo,_YawsFile, _UT, _ARG) ->
yaws:outh_set_content_type(MimeType),
{streamcontent_from_pid, MimeType, Pid};
handle_out_reply({websocket, _OwnerPid, _SocketMode}=Reply,
_LineNo,_YawsFile, _UT, _ARG) ->
yaws:accumulate_header({connection, erase}),
Reply;
handle_out_reply({header, H}, _LineNo, _YawsFile, _UT, _ARG) ->
yaws:accumulate_header(H);
handle_out_reply({allheaders, Hs}, _LineNo, _YawsFile, _UT, _ARG) ->
yaws:outh_clear_headers(),
foreach(fun({header, Head}) -> yaws:accumulate_header(Head) end, Hs);
handle_out_reply({status, Code},_LineNo,_YawsFile,_UT,_ARG)
when is_integer(Code) ->
yaws:outh_set_status_code(Code);
handle_out_reply({'EXIT', normal}, _LineNo, _YawsFile, _UT, _ARG) ->
exit(normal);
handle_out_reply({ssi, File, Delimiter, Bindings}, LineNo, YawsFile, UT, ARG) ->
case ssi(File, Delimiter, Bindings, UT, ARG) of
{error, Rsn} ->
L = ?F("yaws code at~s:~p had the following err:~n~p",
[YawsFile, LineNo, Rsn]),
handle_crash(ARG, L);
OutData ->
accumulate_content(OutData)
end;
handle_out_reply(break, _LineNo, _YawsFile, _UT, _ARG) ->
break;
handle_out_reply({redirect_local, Path}, LN, YF, UT, ARG) ->
handle_out_reply({redirect_local, Path, 302}, LN, YF, UT, ARG);
%% What about:
%%
%% handle_out_reply({redirect_local, Path, Status}, LineNo,
%% YawsFile, SC, ARG) when string(Path) ->
%% handle_out_reply({redirect_local, {any_path, Path}, Status}, LineNo,
%% YawsFile, SC, ARG);
%%
%% It would introduce a slight incompatibility with earlier versions,
%% but might be desirable.
handle_out_reply({redirect_local, {any_path, URL}, Status}, LineNo,
YawsFile, _UT, ARG) ->
PathType =
case yaws_api:is_absolute_URI(URL) of
true -> net_path;
false -> case URL of
[$/|_] -> abs_path;
_ -> rel_path
end
end,
handle_out_reply({redirect_local, {PathType, URL}, Status}, LineNo,
YawsFile, _UT, ARG);
handle_out_reply({redirect_local, {net_path, URL}, Status}, _LineNo,
_YawsFile, _UT, _ARG) ->
Loc = ["Location: ", URL, "\r\n"],
new_redir_h(get(outh), Loc, Status),
ok;
handle_out_reply({redirect_local, Path0, Status}, _LineNo,_YawsFile,_UT, ARG) ->
SC=get(sc),
Path = case Path0 of
{abs_path, P} ->
P;
{rel_path, P} ->
{abs_path, RP} = (ARG#arg.req)#http_request.path,
case string:rchr(RP, $/) of
0 ->
[$/|P];
N ->
[lists:sublist(RP, N),P]
end;
P ->
P
end,
Scheme = yaws:redirect_scheme(SC),
Headers = ARG#arg.headers,
HostPort = yaws:redirect_host(SC, Headers#headers.host),
Loc = ["Location: ", Scheme, HostPort, Path, "\r\n"],
new_redir_h(get(outh), Loc, Status),
ok;
handle_out_reply({redirect, URL}, LN, YF, UT, ARG) ->
handle_out_reply({redirect, URL, 302}, LN, YF, UT, ARG);
handle_out_reply({redirect, URL, Status}, _LineNo, _YawsFile, _UT, _ARG) ->
Loc = ["Location: ", URL, "\r\n"],
new_redir_h(get(outh), Loc, Status),
ok;
handle_out_reply({bindings, L}, _LineNo, _YawsFile, _UT, _ARG) ->
foreach(fun({Key, Value}) -> put({binding, Key}, Value) end, L),
ok;
handle_out_reply(ok, _LineNo, _YawsFile, _UT, _ARG) ->
ok;
handle_out_reply({'EXIT', Err}, LineNo, YawsFile, _UT, ARG) ->
L = ?F("~n~nERROR erlang code crashed:~n "
"File: ~s:~w~n"
"Reason: ~p~nReq: ~p~n"
"Stack: ~p~n",
[YawsFile, LineNo, Err, ARG#arg.req, erlang:get_stacktrace()]),
handle_crash(ARG, L);
handle_out_reply({get_more, Cont, State}, _LineNo, _YawsFile, _UT, _ARG) ->
{get_more, Cont, State};
handle_out_reply(Arg = #arg{}, _LineNo, _YawsFile, _UT, _ARG) ->
Arg;
handle_out_reply(Reply, LineNo, YawsFile, _UT, ARG) ->
L = ?F("yaws code at ~s:~p crashed or "
"ret bad val:~p ~nReq: ~p",
[YawsFile, LineNo, Reply, ARG#arg.req]),
handle_crash(ARG, L).
handle_out_reply_l([Reply|T], LineNo, YawsFile, UT, ARG, _Res) ->
case handle_out_reply(Reply, LineNo, YawsFile, UT, ARG) of
break ->
break;
{page, Page} ->
{page, Page};
RetVal ->
handle_out_reply_l(T, LineNo, YawsFile, UT, ARG, RetVal)
end;
handle_out_reply_l([], _LineNo, _YawsFile, _UT, _ARG, Res) ->
Res.
count_trailing_spaces() ->
case get(acc_content) of
undefined -> 0;
discard -> 0;
List ->
Binary = first_binary(List),
yaws_exhtml:count_trailing_spaces(Binary)
end.
first_binary([Binary|_]) when is_binary(Binary) -> Binary;
first_binary([List|_Rest]) when is_list(List) -> first_binary(List).
%% fast server side include with macrolike variable bindings expansion
%%
ssi(File, Delimiter, Bindings) ->
ssi(File, Delimiter, Bindings, get(yaws_ut), get(yaws_arg), get (sc)).
ssi(File, Delimiter, Bindings, UT, ARG) ->
ssi(File, Delimiter, Bindings, UT, ARG, get(sc)).
ssi(File, Delimiter, Bindings, UT, ARG, SC) ->