Current section

Files

Jump to
nitrogen_core src lib wf_context.erl
Raw

src/lib/wf_context.erl

% vim: sw=4 ts=4 et ft=erlang
% Nitrogen Web Framework for Erlang
% Copyright (c) 2008-2010 Rusty Klophaus
% See MIT-LICENSE for licensing information.
-module (wf_context).
-include("wf.hrl").
-export([
bridge/0,
bridge/1,
in_request/0,
socket/0,
caching/0,
caching/1,
path/0,
protocol/0,
uri/0,
url/0,
peer_ip/0,
peer_ip/1,
peer_ip/2,
request_method/0,
request_body/0,
status_code/0,
status_code/1,
content_type/1,
content_type/0,
encoding/0,
encoding/1,
download_as/1,
headers/0,
header/1,
header/2,
anchor/1,
anchor/0,
data/0,
data/1,
clear_data/0,
add_action/2,
actions/0,
next_action/0,
action_queue/0,
action_queue/1,
clear_action_queue/0,
new_action_queue/0,
page_context/0,
page_context/1,
entry_point/0,
entry_point/1,
series_id/0,
series_id/1,
page_module/0,
page_module/1,
path_info/0,
path_info/1,
async_mode/0,
async_mode/1,
event_context/0,
event_context/1,
type/0,
type/1,
event_module/0,
event_module/1,
event_tag/0,
event_tag/1,
event_validation_group/0,
event_validation_group/1,
event_handle_invalid/0,
event_handle_invalid/1,
handlers/0,
handlers/1,
handler/1,
restore_handler/1,
init_context/1,
make_handler/2,
context/0,
context/1,
script_nonce/0,
script_nonce/1,
content_security_policy/0,
content_security_policy/1
]).
-export([increment/1]).
%% Exports for backwards compatibility
-export([
request_bridge/0,
request_bridge/1,
response_bridge/0,
response_bridge/1
]).
%% This particular macro is really no longer needed the way it was when we were
%% using tuple calls, but I like the way the macros are highlighted when
%% a call uses ?BRIDGE, so I'm keeping it that way.
-define(BRIDGE, bridge()).
%%% REQUEST AND RESPONSE BRIDGE %%%
bridge() ->
Context=context(),
Context#context.bridge.
bridge(Bridge) ->
Context = context(),
context(Context#context{bridge=Bridge}).
in_request() ->
%% If we have a context set and it is a #context{} tuple, then we are in a
%% request.
is_record(context(), context).
socket() ->
sbw:socket(?BRIDGE).
path() ->
sbw:path(?BRIDGE).
protocol() ->
sbw:protocol(?BRIDGE).
uri() ->
sbw:uri(?BRIDGE).
url() ->
Protocol = wf:to_list(protocol()),
Host = header(host),
Uri = uri(),
Protocol ++ "://" ++ Host ++ Uri.
peer_ip() ->
sbw:peer_ip(?BRIDGE).
peer_ip(Proxies) ->
peer_ip(Proxies,x_forwarded_for).
peer_ip(Proxies,ForwardedHeader) ->
ConnIP = peer_ip(),
case header(ForwardedHeader) of
undefined -> ConnIP;
RawForwardedIP ->
ForwardedIP = wf_convert:parse_ip(RawForwardedIP),
DoesIPMatch = fun(Proxy) ->
wf_convert:parse_ip(Proxy) =:= ConnIP
end,
case lists:any(DoesIPMatch,Proxies) of
true -> ForwardedIP;
false -> ConnIP
end
end.
request_method() ->
case sbw:request_method(?BRIDGE) of
'GET' -> get;
get -> get;
'POST' -> post;
post -> post;
'DELETE' -> delete;
delete -> delete;
'PUT' -> put;
put -> put;
'TRACE' -> trace;
trace -> trace;
'HEAD' -> head;
head -> head;
'CONNECT' -> connect;
connect -> connect;
'OPTIONS' -> options;
options -> options;
Other -> list_to_existing_atom(string:to_lower(wf:to_list(Other)))
end.
request_body() ->
sbw:request_body(?BRIDGE).
status_code() ->
sbw:get_status_code(?BRIDGE).
status_code(StatusCode) ->
Bridge2 = sbw:set_status_code(StatusCode,?BRIDGE),
bridge(Bridge2),
ok.
content_type(ContentType) ->
header("Content-Type", ContentType).
content_type() ->
case sbw:get_response_header(<<"content-type">>,?BRIDGE) of
undefined -> "text/html";
ContentType -> ContentType
end.
download_as(Filename0) ->
Filename = wf_convert:url_encode(Filename0),
header("Content-Disposition", "attachment; filename=\"" ++ Filename ++ "\"").
headers() ->
sbw:headers(?BRIDGE).
header(Header) ->
sbw:header(Header, ?BRIDGE).
header(Header, Value) ->
Bridge2 = sbw:set_header(Header, Value, ?BRIDGE),
bridge(Bridge2),
ok.
-spec encoding(Encoding :: encoding()) -> ok.
encoding(Encoding) ->
Context = context(),
context(Context#context { encoding=Encoding }).
encoding() ->
Context = context(),
Context#context.encoding.
script_nonce() ->
Context = context(),
Context#context.script_nonce.
script_nonce(Value) ->
Context = context(),
context(Context#context{ script_nonce=Value }).
content_security_policy() ->
content_security_policy(wf_security_policy:generate()).
content_security_policy(undefined) ->
ok;
content_security_policy(Policy) ->
header("Content-Security-Policy", Policy).
%%% TRANSIENT CONTEXT %%%
anchor(Anchor) ->
Context = context(),
context(Context#context { anchor=Anchor }).
anchor() ->
Context = context(),
Context#context.anchor.
data() ->
Context = context(),
Context#context.data.
data(Data) ->
Context = context(),
context(Context#context { data = Data }).
clear_data() ->
Context = context(),
context(Context#context { data = [] }).
-spec add_action(Priority :: wire_priority(), Action :: actions()) -> ok.
add_action(Priority, Action) when ?IS_ACTION_PRIORITY(Priority) ->
ActionQueue = action_queue(),
NewActionQueue = wf_action_queue:in(Priority, Action, ActionQueue),
action_queue(NewActionQueue).
actions() ->
ActionQueue = action_queue(),
Actions = wf_action_queue:all(ActionQueue),
clear_action_queue(),
Actions.
-spec next_action() -> {ok, actions()} | empty.
next_action() ->
ActionQueue = action_queue(),
case wf_action_queue:out(ActionQueue) of
{ok, Action, NewActionQueue} ->
action_queue(NewActionQueue),
{ok, Action};
{error, empty} ->
empty
end.
action_queue() ->
Context = context(),
Context#context.action_queue.
action_queue(ActionQueue) ->
Context = context(),
context(Context#context { action_queue = ActionQueue }).
clear_action_queue() ->
action_queue(new_action_queue()).
new_action_queue() ->
wf_action_queue:new().
%%% PAGE CONTEXT %%%
page_context() ->
Context = context(),
Context#context.page_context.
page_context(PageContext) ->
Context = context(),
context(Context#context { page_context = PageContext }).
series_id() ->
Page = page_context(),
Page#page_context.series_id.
series_id(SeriesID) ->
Page = page_context(),
page_context(Page#page_context { series_id = SeriesID }).
page_module() ->
Page = page_context(),
Page#page_context.module.
page_module(Module) ->
Page = page_context(),
page_context(Page#page_context { module = Module }).
entry_point() ->
Page = page_context(),
Page#page_context.entry_point.
entry_point(EntryPoint) ->
Page = page_context(),
page_context(Page#page_context { entry_point = EntryPoint}).
path_info() ->
Page = page_context(),
Page#page_context.path_info.
path_info(PathInfo) ->
Page = page_context(),
page_context(Page#page_context { path_info = PathInfo }).
async_mode() ->
Page = page_context(),
Page#page_context.async_mode.
async_mode(AsyncMode) ->
Page = page_context(),
page_context(Page#page_context { async_mode=AsyncMode }).
%%% EVENT CONTEXT %%%
event_context() ->
Context = context(),
Context#context.event_context.
event_context(EventContext) ->
Context = context(),
context(Context#context { event_context = EventContext }).
type() ->
Context = context(),
Context#context.type.
type(Type) -> % either first_request, postback_request, postback_websocket, comet, or static_file
Context = context(),
context(Context#context { type = Type }).
event_module() ->
Event = event_context(),
Event#event_context.module.
event_module(Module) ->
Event = event_context(),
event_context(Event#event_context { module = Module }).
event_tag() ->
Event = event_context(),
Event#event_context.tag.
event_tag(Tag) ->
Event = event_context(),
event_context(Event#event_context { tag = Tag }).
event_validation_group() ->
Event = event_context(),
Event#event_context.validation_group.
event_validation_group(ValidationGroup) ->
Event = event_context(),
event_context(Event#event_context { validation_group = ValidationGroup }).
event_handle_invalid() ->
Event = event_context(),
Event#event_context.handle_invalid.
event_handle_invalid(HandleInvalid) ->
Event = event_context(),
event_context(Event#event_context { handle_invalid = HandleInvalid }).
%%% HANDLERS %%%
handlers() ->
Context = context(),
Context#context.handler_list.
handlers(Handlers) ->
Context = context(),
context(Context#context { handler_list = Handlers }).
handler(HandlerName) ->
Handlers = handlers(),
case lists:keyfind(HandlerName, #handler_context.name, Handlers) of
false -> undefined;
HandlerContext -> HandlerContext
end.
restore_handler(NewHandler) ->
Handlers = handlers(),
NewHandlers = [maybe_restore_handler(H, NewHandler) || H <- Handlers],
handlers(NewHandlers).
maybe_restore_handler(Orig = #handler_context{name=Name}, New = #handler_context{name=Name}) ->
New#handler_context{config=Orig#handler_context.config};
maybe_restore_handler(Orig, _New) ->
Orig.
%% MAYBE DO THIS?
%%serializable_handlers() ->
%% [H || H <- handlers(), is_handler_serializable(H)].
%%
%%is_handler_serializable(#handler_context{module=Module}) ->
%% case erlang:function_exported(Module, is_serializable, 0) of
%% true -> Module:is_serializable();
%% false -> true
%% end.
%%% CONTEXT CONSTRUCTION %%%
init_context(Bridge) ->
% Create the new context using the default handlers.
Context = #context {
bridge = Bridge,
page_context = #page_context { series_id = wf:temp_id() },
event_context = #event_context {},
action_queue = new_action_queue(),
script_nonce = wf_security_policy:nonce(),
handler_list = [
% Core handlers...
make_handler(config_handler, default_config_handler),
make_handler(log_handler, default_log_handler),
make_handler(process_registry_handler, nprocreg_registry_handler),
make_handler(cache_handler, default_cache_handler),
make_handler(query_handler, default_query_handler),
make_handler(crash_handler, default_crash_handler),
make_handler(websocket_handler, default_websocket_handler),
% Stateful handlers...
make_handler(session_handler, canister_session_handler),
make_handler(state_handler, default_state_handler),
make_handler(identity_handler, default_identity_handler),
make_handler(role_handler, default_role_handler),
% Handlers that possibly redirect...
make_handler(route_handler, dynamic_route_handler),
make_handler(security_handler, default_security_handler),
make_handler(postback_handler, default_postback_handler)
]
},
context(Context).
make_handler(Name, Module) ->
#handler_context {
name=Name,
module=Module,
state=[]
}.
caching() ->
Context = context(),
Context#context.caching.
caching(Caching) ->
Context = context(),
context(Context#context{caching=Caching}).
%%% GET AND SET CONTEXT %%%
% Yes, the context is stored in the process dictionary. It makes the Nitrogen
% code much cleaner. Trust me.
context() ->
get(context).
context(Context) ->
put(context, Context).
%% for debugging. Remove when ready
increment(Key) ->
case get(Key) of
undefined -> put(Key, 1);
V -> io:format("~p=~p~n",[Key, V+1]), put(Key, V+1)
end.
%% Kept for backwards compatibility with nitrogen 2.2 and below (and
%% simple_bridge 1.x)
request_bridge() ->
bridge().
request_bridge(Bridge) ->
bridge(Bridge).
response_bridge() ->
bridge().
response_bridge(Bridge) ->
bridge(Bridge).