Yaws Fixes and Cowboy support #11

Merged
merged 26 commits into from Apr 28, 2012
Select commit
+515 −176
View
4 .gitignore
@@ -1,2 +1,4 @@
erl_crash.dump
-.eunit/
+*~
+.*.sw?
+.eunit/
View
5 Makefile
@@ -1,6 +1,9 @@
all: compile
-compile:
+get-deps:
+ ./rebar get-deps
+
+compile: get-deps
./rebar compile
clean:
View
8 README.markdown
@@ -88,6 +88,10 @@ Once you have created the request bridge object (a parameterized module), it pro
* *request_bridge_wrapper.erl* - A parameterized module that wraps a request.
* *inets_request_bridge.erl* - The request bridge module for Inets.
* *mochiweb_request_bridge.erl* - The request bridge module for Mochiweb.
+* *webmachine_request_bridge.erl* - The request bridge module for Webmachine.
+* *cowboy_request_bridge.erl* - The request bridge module for Cowboy.
+* *yaws_request_bridge.erl* - The request bridge module for Yaws.
+* *misultin_request_bridge.erl* - The request bridge module for Misultin.
* *???_request_bridge.erl* - Support for more servers on the way.
To extend the SimpleBridge to work with other HTTP servers (or other versions of Inets, Mochiweb, or Yaws), copy and modify inets_request_bridge.erl or mochiweb_request_bridge.erl.
@@ -144,6 +148,10 @@ Finally, you build the response to send to your HTTP server with the build_respo
* *response_bridge_wrapper.erl* - A parameterized module that wraps a response.
* *inets_response_bridge.erl* - The response bridge module for Inets.
* *mochiweb_response_bridge.erl* - The response bridge module for Mochiweb.
+* *webmachine_response_bridge.erl* - The response bridge module for Webmachine.
+* *cowboy_response_bridge.erl* - The response bridge module for Cowboy.
+* *yaws_response_bridge.erl* - The response bridge module for Yaws.
+* *misultin_response_bridge.erl* - The response bridge module for Misultin.
* *???_response_bridge.erl* - Support for more servers on the way.
To extend the SimpleBridge to other HTTP servers (or other versions of Inets, Mochiweb, or Yaws),
View
3 include/simple_bridge.hrl
@@ -6,4 +6,5 @@
-record(cookie, { name, value, path="/", minutes_to_live=20 }).
-record(header, { name, value }).
-record(response, { statuscode=200, headers=[], cookies=[], data=[] }).
--record(uploaded_file, { original_name, temp_file, size }).
+-record(uploaded_file, { original_name, temp_file, size }).
+-record(request_cache, {request, docroot="", body=""}).
View
112 include/yaws_api.hrl
@@ -1,112 +0,0 @@
-%%%----------------------------------------------------------------------
-%%% File : yaws_api.hrl
-%%% Author : Claes Wikstrom <klacke@hyber.org>
-%%% Purpose :
-%%% Created : 24 Jan 2002 by Claes Wikstrom <klacke@hyber.org>
-%%%----------------------------------------------------------------------
-
--author('klacke@hyber.org').
-
--record(arg, {
- clisock, %% the socket leading to the peer client
- client_ip_port, %% {ClientIp, ClientPort} tuple
- headers, %% headers
- req, %% request
- clidata, %% The client data (as a binary in POST requests)
- server_path, %% The normalized server path
- %% (pre-querystring part of URI)
- querydata, %% For URIs of the form ...?querydata
- %% equiv of cgi QUERY_STRING
- appmoddata, %% (deprecated - use pathinfo instead) the remainder
- %% of the path leading up to the query
- docroot, %% Physical base location of data for this request
- docroot_mount, %% virtual directory e.g /myapp/ that the docroot
- %% refers to.
- fullpath, %% full deep path to yaws file
- cont, %% Continuation for chunked multipart uploads
- state, %% State for use by users of the out/1 callback
- pid, %% pid of the yaws worker process
- opaque, %% useful to pass static data
- appmod_prepath, %% (deprecated - use prepath instead) path in front
- %%of: <appmod><appmoddata>
- prepath, %% Path prior to 'dynamic' segment of URI.
- %% ie http://some.host/<prepath>/<script-point>/d/e
- %% where <script-point> is an appmod mount point,
- %% or .yaws,.php,.cgi,.fcgi etc script file.
- pathinfo %% Set to '/d/e' when calling c.yaws for the request
- %% http://some.host/a/b/c.yaws/d/e
- %% equiv of cgi PATH_INFO
- }).
-
-
--record(http_request, {method,
- path,
- version}).
-
--record(http_response, {version,
- status,
- phrase}).
-
--record(headers, {
- connection,
- accept,
- host,
- if_modified_since,
- if_match,
- if_none_match,
- if_range,
- if_unmodified_since,
- range,
- referer,
- user_agent,
- accept_ranges,
- cookie = [],
- keep_alive,
- location,
- content_length,
- content_type,
- content_encoding,
- authorization,
- transfer_encoding,
- other = [] %% misc other headers
- }).
-
-
-
-
--record(url,
- {scheme, %% undefined means not set
- host, %% undefined means not set
- port, %% undefined means not set
- path = [],
- querypart = []}).
-
-
--record(setcookie,{
- key,
- value,
- quoted,
- comment,
- comment_url,
- discard,
- domain,
- max_age,
- expires,
- path,
- port,
- secure,
- version}).
-
-
--record(redir_self, {
- host, %% string() - our own host
- scheme, %% http | https
- scheme_str, %% "https://" | "http://"
- port, %% integer() - our own port
- port_str %% "" | ":<int>" - the optional port part
- %% to append to the url
- }).
-
-
-
-
View
BIN rebar
Binary file not shown.
View
6 rebar.config
@@ -1,4 +1,10 @@
% -*- Erlang -*-
+%% vim: ts=4 sw=4 et ft=erlang
+
+{deps, [
+ {mimetypes, ".*", {git, "git://github.com/spawngrid/mimetypes.git", {tag, "HEAD"}}}
+]}.
+
{erl_opts, [fail_on_warning, debug_info]}.
{cover_enabled, true}.
{xref_checks, [undefined_function_calls]}.
View
166 src/cowboy_bridge_modules/cowboy_request_bridge.erl
@@ -0,0 +1,166 @@
+%% vim: ts=4 sw=4 et
+% Simple Bridge Cowboy
+% Copyright (c) 2012 Jesse Gumm
+% See MIT-LICENSE for licensing information.
+
+-module (cowboy_request_bridge).
+-behaviour (simple_bridge_request).
+-include_lib ("simple_bridge.hrl").
+
+-export ([
+ init/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
+]).
+
+-define(GET,_RequestCache=#request_cache{request=Req}=cowboy_request_server:get(ReqKey)).
+-define(PUT,cowboy_request_server:set(ReqKey,NewRequestCache)).
+
+new_key() ->
+ {cowboy_bridge,now()}.
+
+init({Req,DocRoot}) ->
+ ReqKey = new_key(),
+ NewRequestCache = #request_cache{
+ body=not_loaded,
+ request=Req,
+ docroot=DocRoot
+ },
+ ?PUT,
+ ReqKey.
+
+request_method(ReqKey) ->
+ ?GET,
+ {Method, Req} = cowboy_http_req:method(Req),
+ Method.
+
+path(ReqKey) ->
+ ?GET,
+ {Path, Req} = cowboy_http_req:path(Req),
+ case Path of
+ [] -> "/";
+ _ -> b2l(filename:join(Path))
+ end.
+
+uri(ReqKey) ->
+ ?GET,
+ {RawPath, Req} = cowboy_http_request:raw_path(Req),
+ b2l(RawPath).
+
+peer_ip(ReqKey) ->
+ ?GET,
+ {{IP, _Port}, NewReq} = cowboy_http_req:peer(Req),
+ NewRequestCache = _RequestCache#request_cache{request=NewReq},
+ ?PUT,
+ IP.
+
+peer_port(ReqKey) ->
+ ?GET,
+ {{_IP, Port}, NewReq} = cowboy_http_req:peer(Req),
+ NewRequestCache = _RequestCache#request_cache{request=NewReq},
+ ?PUT,
+ Port.
+
+headers(ReqKey) ->
+ ?GET,
+ {Headers,Req} = cowboy_http_req:headers(Req),
+ [{simple_bridge_util:atomize_header(Header),b2l(Val)} || {Header,Val} <- Headers].
+
+
+cookies(ReqKey) ->
+ ?GET,
+ {Cookies, NewReq} = cowboy_http_req:cookies(Req),
+ NewRequestCache = _RequestCache#request_cache{request=NewReq},
+ ?PUT,
+ [{b2l(K),b2l(V)} || {K,V} <- Cookies].
+
+query_params(ReqKey) ->
+ ?GET,
+ {QsVals, NewReq} = cowboy_http_req:qs_vals(Req),
+ NewRequestCache = _RequestCache#request_cache{request=NewReq},
+ ?PUT,
+ [{b2l(K),b2l(V)} || {K,V} <- QsVals].
+
+
+
+post_params(ReqKey) ->
+ Body = request_body(ReqKey,binary),
+ BodyQs = parse_qs(Body),
+ [{b2l(K),b2l(V)} || {K,V} <- BodyQs].
+
+request_body(ReqKey) ->
+ request_body(ReqKey,string).
+
+request_body(ReqKey,binary) ->
+ ?GET,
+ %% We cache the body here because we can't request the body twice in cowboy or it'll crash
+ {Body,NewReq} = case _RequestCache#request_cache.body of
+ not_loaded ->
+ {ok, B, R} = cowboy_http_req:body(Req),
+ {B,R};
+ B -> {B,Req}
+ end,
+ NewRequestCache = _RequestCache#request_cache {
+ body=Body,
+ request=NewReq
+ },
+ ?PUT,
+ Body;
+request_body(ReqKey,string) ->
+ b2l(request_body(ReqKey,binary)).
+
+
+socket(ReqKey) ->
+ ?GET,
+ {ok, _Transport, Socket} = cowboy_http_req:transport(Req),
+ Socket.
+
+%% TODO: Cowboy's stream_body doesn't support customizable Length and Timeout
+recv_from_socket(_Length, _Timeout, ReqKey) ->
+ ?GET,
+ %cowboy_http_req:init_stream(
+ case cowboy_http_req:stream_body(Req) of
+ {ok, Data, NewReq} ->
+ NewRequestCache = _RequestCache#request_cache{request=NewReq},
+ ?PUT,
+ Data;
+ {done, NewReq} ->
+ NewRequestCache = _RequestCache#request_cache{request=NewReq},
+ ?PUT,
+ <<"">>;
+ {error, Reason} ->
+ exit({error, Reason}) %% exit(normal) instead?
+ end.
+
+
+
+%% parse_qs, borrowed from Cowboy by Loic Hugian :)
+parse_qs(<<>>) ->
+ [];
+parse_qs(Qs) ->
+ URLDecode = fun cowboy_http:urldecode/1,
+ Tokens = binary:split(Qs, <<"&">>, [global, trim]),
+ [case binary:split(Token, <<"=">>) of
+ [Token] -> {URLDecode(Token), true};
+ [Name, Value] -> {URLDecode(Name), URLDecode(Value)}
+ end || Token <- Tokens].
+
+
+b2l(B) when is_binary(B) ->
+ binary_to_list(B);
+b2l(B) ->
+ B.
+
+l2b(L) when is_list(L) ->
+ list_to_binary(L);
+l2b(L) ->
+ L.
+
+b2a(B) when is_binary(B) ->
+ list_to_atom(binary_to_list(B)).
+
+
+
View
79 src/cowboy_bridge_modules/cowboy_request_server.erl
@@ -0,0 +1,79 @@
+%% vim: ts=4 sw=4 et
+%% Cowboy Request Server. Becaue cowboy returns Request object for each
+%% request, this helps to manage that object so it can be pushed back onto
+%% the pile after it's done being used.
+%%
+%% Right now, it's nasty and uses the process dict. Not exactly the smoothest approach.
+%%
+%% 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
+%% but the concerns are for memory usage for expired requests. We can at least
+%% Clean up our requests when complete, with a delete(Key), but I'd like to also
+%% make sure that the handler cleans itself up if the request crashes, say if
+%% nitrogen crashes while doing whatever.
+%%
+%% Also, while using the process dict, the key will always be 'proc_dict_cowboy_request'
+
+-module(cowboy_request_server).
+-include_lib("simple_bridge.hrl").
+
+-export([set/2,get/1]).
+
+%-behaviour(gen_server).
+-compile({no_auto_import,[get/1]}).
+
+set(Key,RequestCache) ->
+ %error_logger:info_msg("Saving ~p~n",[Req]),
+ erlang:put(Key,RequestCache).
+
+get(Key) ->
+ %error_logger:info_msg("Getting ~p~n",[Key]),
+ _RequestCache = erlang:get(Key).
+
+
+%% -define(M,?MODULE).
+%%
+%% -export([start/0,start_link/0,set/2,get/1,delete/1]).
+%% -export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,code_change/3]).
+%%
+%% start_link() ->
+%% gen_server:start_link({local, ?M}, ?M, [], []).
+%%
+%% start() ->
+%% gen_server:start({local, ?M}, ?M, [], []).
+%%
+%% %% Public Functions
+%% init(_) ->
+%% {ok, dict:new()}.
+%%
+%% set(Key, Req) ->
+%% ok = gen_server:call(?M,{set,Key,Req}).
+%%
+%% get(Key) ->
+%% {ok, Req} = gen_server:call(?M,{get,Key}),
+%% Req.
+%%
+%% delete(Key) ->
+%% ok = gen_server:call(?M,{delete,Key}).
+%%
+%% %% Private functions
+%% handle_call({set,Key,Req},_From,Dict) ->
+%% NewDict = dict:store(Key,Req,Dict),
+%% {reply,ok,NewDict};
+%% handle_call({get,Key},_From,Dict) ->
+%% Reply = dict:find(Key,Dict),
+%% {reply,Reply,Dict};
+%% handle_call({delete,Key},_From,Dict) ->
+%% NewDict = dict:erase(Key,Dict),
+%% {reply,ok,NewDict}.
+%%
+%% handle_info(_Info,State) ->
+%% {noreply,State}.
+%%
+%% handle_cast(_,State) ->
+%% {noreply,State}.
+%%
+%% terminate(_Reason,_State) ->
+%% ok.
+%%
+%% code_change(_OldVsn,State,_Extra) ->
+%% {ok,State}.
View
109 src/cowboy_bridge_modules/cowboy_response_bridge.erl
@@ -0,0 +1,109 @@
+%% vim: ts=4 sw=4 et
+% Simple Bridge
+% Copyright (c) 2008-2012 Rusty Klophaus
+% See MIT-LICENSE for licensing information.
+
+-module (cowboy_response_bridge).
+-behaviour (simple_bridge_response).
+-include_lib ("simple_bridge.hrl").
+-export ([build_response/2,init/1]).
+
+
+init(Request) when
+ is_tuple(Request),
+ element(1,Request)==simple_bridge_request_wrapper,
+ element(2,Request)==cowboy_request_bridge ->
+ element(3,Request); %% The third element of Request is the RequestKey from response_bridge
+init({Req,DocRoot}) ->
+ %% Since cowboy request and response information are the same, this synchronizes it
+ cowboy_request_bridge:init({Req,DocRoot}).
+
+build_response(ReqKey, Res) ->
+ RequestCache = #request_cache{request=Req,docroot=DocRoot} = cowboy_request_server:get(ReqKey),
+ % Some values...
+ Code = Res#response.statuscode,
+
+ case Res#response.data of
+ {data, Body} ->
+
+ % Assemble headers...
+ Headers = lists:flatten([
+ [{X#header.name, X#header.value} || X <- Res#response.headers]
+ ]),
+
+ % Ensure content type...
+ F = fun(Key) -> lists:keymember(Key, 1, Headers) end,
+ HasContentType = lists:any(F, ["content-type", "Content-Type", "CONTENT-TYPE"]),
+ Headers2 = case HasContentType of
+ true -> Headers;
+ false -> [{"Content-Type", "text/html"}|Headers]
+ end,
+
+ % Send the cowboy cookies
+ {ok, FinReq} = send(Code,Headers2,Res#response.cookies,Body,Req),
+
+ NewRequestCache = RequestCache#request_cache{
+ request=FinReq
+ },
+ cowboy_request_server:set(ReqKey,NewRequestCache),
+ {ok,FinReq};
+
+ {file, Path} ->
+ %% Calculate expire date far into future...
+ Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
+ TenYears = 10 * 365 * 24 * 60 * 60,
+ Seconds1 = calendar:gregorian_seconds_to_datetime(Seconds + TenYears),
+ ExpireDate = httpd_util:rfc1123_date(Seconds1),
+
+ [$. | Ext] = filename:extension(Path),
+ Mimetype = mimetypes:extension(Ext),
+
+ %% Create the response telling Mochiweb to serve the file...
+ Headers = [
+ {"Expires", ExpireDate},
+ {"Content-Type",Mimetype}
+ ],
+
+ io:format("Serving static file ~p~n",[Path]),
+
+ FullPath = filename:join(DocRoot,Path),
+ {ok, FinReq} = case file:read_file(FullPath) of
+ {error,enoent} ->
+ {ok, _R} = send(404,[],[],"Not Found",Req);
+ {ok,Bin} ->
+ {ok, _R} = send(200,Headers,[],Bin,Req)
+ end,
+
+ NewRequestCache = RequestCache#request_cache{
+ request=FinReq
+ },
+ cowboy_request_server:set(ReqKey,NewRequestCache),
+ {ok, FinReq}
+ end.
+
+send(Code,Headers,Cookies,Body,Req) ->
+ Req1 = prepare_cookies(Req,Cookies),
+ Req2 = prepare_headers(Req1,Headers),
+ {ok, Req3} = cowboy_http_req:set_resp_body(Body,Req2),
+ {ok, _ReqFinal} = cowboy_http_req:reply(Code, Req3).
+
+prepare_cookies(Req,Cookies) ->
+ lists:foldl(fun(C,R) ->
+ Name = iol2b(C#cookie.name),
+ Value = iol2b(C#cookie.value),
+ Path = iol2b(C#cookie.path),
+ SecsToLive = C#cookie.minutes_to_live * 60,
+ Options = [{path,Path},{max_age,SecsToLive}],
+ {ok,NewReq} = cowboy_http_req:set_resp_cookie(Name,Value,Options,R),
+ NewReq
+ end,Req,Cookies).
+
+prepare_headers(Req,Headers) ->
+ lists:foldl(fun({Header,Value},R) ->
+ {ok,NewReq} = cowboy_http_req:set_resp_header(iol2b(Header),iol2b(Value),R),
+ NewReq
+ end,Req,Headers).
+
+
+iol2b(V) when is_binary(V) -> V;
+iol2b(V) -> iolist_to_binary(V).
View
5 src/inets_bridge_modules/inets_response_bridge.erl
@@ -5,7 +5,10 @@
-module (inets_response_bridge).
-behaviour (simple_bridge_response).
-include_lib ("simple_bridge.hrl").
--export ([build_response/2]).
+-export ([build_response/2,init/1]).
+
+init(Req) ->
+ Req.
build_response(Req, Res) ->
ResponseCode = Res#response.statuscode,
View
5 src/mochiweb_bridge_modules/mochiweb_request_bridge.erl
@@ -14,6 +14,9 @@
socket/1, recv_from_socket/3
]).
+%% Mochiweb's max request size is 1MB, let's updated it to 1GB
+-define(MAX_BODY_SIZE, (1024*1024*1024)).
+
init({Req, DocRoot}) ->
{Req, DocRoot}.
@@ -74,7 +77,7 @@ post_params({Req, _DocRoot}) ->
Req:parse_post().
request_body({Req, _DocRoot}) ->
- Req:recv_body().
+ Req:recv_body(?MAX_BODY_SIZE).
socket({Req, _DocRoot}) ->
Req:get(socket).
View
5 src/mochiweb_bridge_modules/mochiweb_response_bridge.erl
@@ -5,7 +5,10 @@
-module (mochiweb_response_bridge).
-behaviour (simple_bridge_response).
-include_lib ("simple_bridge.hrl").
--export ([build_response/2]).
+-export ([build_response/2,init/1]).
+
+init({Req,DocRoot}) ->
+ {Req,DocRoot}.
build_response({Req, DocRoot}, Res) ->
% Some values...
View
3 src/simple_bridge_response.erl
@@ -19,7 +19,8 @@ make(Module, ResponseData) ->
end.
make_nocatch(Mod, ResponseData) ->
- simple_bridge_response_wrapper:new(Mod, ResponseData, #response{}).
+ ResponseData1 = Mod:init(ResponseData),
+ simple_bridge_response_wrapper:new(Mod, ResponseData1, #response{}).
behaviour_info(callbacks) -> [
{build_response, 2}
View
22 src/simple_bridge_util.erl
@@ -0,0 +1,22 @@
+
+-module(simple_bridge_util).
+-export([atomize_header/1]).
+
+
+%% converts a Header to a lower-case, underscored version
+%% ie. "X-Forwarded-For" -> x_forwarded_for
+atomize_header(Header) when is_binary(Header) ->
+ atomize_header(binary_to_list(Header));
+atomize_header(Header) when is_atom(Header) ->
+ atomize_header(atom_to_list(Header));
+atomize_header(Header) when is_list(Header) ->
+ LowerUnderscore = fun(H) ->
+ if
+ H >= 65 andalso H =< 90 ->
+ H + 32; % Convert "A" to "a" by adding 32 to its ASCII val
+ H == 45 ->
+ 95; %% convert "-" to "_"
+ true -> H
+ end
+ end,
+ list_to_atom(lists:map(LowerUnderscore,Header)).
View
5 src/webmachine_bridge_modules/webmachine_response_bridge.erl
@@ -5,7 +5,10 @@
-module (webmachine_response_bridge).
-behaviour (simple_bridge_response).
-include_lib ("simple_bridge.hrl").
--export ([build_response/2]).
+-export ([build_response/2,init/1]).
+
+init(Req) ->
+ Req.
build_response(Req, Res) ->
Code = Res#response.statuscode,
View
69 src/yaws_bridge_modules/yaws_request_bridge.erl
@@ -3,7 +3,6 @@
% See MIT-LICENSE for licensing information.
-module(yaws_request_bridge).
--include_lib ("yaws_api.hrl").
-include_lib ("simple_bridge.hrl").
-export ([
init/1,
@@ -18,14 +17,13 @@ init(Req) ->
Req.
request_method(Arg) ->
- (Arg#arg.req)#http_request.method.
+ yaws_api:http_request_method(yaws_api:arg_req(Arg)).
path(Arg) ->
- Arg#arg.server_path.
+ yaws_api:arg_server_path(Arg).
uri(Arg) ->
- Req = Arg#arg.req,
- {abs_path, Path} = Req#http_request.path,
+ {abs_path, Path} = yaws_api:http_request_path(yaws_api:arg_req(Arg)),
Path.
peer_ip(Arg) ->
@@ -39,38 +37,45 @@ peer_port(Arg) ->
Port.
headers(Arg) ->
- Headers = Arg#arg.headers,
+ Headers = yaws_api:arg_headers(Arg),
+
+ %% Get the other headers and format them to fit the paradigm we're using above
+ Others = yaws_api:headers_other(Headers),
+ Others2 = [{simple_bridge_util:atomize_header(Header),Value} || {http_header,_Num,Header,_,Value} <- Others],
+
[
- {connection, Headers#headers.connection},
- {accept, Headers#headers.accept},
- {host, Headers#headers.host},
- {if_modified_since, Headers#headers.if_modified_since},
- {if_match, Headers#headers.if_match},
- {if_none_match, Headers#headers.if_none_match},
- {if_range, Headers#headers.if_range},
- {if_unmodified_since, Headers#headers.if_unmodified_since},
- {range, Headers#headers.range},
- {referer, Headers#headers.referer},
- {user_agent, Headers#headers.user_agent},
- {accept_ranges, Headers#headers.accept_ranges},
- {cookie, Headers#headers.cookie},
- {keep_alive, Headers#headers.keep_alive},
- {location, Headers#headers.location},
- {content_length, Headers#headers.content_length},
- {content_type, Headers#headers.content_type},
- {content_encoding, Headers#headers.content_encoding},
- {authorization, Headers#headers.authorization},
- {transfer_encoding, Headers#headers.transfer_encoding}
+ {connection, yaws_api:headers_connection(Headers)},
+ {accept, yaws_api:headers_accept(Headers)},
+ {host, yaws_api:headers_host(Headers)},
+ {if_modified_since, yaws_api:headers_if_modified_since(Headers)},
+ {if_match, yaws_api:headers_if_match(Headers)},
+ {if_none_match, yaws_api:headers_if_none_match(Headers)},
+ {if_range, yaws_api:headers_if_range(Headers)},
+ {if_unmodified_since, yaws_api:headers_if_unmodified_since(Headers)},
+ {range, yaws_api:headers_range(Headers)},
+ {referer, yaws_api:headers_referer(Headers)},
+ {user_agent, yaws_api:headers_user_agent(Headers)},
+ {accept_ranges, yaws_api:headers_accept_ranges(Headers)},
+ {cookie, yaws_api:headers_cookie(Headers)},
+ {keep_alive, yaws_api:headers_keep_alive(Headers)},
+ {location, yaws_api:headers_location(Headers)},
+ {content_length, yaws_api:headers_content_length(Headers)},
+ {content_type, yaws_api:headers_content_type(Headers)},
+ {content_encoding, yaws_api:headers_content_encoding(Headers)},
+ {authorization, yaws_api:headers_authorization(Headers)},
+ {transfer_encoding, yaws_api:headers_transfer_encoding(Headers)},
+ {x_forwarded_for, yaws_api:headers_x_forwarded_for(Headers)}
+ | Others2
].
cookie(Key, Req) ->
Key1 = wf:to_list(Key),
- Headers = Req#arg.headers,
- yaws_api:find_cookie_val(Key1, Headers#headers.cookie).
+ Headers = yaws_api:arg_headers(Req),
+ yaws_api:find_cookie_val(Key1, yaws_api:headers_cookie(Headers)).
cookies(Req) ->
- Headers = Req#arg.headers,
- CookieList = Headers#headers.cookie,
+ Headers = yaws_api:arg_headers(Req),
+ CookieList = yaws_api:headers_cookie(Headers),
F = fun(Cookie) ->
Key = hd(string:tokens(Cookie, "=")),
Val = yaws_api:find_cookie_val(Key, [Cookie]),
@@ -85,13 +90,13 @@ post_params(Arg) ->
yaws_api:parse_post(Arg).
request_body(Arg) ->
- case Arg#arg.clidata of
+ case yaws_api:arg_clidata(Arg) of
{partial, Data} -> Data;
Data -> Data
end.
socket(Arg) ->
- Arg#arg.clisock.
+ yaws_api:arg_clisock(Arg).
recv_from_socket(Length, Timeout, Arg) ->
Socket = socket(Arg),
View
85 src/yaws_bridge_modules/yaws_response_bridge.erl
@@ -4,50 +4,87 @@
-module(yaws_response_bridge).
-behaviour (simple_bridge_response).
--include_lib ("yaws_api.hrl").
-include_lib ("simple_bridge.hrl").
--export ([build_response/2]).
+-export ([build_response/2,init/1]).
+
+init(_Arg) ->
+ _Arg.
build_response(_Arg, Res) ->
% Get vars...
Code = Res#response.statuscode,
+
+ %% Assemble headers...
+ Headers = assemble_headers(Res),
+
+
case Res#response.data of
{data, Body} ->
- % Assemble headers...
- Headers = lists:flatten([
- [{header, {X#header.name, X#header.value}} || X <- Res#response.headers],
- [create_cookie(X) || X <- Res#response.cookies]
- ]),
-
- % Get the content type...
- ContentType = coalesce([
- kvl3(content_type, Res#response.headers),
- kvl3("content-type", Res#response.headers),
- kvl3("Content-Type", Res#response.headers),
- kvl3("CONTENT-TYPE", Res#response.headers),
- "text/html"
- ]),
+
+ %% Get the content type...
+ ContentType = get_content_type(Res),
% Send the yaws response...
lists:flatten([
- {status, Code},
- Headers,
- {content, ContentType, Body}
- ]);
+ {status, Code},
+ Headers,
+ {content, ContentType, Body}
+ ]);
{file, Path} ->
+ %% Note: This section should only be entered in the event that a static file is
+ %% requested that isn't found in the 'appmod' section of the yaws.conf file.
+ %% I've not found a way to "pass the buck" back to yaws and say "even though this
+ %% directory isn't found in the appmod, I want you to serve it anyway". This
+ %% means that with the current implementation, you don't want to be serving files
+ %% big files that aren't covered in the appmod section, primarily because this little
+ %% snippet loads the entire file into memory then passes it off to yaws to be served,
+ %% rather than streaming it. I'll need to look further into to either 1) Pass the buck
+ %% completely back to Yaws, or 2) how the streamcontent return types work as define in
+ %% yaws_server:handle_out_reply
+
%% Calculate expire date far into future...
Seconds = calendar:datetime_to_gregorian_seconds(calendar:local_time()),
TenYears = 10 * 365 * 24 * 60 * 60,
Seconds1 = calendar:gregorian_seconds_to_datetime(Seconds + TenYears),
ExpireDate = httpd_util:rfc1123_date(Seconds1),
- % Create the response telling Yaws to server file...
- Options = [{header, {"Expires", ExpireDate}}],
- Path = filename:join(".", Path),
- {page, {Options, Path}}
+ %% Docroot needed to find file in Path
+ Docroot = yaws_api:arg_docroot(_Arg),
+ FullPath = [Docroot,Path],
+
+ %% Get the content type as defined by yaws
+ ContentType = yaws_api:mime_type(Path),
+
+ %% Get the file content
+ FullResponse = case file:read_file(FullPath) of
+ {error,enoent} ->
+ yaws_outmod:out404(_Arg);
+ {ok,Bin} ->
+ [
+ {status, Code},
+ [{header, {"Expires", ExpireDate}} | Headers],
+ {content, ContentType, Bin}
+ ]
+ end,
+ lists:flatten(FullResponse)
end.
+assemble_headers(Res) ->
+ lists:flatten([
+ [{header, {X#header.name, X#header.value}} || X <- Res#response.headers],
+ [create_cookie(X) || X <- Res#response.cookies]
+ ]).
+
+get_content_type(Res) ->
+ coalesce([
+ kvl3(content_type, Res#response.headers),
+ kvl3("content-type", Res#response.headers),
+ kvl3("Content-Type", Res#response.headers),
+ kvl3("CONTENT-TYPE", Res#response.headers),
+ "text/html"
+ ]).
+
kvl3(Key,L) ->
case lists:keysearch(Key,2,L) of
{value, {_,_,Val}} -> Val;