Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add Cowboy Support (Thanks to @tuncer and @essen)

  • Loading branch information...
commit 121ace453dd113968ead8387d97cb3d1acdbcbb5 1 parent b5c7a1a
Jesse Gumm authored
3  include/simple_bridge.hrl
@@ -6,4 +6,5 @@
6 6 -record(cookie, { name, value, path="/", minutes_to_live=20 }).
7 7 -record(header, { name, value }).
8 8 -record(response, { statuscode=200, headers=[], cookies=[], data=[] }).
9   --record(uploaded_file, { original_name, temp_file, size }).
  9 +-record(uploaded_file, { original_name, temp_file, size }).
  10 +-record(request_cache, {request, docroot="", body=""}).
6 rebar.config
... ... @@ -1,4 +1,10 @@
1 1 % -*- Erlang -*-
  2 +%% vim: ts=4 sw=4 et ft=erlang
  3 +
  4 +{deps, [
  5 + {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", {tag, "HEAD"}}}
  6 +]}.
  7 +
2 8 {erl_opts, [fail_on_warning, debug_info]}.
3 9 {cover_enabled, true}.
4 10 {xref_checks, [undefined_function_calls]}.
162 src/cowboy_bridge_modules/cowboy_request_bridge.erl
... ... @@ -0,0 +1,162 @@
  1 +%% vim: ts=4 sw=4 et
  2 +% Simple Bridge Cowboy
  3 +% Copyright (c) 2012 Jesse Gumm
  4 +% See MIT-LICENSE for licensing information.
  5 +
  6 +-module (cowboy_request_bridge).
  7 +-behaviour (simple_bridge_request).
  8 +-include_lib ("simple_bridge.hrl").
  9 +
  10 +-export ([
  11 + init/1,
  12 + request_method/1, path/1, uri/1,
  13 + peer_ip/1, peer_port/1,
  14 + headers/1, cookies/1,
  15 + query_params/1, post_params/1, request_body/1,
  16 + socket/1, recv_from_socket/3
  17 +]).
  18 +
  19 +-define(GET,_RequestCache=#request_cache{request=Req}=cowboy_request_server:get(ReqKey)).
  20 +-define(PUT,cowboy_request_server:set(ReqKey,NewRequestCache)).
  21 +
  22 +new_key() ->
  23 + {cowboy_bridge,now()}.
  24 +
  25 +init({Req,DocRoot}) ->
  26 + ReqKey = new_key(),
  27 + NewRequestCache = #request_cache{
  28 + body=not_loaded,
  29 + request=Req,
  30 + docroot=DocRoot
  31 + },
  32 + ?PUT,
  33 + ReqKey.
  34 +
  35 +request_method(ReqKey) ->
  36 + ?GET,
  37 + {Method, Req} = cowboy_http_req:method(Req),
  38 + Method.
  39 +
  40 +path(ReqKey) ->
  41 + ?GET,
  42 + {Path, Req} = cowboy_http_req:path(Req),
  43 + case Path of
  44 + [] -> "/";
  45 + _ -> b2l(filename:join(Path))
  46 + end.
  47 +
  48 +uri(ReqKey) ->
  49 + ?GET,
  50 + {RawPath, Req} = cowboy_http_request:raw_path(Req),
  51 + b2l(RawPath).
  52 +
  53 +peer_ip(ReqKey) ->
  54 + ?GET,
  55 + {{IP, _Port, Req}} = cowboy_http_req:peer(Req),
  56 + IP.
  57 +
  58 +peer_port(ReqKey) ->
  59 + ?GET,
  60 + {{_IP, Port, Req}} = cowboy_http_req:peer(Req),
  61 + Port.
  62 +
  63 +headers(ReqKey) ->
  64 + ?GET,
  65 + {Headers,Req} = cowboy_http_req:headers(Req),
  66 + [{simple_bridge_util:atomize_header(Header),b2l(Val)} || {Header,Val} <- Headers].
  67 +
  68 +
  69 +cookies(ReqKey) ->
  70 + ?GET,
  71 + {Cookies, NewReq} = cowboy_http_req:cookies(Req),
  72 + NewRequestCache = _RequestCache#request_cache{request=NewReq},
  73 + ?PUT,
  74 + [{b2l(K),b2l(V)} || {K,V} <- Cookies].
  75 +
  76 +query_params(ReqKey) ->
  77 + ?GET,
  78 + {QsVals, NewReq} = cowboy_http_req:qs_vals(Req),
  79 + NewRequestCache = _RequestCache#request_cache{request=NewReq},
  80 + ?PUT,
  81 + [{b2l(K),b2l(V)} || {K,V} <- QsVals].
  82 +
  83 +
  84 +
  85 +post_params(ReqKey) ->
  86 + Body = request_body(ReqKey,binary),
  87 + BodyQs = parse_qs(Body),
  88 + [{b2l(K),b2l(V)} || {K,V} <- BodyQs].
  89 +
  90 +request_body(ReqKey) ->
  91 + request_body(ReqKey,string).
  92 +
  93 +request_body(ReqKey,binary) ->
  94 + ?GET,
  95 + %% We cache the body here because we can't request the body twice in cowboy or it'll crash
  96 + {Body,NewReq} = case _RequestCache#request_cache.body of
  97 + not_loaded ->
  98 + {ok, B, R} = cowboy_http_req:body(Req),
  99 + {B,R};
  100 + B -> {B,Req}
  101 + end,
  102 + NewRequestCache = _RequestCache#request_cache {
  103 + body=Body,
  104 + request=NewReq
  105 + },
  106 + ?PUT,
  107 + Body;
  108 +request_body(ReqKey,string) ->
  109 + b2l(request_body(ReqKey,binary)).
  110 +
  111 +
  112 +socket(ReqKey) ->
  113 + ?GET,
  114 + {ok, _Transport, Socket} = cowboy_http_req:transport(Req),
  115 + Socket.
  116 +
  117 +%% TODO: Cowboy's stream_body doesn't support customizable Length and Timeout
  118 +recv_from_socket(_Length, _Timeout, ReqKey) ->
  119 + ?GET,
  120 + %cowboy_http_req:init_stream(
  121 + case cowboy_http_req:stream_body(Req) of
  122 + {ok, Data, NewReq} ->
  123 + NewRequestCache = _RequestCache#request_cache{request=NewReq},
  124 + ?PUT,
  125 + Data;
  126 + {done, NewReq} ->
  127 + NewRequestCache = _RequestCache#request_cache{request=NewReq},
  128 + ?PUT,
  129 + <<"">>;
  130 + {error, Reason} ->
  131 + exit({error, Reason}) %% exit(normal) instead?
  132 + end.
  133 +
  134 +
  135 +
  136 +%% parse_qs, borrowed from Cowboy by Loic Hugian :)
  137 +parse_qs(<<>>) ->
  138 + [];
  139 +parse_qs(Qs) ->
  140 + URLDecode = fun cowboy_http:urldecode/1,
  141 + Tokens = binary:split(Qs, <<"&">>, [global, trim]),
  142 + [case binary:split(Token, <<"=">>) of
  143 + [Token] -> {URLDecode(Token), true};
  144 + [Name, Value] -> {URLDecode(Name), URLDecode(Value)}
  145 + end || Token <- Tokens].
  146 +
  147 +
  148 +b2l(B) when is_binary(B) ->
  149 + binary_to_list(B);
  150 +b2l(B) ->
  151 + B.
  152 +
  153 +l2b(L) when is_list(L) ->
  154 + list_to_binary(L);
  155 +l2b(L) ->
  156 + L.
  157 +
  158 +b2a(B) when is_binary(B) ->
  159 + list_to_atom(binary_to_list(B)).
  160 +
  161 +
  162 +
