wmexamples / src / couchdb_proxy.erl

%% @author Bryan Fink
%% @doc couchdb_proxy is intended to be a simple webmachine resource
%%      for proxying Webmachine requests to CouchDB.  In theory, it's
%%      general enough to be a simple proxy to most any other HTTP
%%      service, but I make no guarantees about assumption it makes that
%%      are CouchDB-specific.
%%      Load this with a dispatch line like:
%%      {['*'], couchdb_proxy, {ExternalPath, CouchPath}}.
%%      Where:
%%        ExternalPath is the base path to this resource, like
%%          "http://localhost:8000/"
%%        CouchPath is the base path to your CouchDB server, like
%%          "http://localhost:5984/"
%%      Another useful example might be:
%%        {["couch", '*'], couchdb_proxy,
%%         {"http://localhost:8000/couch/",
%%          "http://localhost:5984/"}}
%%      Which would redirect requests from
%%        http://localhost:8000/couch/DATABASE/KEY
%%      to
%%        http://localhost:5984/DATABASE/KEY

init(Config) -> {ok, Config}.

%% request to couchdb is made in service_available, such that
%% if couchdb isn't up, we return 503 Service Unavailable, as expected
service_available(RP, C={_ExternalPath, CouchPath}) ->
    %% point path at couchdb server
    Path = lists:append(
              case wrq:req_qs(RP) of
                  [] -> [];
                  Qs -> [$?|mochiweb_util:urlencode(Qs)]

    %% translate webmachine details to ibrowse details
    Headers = clean_request_headers(
    Method = wm_to_ibrowse_method(wrq:method(RP)),
    ReqBody = case wrq:req_body(RP) of
                  undefined -> [];
                  B -> B

    case ibrowse:send_req(Path, Headers, Method, ReqBody) of
        {ok, Status, CouchHeaders, RespBody} ->
            RespHeaders = fix_location(CouchHeaders, C),

            %% stop resource processing here and return whatever
            %% couchdb wanted to return
            {{halt, list_to_integer(Status)},
                                  wrq:set_resp_body(RespBody, RP)),
        _ ->
            {false, RP, C}

%% ibrowse will recalculate Host and Content-Length headers,
%% and will muck them up if they're manually specified
clean_request_headers(Headers) ->
    [{K,V} || {K,V} <- Headers,
              K /= 'Host', K /= 'Content-Length'].

%% webmachine expresses method as all-caps string or atom,
%% while ibrowse uses all-lowercase atom
wm_to_ibrowse_method(Method) when is_list(Method) ->
wm_to_ibrowse_method(Method) when is_atom(Method) ->

%% couchdb returns a fully-qualified URI in Location -
%% hack off the couch host, and drop in this proxy host
fix_location([], _) -> [];
fix_location([{"Location", CouchDataPath}|Rest],
             {ExternalPath, CouchPath}) ->
    DataPath = lists:nthtail(length(CouchPath), CouchDataPath),
    [{"Location", ExternalPath++DataPath}|Rest];
fix_location([H|T], C) ->
    [H|fix_location(T, C)].
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.