src/mochiweb_bridge_modules/mochiweb_simple_bridge.erl

% vim: sw=4 ts=4 et
% Simple Bridge
% Copyright (c) 2008-2010 Rusty Klophaus
% See MIT-LICENSE for licensing information.

-module (mochiweb_simple_bridge).
-behaviour (simple_bridge).
-include("simple_bridge.hrl").
-export ([
    init/1,
    protocol/1,
    host/1,
    request_method/1, 
    path/1,
    uri/1,
    peer_ip/1,
    peer_port/1,
    headers/1,
    cookies/1,
    query_params/1,
    post_params/1,
    request_body/1,
    socket/1,
    recv_from_socket/3,
    protocol_version/1,
    native_header_type/0
]).

-export([
    build_response/2
]).

%% Max Body of 10MB by default
%% NOTE: It seems to be common for folks to manually override this directly in
%% the code here.  That, however, is not necessary.  It's far simpler to set
%% the faux-mochiweb environment variable max_reqest_size. I say "faux-mochiweb"
%% because it isn't ACTUALLY a stock mochiweb variable, but just one that
%% simple_bridge uses to override this hard-coded default.
%%
%% Either put the value into a mochiweb.config and load that, or you can
%% just specify it in code or the console with:
%% 
%%      application:set_env(mochiweb, max_request_size, Size).
%%
%% where size is an integer containing the number of bytes
-define(MAX_RECV_BODY,(1024*1024*10)).

%% REQUEST FUNCTIONS

init(Req) -> 
    Req.

protocol(Req) ->
    mochiweb_request:get(scheme, Req).

host(Req) ->
    Headers = mochiweb_request:get(headers, Req),
    Host = mochiweb_headers:get_value("host", Headers),
    XForwardedFor = mochiweb_headers:get_value("x-forwarded-for", Headers),
    simple_bridge_util:infer_host(undefined, Host, XForwardedFor).

request_method(Req) -> 
    mochiweb_request:get(method, Req).

path(Req) -> 
    RawPath = mochiweb_request:get(raw_path, Req),
    {Path, _, _} = mochiweb_util:urlsplit_path(RawPath),
    Path.

uri(Req) ->
    mochiweb_request:get(raw_path, Req).

peer_ip(Req) -> 
    case mochiweb_request:get(socket, Req) of
        false -> {127, 0, 0, 1};
        Socket ->
            {ok, {IP, _Port}} = mochiweb_socket:peername(Socket),
            IP
    end.

peer_port(Req) -> 
    Socket = mochiweb_request:get(socket, Req),
    {ok, {_IP, Port}} = mochiweb_socket:peername(Socket),
    Port.

native_header_type() -> list.

headers(Req) ->
    Headers = mochiweb_request:get(headers, Req),
    mochiweb_headers:to_list(Headers).

cookies(Req) ->
    mochiweb_request:parse_cookie(Req).

query_params(Req) ->
    mochiweb_request:parse_qs(Req).

post_params(Req) ->
    mochiweb_request:parse_post(Req).

request_body(Req) ->
    MaxBody = case application:get_env(mochiweb,max_request_size) of
        undefined -> 
            ?MAX_RECV_BODY;
        {ok, Max} when is_integer(Max) -> 
            Max;
        Other -> 
            error_logger:warning_msg("Mochiweb Simple Bridge Configuration Error!  Unknown value for 'mochiweb' application variable 'max_request_size': ~p. Expected: integer() or undefined. Using Default of ~p~n",[Other,?MAX_RECV_BODY]),
            ?MAX_RECV_BODY
    end,
    mochiweb_request:recv_body(MaxBody, Req).

socket(Req) ->  
    mochiweb_request:get(socket, Req).

recv_from_socket(Length, Timeout, Req) -> 
    Socket = socket(Req),
    case gen_tcp:recv(Socket, Length, Timeout) of
        {ok, Data} -> 
            put(mochiweb_request_recv, true),
            Data;
        _Other -> 
            exit(normal)
    end.

protocol_version(Req) ->
    case mochiweb_request:get(version, Req) of
        'HTTP/1.1' -> {1, 1};
        'HTTP/1.0' -> {1, 0};
        {H, L} -> {H, L}
    end.


%% RESPONSE BRIDGE

build_response(Req, Res) ->
    % Some values...
    Code = Res#response.status_code,

    %% Assemble headers...
    Headers = lists:flatten([
			     [{X#header.name, X#header.value} || X <- Res#response.headers],
			     [create_cookie_header(X) || X <- Res#response.cookies]
			  ]),

    case Res#response.data of
        {data, Body} ->
            % Send the mochiweb response...
            Headers2 = simple_bridge_util:ensure_header(Headers,{"Content-Type","text/html"}),
            Response = {Code, Headers2, Body},
            mochiweb_request:respond(Response, Req);
        {file, Path} ->
            %% Create the response telling Mochiweb to serve the file...
            Headers2 = simple_bridge_util:ensure_expires_header(Headers),
            DocRoot = simple_bridge_util:get_docroot(mochiweb),
            mochiweb_request:serve_file(tl(Path), DocRoot, Headers2, Req)
    end,
    ok.

create_cookie_header(Cookie) ->
    Name = Cookie#cookie.name,
    Value = Cookie#cookie.value,
    Options = [
               {domain, Cookie#cookie.domain},
               {path, Cookie#cookie.path},
               {max_age, Cookie#cookie.max_age},
               {secure, Cookie#cookie.secure},
               {http_only, Cookie#cookie.http_only},
               {same_site, simple_bridge_util:to_existing_atom(Cookie#cookie.same_site)}
              ],
    mochiweb_cookies:cookie(Name, Value, Options).