79 src/cowboy_bridge_modules/cowboy_request_server.erl
... ... @@ -0,0 +1,79 @@
  1 +%% vim: ts=4 sw=4 et
  2 +%% Cowboy Request Server. Becaue cowboy returns Request object for each
  3 +%% request, this helps to manage that object so it can be pushed back onto
  4 +%% the pile after it's done being used.
  5 +%%
  6 +%% Right now, it's nasty and uses the process dict. Not exactly the smoothest approach.
  7 +%%
  8 +%% I keep it at set/2 and get/1 as I'd like to eventually update it to be a server that uses a dict
  9 +%% but the concerns are for memory usage for expired requests. We can at least
  10 +%% Clean up our requests when complete, with a delete(Key), but I'd like to also
  11 +%% make sure that the handler cleans itself up if the request crashes, say if
  12 +%% nitrogen crashes while doing whatever.
  13 +%%
  14 +%% Also, while using the process dict, the key will always be 'proc_dict_cowboy_request'
  15 +
  16 +-module(cowboy_request_server).
  17 +-include_lib("simple_bridge.hrl").
  18 +
  19 +-export([set/2,get/1]).
  20 +
  21 +%-behaviour(gen_server).
  22 +-compile({no_auto_import,[get/1]}).
  23 +
  24 +set(Key,RequestCache) ->
  25 + %error_logger:info_msg("Saving ~p~n",[Req]),
  26 + erlang:put(Key,RequestCache).
  27 +
  28 +get(Key) ->
  29 + %error_logger:info_msg("Getting ~p~n",[Key]),
  30 + _RequestCache = erlang:get(Key).
  31 +
  32 +
  33 +%% -define(M,?MODULE).
  34 +%%
  35 +%% -export([start/0,start_link/0,set/2,get/1,delete/1]).
  36 +%% -export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,code_change/3]).
  37 +%%
  38 +%% start_link() ->
  39 +%% gen_server:start_link({local, ?M}, ?M, [], []).
  40 +%%
  41 +%% start() ->
  42 +%% gen_server:start({local, ?M}, ?M, [], []).
  43 +%%
  44 +%% %% Public Functions
  45 +%% init(_) ->
  46 +%% {ok, dict:new()}.
  47 +%%
  48 +%% set(Key, Req) ->
  49 +%% ok = gen_server:call(?M,{set,Key,Req}).
  50 +%%
  51 +%% get(Key) ->
  52 +%% {ok, Req} = gen_server:call(?M,{get,Key}),
  53 +%% Req.
  54 +%%
  55 +%% delete(Key) ->
  56 +%% ok = gen_server:call(?M,{delete,Key}).
  57 +%%
  58 +%% %% Private functions
  59 +%% handle_call({set,Key,Req},_From,Dict) ->
  60 +%% NewDict = dict:store(Key,Req,Dict),
  61 +%% {reply,ok,NewDict};
  62 +%% handle_call({get,Key},_From,Dict) ->
  63 +%% Reply = dict:find(Key,Dict),
  64 +%% {reply,Reply,Dict};
  65 +%% handle_call({delete,Key},_From,Dict) ->
  66 +%% NewDict = dict:erase(Key,Dict),
  67 +%% {reply,ok,NewDict}.
  68 +%%
  69 +%% handle_info(_Info,State) ->
  70 +%% {noreply,State}.
  71 +%%
  72 +%% handle_cast(_,State) ->
  73 +%% {noreply,State}.
  74 +%%
  75 +%% terminate(_Reason,_State) ->
  76 +%% ok.
  77 +%%
  78 +%% code_change(_OldVsn,State,_Extra) ->
  79 +%% {ok,State}.
