Commits

Anonymous committed bd68f40

couchdb_proxy resource, README file

Comments (0)

Files changed (5)

+Title: README for wmexamples
+Author: Bryan Fink
+Source URL: http://bitbucket.org/bryan/wmexamples/
+Created: 2009.May.18
+------
+
+wmexamples is a demonstration Webmachine application.  It contains a handful of Webmachine resources that I have posted elsewhere, but have chosen to assembled here to show off.  To run this application, you'll need:
+
+- Erlang - http://erlang.org/
+- Webmachine - http://bitbucket.org/justin/webmachine
+- ibrowse - http://code.google.com/p/erl-ibrowse/
+
+Note: ibrowse is only needed for the CouchDB proxy resource.  If you will not be using couchdb_proxy, you can remove the dependency on ibrowse by commenting out the "ensure_started(ibrowse)" and "application:stop(ibrowse)" lines in src/wmexamples.erl.
+
+
+Installation
+---
+
+Checkout/unpack wmexamples.
+
+If the directory in which wmexamples lives also contains webmachine and ibrowse, you should be all set.  If not, you'll need to change the symlinks in wmexamples/deps to point to where webmachine and ibrowse are installed.
+
+cd wmexamples
+make
+
+If everything makes fine, run ./start.sh or ./start-dev.sh, then point curl or a browser at http://localhost:8000.  You should get a "Hello, new world" message back.
+
+
+Example Resources
+---
+
+The best place to determine what resources are enabled is in priv/dispatch.conf.  That file describes the dispatch table for this Webmachine application, and also contains helpful comments.  Other short descriptions of what examples are included are below.
+
+
+CouchDB Proxy Resource
+---
+
+couchdb_proxy.erl describe a proxy resource for exposing raw CouchDB through a webmachine app.  It does very minimal request processing - only fixing Location headers such that clients of the webmachine resource direct their requests properly.
+
+To use couchdb_proxy.erl in your own application, copy the file to your application's src tree, and also make sure the ibrowse is started before any requests are made to the resource (wmexamples starts ibrowse at startup in wmexamples.erl).
+../../ibrowse

priv/dispatch.conf

 %% -*- mode: erlang -*-
 
 {[], wmexamples_resource, []}.
+
+%% CouchDB proxy example
+%% This will expose all couchdb functionality through webmachine
+%% such that requests to
+%%   http://localhost:5984/            (default couch host:port)
+%% and
+%%   http://localhost:8000/couch/      (chosen proxy host:port/path)
+%% are equivalent.
+%%
+%% A few commands to try:
+%%   curl http://localhost:8000/couch/
+%%   curl -X PUT http://localhost:8000/couch/mytestdb
+%%   curl -X PUT http://localhost:8000/couch/mytestdb/123 \
+%%        --data "{\"hello\":\"goodbye\"}"
+%%   curl http://localhost:8000/couch/mytestdb/123
+{["couch",'*'],                   %% expose resource at /couch/*
+ couchdb_proxy,                   %% defined in couchdb_proxy.erl
+ {"http://localhost:8000/couch/", %% "ExternalPath" init param
+  "http://localhost:5984/"}}.     %% "CouchPath" init param

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
+-module(couchdb_proxy).
+-export([init/1,
+         service_available/2]).
+-include_lib("webmachine/include/webmachine.hrl").
+
+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(
+             [CouchPath,
+              wrq:disp_path(RP),
+              case wrq:req_qs(RP) of
+                  [] -> [];
+                  Qs -> [$?|mochiweb_util:urlencode(Qs)]
+              end]),
+
+    %% translate webmachine details to ibrowse details
+    Headers = clean_request_headers(
+                mochiweb_headers:to_list(wrq:req_headers(RP))),
+    Method = wm_to_ibrowse_method(wrq:method(RP)),
+    ReqBody = case wrq:req_body(RP) of
+                  undefined -> [];
+                  B -> B
+              end,
+
+    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_headers(RespHeaders,
+                                  wrq:set_resp_body(RespBody, RP)),
+             C};
+        _ ->
+            {false, RP, C}
+    end.
+
+%% 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) ->
+    list_to_atom(string:to_lower(Method));
+wm_to_ibrowse_method(Method) when is_atom(Method) ->
+    wm_to_ibrowse_method(atom_to_list(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)].

src/wmexamples.erl

     wmexamples_deps:ensure(),
     ensure_started(crypto),
     ensure_started(webmachine),
+    ensure_started(ibrowse),
     application:start(wmexamples).
 
 %% @spec stop() -> ok
 %% @doc Stop the wmexamples server.
 stop() ->
     Res = application:stop(wmexamples),
+    application:stop(ibrowse),
     application:stop(webmachine),
     application:stop(crypto),
     Res.