251 lines
7.1 KiB
HTML
251 lines
7.1 KiB
HTML
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
|
||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
<head>
|
||
|
<meta name="author" content="Basho Technologies" />
|
||
|
<meta name="description" content="Webmachine examples" />
|
||
|
<meta name="keywords" content="webmachine http rest web" />
|
||
|
<meta http-equiv="content-type" content="text/html;charset=utf-8" />
|
||
|
<link rel="stylesheet" href="css/style-1c.css" type="text/css" />
|
||
|
<title>Webmachine examples</title>
|
||
|
</head>
|
||
|
<body>
|
||
|
<div id="content">
|
||
|
<h1><span class="hr"></span><a href="/">webmachine</a></h1>
|
||
|
<ul id="top">
|
||
|
<li><a href="/">Home</a></li>
|
||
|
<li><a href="http://bitbucket.org/justin/webmachine/">Source Code</a></li>
|
||
|
<li><a href="contact.html">Contact</a></li>
|
||
|
</ul>
|
||
|
<div id="left">
|
||
|
<h3>Webmachine examples</h3>
|
||
|
|
||
|
<p>
|
||
|
The simplest possible example is the one produced via the
|
||
|
<a href="quickstart.html">quickstart</a>.
|
||
|
</p>
|
||
|
<p>
|
||
|
For an example of a read/write filesystem server showing several
|
||
|
interesting features and supporting GET, PUT, DELETE, and POST, see
|
||
|
<a href="http://bitbucket.org/justin/webmachine/src/tip/demo/src/demo_fs_resource.erl">demo_fs_resource</a>.
|
||
|
</p>
|
||
|
<p>
|
||
|
For a very simple resource demonstrating content negotiation, basic
|
||
|
auth, and some caching headers, see
|
||
|
<a href="http://bitbucket.org/justin/webmachine/src/tip/demo/src/webmachine_demo_resource.erl">webmachine_demo_resource</a>.
|
||
|
</p>
|
||
|
<p>
|
||
|
Some example code based on webmachine_demo_resource follows.
|
||
|
</p>
|
||
|
<p>
|
||
|
The simplest working resource could export only one function in
|
||
|
addition to init/1:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
-module(webmachine_demo_resource).
|
||
|
-export([init/1, to_html/2]).
|
||
|
-include_lib("webmachine/include/webmachine.hrl").
|
||
|
|
||
|
init([]) -> {ok, undefined}.
|
||
|
|
||
|
to_html(ReqData, Context) -> {"<html><body>Hello, new world</body></html>", ReqData, Context}.
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
That's really it -- a working webmachine resource. That resource will
|
||
|
respond to all valid GET requests with the exact same response.
|
||
|
</p>
|
||
|
<p>
|
||
|
Many interesting bits of HTTP are handled automatically by
|
||
|
Webmachine. For instance, if a client sends a request to that trivial
|
||
|
resource with an Accept header that does not allow for a text/html
|
||
|
response, they will receive a 406 Not Acceptable.
|
||
|
</p>
|
||
|
<p>
|
||
|
Suppose I wanted to serve a plaintext client as well. I could note
|
||
|
that I provide more than just HTML:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
content_types_provided(ReqData, Context) ->
|
||
|
{[{"text/html", to_html},{"text/plain",to_text}], ReqData, Context}.
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
I already have my HTML representation produced, so I add a text one:
|
||
|
(and while I'm at it, I'll show that it's trivial to produce dynamic content as well)
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
to_text(ReqData, Context) ->
|
||
|
Path = wrq:disp_path(ReqData),
|
||
|
Body = io_lib:format("Hello ~s from webmachine.~n", [Path]),
|
||
|
{Body, ReqData, Context}.
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
Now that this resource provides multiple media types, it automatically performs conneg:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
$ telnet localhost 8000
|
||
|
Trying 127.0.0.1...
|
||
|
Connected to localhost.
|
||
|
Escape character is '^]'.
|
||
|
GET /demo/a/resource/path HTTP/1.1
|
||
|
Accept: text/plain
|
||
|
|
||
|
HTTP/1.1 200 OK
|
||
|
Vary: Accept
|
||
|
Server: MochiWeb/1.1 WebMachine/0.97
|
||
|
Date: Sun, 15 Mar 2009 02:54:02 GMT
|
||
|
Content-Type: text/plain
|
||
|
Content-Length: 39
|
||
|
|
||
|
Hello a/resource/path from webmachine.
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
What about authorization? Webmachine resources default to assuming the
|
||
|
client is authorized, but that can easily be overridden. Here's an
|
||
|
overly simplistic but illustrative example:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
is_authorized(ReqData, Context) ->
|
||
|
case wrq:disp_path(ReqData) of
|
||
|
"authdemo" ->
|
||
|
case wrq:get_req_header("authorization", ReqData) of
|
||
|
"Basic "++Base64 ->
|
||
|
Str = base64:mime_decode_to_string(Base64),
|
||
|
case string:tokens(Str, ":") of
|
||
|
["authdemo", "demo1"] ->
|
||
|
{true, ReqData, Context};
|
||
|
_ ->
|
||
|
{"Basic realm=webmachine", ReqData, Context}
|
||
|
end;
|
||
|
_ ->
|
||
|
{"Basic realm=webmachine", ReqData, Context}
|
||
|
end;
|
||
|
_ -> {true, ReqData, Context}
|
||
|
end.
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
With that function in the resource, all paths except
|
||
|
<code>/authdemo</code> from this resource's root are authorized.
|
||
|
For that one path,
|
||
|
the UA will be asked to do basic authorization with the user/pass of
|
||
|
authdemo/demo1. It should go without saying that this isn't quite the
|
||
|
same function that we use in our real apps, but it is nice and simple.
|
||
|
</p>
|
||
|
<p>
|
||
|
If you've generated the application from the
|
||
|
<a href="quickstart.html">quickstart</a>, make sure
|
||
|
you've added this line to your dispatch.conf file:
|
||
|
</p>
|
||
|
<pre>
|
||
|
{["demo", '*'], mywebdemo_resource, []}.
|
||
|
</pre>
|
||
|
<p>
|
||
|
Now you can point your browser at
|
||
|
<code>http://localhost:8000/demo/authdemo</code> with the demo app running:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
$ curl -v http://localhost:8000/demo/authdemo
|
||
|
> GET /demo/authdemo HTTP/1.1
|
||
|
> Host: localhost:8000
|
||
|
> Accept: */*
|
||
|
>
|
||
|
< HTTP/1.1 401 Unauthorized
|
||
|
< WWW-Authenticate: Basic realm=webmachine
|
||
|
< Server: MochiWeb/1.1 WebMachine/0.97
|
||
|
< Date: Sun, 15 Mar 2009 02:57:43 GMT
|
||
|
< Content-Length: 0
|
||
|
|
||
|
<
|
||
|
</pre>
|
||
|
<p></p>
|
||
|
<pre>
|
||
|
$ curl -v -u authdemo:demo1 http://localhost:8000/demo/authdemo
|
||
|
* Server auth using Basic with user 'authdemo'
|
||
|
> GET /demo/authdemo HTTP/1.1
|
||
|
> Authorization: Basic YXV0aGRlbW86ZGVtbzE=
|
||
|
> Host: localhost:8000
|
||
|
> Accept: */*
|
||
|
>
|
||
|
< HTTP/1.1 200 OK
|
||
|
< Vary: Accept
|
||
|
< Server: MochiWeb/1.1 WebMachine/0.97
|
||
|
|
||
|
< Date: Sun, 15 Mar 2009 02:59:02 GMT
|
||
|
< Content-Type: text/html
|
||
|
< Content-Length: 59
|
||
|
<
|
||
|
<html><body>Hello authdemo from webmachine.
|
||
|
</body></html>
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
HTTP caching support is also quite easy, with functions allowing
|
||
|
resources to define (e.g.) <code>last_modified</code>,
|
||
|
<code>expires</code>, and <code>generate_etag.</code> For instance, since
|
||
|
representations of this resource vary only by URI Path, I could use an
|
||
|
extremely simple entity tag unfit for most real applications but
|
||
|
sufficient for this example:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
generate_etag(ReqData, Context) -> {wrq:raw_path(ReqData), ReqData, Context}.
|
||
|
</pre>
|
||
|
|
||
|
<p>Similarly, here's a trivial expires rule:</p>
|
||
|
|
||
|
<pre>
|
||
|
expires(ReqData, Context) -> {{{2021,1,1},{0,0,0}}, ReqData, Context}.
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
And now the response from our earlier request is appropriately tagged:
|
||
|
</p>
|
||
|
|
||
|
<pre>
|
||
|
HTTP/1.1 200 OK
|
||
|
Vary: Accept
|
||
|
Server: MochiWeb/1.1 WebMachine/0.97
|
||
|
Expires: Fri, 01 Jan 2021 00:00:00 GMT
|
||
|
ETag: /demo/authdemo
|
||
|
Date: Sun, 15 Mar 2009 02:59:02 GMT
|
||
|
Content-Type: text/html
|
||
|
Content-Length: 59
|
||
|
|
||
|
<html><body>Hello authdemo from webmachine.
|
||
|
</body></html>
|
||
|
</pre>
|
||
|
|
||
|
<p>
|
||
|
For more details, read the source of the resources linked at the top
|
||
|
of this page.
|
||
|
</p>
|
||
|
|
||
|
</div>
|
||
|
<div id="footer">
|
||
|
|
||
|
</div>
|
||
|
</div>
|
||
|
|
||
|
<script type="text/javascript">
|
||
|
var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
|
||
|
document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
|
||
|
</script>
|
||
|
<script type="text/javascript">
|
||
|
try {
|
||
|
var pageTracker = _gat._getTracker("UA-4979965-5");
|
||
|
pageTracker._trackPageview();
|
||
|
} catch(err) {}</script>
|
||
|
|
||
|
</body>
|
||
|
</html>
|
||
|
|