110 src/cowboy_bridge_modules/cowboy_response_bridge.erl
... ... @@ -0,0 +1,110 @@
  1 +%% vim: ts=4 sw=4 et
  2 +% Simple Bridge
  3 +% Copyright (c) 2008-2012 Rusty Klophaus
  4 +% See MIT-LICENSE for licensing information.
  5 +
  6 +-module (cowboy_response_bridge).
  7 +-behaviour (simple_bridge_response).
  8 +-include_lib ("simple_bridge.hrl").
  9 +-export ([build_response/2,init/1]).
  10 +
  11 +
  12 +init(Request) when
  13 + is_tuple(Request),
  14 + element(1,Request)==simple_bridge_request_wrapper,
  15 + element(2,Request)==cowboy_request_bridge ->
  16 + element(3,Request); %% The third element of Request is the RequestKey from response_bridge
  17 +init({Req,DocRoot}) ->
  18 + %% Since cowboy request and response information are the same, this synchronizes it
  19 + cowboy_request_bridge:init({Req,DocRoot}).
  20 +
  21 +build_response(ReqKey, Res) ->
  22 + RequestCache = #request_cache{request=Req,docroot=DocRoot} = cowboy_request_server:get(ReqKey),
  23 + % Some values...
  24 + Code = Res#response.statuscode,
  25 +
  26 + case Res#response.data of
  27 + {data, Body} ->
  28 +
  29 + % Assemble headers...
  30 + Headers = lists:flatten([
  31 + [{X#header.name, X#header.value} || X <- Res#response.headers]
  32 + ]),
  33 +
  34 + % Ensure content type...
  35 + F = fun(Key) -> lists:keymember(Key, 1, Headers) end,
  36 + HasContentType = lists:any(F, ["content-type", "Content-Type", "CONTENT-TYPE"]),
  37 + Headers2 = case HasContentType of
  38 + true -> Headers;
  39 + false -> [{"Content-Type", "text/html"}|Headers]
  40 + end,
  41 +
  42 + % Send the cowboy cookies
  43 + {ok, FinReq} = send(Code,Headers2,Res#response.cookies,Body,Req),
  44 +
  45 + NewRequestCache = RequestCache#request_cache{
  46 + request=FinReq
  47 + },
  48 + cowboy_request_server:set(ReqKey,NewRequestCache),
  49 + {ok,FinReq};
  50 +
  51 + {file, Path} ->
  52 + %% Calculate expire date far into future...
  53 + Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
  54 + TenYears = 10 * 365 * 24 * 60 * 60,
  55 + Seconds1 = calendar:gregorian_seconds_to_datetime(Seconds + TenYears),
  56 + ExpireDate = httpd_util:rfc1123_date(Seconds1),
  57 +
  58 + [$. | Ext] = filename:extension(Path),
  59 + %% We can use the mimetypes module because it's a dependency for cowboy
  60 + Mimetype = mimetypes:extension(Ext),
  61 +
  62 + %% Create the response telling Mochiweb to serve the file...
  63 + Headers = [
  64 + {"Expires", ExpireDate},
  65 + {"Content-Type",Mimetype}
  66 + ],
  67 +
  68 + io:format("Serving static file ~p~n",[Path]),
  69 +
  70 + FullPath = filename:join(DocRoot,Path),
  71 + {ok, FinReq} = case file:read_file(FullPath) of
  72 + {error,enoent} ->
  73 + {ok, _R} = send(404,[],[],"Not Found",Req);
  74 + {ok,Bin} ->
  75 + {ok, _R} = send(200,Headers,[],Bin,Req)
  76 + end,
  77 +
  78 + NewRequestCache = RequestCache#request_cache{
  79 + request=FinReq
  80 + },
  81 + cowboy_request_server:set(ReqKey,NewRequestCache),
  82 + {ok, FinReq}
  83 + end.
  84 +
  85 +send(Code,Headers,Cookies,Body,Req) ->
  86 + Req1 = prepare_cookies(Req,Cookies),
  87 + Req2 = prepare_headers(Req1,Headers),
  88 + {ok, Req3} = cowboy_http_req:set_resp_body(Body,Req2),
  89 + {ok, _ReqFinal} = cowboy_http_req:reply(Code, Req3).
  90 +
  91 +prepare_cookies(Req,Cookies) ->
  92 + lists:foldl(fun(C,R) ->
  93 + Name = iol2b(C#cookie.name),
  94 + Value = iol2b(C#cookie.value),
  95 + Path = iol2b(C#cookie.path),
  96 + SecsToLive = C#cookie.minutes_to_live * 60,
  97 + Options = [{path,Path},{max_age,SecsToLive}],
  98 + {ok,NewReq} = cowboy_http_req:set_resp_cookie(Name,Value,Options,R),
  99 + NewReq
  100 + end,Req,Cookies).
  101 +
  102 +prepare_headers(Req,Headers) ->
  103 + lists:foldl(fun({Header,Value},R) ->
  104 + {ok,NewReq} = cowboy_http_req:set_resp_header(iol2b(Header),iol2b(Value),R),
  105 + NewReq
  106 + end,Req,Headers).
  107 +
  108 +
  109 +iol2b(V) when is_binary(V) -> V;
  110 +iol2b(V) -> iolist_to_binary(V).
5 src/inets_bridge_modules/inets_response_bridge.erl
@@ -5,7 +5,10 @@
5 5 -module (inets_response_bridge).
6 6 -behaviour (simple_bridge_response).
7 7 -include_lib ("simple_bridge.hrl").
8   --export ([build_response/2]).
  8 +-export ([build_response/2,init/1]).
  9 +
  10 +init(Req) ->
  11 + Req.
9 12
10 13 build_response(Req, Res) ->
11 14 ResponseCode = Res#response.statuscode,
5 src/mochiweb_bridge_modules/mochiweb_response_bridge.erl
@@ -5,7 +5,10 @@
5 5 -module (mochiweb_response_bridge).
6 6 -behaviour (simple_bridge_response).
7 7 -include_lib ("simple_bridge.hrl").
8   --export ([build_response/2]).
  8 +-export ([build_response/2,init/1]).
  9 +
  10 +init({Req,DocRoot}) ->
  11 + {Req,DocRoot}.
9 12
10 13 build_response({Req, DocRoot}, Res) ->
11 14 % Some values...
3  src/simple_bridge_response.erl
@@ -19,7 +19,8 @@ make(Module, ResponseData) ->
19 19 end.
20 20
21 21 make_nocatch(Mod, ResponseData) ->
22   - simple_bridge_response_wrapper:new(Mod, ResponseData, #response{}).
  22 + ResponseData1 = Mod:init(ResponseData),
  23 + simple_bridge_response_wrapper:new(Mod, ResponseData1, #response{}).
23 24
24 25 behaviour_info(callbacks) -> [
25 26 {build_response, 2}
22 src/simple_bridge_util.erl
... ... @@ -0,0 +1,22 @@
  1 +
  2 +-module(simple_bridge_util).
  3 +-export([atomize_header/1]).
  4 +
  5 +
  6 +%% converts a Header to a lower-case, underscored version
  7 +%% ie. "X-Forwarded-For" -> x_forwarded_for
  8 +atomize_header(Header) when is_binary(Header) ->
  9 + atomize_header(binary_to_list(Header));
  10 +atomize_header(Header) when is_atom(Header) ->
  11 + atomize_header(atom_to_list(Header));
  12 +atomize_header(Header) when is_list(Header) ->
  13 + LowerUnderscore = fun(H) ->
  14 + if
  15 + H >= 65 andalso H =< 90 ->
  16 + H + 32; % Convert "A" to "a" by adding 32 to its ASCII val
  17 + H == 45 ->
  18 + 95; %% convert "-" to "_"
  19 + true -> H
  20 + end
  21 + end,
  22 + list_to_atom(lists:map(LowerUnderscore,Header)).
5 src/webmachine_bridge_modules/webmachine_response_bridge.erl
@@ -5,7 +5,10 @@
5 5 -module (webmachine_response_bridge).
6 6 -behaviour (simple_bridge_response).
7 7 -include_lib ("simple_bridge.hrl").
8   --export ([build_response/2]).
  8 +-export ([build_response/2,init/1]).
  9 +
  10 +init(Req) ->
  11 + Req.
9 12
10 13 build_response(Req, Res) ->
11 14 Code = Res#response.statuscode,
5 src/yaws_bridge_modules/yaws_response_bridge.erl
@@ -5,7 +5,10 @@
5 5 -module(yaws_response_bridge).
6 6 -behaviour (simple_bridge_response).
7 7 -include_lib ("simple_bridge.hrl").
8   --export ([build_response/2]).
  8 +-export ([build_response/2,init/1]).
  9 +
  10 +init(_Arg) ->
  11 + _Arg.
9 12
10 13 build_response(_Arg, Res) ->
11 14 % Get vars...

0 comments on commit 121ace4

Please sign in to comment.
Something went wrong with that request. Please try again.