bryan / wmexamples
Example resources for Webmachine (http://bitbucket.org/justin/webmachine). This project contains several sample webmachine resources that I've posted elsewhere at various times. This is a complete Webmachine app ready to be run and examined.
Clone this repository (size: 42.5 KB): HTTPS / SSH
$ hg clone http://bitbucket.org/bryan/wmexamples/
| commit 18: | a6ddaedf483e |
| parent 17: | 3796e75618aa |
| branch: | default |
| tags: | tip |
point to new ibrowse-1.5 repo on github
8 months ago
wmexamples /
src
/
couchdb_proxy.erl
| # Introduced |
|---|
1 |
bd68f4076442 |
%% @author Bryan Fink |
2 |
bd68f4076442 |
%% @doc couchdb_proxy is intended to be a simple webmachine resource |
3 |
bd68f4076442 |
%% for proxying Webmachine requests to CouchDB. In theory, it's |
4 |
bd68f4076442 |
%% general enough to be a simple proxy to most any other HTTP |
5 |
bd68f4076442 |
%% service, but I make no guarantees about assumption it makes that |
6 |
bd68f4076442 |
%% are CouchDB-specific. |
7 |
bd68f4076442 |
%% |
8 |
bd68f4076442 |
%% Load this with a dispatch line like: |
9 |
bd68f4076442 |
%% {['*'], couchdb_proxy, {ExternalPath, CouchPath}}. |
10 |
bd68f4076442 |
%% Where: |
11 |
bd68f4076442 |
%% ExternalPath is the base path to this resource, like |
12 |
bd68f4076442 |
%% "http://localhost:8000/" |
13 |
bd68f4076442 |
%% CouchPath is the base path to your CouchDB server, like |
14 |
bd68f4076442 |
%% "http://localhost:5984/" |
15 |
bd68f4076442 |
%% |
16 |
bd68f4076442 |
%% Another useful example might be: |
17 |
bd68f4076442 |
%% {["couch", '*'], couchdb_proxy, |
18 |
bd68f4076442 |
%% {"http://localhost:8000/couch/", |
19 |
bd68f4076442 |
%% "http://localhost:5984/"}} |
20 |
bd68f4076442 |
%% Which would redirect requests from |
21 |
bd68f4076442 |
%% http://localhost:8000/couch/DATABASE/KEY |
22 |
bd68f4076442 |
%% to |
23 |
bd68f4076442 |
%% http://localhost:5984/DATABASE/KEY |
24 |
bd68f4076442 |
-module(couchdb_proxy). |
25 |
bd68f4076442 |
-export([init/1, |
26 |
bd68f4076442 |
service_available/2]). |
27 |
bd68f4076442 |
-include_lib("webmachine/include/webmachine.hrl"). |
28 |
bd68f4076442 |
|
29 |
bd68f4076442 |
init(Config) -> {ok, Config}. |
30 |
bd68f4076442 |
|
31 |
bd68f4076442 |
%% request to couchdb is made in service_available, such that |
32 |
bd68f4076442 |
%% if couchdb isn't up, we return 503 Service Unavailable, as expected |
33 |
bd68f4076442 |
service_available(RP, C={_ExternalPath, CouchPath}) -> |
34 |
bd68f4076442 |
%% point path at couchdb server |
35 |
bd68f4076442 |
Path = lists:append( |
36 |
bd68f4076442 |
[CouchPath, |
37 |
bd68f4076442 |
wrq:disp_path(RP), |
38 |
bd68f4076442 |
case wrq:req_qs(RP) of |
39 |
bd68f4076442 |
[] -> []; |
40 |
bd68f4076442 |
Qs -> [$?|mochiweb_util:urlencode(Qs)] |
41 |
bd68f4076442 |
end]), |
42 |
bd68f4076442 |
|
43 |
bd68f4076442 |
%% translate webmachine details to ibrowse details |
44 |
bd68f4076442 |
Headers = clean_request_headers( |
45 |
bd68f4076442 |
mochiweb_headers:to_list(wrq:req_headers(RP))), |
46 |
bd68f4076442 |
Method = wm_to_ibrowse_method(wrq:method(RP)), |
47 |
bd68f4076442 |
ReqBody = case wrq:req_body(RP) of |
48 |
bd68f4076442 |
undefined -> []; |
49 |
bd68f4076442 |
B -> B |
50 |
bd68f4076442 |
end, |
51 |
bd68f4076442 |
|
52 |
bd68f4076442 |
case ibrowse:send_req(Path, Headers, Method, ReqBody) of |
53 |
bd68f4076442 |
{ok, Status, CouchHeaders, RespBody} -> |
54 |
bd68f4076442 |
RespHeaders = fix_location(CouchHeaders, C), |
55 |
bd68f4076442 |
|
56 |
bd68f4076442 |
%% stop resource processing here and return whatever |
57 |
bd68f4076442 |
%% couchdb wanted to return |
58 |
bd68f4076442 |
{{halt, list_to_integer(Status)}, |
59 |
bd68f4076442 |
wrq:set_resp_headers(RespHeaders, |
60 |
bd68f4076442 |
wrq:set_resp_body(RespBody, RP)), |
61 |
bd68f4076442 |
C}; |
62 |
bd68f4076442 |
_ -> |
63 |
bd68f4076442 |
{false, RP, C} |
64 |
bd68f4076442 |
end. |
65 |
bd68f4076442 |
|
66 |
bd68f4076442 |
%% ibrowse will recalculate Host and Content-Length headers, |
67 |
bd68f4076442 |
%% and will muck them up if they're manually specified |
68 |
bd68f4076442 |
clean_request_headers(Headers) -> |
69 |
bd68f4076442 |
[{K,V} || {K,V} <- Headers, |
70 |
bd68f4076442 |
K /= 'Host', K /= 'Content-Length']. |
71 |
bd68f4076442 |
|
72 |
bd68f4076442 |
%% webmachine expresses method as all-caps string or atom, |
73 |
bd68f4076442 |
%% while ibrowse uses all-lowercase atom |
74 |
bd68f4076442 |
wm_to_ibrowse_method(Method) when is_list(Method) -> |
75 |
bd68f4076442 |
list_to_atom(string:to_lower(Method)); |
76 |
bd68f4076442 |
wm_to_ibrowse_method(Method) when is_atom(Method) -> |
77 |
bd68f4076442 |
wm_to_ibrowse_method(atom_to_list(Method)). |
78 |
bd68f4076442 |
|
79 |
bd68f4076442 |
%% couchdb returns a fully-qualified URI in Location - |
80 |
bd68f4076442 |
%% hack off the couch host, and drop in this proxy host |
81 |
bd68f4076442 |
fix_location([], _) -> []; |
82 |
bd68f4076442 |
fix_location([{"Location", CouchDataPath}|Rest], |
83 |
bd68f4076442 |
{ExternalPath, CouchPath}) -> |
84 |
bd68f4076442 |
DataPath = lists:nthtail(length(CouchPath), CouchDataPath), |
85 |
bd68f4076442 |
[{"Location", ExternalPath++DataPath}|Rest]; |
86 |
bd68f4076442 |
fix_location([H|T], C) -> |
87 |
bd68f4076442 |
[H|fix_location(T, C)]. |
