Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

cache_refresh to 0 if debug, added a cookie_session-server for persis…

…tant cookie sessions

git-svn-id: https://erlyaws.svn.sourceforge.net/svnroot/erlyaws/trunk/yaws@173 9fbdc01b-0d2c-0410-bfb7-fb27d70d8b52
  • Loading branch information...
commit 80771c8649344a00db5900b9ab00d8d61b61554d 1 parent abc3a43
@klacke authored
View
12 include/yaws_api.hrl
@@ -51,3 +51,15 @@
+%% portal server has a queue of these
+-record(ysession,
+ {cookie, %% the cookie assigned to the session
+ auth, %% the #auth structure as returned from portal_auth
+ to, %% greg secs untill timeout death
+ user, %% The user name
+ passwd, %% And the passwd of that user
+ starttime, %% When calendar:local_time() did sess start
+ opaque %% any data the user supplies
+ }).
+
+
View
3  src/Makefile
@@ -22,7 +22,8 @@ MODULES=yaws \
yaws_ssl \
yaws_vsn \
mime_type_c \
- mime_types
+ mime_types \
+ yaws_session_server
EBIN_FILES=$(MODULES:%=../ebin/%.$(EMULATOR)) ../ebin/yaws.app
View
30 src/yaws_api.erl
@@ -19,6 +19,10 @@
-export([find_cookie_val/2, secs/0, url_decode/1]).
-export([get_line/1, mime_type/1]).
-export([stream_chunk_deliver/2, stream_chunk_end/1]).
+-export([new_cookie_session/3,
+ cookieval_to_session/1,
+ print_cookie_sessions/0,
+ replace_cookie_session/2]).
%% these are a bunch of function that are useful inside
%% yaws scripts
@@ -511,7 +515,7 @@ setcookie(_Name, _Value, _Path, _Expire, _Domain, _Secure) ->
-%% This function can be passed the cookie we get in the Arg#arg.cookies
+%% This function can be passed the cookie we get in the Arg#arg.headers.cookies
%% to search for a specific cookie
%% return [] if not found
%% Str if found
@@ -597,17 +601,35 @@ get_line("\r\n" ++ Tail, Cur) ->
get_line([H|T], Cur) ->
get_line(T, [H|Cur]).
+
+
mime_type(FileName) ->
{_, MT} = mime_types:t(filename:extension(FileName)),
MT.
-
-
-
stream_chunk_deliver(YawsPid, Data) ->
YawsPid ! {streamcontent, Data}.
stream_chunk_end(YawsPid) ->
YawsPid ! endofstreamcontent.
+
+
+
+new_cookie_session(User, Passwd, Opaque) ->
+ yaws_session_server:new_session(User, Passwd, Opaque).
+
+%% as returned in #ysession.cookie
+cookieval_to_session(CookieVal) ->
+ yaws_session_server:cookieval_to_session(CookieVal).
+
+print_cookie_sessions() ->
+ yaws_session_server:print_sessions().
+
+replace_cookie_session(Session, User) ->
+ yaws_session_server:replace_session(Session, User).
+
+
+
+
View
19 src/yaws_config.erl
@@ -41,7 +41,7 @@ load({file, File}, Trace, Debug) ->
yaws_log:infolog("Using config file ~s", [File]),
case file:open(File, [read]) of
{ok, FD} ->
- GC = make_default_gconf(),
+ GC = make_default_gconf(Debug),
R = (catch fload(FD, globals, GC#gconf{file = File,
trace = Trace,
debug = Debug
@@ -153,12 +153,18 @@ add_port(C, Port) ->
end.
-make_default_gconf() ->
+make_default_gconf(Debug) ->
Y = yaws_dir(),
#gconf{yaws_dir = Y,
ebin_dir = [filename:join([Y, "examples/ebin"])],
include_dir = [filename:join([Y, "examples/include"])],
logdir = ".",
+ cache_refresh_secs = if
+ Debug == true ->
+ 0;
+ true ->
+ 30
+ end,
uid = os:cmd("id -u") -- [10],
yaws = "Yaws " ++ yaws_vsn:version()}.
@@ -302,6 +308,15 @@ fload(FD, server, GC, C, Cs, Lno, Chars) ->
case toks(Chars) of
[] ->
fload(FD, server, GC, C, Cs, Lno+1, Next);
+
+ ["access_log", '=', Bool] ->
+ case is_bool(Bool) of
+ {true, Val} ->
+ C2 = C#sconf{access_log = Val},
+ fload(FD, server, GC, C2, Cs, Lno+1, Next);
+ false ->
+ {error, ?F("Expect true|false at line ~w", [Lno])}
+ end;
["port", '=', Val] ->
case (catch list_to_integer(Val)) of
I when integer(I) ->
View
24 src/yaws_server.erl
@@ -1789,7 +1789,7 @@ url_type(GC, SC, Path) ->
FI = UT#urltype.finfo,
Refresh = GC#gconf.cache_refresh_secs,
if
- ((N-When) > Refresh) ->
+ ((N-When) >= Refresh) ->
?Debug("Timed out entry for ~s ~p~n", [Path, {When, N}]),
%% more than 30 secs old entry
ets:delete(E, {url, Path}),
@@ -2043,28 +2043,6 @@ drop_till_dot([]) ->
[].
-
-
-% %% FIXME add all mime types here
-% suffix_type("sway." ++ _) ->
-% {yaws, "text/html"};
-% suffix_type("lmth." ++ _) ->
-% {regular, "text/html"};
-% suffix_type("gpj." ++ _) ->
-% {regular, "image/jpeg"};
-% suffix_type("gnp." ++ _) ->
-% {regular, "image/png"};
-% suffix_type("fig." ++ _) ->
-% {regular, "image/gif"};
-% suffix_type("3pm." ++ _) ->
-% {regular, "audio/mpeg"};
-% suffix_type("zg." ++ _) ->
-% {regular, "application/x-gzip"};
-% suffix_type(_) ->
-% {regular, "application/octet-stream"}.
-
-
-
flush(SC, Sock, Sz) ->
case SC#sconf.ssl of
undefined ->
View
313 src/yaws_session_server.erl
@@ -0,0 +1,313 @@
+%%%----------------------------------------------------------------------
+%%% File : yaws_session_server.erl
+%%% Author : Claes Wikstrom <klacke@hyber.org>
+%%% Purpose : maintain state for cookie sessions
+%%% Created : 17 Sep 2002 by Claes Wikstrom <klacke@hyber.org>
+%%%----------------------------------------------------------------------
+
+-module(yaws_session_server).
+-author('klacke@hyber.org').
+
+
+-behaviour(gen_server).
+
+%% External exports
+-export([start_link/0, start/0]).
+
+%% gen_server callbacks
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
+-compile(export_all).
+
+-include("../include/yaws_api.hrl").
+
+
+-record(state,
+ {ss, %% time queue of sessions
+ ttl = (1000 * 60 * 30) %% 30 minutes
+ }).
+
+
+
+
+%%%----------------------------------------------------------------------
+%%% API
+%%%----------------------------------------------------------------------
+start_link() ->
+ gen_server:start_link({local, yaws_session_server},
+ yaws_session_server, [], []).
+start() ->
+ gen_server:start({local, yaws_session_server},
+ yaws_session_server, [], []).
+stop() ->
+ gen_server:call(?MODULE, stop).
+
+
+new_session(User, Passwd, Opaque) ->
+ gen_server:call(?MODULE, {new_session, User, Passwd, Opaque}).
+cookieval_to_session(C) ->
+ gen_server:call(?MODULE, {cookieval_to_session, C}).
+print_sessions() ->
+ Ss = qto_list(gen_server:call(?MODULE, sessions)),
+ io:format("** ~p users logged in ~n~n", [length(Ss)]),
+ N = gnow(),
+ lists:foreach(fun(S) ->
+ io:format("User ~p ~n", [S#ysession.user]),
+ io:format("Cookie ~p ~n", [S#ysession.cookie]),
+ io:format("Start ~p ~n", [S#ysession.starttime]),
+ io:format("TTL ~p secs~n", [S#ysession.to - N]),
+ io:format("Opaque ~p ~n~n~n", [S#ysession.opaque]),
+ ok
+ end, Ss).
+
+
+replace_session(Session, User) ->
+ gen_server:call(?MODULE, {replace_session, Session, User}).
+
+
+
+%%%----------------------------------------------------------------------
+%%% Callback functions from gen_server
+%%%----------------------------------------------------------------------
+
+%%----------------------------------------------------------------------
+%% Func: init/1
+%% Returns: {ok, State} |
+%% {ok, State, Timeout} |
+%% ignore |
+%% {stop, Reason}
+%%----------------------------------------------------------------------
+init([]) ->
+ {X,Y,Z} = seed(),
+ random:seed(X, Y, Z),
+ {ok, #state{ss = qnew()}}.
+
+
+
+%% pretty good seed, but non portable
+seed() ->
+ case (catch list_to_binary(
+ os:cmd("dd if=/dev/random ibs=12 count=1 2>/dev/null"))) of
+ <<X:32, Y:32, Z:32>> ->
+ {X, Y, Z};
+ _ ->
+ now()
+ 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({new_session, User, Passwd, Opaque}, _From, State) ->
+ Ss = State#state.ss,
+ Now = gnow(),
+ case qkeysearch(User, #ysession.user, Ss) of
+ {value, S} ->
+ tret({error, {has_session, S}}, Now, State);
+ false ->
+ N = random:uniform(16#ffffffffffffffff), %% 64 bits
+ TS = calendar:local_time(),
+ C = atom_to_list(node()) ++ [$-|integer_to_list(N)],
+ NS = #ysession{cookie = C,
+ user = User,
+ passwd = Passwd,
+ starttime = TS,
+ opaque = Opaque,
+ to = gnow() + State#state.ttl},
+ tret({ok, NS}, Now, State#state{ss = qin(NS, Ss)})
+ end;
+
+
+handle_call({cookieval_to_session, C}, _From, State) ->
+ Q = State#state.ss,
+ Now = gnow(),
+ case qkey_delete(C, #ysession.cookie, Q) of
+ {value, Session, Q2} ->
+ Q3 = qin(Session#ysession{to = Now + State#state.ttl}, Q2),
+ tret({ok, Session}, Now, State#state{ss = Q3});
+ false ->
+ tret({error,no_session}, Now, State)
+ end;
+
+handle_call({replace_session, S, User}, From, State) ->
+ Q = State#state.ss,
+ Now = gnow(),
+ case qkey_delete(User, #ysession.user, Q) of
+ {value, Session, Q2} ->
+ Q3 = qin(S#ysession{to = Now + State#state.ttl}, Q2),
+ tret(ok, Now, State#state{ss = Q3});
+ false ->
+ tret({error,no_session}, Now, State)
+ end;
+
+
+handle_call(sessions, _From, State) ->
+ tret(State#state.ss, gnow(), State);
+handle_call(stop, _From, State) ->
+ {stop, stopped, State}.
+
+
+
+%% a timeout return value based on the oldest session in
+%% the queue
+
+tret(Val, Now, State) ->
+ {S2, TO} = state_to_timeout(State, Now),
+ {reply, Val, S2, TO}.
+
+
+
+state_to_timeout(State, Now) ->
+ Q = State#state.ss,
+ case qfirst(Q) of
+ {{value, OldestSession}, Q2} ->
+ TO = OldestSession#ysession.to,
+ if
+ TO > Now ->
+ {State#state{ss = Q2}, (TO - Now) * 1000};
+ true ->
+ %% oldest item has timed out
+ %% we need to remove it
+ {{value, O}, Q3} = qout(Q2),
+ report_timedout_sess(O),
+ state_to_timeout(State#state{ss = Q3}, Now)
+ end;
+ {empty, _} ->
+ {State, infinity}
+ end.
+
+
+report_timedout_sess(S) ->
+ error_logger:info_msg("Session for user ~p timedout ", [S#ysession.user]).
+
+
+%%----------------------------------------------------------------------
+%% 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(timeout, State) ->
+ Q = State#state.ss,
+ case qout(Q) of
+ {{value, S}, Q2} ->
+ report_timedout_sess(S),
+ {S2, TO} = state_to_timeout(State#state{ss = Q2}, gnow()),
+ {noreply, S2, TO};
+ {empty, _} ->
+ {noreply, State}
+ end.
+
+
+%%----------------------------------------------------------------------
+%% Func: terminate/2
+%% Purpose: Shutdown the server
+%% Returns: any (ignored by gen_server)
+%%----------------------------------------------------------------------
+terminate(_Reason, _State) ->
+ ok.
+
+%%%----------------------------------------------------------------------
+%%% Internal functions
+%%%----------------------------------------------------------------------
+
+
+
+gnow() ->
+ calendar:datetime_to_gregorian_seconds(
+ calendar:local_time()).
+
+
+
+
+%% queue ops
+
+qnew() -> {[], []}.
+
+qin(X, {In, Out}) -> {[X|In], Out}.
+
+
+qout({In, [H|Out]}) ->
+ {{value, H}, {In, Out}};
+qout({[], []}) ->
+ {empty, {[],[]}};
+qout({In, _}) ->
+ qout({[], lists:reverse(In)}).
+
+
+
+qfirst({In, [H|Out]}) ->
+ {{value, H}, {In, [H|Out]}};
+qfirst({[], []}) ->
+ {empty, {[],[]}};
+qfirst({In, _}) ->
+ qfirst({[], lists:reverse(In)}).
+
+
+
+
+qto_list({In, Out}) ->
+ lists:append(Out, lists:reverse(In)).
+
+
+qkey_delete(Key, Pos, {Head, Tail}) ->
+ case qkey_search_delete(Key, Pos, Head, []) of
+ false ->
+ case qkey_search_delete(Key,Pos, Tail, []) of
+ false ->
+ false;
+ {value, Val, L2} ->
+ {value, Val, {Head, L2}}
+ end;
+ {value, Val, L3} ->
+ {value, Val, {L3, Tail}}
+ end.
+
+
+
+qkeysearch(Key, Pos, {Head, Tail}) ->
+ case lists:keysearch(Key, Pos, Head) of
+ {value, Val} ->
+ {value, Val};
+ false ->
+ lists:keysearch(Key,Pos, Tail)
+ end.
+
+
+
+qkeyreplace(Key, Pos, Item, {Head, Tail}) ->
+ {lists:keyreplace(Key,Pos,Head,Item),
+ lists:keyreplace(Key,Pos,Tail,Item)}.
+
+
+
+%% internal functions
+
+qkey_search_delete(_Key, _Pos, [], _Ack) ->
+ false;
+qkey_search_delete(Key,Pos, [H|T], Ack) ->
+ if
+ element(Pos, H) == Key ->
+ {value, H, lists:reverse(Ack) ++ T};
+ true ->
+ qkey_search_delete(Key,Pos, T, [H|Ack])
+ end.
+
View
6 src/yaws_sup.erl
@@ -30,10 +30,14 @@ start_link() ->
%%----------------------------------------------------------------------
%%----------------------------------------------------------------------
init([]) ->
+
+ Sess = {yaws_session_server, {yaws_session_server, start_link, []},
+ permanent, 5000, worker, [yaws_session_server]},
+
YawsLog = {yaws_log, {yaws_log, start_link, []},
permanent, 5000, worker, [yaws_log]},
YawsServ = {yaws_server, {yaws_server, start_link, []},
permanent, 5000, worker, [yaws_server]},
- {ok,{{one_for_all,4,30}, [YawsLog, YawsServ]}}.
+ {ok,{{one_for_all,4,30}, [YawsLog, YawsServ, Sess]}}.
View
72 www/api.yaws
@@ -14,78 +14,10 @@ First and foremost, the entire regular erlang API is available, this
include mnesia ans all other erlang applications that are part of the
regular erlang system.
<p>
-Here we describe the functions available in the module
-<code>yaws_api</code>.
-
-
-<hr><h3> <code>redirect(Location)</code></h3>
-Issue a HTTP redirect to <code>Location</code>
-
-
-<hr><h3> <code>setcookie(Name, Value, [Path, [Expire, [Domain, Secure]]])</code></h3>
-Sets a cookie. Usage examples avilable in <a href="cookies.yaws">cookies.yaws</a>
-
-
-
-<hr><h3> <code>pre_ssi_files(DocRoot, Files)</code></h3>
-This function will perform a server side include on the files in
-<code>Files</code>. The data will be transformed so that all
-&lt;, &gt;, &amp; cahracters are htmlized. This useful for including
-code snippets in the yaws code.
-
-
-<hr><h3> <code>pre_ssi_string(String)</code></h3>
-Same as above, but for an explicit string
-
-
-
-<hr><h3><code>htmlize(Binary)</code></h3>
-Transform data so that all
-&lt;, &gt;, &amp; characters and line breaks are htmlized.
-
-<hr><h3><code>f(Fmt, Args)</code></h3>
-This function is automatically included in all yaws code. It is just
-a shortcut for: <code>io_lib:format(Fmt, Args)
-
-
-<hr><h3><code>fl([Fmt, Args .......])</code></h3>
-This function is automatically included in all yaws code. It will
-apply io_lib:format to a consecutive list of Fmt and Args arguments.
-
-
-<hr><h3><code>ssi([File1, File2 ...])</code></h3>
-Server side include, the files will be included as is into the
-document.
-
-
-
-<hr>
-<h3> <tt> parse_post_data(Arg) </tt> </h2>
-
-<p>
-When data is POST ed to a yaws page,
-this function can be used to parse the
-data in a convenient way. Data which is posted from a form is naturally
-arranged as a list of {key,value} pairs.
-<p> Similarly, when data is fed to a yaws page in the URL as a query part,
-the query can be parsed by this function. More info on this at
-<a href="query.yaws">query.yaws</a>
-
-<p>We can see examples of the usage of this function in the files
-<a href="form.yaws">form.yaws</a> which feeds the POST data to the file
-<a href="post.yaws">post.yaws</a>. The file post.yaws will call this function
-to parse the data POSTed from the previous page.
-
-
-
-
-<hr>
-<h3><code> code_to_phrase(Code) </code></h2>
-<p>Makes a mapping from HTTP status codes to HTTP status code phrases.
-
-
+Here we describe the functions available in the yaws_api module
+<a href="man.yaws?page=yaws_api"> man page for <tt>yaws_api (5)</tt> </a>
<erl>
Please sign in to comment.
Something went wrong with that request. Please try again.