Sean Cribbshttp://seancribbs.comJekyllSean Cribbshttp://seancribbs.com2022-05-06T09:13:09-05:00Sean Cribbs2016-08-17T00:00:00-05:00Webmachine in Elixir Tutorial, Part 4http://seancribbs.com/tech/2016/08/17/webmachine-elixir-tutorial-4/<p>In the <a href="/tech/2016/07/18/webmachine-elixir-tutorial-3/">previous installment</a>, we displayed some dynamic
content in our webpage that was loaded from an <code class="language-plaintext highlighter-rouge">ets</code> table. Even a
social network themed around <em>The Wire</em> is no fun if you can’t add
your favorite quotes to it. Let’s hook up a form so that we can submit
to post new tweets, and a resource to accept that POST.</p>
<h2 id="creating-tweets">Creating tweets</h2>
<p>Our HTML file already has portions of a form in it, but let’s add the
ability to select your character.</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html"> <span class="nt"><div</span> <span class="na">id=</span><span class="s">"add-tweet-form"</span> <span class="na">class=</span><span class="s">"add-tweet-form"</span><span class="nt">></span>
<span class="nt"><select</span> <span class="na">id=</span><span class="s">"add-tweet-avatar"</span> <span class="na">class=</span><span class="s">"person"</span><span class="nt">></span>
<span class="nt"><option</span> <span class="na">value=</span><span class="s">"http://upload.wikimedia.org/wikipedia/en/thumb/f/f4/The_Wire_Jimmy_McNulty.jpg/250px-The_Wire_Jimmy_McNulty.jpg"</span><span class="nt">></span>Jimmy<span class="nt"></option></span>
<span class="nt"><option</span> <span class="na">value=</span><span class="s">"http://upload.wikimedia.org/wikipedia/en/thumb/1/15/The_Wire_Bunk.jpg/250px-The_Wire_Bunk.jpg"</span><span class="nt">></span>Bunk<span class="nt"></option></span>
<span class="nt"><option</span> <span class="na">value=</span><span class="s">"http://upload.wikimedia.org/wikipedia/en/thumb/6/6c/The_Wire_Kima.jpg/250px-The_Wire_Kima.jpg"</span><span class="nt">></span>Kima<span class="nt"></option></span>
<span class="nt"><option</span> <span class="na">value=</span><span class="s">"http://upload.wikimedia.org/wikipedia/en/thumb/7/73/The_Wire_Bubbles.jpg/250px-The_Wire_Bubbles.jpg"</span><span class="nt">></span>Bubbles<span class="nt"></option></span>
<span class="nt"><option</span> <span class="na">value=</span><span class="s">"http://upload.wikimedia.org/wikipedia/en/thumb/2/2f/The_Wire_Avon.jpg/250px-The_Wire_Avon.jpg"</span><span class="nt">></span>Avon<span class="nt"></option></span>
<span class="nt"><option</span> <span class="na">value=</span><span class="s">"http://upload.wikimedia.org/wikipedia/en/thumb/b/b7/The_Wire_Stringer_Bell.jpg/250px-The_Wire_Stringer_Bell.jpg"</span><span class="nt">></span>Stringer<span class="nt"></option></span>
<span class="nt"><option</span> <span class="na">value=</span><span class="s">"http://upload.wikimedia.org/wikipedia/en/thumb/7/78/The_Wire_Omar.jpg/250px-The_Wire_Omar.jpg"</span><span class="nt">></span>Omar<span class="nt"></option></span>
<span class="nt"></select></span>
<span class="nt"><input</span> <span class="na">id=</span><span class="s">"add-tweet-message"</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">class=</span><span class="s">"message"</span> <span class="nt">/></span>
<span class="nt"><a</span> <span class="na">id=</span><span class="s">"add-tweet-submit"</span> <span class="na">class=</span><span class="s">"button post"</span><span class="nt">></span>POST!<span class="nt"></a></span>
<span class="nt"></div></span></code></pre></figure>
<p>Now we need some JavaScript to show and hide the “form”, and submit it.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"> <span class="c1">// Toggle the visibility of the form</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#add-tweet</span><span class="dl">'</span><span class="p">).</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#add-tweet-form</span><span class="dl">'</span><span class="p">).</span><span class="nx">toggle</span><span class="p">();</span>
<span class="p">});</span>
<span class="c1">// Submit the form on click of the "POST!" button</span>
<span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#add-tweet-submit</span><span class="dl">'</span><span class="p">).</span><span class="nx">click</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">tweetMessageField</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#add-tweet-message</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">tweetMessageAvatar</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#add-tweet-avatar</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">tweetMessageForm</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#add-tweet-form</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">var</span> <span class="nx">tweetMessage</span> <span class="o">=</span> <span class="nx">tweetMessageField</span><span class="p">.</span><span class="nx">val</span><span class="p">();</span>
<span class="kd">var</span> <span class="nx">tweetAvatar</span> <span class="o">=</span> <span class="nx">tweetMessageAvatar</span><span class="p">.</span><span class="nx">val</span><span class="p">();</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="na">type</span><span class="p">:</span> <span class="dl">'</span><span class="s1">POST</span><span class="dl">'</span><span class="p">,</span>
<span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/tweets</span><span class="dl">'</span><span class="p">,</span>
<span class="na">contentType</span><span class="p">:</span> <span class="dl">'</span><span class="s1">application/json</span><span class="dl">'</span><span class="p">,</span>
<span class="na">data</span><span class="p">:</span> <span class="nx">JSON</span><span class="p">.</span><span class="nx">stringify</span><span class="p">({</span> <span class="na">tweet</span><span class="p">:</span> <span class="p">{</span>
<span class="na">avatar</span><span class="p">:</span> <span class="nx">tweetAvatar</span><span class="p">,</span>
<span class="na">message</span><span class="p">:</span> <span class="nx">tweetMessage</span> <span class="p">}}),</span>
<span class="na">success</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">tweetMessageField</span><span class="p">.</span><span class="nx">val</span><span class="p">(</span><span class="dl">''</span><span class="p">);</span>
<span class="nx">tweetMessageForm</span><span class="p">.</span><span class="nx">toggle</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>If you refresh the browser and click the button to “POST!”, you should
get an error in the JavaScript console, specifically <code class="language-plaintext highlighter-rouge">405 Method Not
Allowed</code>. This is because our <code class="language-plaintext highlighter-rouge">TweetList</code> resource doesn’t accept
POST!</p>
<p>We have two options here, we can modify our existing resource or
create a new resource to handle the form submission. The difference
will be whether we can tolerate the extra logic for accepting the form
amongst the logic for producing a list of tweets, or whether we prefer
to create a resource that only accepts the form. Personally, I could
handle either way (and potentially change my mind later) but I’m going
to choose the latter for clarity and to demonstrate another feature of
Webmachine. Let’s create our new resource!</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Tweet</span> <span class="k">do</span>
<span class="c1"># Boilerplate again! In this case the state is the contents of our</span>
<span class="c1"># tweet, initially an empty identifier and attribute list.</span>
<span class="k">def</span> <span class="n">init</span><span class="p">(</span><span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">{</span><span class="no">nil</span><span class="p">,</span> <span class="p">[]}}</span>
<span class="k">def</span> <span class="n">ping</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:pong</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>Now, the whole goal of this resource was to accept <code class="language-plaintext highlighter-rouge">POST</code> requests, so
we better allow them.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># This resource supports POST for creating tweets. We colon-prefix</span>
<span class="c1"># the POST atom to ensure the Elixir compiler doesn't treat it as</span>
<span class="c1"># a module name:</span>
<span class="c1">#</span>
<span class="c1"># iex> :io.format('~s~n', [POST])</span>
<span class="c1"># Elixir.POST</span>
<span class="c1"># :ok</span>
<span class="c1"># iex> :io.format('~s~n', [:POST])</span>
<span class="c1"># POST</span>
<span class="c1"># :ok</span>
<span class="c1"># iex> POST == :POST</span>
<span class="c1"># false</span>
<span class="k">def</span> <span class="n">allowed_methods</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{[</span><span class="ss">:POST</span><span class="p">],</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>Now we can talk about what our options are for accepting the form. We
can take two paths: first, we assume the accepting resource always
exists and simply handles the POST in its own way; second, we assume
the resource CREATES new resources, and treat it as a PUT to a new
URI.</p>
<p>The latter way will feel very familiar if you’ve used Rails before,
except that you need to pick the new URI before the request body is
accepted! This trips developers up frequently, but is easy to work
around. Since we already know how to allocate unique identifiers using
<code class="language-plaintext highlighter-rouge">monotonic_time</code>, constructing a new unique URI should be
straightforward.</p>
<p>Our first step is to tell Webmachine that our resource <em>doesn’t</em>
exist, that POST means <em>creating</em> a new resource, and that it’s okay
to allow POST when the resource doesn’t exist:</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># When we accept POST, our resource is missing!</span>
<span class="k">def</span> <span class="n">resource_exists</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="no">false</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="c1"># Accepting POST means creating a resource.</span>
<span class="k">def</span> <span class="n">post_is_create</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="no">true</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="c1"># Allow POST to missing resources</span>
<span class="k">def</span> <span class="n">allow_missing_post</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="no">true</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>Now we should pick the URI where our new tweet will live. Whether or
not we allow fetching that new URI is another question entirely! We
might revisit that later in the tutorial.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Generate the path for the new resource, populating the ID in the</span>
<span class="c1"># state as well.</span>
<span class="k">def</span> <span class="n">create_path</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="p">{</span><span class="n">_id</span><span class="p">,</span> <span class="n">attrs</span><span class="p">})</span> <span class="k">do</span>
<span class="n">new_id</span> <span class="o">=</span> <span class="no">System</span><span class="o">.</span><span class="n">monotonic_time</span>
<span class="p">{</span><span class="s1">'/tweets/</span><span class="si">#{</span><span class="n">new_id</span><span class="si">}</span><span class="s1">'</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="p">{</span><span class="n">new_id</span><span class="p">,</span> <span class="n">attrs</span><span class="p">}}</span>
<span class="k">end</span></code></pre></figure>
<p>If our application were backed by a database like PostgreSQL, we
could use an SQL query here to fetch the next ID in the table’s
sequence. Instead, we just call <code class="language-plaintext highlighter-rouge">monotonic_time</code>. Note how we
capture the generated ID in the resource state for when we insert
the tweet into the ETS table.</p>
<p>We’re submitting <code class="language-plaintext highlighter-rouge">application/json</code> from the Ajax request, but
Webmachine doesn’t know that it’s ok to accept it, or what to do with
it when it arrives. Similar to <code class="language-plaintext highlighter-rouge">content_types_provided</code>, we can
specify this with the <code class="language-plaintext highlighter-rouge">content_types_accepted</code> callback.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># We take JSON</span>
<span class="k">def</span> <span class="n">content_types_accepted</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{[{</span><span class="s1">'application/json'</span><span class="p">,</span> <span class="ss">:from_json</span><span class="p">}],</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>Finally, we parse the incoming JSON, extract the fields, and put
the data in the ETS table.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="k">def</span> <span class="n">from_json</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="p">{</span><span class="n">id</span><span class="p">,</span> <span class="n">attrs</span><span class="p">})</span> <span class="k">do</span>
<span class="c1"># We use a try here so that our pattern match throws if we fail to</span>
<span class="c1"># decode or extract something from the request body.</span>
<span class="k">try</span> <span class="k">do</span>
<span class="c1"># Parse the request body, extracting the attributes of the tweet</span>
<span class="c1"># First fetch the request body</span>
<span class="n">req_body</span> <span class="o">=</span> <span class="ss">:wrq</span><span class="o">.</span><span class="n">req_body</span><span class="p">(</span><span class="n">req_data</span><span class="p">)</span>
<span class="c1"># Second, decode the JSON and destructure it</span>
<span class="p">{</span><span class="ss">:struct</span><span class="p">,</span> <span class="p">[{</span><span class="s2">"tweet"</span><span class="p">,</span> <span class="p">{</span><span class="ss">:struct</span><span class="p">,</span> <span class="n">attrs</span><span class="p">}}]}</span> <span class="o">=</span> <span class="ss">:mochijson2</span><span class="o">.</span><span class="n">decode</span><span class="p">(</span><span class="n">req_body</span><span class="p">)</span>
<span class="c1"># Now fetch the message and avatar attributes from the JSON</span>
<span class="p">{</span><span class="s2">"message"</span><span class="p">,</span> <span class="n">message</span><span class="p">}</span> <span class="o">=</span> <span class="no">List</span><span class="o">.</span><span class="n">keyfind</span><span class="p">(</span><span class="n">attrs</span><span class="p">,</span> <span class="s2">"message"</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="p">{</span><span class="s2">"avatar"</span><span class="p">,</span> <span class="n">avatar</span><span class="p">}</span> <span class="o">=</span> <span class="no">List</span><span class="o">.</span><span class="n">keyfind</span><span class="p">(</span><span class="n">attrs</span><span class="p">,</span> <span class="s2">"avatar"</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
<span class="c1"># Finally construct the data to go into ETS</span>
<span class="n">new_attrs</span> <span class="o">=</span> <span class="p">[</span><span class="ss">avatar:</span> <span class="n">avatar</span><span class="p">,</span> <span class="ss">message:</span> <span class="n">message</span><span class="p">,</span> <span class="ss">time:</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">timestamp</span><span class="p">]</span>
<span class="c1"># Insert into ETS and return true</span>
<span class="ss">:ets</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="ss">:tweets</span><span class="p">,</span> <span class="p">[{</span><span class="n">id</span><span class="p">,</span> <span class="n">new_attrs</span><span class="p">}])</span>
<span class="p">{</span><span class="no">true</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="p">{</span><span class="n">id</span><span class="p">,</span> <span class="n">new_attrs</span><span class="p">}}</span>
<span class="k">rescue</span>
<span class="c1"># If we threw from the above block, we should fail the request</span>
<span class="c1"># from the client. MatchError could be raised from our</span>
<span class="c1"># pattern-match when decoding JSON, or from :mochijson2</span>
<span class="c1"># itself. CaseClauseError is raised by :mochijson2 alone when </span>
<span class="c1"># we get bad JSON.</span>
<span class="n">err</span> <span class="ow">in</span> <span class="p">[</span><span class="no">MatchError</span><span class="p">,</span> <span class="no">CaseClauseError</span><span class="p">]</span> <span class="o">-></span>
<span class="p">{</span><span class="no">false</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="p">{</span><span class="n">id</span><span class="p">,</span> <span class="n">attrs</span><span class="p">}}</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Before our new resource will work, however, we need to dispatch to it!
Since we wanted to use the same URI as the <code class="language-plaintext highlighter-rouge">TweetList</code> resource, we
need to make sure that only <code class="language-plaintext highlighter-rouge">POST</code> requests make it to the <code class="language-plaintext highlighter-rouge">Tweet</code>
resource. This is where a route guard comes in. Route guard functions
take one argument, the <code class="language-plaintext highlighter-rouge">req_data</code> we’ve been passing around, and
should return a boolean. If the route is a 4-tuple, with the second
element being a route guard function, that guard will be tested before
dispatching is done (but after the path has matched).</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1"># --- lib/tweeter.ex ---</span>
<span class="c1"># Some configuration that Webmachine needs</span>
<span class="n">web_config</span> <span class="o">=</span> <span class="p">[</span><span class="ss">ip:</span> <span class="p">{</span><span class="mi">127</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">},</span>
<span class="ss">port:</span> <span class="mi">8080</span><span class="p">,</span>
<span class="ss">dispatch:</span> <span class="p">[</span>
<span class="c1"># Note the guard function in the second position</span>
<span class="p">{[</span><span class="s1">'tweets'</span><span class="p">],</span> <span class="o">&</span><span class="p">(</span><span class="ss">:wrq</span><span class="o">.</span><span class="n">method</span><span class="p">(</span><span class="nv">&1</span><span class="p">)</span> <span class="o">==</span> <span class="ss">:POST</span><span class="p">),</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Tweet</span><span class="p">,</span> <span class="p">[]},</span>
<span class="p">{[</span><span class="s1">'tweets'</span><span class="p">],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">TweetList</span><span class="p">,</span> <span class="p">[]},</span>
<span class="p">{[],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Assets</span><span class="p">,</span> <span class="p">[]},</span>
<span class="p">{[:</span><span class="s1">'*'</span><span class="p">],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Assets</span><span class="p">,</span> <span class="p">[]}</span>
<span class="p">]]</span></code></pre></figure>
<p>Now reload <code class="language-plaintext highlighter-rouge">mix</code> and see if you can post a tweet! (You might need to
reload the page after posting too.)</p>
<h2 id="up-next">Up next</h2>
<p>In our next and final installment, we’ll learn how to deliver live
updates to the client.</p>
Sean Cribbs2016-07-18T00:00:00-05:00Webmachine in Elixir Tutorial, Part 3http://seancribbs.com/tech/2016/07/18/webmachine-elixir-tutorial-3/<p><a href="/tech/2016/07/14/webmachine-elixir-tutorial-2/">Last time</a> we
started serving static files from the <code class="language-plaintext highlighter-rouge">priv</code> directory, supporting
validation caching, conditional requests, and compression. But since
static files are pretty boring, let’s populate our application’s page
with some dynamic content.</p>
<h2 id="dynamic-content">Dynamic content</h2>
<p>Instead of the static “tweets” in the homepage, let’s replace them
with some content injected via Ajax.</p>
<p>First, remove all of the <code class="language-plaintext highlighter-rouge"><li></code> elements from the <code class="language-plaintext highlighter-rouge"><ul></code> in <code class="language-plaintext highlighter-rouge">priv/www/index.html</code>
(shown below):</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt"><div</span> <span class="na">id=</span><span class="s">"content"</span><span class="nt">></span>
<span class="nt"><ul</span> <span class="na">id=</span><span class="s">"tweet-list"</span> <span class="na">class=</span><span class="s">"tweet-list"</span><span class="nt">></span>
<span class="c"><!-- Remove all the "li" elements here --></span>
<span class="nt"></ul></span>
<span class="nt"></div></span></code></pre></figure>
<p>To generate the dynamic content, we need to keep the base data
somewhere that we can fetch at runtime, we need to generate some
JSON for the browser, and we need to render that JSON into
HTML.</p>
<p>Let’s start by storing our tweets somewhere. Normally one would use a
real database and probably pull in Ecto to handle it. Since this is
just a tutorial, we’ll use <code class="language-plaintext highlighter-rouge">ets</code>
(<a href="http://erlang.org/doc/man/ets.html">Erlang Term Storage</a>). Create
the <code class="language-plaintext highlighter-rouge">ets</code> table and populate it when the application starts up in the
<code class="language-plaintext highlighter-rouge">start/2</code> function in <code class="language-plaintext highlighter-rouge">tweeter.ex</code>.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Monotonic time gives us unique, increasing integers to use as</span>
<span class="c1"># identifiers.</span>
<span class="k">defp</span> <span class="n">now</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">monotonic_time</span>
<span class="c1"># The timestamp is a standard Erlang 3-tuple format expected by</span>
<span class="c1"># Webmachine</span>
<span class="k">defp</span> <span class="n">timestamp</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="ss">:erlang</span><span class="o">.</span><span class="n">timestamp</span>
<span class="c1">## --- inside start/2</span>
<span class="c1"># Create a public ets table to store our tweets</span>
<span class="ss">:ets</span><span class="o">.</span><span class="n">new</span><span class="p">(</span><span class="ss">:tweets</span><span class="p">,</span> <span class="p">[</span><span class="ss">:public</span><span class="p">,</span> <span class="ss">:ordered_set</span><span class="p">,</span> <span class="ss">:named_table</span><span class="p">,</span>
<span class="ss">read_concurrency:</span> <span class="no">true</span><span class="p">,</span> <span class="ss">write_concurrency:</span> <span class="no">true</span><span class="p">])</span>
<span class="c1"># Insert a bunch of tweets</span>
<span class="ss">:ets</span><span class="o">.</span><span class="n">insert</span><span class="p">(</span><span class="ss">:tweets</span><span class="p">,</span>
<span class="p">[{</span><span class="n">now</span><span class="p">,</span> <span class="p">[</span><span class="ss">avatar:</span> <span class="s2">"http://upload.wikimedia.org/wikipedia/en/thumb/f/f4/The_Wire_Jimmy_McNulty.jpg/250px-The_Wire_Jimmy_McNulty.jpg"</span><span class="p">,</span>
<span class="ss">message:</span> <span class="s2">"Pawns."</span><span class="p">,</span>
<span class="ss">time:</span> <span class="n">timestamp</span><span class="p">]},</span>
<span class="p">{</span><span class="n">now</span><span class="p">,</span> <span class="p">[</span><span class="ss">avatar:</span> <span class="s2">"http://upload.wikimedia.org/wikipedia/en/thumb/1/15/The_Wire_Bunk.jpg/250px-The_Wire_Bunk.jpg"</span><span class="p">,</span>
<span class="ss">message:</span> <span class="s2">"A man's gotta have a code."</span><span class="p">,</span>
<span class="ss">time:</span> <span class="n">timestamp</span><span class="p">]},</span>
<span class="p">{</span><span class="n">now</span><span class="p">,</span> <span class="p">[</span><span class="ss">avatar:</span> <span class="s2">"http://upload.wikimedia.org/wikipedia/en/thumb/f/f4/The_Wire_Jimmy_McNulty.jpg/250px-The_Wire_Jimmy_McNulty.jpg"</span><span class="p">,</span>
<span class="ss">message:</span> <span class="s2">"You boys have a taste?"</span><span class="p">,</span>
<span class="ss">time:</span> <span class="n">timestamp</span><span class="p">]}</span>
<span class="p">])</span></code></pre></figure>
<p>If you refresh your browser now, you won’t see anything because we
deleted the content without repopulating it. Instead, start up
<code class="language-plaintext highlighter-rouge">iex</code> and view the table using <code class="language-plaintext highlighter-rouge">:ets.i(:tweets)</code>:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>iex <span class="nt">-S</span> mix
Erlang/OTP 18 <span class="o">[</span>erts-7.2.1] <span class="o">[</span><span class="nb">source</span><span class="o">]</span> <span class="o">[</span>64-bit] <span class="o">[</span>smp:8:8] <span class="o">[</span>async-threads:10] <span class="o">[</span>hipe] <span class="o">[</span>kernel-poll:false] <span class="o">[</span>dtrace]
Compiled lib/tweeter.ex
Interactive Elixir <span class="o">(</span>1.2.3<span class="o">)</span> - press Ctrl+C to <span class="nb">exit</span> <span class="o">(</span><span class="nb">type </span>h<span class="o">()</span> ENTER <span class="k">for </span><span class="nb">help</span><span class="o">)</span>
iex<span class="o">(</span>1<span class="o">)></span> :ets.i<span class="o">(</span>:tweets<span class="o">)</span>
<1 <span class="o">></span> <span class="o">{</span><span class="nt">-576460751446910149</span>,
<span class="o">[{</span>avatar,<<<span class="s2">"http://upload.wikimedia.org/wiki ...
<2 > {-576460751446908987,
[{avatar,<<"</span>http://upload.wikimedia.org/wiki ...
<3 <span class="o">></span> <span class="o">{</span><span class="nt">-576460751446908195</span>,
<span class="o">[{</span>avatar,<<<span class="s2">"http://upload.wikimedia.org/wiki ...
EOT (q)uit (p)Digits (k)ill /Regexp -->q
:ok</span></code></pre></figure>
<p>For some reason, the monotonic time returns a negative number on my
machine, but it should be sufficient to ensure uniqueness and
ordering. Now we should get that content into the browser, so it’s
time to make a new resource! Open up
<code class="language-plaintext highlighter-rouge">lib/tweeter/resources/tweet_list.ex</code> and paste in the boilerplate:</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">TweetList</span> <span class="k">do</span>
<span class="c1"># Basic initialization</span>
<span class="k">def</span> <span class="n">init</span><span class="p">(</span><span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">[]}</span>
<span class="c1"># Boilerplate function, which we should inject later</span>
<span class="k">def</span> <span class="n">ping</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:pong</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>I’ve chosen a list this time for the resource state, because we
have a list of tweets to send to the browser. Let’s tell Webmachine
we want to serve JSON and how to render it:</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">def</span> <span class="n">content_types_provided</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># Provide JSON!</span>
<span class="p">{[{</span><span class="s1">'application/json'</span><span class="p">,</span> <span class="ss">:to_json</span><span class="p">}],</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">to_json</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># We assume we have already fetched the tweets from ets into the state:</span>
<span class="n">tweet_list</span> <span class="o">=</span> <span class="n">for</span> <span class="p">{</span><span class="n">_id</span><span class="p">,</span> <span class="n">attributes</span><span class="p">}</span> <span class="o"><-</span> <span class="n">state</span><span class="p">,</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:struct</span><span class="p">,</span> <span class="n">attributes</span><span class="p">}</span>
<span class="c1"># We could use Poison or jsx here, but mochijson2 is included with</span>
<span class="c1"># mochiweb.</span>
<span class="p">{</span><span class="ss">:mochijson2</span><span class="o">.</span><span class="n">encode</span><span class="p">({</span><span class="ss">:struct</span><span class="p">,</span> <span class="p">[</span><span class="ss">tweets:</span> <span class="n">tweet_list</span><span class="p">]}),</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>Before we actually fetch the data from ETS, let’s hook up our new
resource and see that it’s working.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1"># -- lib/tweeter.ex</span>
<span class="c1"># Some configuration that Webmachine needs</span>
<span class="n">web_config</span> <span class="o">=</span> <span class="p">[</span><span class="ss">ip:</span> <span class="p">{</span><span class="mi">127</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">},</span>
<span class="ss">port:</span> <span class="mi">8080</span><span class="p">,</span>
<span class="ss">dispatch:</span> <span class="p">[</span>
<span class="p">{[</span><span class="s1">'tweets'</span><span class="p">],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">TweetList</span><span class="p">,</span> <span class="p">[]},</span>
<span class="p">{[],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Assets</span><span class="p">,</span> <span class="p">[]},</span>
<span class="p">{[:</span><span class="s1">'*'</span><span class="p">],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Assets</span><span class="p">,</span> <span class="p">[]}</span>
<span class="p">]]</span></code></pre></figure>
<p>Now we should be able to bounce our server and see the JSON via <code class="language-plaintext highlighter-rouge">curl</code>:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>curl <span class="nt">-i</span> http://localhost:8080/tweets
HTTP/1.1 200 OK
Server: MochiWeb/1.1 WebMachine/1.10.9 <span class="o">(</span>cafe not found<span class="o">)</span>
Date: Wed, 16 Mar 2016 15:49:36 GMT
Content-Type: application/json
Content-Length: 13
<span class="o">{</span><span class="s2">"tweets"</span>:[]<span class="o">}</span></code></pre></figure>
<p>Now let’s go back and fetch the tweets from ETS properly:</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="c1"># Fetches the data from ets, even though our resource "always"</span>
<span class="c1"># exists.</span>
<span class="k">def</span> <span class="n">resource_exists</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">_state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="no">true</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="ss">:ets</span><span class="o">.</span><span class="n">tab2list</span><span class="p">(</span><span class="ss">:tweets</span><span class="p">)}</span>
<span class="k">end</span></code></pre></figure>
<p>After bouncing our server, I get this response:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>curl <span class="nt">-i</span> http://localhost:8080/tweets
HTTP/1.1 500 Internal Server Error
Server: MochiWeb/1.1 WebMachine/1.10.9 <span class="o">(</span>cafe not found<span class="o">)</span>
Date: Wed, 16 Mar 2016 15:51:11 GMT
Content-Type: text/html
Content-Length: 1166
<html><<span class="nb">head</span><span class="o">></span><title>500 Internal Server Error</title></head><body><h1>Internal Server Error</h1>The server encountered an error <span class="k">while </span>processing this request:<br><pre><span class="o">{</span>error,<span class="o">{</span><span class="nb">exit</span>,<span class="o">{</span>json_encode,<span class="o">{</span>bad_term,<span class="o">{</span>1458,141859,956053<span class="o">}}}</span>,
<span class="o">[{</span>mochijson2,json_encode,2,
<span class="o">[{</span>file,<span class="s2">"src/mochijson2.erl"</span><span class="o">}</span>,<span class="o">{</span>line,181<span class="o">}]}</span>,
<span class="o">{</span>mochijson2,<span class="s1">'-json_encode_proplist/2-fun-0-'</span>,3,
<span class="o">[{</span>file,<span class="s2">"src/mochijson2.erl"</span><span class="o">}</span>,<span class="o">{</span>line,199<span class="o">}]}</span>,
<span class="o">{</span>lists,foldl,3,[<span class="o">{</span>file,<span class="s2">"lists.erl"</span><span class="o">}</span>,<span class="o">{</span>line,1262<span class="o">}]}</span>,
<span class="o">{</span>mochijson2,json_encode_proplist,2,
<span class="o">[{</span>file,<span class="s2">"src/mochijson2.erl"</span><span class="o">}</span>,<span class="o">{</span>line,202<span class="o">}]}</span>,
<span class="o">{</span>mochijson2,<span class="s1">'-json_encode_array/2-fun-0-'</span>,3,
<span class="o">[{</span>file,<span class="s2">"src/mochijson2.erl"</span><span class="o">}</span>,<span class="o">{</span>line,189<span class="o">}]}</span>,
<span class="o">{</span>lists,foldl,3,[<span class="o">{</span>file,<span class="s2">"lists.erl"</span><span class="o">}</span>,<span class="o">{</span>line,1262<span class="o">}]}</span>,
<span class="o">{</span>mochijson2,json_encode_array,2,
<span class="o">[{</span>file,<span class="s2">"src/mochijson2.erl"</span><span class="o">}</span>,<span class="o">{</span>line,191<span class="o">}]}</span>,
<span class="o">{</span>mochijson2,<span class="s1">'-json_encode_proplist/2-fun-0-'</span>,3,
<span class="o">[{</span>file,<span class="s2">"src/mochijson2.erl"</span><span class="o">}</span>,<span class="o">{</span>line,199<span class="o">}]}]}}</span></pre><P><HR><ADDRESS>mochiweb+webmachine web server</ADDRESS></body></html></code></pre></figure>
<p>Woops! We got that timestamp but didn’t make it something that
makes sense in JSON. Let’s turn it into a numerical timestamp for
the client (microseconds since the epoch, basically).</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">def</span> <span class="n">to_json</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># We assume we have already fetched the tweets from ets into the state:</span>
<span class="n">tweet_list</span> <span class="o">=</span> <span class="n">for</span> <span class="p">{</span><span class="n">_id</span><span class="p">,</span> <span class="n">attributes</span><span class="p">}</span> <span class="o"><-</span> <span class="n">state</span> <span class="k">do</span>
<span class="p">{</span><span class="ss">:struct</span><span class="p">,</span> <span class="n">put_in</span><span class="p">(</span><span class="n">attributes</span><span class="p">,</span> <span class="p">[</span><span class="ss">:time</span><span class="p">],</span> <span class="n">convert_timestamp</span><span class="p">(</span><span class="n">attributes</span><span class="p">[</span><span class="ss">:time</span><span class="p">]))}</span>
<span class="k">end</span>
<span class="c1"># We could use Poison or jsx here, but mochijson2 is included with</span>
<span class="c1"># mochiweb.</span>
<span class="p">{</span><span class="ss">:mochijson2</span><span class="o">.</span><span class="n">encode</span><span class="p">(</span><span class="n">tweet_list</span><span class="p">),</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">defp</span> <span class="n">convert_timestamp</span><span class="p">({</span><span class="n">mega</span><span class="p">,</span> <span class="n">sec</span><span class="p">,</span> <span class="n">micro</span><span class="p">})</span> <span class="k">do</span>
<span class="n">mega</span> <span class="o">*</span> <span class="mi">1000000</span> <span class="o">*</span> <span class="mi">1000000</span> <span class="o">+</span> <span class="n">sec</span> <span class="o">*</span> <span class="mi">1000000</span> <span class="o">+</span> <span class="n">micro</span>
<span class="k">end</span></code></pre></figure>
<p>Let’s try fetching our resource again:</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>curl <span class="nt">-i</span> http://localhost:8080/tweets
HTTP/1.1 200 OK
Server: MochiWeb/1.1 WebMachine/1.10.9 <span class="o">(</span>cafe not found<span class="o">)</span>
Date: Wed, 16 Mar 2016 15:51:57 GMT
Content-Type: application/json
Content-Length: 534
<span class="o">{</span><span class="s2">"tweets"</span>:[<span class="o">{</span><span class="s2">"avatar"</span>:<span class="s2">"http://upload.wikimedia.org/wikipedia/en/thumb/f/f4/The_Wire_Jimmy_McNulty.jpg/250px-The_Wire_Jimmy_McNulty.jpg"</span>,<span class="s2">"message"</span>:<span class="s2">"Pawns."</span>,<span class="s2">"time"</span>:1458141859956053<span class="o">}</span>,<span class="o">{</span><span class="s2">"avatar"</span>:<span class="s2">"http://upload.wikimedia.org/wikipedia/en/thumb/1/15/The_Wire_Bunk.jpg/250px-The_Wire_Bunk.jpg"</span>,<span class="s2">"message"</span>:<span class="s2">"A man's gotta have a code."</span>,<span class="s2">"time"</span>:1458141859956054<span class="o">}</span>,<span class="o">{</span><span class="s2">"avatar"</span>:<span class="s2">"http://upload.wikimedia.org/wikipedia/en/thumb/f/f4/The_Wire_Jimmy_McNulty.jpg/250px-The_Wire_Jimmy_McNulty.jpg"</span>,<span class="s2">"message"</span>:<span class="s2">"You boys have a taste?"</span>,<span class="s2">"time"</span>:1458141859956056<span class="o">}]}</span></code></pre></figure>
<p>Great! Now we can hook this up to our front-end with a little
JavaScript. We’re going to keep it simple by using jQuery instead
of a framework.</p>
<figure class="highlight"><pre><code class="language-javascript" data-lang="javascript"><span class="c1">// priv/www/js/app.js</span>
<span class="nx">$</span><span class="p">(</span><span class="nb">document</span><span class="p">).</span><span class="nx">ready</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">generateTweet</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">tweet</span><span class="p">)</span> <span class="p">{</span>
<span class="k">return</span> <span class="dl">"</span><span class="s2"><li><div class='avatar' style='background: url(</span><span class="dl">"</span> <span class="o">+</span>
<span class="nx">tweet</span><span class="p">.</span><span class="nx">avatar</span> <span class="o">+</span>
<span class="dl">"</span><span class="s2">); background-size: auto 50px; background-position: center center;'></div><div class='message'></span><span class="dl">"</span>
<span class="o">+</span> <span class="nx">tweet</span><span class="p">.</span><span class="nx">message</span> <span class="o">+</span> <span class="dl">"</span><span class="s2"></div></li></span><span class="dl">"</span><span class="p">;</span>
<span class="p">};</span>
<span class="nx">$</span><span class="p">.</span><span class="nx">ajax</span><span class="p">({</span>
<span class="na">url</span><span class="p">:</span> <span class="dl">'</span><span class="s1">/tweets</span><span class="dl">'</span><span class="p">,</span>
<span class="na">success</span><span class="p">:</span> <span class="kd">function</span><span class="p">(</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
<span class="k">if</span><span class="p">(</span><span class="nx">d</span><span class="p">.</span><span class="nx">tweets</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">tweetList</span> <span class="o">=</span> <span class="nx">$</span><span class="p">(</span><span class="dl">'</span><span class="s1">#tweet-list</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">d</span><span class="p">.</span><span class="nx">tweets</span><span class="p">.</span><span class="nx">reverse</span><span class="p">().</span><span class="nx">forEach</span><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">i</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">tweetList</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">generateTweet</span><span class="p">(</span><span class="nx">i</span><span class="p">));</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">});</span></code></pre></figure>
<p>And hook up our JavaScript code at the bottom of <code class="language-plaintext highlighter-rouge">index.html</code>:</p>
<figure class="highlight"><pre><code class="language-html" data-lang="html"> <span class="nt"></article></span>
<span class="c"><!-- This is probably bad form, but it's a demo. --></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"application/javascript"</span> <span class="na">src=</span><span class="s">"http://code.jquery.com/jquery-1.12.1.min.js"</span><span class="nt">></script></span>
<span class="nt"><script </span><span class="na">type=</span><span class="s">"application/javascript"</span> <span class="na">src=</span><span class="s">"js/app.js"</span><span class="nt">></script></span>
<span class="nt"></body></span>
<span class="nt"></html></span></code></pre></figure>
<p>Refresh your browser and see the Ajax populate the tweet-list!</p>
<h2 id="up-next">Up next</h2>
<p>Now that we’ve got some dynamic content being displayed, in the
<a href="/tech/2016/08/17/webmachine-elixir-tutorial-4/">next installment</a> we’ll allow the browser to post new tweets and insert them into
our <code class="language-plaintext highlighter-rouge">ets</code> table.</p>
Sean Cribbs2016-07-14T00:00:00-05:00Webmachine in Elixir Tutorial, Part 2http://seancribbs.com/tech/2016/07/14/webmachine-elixir-tutorial-2/<p><a href="/tech/2016/07/07/webmachine-elixir-tutorial-1/">Last time</a>,
we got our project set up and serving some simple dynamic content. In
this installment, we’ll show how to serve static files via Webmachine
so we can discuss lots of its best features.</p>
<h2 id="serving-static-files">Serving static files</h2>
<p>Most times you would let a web-server like Apache or nginx serve your
static files, but for our tutorial it’s nice to serve our content
directly via Webmachine. By doing so, we can demonstrate several
important features related to the dispatcher, content-negotiation, and
conditional requests. Basically, everything you’d expect out of a
well-configured web-server but in a resource module! First make a
<code class="language-plaintext highlighter-rouge">priv</code> directory (where OTP apps store non-code files) and we’ll put
our design assets in there. For now, we’ll just copy files from the
<code class="language-plaintext highlighter-rouge">webmachine-tutorial</code> repo.</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span><span class="nb">mkdir</span> <span class="nt">-p</span> priv/www/css priv/www/img
<span class="nv">$ </span>curl <span class="nt">-o</span> priv/www/css/master.css https://raw.githubusercontent.com/cmeiklejohn/webmachine-tutorial/bf86b8230259ed710bf1ab3f32a5c64bfb9f03bc/priv/www/css/master.css
<span class="nv">$ </span>curl <span class="nt">-o</span> priv/www/index.html https://raw.githubusercontent.com/cmeiklejohn/webmachine-tutorial/bf86b8230259ed710bf1ab3f32a5c64bfb9f03bc/priv/www/index.html
<span class="nv">$ </span>curl <span class="nt">-o</span> priv/www/img/noise.png https://raw.githubusercontent.com/cmeiklejohn/webmachine-tutorial/bf86b8230259ed710bf1ab3f32a5c64bfb9f03bc/priv/www/img/noise.png</code></pre></figure>
<p>Let’s make a new resource module called <code class="language-plaintext highlighter-rouge">Tweeter.Resources.Assets</code> and
fill out the boilerplate. For our resource state, we’ll use a map this
time, but we’ll probably change it to a struct later.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Assets</span> <span class="k">do</span>
<span class="c1"># Basic initialization</span>
<span class="k">def</span> <span class="n">init</span><span class="p">(</span><span class="n">_</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="p">%{}}</span>
<span class="c1"># Boilerplate function, which we should inject later</span>
<span class="k">def</span> <span class="n">ping</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">),</span> <span class="k">do</span><span class="p">:</span> <span class="p">{</span><span class="ss">:pong</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>Now we need to think about a few things, namely, how to determine
which file is being requested, what media type it is, and then how to
read it from the filesystem out to the client. Let’s start from the
end, assuming we’ve already determined the correct file to read. We’ll
make a body-producing function that simply reads the file and sends it
to the client. This is not the most efficient way – <code class="language-plaintext highlighter-rouge">sendfile()</code> or
other streaming would be better – but we are serving small files so
it won’t be too bad.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Body-producing function</span>
<span class="k">def</span> <span class="n">produce_resource</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="p">%{</span><span class="ss">filename:</span> <span class="n">filename</span><span class="p">}</span> <span class="o">=</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="no">File</span><span class="o">.</span><span class="n">read!</span><span class="p">(</span><span class="n">filename</span><span class="p">),</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>That was easy! Continuing backwards through our list, let’s
determine the media type of the file and point it at our
body-producing function using the <code class="language-plaintext highlighter-rouge">content_types_provided</code>
Webmachine callback. This callback tells Webmachine what media
types you provide, and what to call to produce each one. Since ours
is just reading a file from the filesystem, we’ll call
<code class="language-plaintext highlighter-rouge">produce_resource</code>, but vary the type it produces.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Content-negotiation callback</span>
<span class="k">def</span> <span class="n">content_types_provided</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="n">filename</span> <span class="o">=</span> <span class="k">case</span> <span class="ss">:wrq</span><span class="o">.</span><span class="n">disp_path</span><span class="p">(</span><span class="n">req_data</span><span class="p">)</span> <span class="k">do</span>
<span class="s1">''</span> <span class="o">-></span> <span class="s1">'index.html'</span>
<span class="n">f</span> <span class="o">-></span> <span class="n">f</span>
<span class="k">end</span>
<span class="n">media_type</span> <span class="o">=</span> <span class="ss">:webmachine_util</span><span class="o">.</span><span class="n">guess_mime</span><span class="p">(</span><span class="n">filename</span><span class="p">)</span>
<span class="p">{[{</span><span class="n">media_type</span><span class="p">,</span> <span class="ss">:produce_resource</span><span class="p">}],</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>This is the first time we’ve used a Webmachine library function in
a resource. <code class="language-plaintext highlighter-rouge">:wrq.disp_path</code> gives us the portion of the path that
the dispatcher matched against. So at the root URL, this will be
the empty string, otherwise, it’ll be a partial path to some file,
like <code class="language-plaintext highlighter-rouge">css/master.css</code>. Then <code class="language-plaintext highlighter-rouge">:webmachine_util.guess_mime</code> is used
to guess what a proper media type will be. For fun, let’s try that
function from the shell via <code class="language-plaintext highlighter-rouge">iex -S mix</code>.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="n">iex</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span><span class="o">></span> <span class="ss">:webmachine_util</span><span class="o">.</span><span class="n">guess_mime</span><span class="p">(</span><span class="s1">'foo.png'</span><span class="p">)</span>
<span class="s1">'image/png'</span>
<span class="n">iex</span><span class="p">(</span><span class="mi">2</span><span class="p">)</span><span class="o">></span> <span class="ss">:webmachine_util</span><span class="o">.</span><span class="n">guess_mime</span><span class="p">(</span><span class="s1">'application.js'</span><span class="p">)</span>
<span class="s1">'application/x-javascript'</span>
<span class="n">iex</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span><span class="o">></span> <span class="ss">:webmachine_util</span><span class="o">.</span><span class="n">guess_mime</span><span class="p">(</span><span class="s1">'home.html'</span><span class="p">)</span>
<span class="s1">'text/html'</span>
<span class="n">iex</span><span class="p">(</span><span class="mi">4</span><span class="p">)</span><span class="o">></span> <span class="ss">:webmachine_util</span><span class="o">.</span><span class="n">guess_mime</span><span class="p">(</span><span class="s1">'module.erl'</span><span class="p">)</span>
<span class="s1">'text/plain'</span></code></pre></figure>
<p>Now that we have a body producing function, and the correct MIME
type, let’s find the file on the filesystem, via one of the most
important callbacks <code class="language-plaintext highlighter-rouge">resource_exists</code>. Obviously, if the file
doesn’t exist in our static assets, we should return a <code class="language-plaintext highlighter-rouge">404 Not
Found</code>, and this is also a perfect place to populate the state with
an absolute path to the requested file.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Find the file!</span>
<span class="k">def</span> <span class="n">resource_exists</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="n">priv_dir</span> <span class="o">=</span> <span class="no">Path</span><span class="o">.</span><span class="n">join</span> <span class="ss">:code</span><span class="o">.</span><span class="n">priv_dir</span><span class="p">(</span><span class="ss">:tweeter</span><span class="p">),</span> <span class="s2">"www"</span>
<span class="n">absolute_path</span> <span class="o">=</span> <span class="no">Path</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">priv_dir</span><span class="p">,</span> <span class="ss">:wrq</span><span class="o">.</span><span class="n">disp_path</span><span class="p">(</span><span class="n">req_data</span><span class="p">))</span> <span class="o">|></span> <span class="no">Path</span><span class="o">.</span><span class="n">expand</span>
<span class="p">{</span><span class="no">File</span><span class="o">.</span><span class="n">regular?</span><span class="p">(</span><span class="n">absolute_path</span><span class="p">),</span>
<span class="n">req_data</span><span class="p">,</span>
<span class="p">%{</span><span class="n">state</span> <span class="o">|</span> <span class="ss">filename:</span> <span class="n">absolute_path</span><span class="p">}}</span>
<span class="k">end</span></code></pre></figure>
<p>Before we move on, there’s some repeated functionality with
<code class="language-plaintext highlighter-rouge">content_types_provided</code>, and we have a minor bug too – at the
root path we want to serve <code class="language-plaintext highlighter-rouge">index.html</code>. Let’s extract that shared
functionality into a new function.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Find the file!</span>
<span class="k">def</span> <span class="n">resource_exists</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># Find the root of our static files, add the identified path</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="no">Path</span><span class="o">.</span><span class="n">join</span> <span class="p">[</span><span class="ss">:code</span><span class="o">.</span><span class="n">priv_dir</span><span class="p">(</span><span class="ss">:tweeter</span><span class="p">),</span> <span class="s2">"www"</span><span class="p">,</span> <span class="n">identify_file</span><span class="p">(</span><span class="n">req_data</span><span class="p">)]</span>
<span class="c1"># Compute the full path</span>
<span class="n">absolute_path</span> <span class="o">=</span> <span class="no">Path</span><span class="o">.</span><span class="n">expand</span> <span class="n">file_path</span>
<span class="p">{</span><span class="no">File</span><span class="o">.</span><span class="n">regular?</span><span class="p">(</span><span class="n">absolute_path</span><span class="p">),</span>
<span class="n">req_data</span><span class="p">,</span>
<span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="ss">:filename</span><span class="p">,</span> <span class="n">absolute_path</span><span class="p">)}</span>
<span class="k">end</span>
<span class="c1"># Content-negotiation callback</span>
<span class="k">def</span> <span class="n">content_types_provided</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="n">media_type</span> <span class="o">=</span> <span class="n">req_data</span> <span class="o">|></span>
<span class="n">identify_file</span> <span class="o">|></span>
<span class="no">String</span><span class="o">.</span><span class="n">to_char_list</span> <span class="o">|></span>
<span class="ss">:webmachine_util</span><span class="o">.</span><span class="n">guess_mime</span>
<span class="p">{[{</span><span class="n">media_type</span><span class="p">,</span> <span class="ss">:produce_resource</span><span class="p">}],</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="c1"># Identifies the file we're trying to serve, normalizing path</span>
<span class="c1"># segments</span>
<span class="k">defp</span> <span class="n">identify_file</span><span class="p">(</span><span class="n">req_data</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># Getting the path tokens removes any duplicate slashes</span>
<span class="k">case</span> <span class="ss">:wrq</span><span class="o">.</span><span class="n">path_tokens</span><span class="p">(</span><span class="n">req_data</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># At the root path (no tokens), we want to serve index.html</span>
<span class="p">[]</span> <span class="o">-></span> <span class="p">[</span><span class="s2">"index.html"</span><span class="p">]</span>
<span class="c1"># Otherwise serve the path they asked for</span>
<span class="n">toks</span> <span class="o">-></span> <span class="n">toks</span>
<span class="k">end</span> <span class="o">|></span> <span class="no">Path</span><span class="o">.</span><span class="n">join</span>
<span class="k">end</span></code></pre></figure>
<p>To get this resource to actually serve content, we now need to hook
it up to the dispatcher. Let’s edit <code class="language-plaintext highlighter-rouge">tweeter.ex</code> again, replacing
our <code class="language-plaintext highlighter-rouge">Hello</code> resource with <code class="language-plaintext highlighter-rouge">Assets</code>.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Some configuration that Webmachine needs</span>
<span class="n">web_config</span> <span class="o">=</span> <span class="p">[</span><span class="ss">ip:</span> <span class="p">{</span><span class="mi">127</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">},</span>
<span class="ss">port:</span> <span class="mi">8080</span><span class="p">,</span>
<span class="ss">dispatch:</span> <span class="p">[</span>
<span class="p">{[],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Assets</span><span class="p">,</span> <span class="p">[]},</span>
<span class="p">{[:</span><span class="o">*</span><span class="p">],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Assets</span><span class="p">,</span> <span class="p">[]}</span>
<span class="p">]]</span></code></pre></figure>
<p>Note the special path segment <code class="language-plaintext highlighter-rouge">:*</code>. This tells the Webmachine
dispatcher to match any number of trailing path
segments. Kill/restart your <code class="language-plaintext highlighter-rouge">mix</code> process and refresh the page!</p>
<h2 id="reducing-waste">Reducing waste</h2>
<p>This strategy of reading a file from disk and sending it to the
client is as old as the web itself, but there’s much more we can
do! HTTP includes caching in the protocol, and it’d be pretty
inefficient for a client to fetch unchanged design assets every
time they refresh the page.</p>
<p>Let’s add some simple validation caching to our assets resource. We
can start by using the <code class="language-plaintext highlighter-rouge">last_modified</code> callback.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Last-Modified date</span>
<span class="k">def</span> <span class="n">last_modified</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="p">%{</span><span class="ss">filename:</span> <span class="n">filename</span><span class="p">}</span> <span class="o">=</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="n">mtime</span> <span class="o">=</span> <span class="no">File</span><span class="o">.</span><span class="n">stat!</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="ss">time:</span> <span class="ss">:universal</span><span class="p">)</span><span class="o">.</span><span class="n">mtime</span>
<span class="p">{</span><span class="n">mtime</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>This is pretty simple: we read the file statistics, pulling out the
<code class="language-plaintext highlighter-rouge">mtime</code> field which represents when it was last modified. We can
use the <code class="language-plaintext highlighter-rouge">File.stat!</code> function instead of its safe equivalent
because of the flow of Webmachine’s decision graph. That is, we
know that <code class="language-plaintext highlighter-rouge">last_modified</code> will not be called if <code class="language-plaintext highlighter-rouge">resource_exists</code>
returns <code class="language-plaintext highlighter-rouge">false</code>.</p>
<p>We can go even further by using entity tags, or “ETag” for
short. These are usually a hash string of various aspects of the
file’s metadata. Since we might be doing that <code class="language-plaintext highlighter-rouge">File.stat!</code> call in
multiple places, let’s put it in <code class="language-plaintext highlighter-rouge">resource_exists</code> while we’re at
it and save the result.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Find the file!</span>
<span class="k">def</span> <span class="n">resource_exists</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="c1"># Find the root of our static files, add the identified path</span>
<span class="n">file_path</span> <span class="o">=</span> <span class="no">Path</span><span class="o">.</span><span class="n">join</span> <span class="p">[</span><span class="ss">:code</span><span class="o">.</span><span class="n">priv_dir</span><span class="p">(</span><span class="ss">:tweeter</span><span class="p">),</span> <span class="s2">"www"</span><span class="p">,</span> <span class="n">identify_file</span><span class="p">(</span><span class="n">req_data</span><span class="p">)]</span>
<span class="c1"># Compute the full path</span>
<span class="n">absolute_path</span> <span class="o">=</span> <span class="no">Path</span><span class="o">.</span><span class="n">expand</span> <span class="n">file_path</span>
<span class="n">state</span> <span class="o">=</span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="ss">:filename</span><span class="p">,</span> <span class="n">absolute_path</span><span class="p">)</span>
<span class="c1"># Return true if it exists and read the file info into the state</span>
<span class="c1"># for future callbacks</span>
<span class="k">if</span> <span class="no">File</span><span class="o">.</span><span class="n">regular?</span><span class="p">(</span><span class="n">absolute_path</span><span class="p">)</span> <span class="k">do</span>
<span class="n">state</span> <span class="o">=</span> <span class="no">Map</span><span class="o">.</span><span class="n">put</span><span class="p">(</span><span class="n">state</span><span class="p">,</span> <span class="ss">:fileinfo</span><span class="p">,</span> <span class="no">File</span><span class="o">.</span><span class="n">stat!</span><span class="p">(</span><span class="n">absolute_path</span><span class="p">))</span>
<span class="p">{</span><span class="no">true</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">else</span>
<span class="p">{</span><span class="no">false</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span>
<span class="c1"># Last-Modified date</span>
<span class="k">def</span> <span class="n">last_modified</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="p">%{</span><span class="ss">fileinfo:</span> <span class="n">fileinfo</span><span class="p">}</span> <span class="o">=</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="n">fileinfo</span><span class="o">.</span><span class="n">mtime</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="c1"># ETag</span>
<span class="k">def</span> <span class="n">generate_etag</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="p">%{</span><span class="ss">fileinfo:</span> <span class="n">fileinfo</span><span class="p">}</span> <span class="o">=</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="n">hash</span> <span class="o">=</span> <span class="p">{</span><span class="n">fileinfo</span><span class="o">.</span><span class="n">inode</span><span class="p">,</span> <span class="n">fileinfo</span><span class="o">.</span><span class="n">mtime</span><span class="p">}</span> <span class="o">|></span>
<span class="ss">:erlang</span><span class="o">.</span><span class="n">phash2</span> <span class="o">|></span>
<span class="ss">:mochihex</span><span class="o">.</span><span class="n">to_hex</span>
<span class="p">{</span><span class="n">hash</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>We use the built-in <code class="language-plaintext highlighter-rouge">:erlang.phash2</code> function to compute the ETag,
but you should probably use a better hash in other resources.</p>
<p>Finally, I noticed that our CSS and HTML, although small, are still
multiple kilobytes. We can reduce the transmission time
significantly through compression, using the <code class="language-plaintext highlighter-rouge">encodings_provided</code>
callback. Somewhat similar to <code class="language-plaintext highlighter-rouge">content_types_provided</code>, it returns
a list of pairs, where the first is an encoding and the second is a
<code class="language-plaintext highlighter-rouge">fn</code> that performs the encoding.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Compression selection</span>
<span class="k">def</span> <span class="n">encodings_provided</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{[{</span><span class="s1">'identity'</span><span class="p">,</span> <span class="o">&</span><span class="p">(</span><span class="nv">&1</span><span class="p">)},</span> <span class="c1"># identity function!</span>
<span class="p">{</span><span class="s1">'gzip'</span><span class="p">,</span> <span class="o">&</span><span class="ss">:zlib</span><span class="o">.</span><span class="n">gzip</span><span class="o">/</span><span class="mi">1</span><span class="p">},</span>
<span class="p">{</span><span class="s1">'deflate'</span><span class="p">,</span> <span class="o">&</span><span class="ss">:zlib</span><span class="o">.</span><span class="n">zip</span><span class="o">/</span><span class="mi">1</span><span class="p">}],</span>
<span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span></code></pre></figure>
<p>Note that this is a case again where Webmachine requires character
lists and not binaries (single-quoted strings). Now that our
compression is in place, I see the <code class="language-plaintext highlighter-rouge">index.html</code> file going from
~1KB to 560B, and the CSS file from 6.7KB to ~1KB. Nice bandwidth
savings!</p>
<h2 id="up-next">Up next</h2>
<p>In the next installment, we’ll learn start serving some
dynamically-generated content from a resource.</p>
Sean Cribbs2016-07-07T00:00:00-05:00Webmachine in Elixir Tutorial, Part 1http://seancribbs.com/tech/2016/07/07/webmachine-elixir-tutorial-1/<p>Webmachine is an Erlang project I’ve known and used for years,
especially thanks to my experience at Basho. One of the things that
made me proud to use it was its opinionated nature about HTTP, namely,
that in most cases you don’t have to manually set status codes or
supply response headers. Instead, you supply predicate functions that
answer very specific questions about your HTTP resource, and the
Webmachine FSM calls those to determine how to respond to each
request. This leads to a very declarative style, which is a great fit
for functional programming, and it becomes simple to extend your
resource with more capabilities.</p>
<p>In contrast, the trend in the Elixir community is to use Plug, often
in conjunction with Phoenix. Plug is very much in the style of Rack
and WSGI which came before it, and I’ve ranted about the problems with
those before (and will not repeat here).</p>
<p>That said, someone has
<a href="https://github.com/awetzel/ewebmachine">“ported” Webmachine to Elixir</a>
and provided a complicated macro-based DSL with which to declare
resources. When I
<a href="https://github.com/webmachine/webmachine-ruby/">ported Webmachine to Ruby</a>,
I eschewed DSLs for a simple inherit-and-override model which was
incredibly successful and easy to understand, in my opinion, not to
mention faster and more efficient than hacks using <code class="language-plaintext highlighter-rouge">instance_eval</code>.</p>
<p>In this post, I hope to convince you that Elixir’s simplicity and
direct interaction with Erlang libraries makes it possible to use
Webmachine in your Elixir project, without a rewrite or translation
layer.</p>
<p>Before I dive into it, you should be aware some caveats.</p>
<p>Most Elixir projects use Cowboy or Elli for the
webserver. Unfortunately, Webmachine is only compatible with
<code class="language-plaintext highlighter-rouge">mochiweb</code>; although previously efforts have been made to connect Yaws
and Cowboy, those have never completed.</p>
<p>There’s some cruft in Webmachine, simply because it was started
before 2009. I hope it won’t be too onerous to work around.</p>
<p>At the time of writing, I’m running Elixir 1.2.3 atop Erlang/OTP
18.2.1 on Mac OS/X, both installed via homebrew.</p>
<p>I’ll assume you know a little bit about Elixir syntax and idioms, and
won’t add too much discussion of the basics.</p>
<h2 id="getting-started">Getting Started</h2>
<p>Let’s start by making a new project with <code class="language-plaintext highlighter-rouge">mix</code>, and then we’ll add
Webmachine and a basic resource that returns some HTML. I’ll be
roughly following the
<a href="https://github.com/cmeiklejohn/webmachine-tutorial">tutorial</a> that
Chris Meiklejohn and I gave to LambdaJam in 2013, which is a very
basic Twitter clone called “Tweeter”.</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>mix new tweeter <span class="nt">--sup</span>
<span class="k">*</span> creating README.md
<span class="k">*</span> creating .gitignore
<span class="k">*</span> creating mix.exs
<span class="k">*</span> creating config
<span class="k">*</span> creating config/config.exs
<span class="k">*</span> creating lib
<span class="k">*</span> creating lib/tweeter.ex
<span class="k">*</span> creating <span class="nb">test</span>
<span class="k">*</span> creating <span class="nb">test</span>/test_helper.exs
<span class="k">*</span> creating <span class="nb">test</span>/tweeter_test.exs
<span class="nv">$ </span><span class="nb">cd </span>tweeter</code></pre></figure>
<p>Now we can add Webmachine to the project and compile. First, edit
<code class="language-plaintext highlighter-rouge">mix.exs</code> to add Webmachine.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Mixfile</span> <span class="k">do</span>
<span class="kn">use</span> <span class="no">Mix</span><span class="o">.</span><span class="no">Project</span>
<span class="k">def</span> <span class="n">project</span> <span class="k">do</span>
<span class="p">[</span><span class="ss">app:</span> <span class="ss">:tweeter</span><span class="p">,</span>
<span class="ss">version:</span> <span class="s2">"0.0.1"</span><span class="p">,</span>
<span class="ss">elixir:</span> <span class="s2">"~> 1.2"</span><span class="p">,</span>
<span class="ss">build_embedded:</span> <span class="no">Mix</span><span class="o">.</span><span class="n">env</span> <span class="o">==</span> <span class="ss">:prod</span><span class="p">,</span>
<span class="ss">start_permanent:</span> <span class="no">Mix</span><span class="o">.</span><span class="n">env</span> <span class="o">==</span> <span class="ss">:prod</span><span class="p">,</span>
<span class="ss">deps:</span> <span class="n">deps</span><span class="p">]</span>
<span class="k">end</span>
<span class="k">def</span> <span class="n">application</span> <span class="k">do</span>
<span class="c1"># Add :webmachine here</span>
<span class="p">[</span><span class="ss">applications:</span> <span class="p">[</span><span class="ss">:logger</span><span class="p">,</span> <span class="ss">:webmachine</span><span class="p">],</span>
<span class="ss">mod:</span> <span class="p">{</span><span class="no">Tweeter</span><span class="p">,</span> <span class="p">[]}]</span>
<span class="k">end</span>
<span class="k">defp</span> <span class="n">deps</span> <span class="k">do</span>
<span class="c1"># Add webmachine dep here</span>
<span class="p">[{</span><span class="ss">:webmachine</span><span class="p">,</span>
<span class="ss">git:</span> <span class="s2">"https://github.com/webmachine/webmachine.git"</span><span class="p">,</span>
<span class="ss">branch:</span> <span class="s2">"master"</span><span class="p">}]</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Fetch the dependencies and compile!</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>mix deps.get
<span class="k">*</span> Getting webmachine <span class="o">(</span>https://github.com/webmachine/webmachine.git<span class="o">)</span>
Cloning into <span class="s1">'/Users/scribb201/dev/tweeter/deps/webmachine'</span>...
remote: Counting objects: 3172, <span class="k">done</span><span class="nb">.</span>
remote: Compressing objects: 100% <span class="o">(</span>4/4<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
remote: Total 3172 <span class="o">(</span>delta 1<span class="o">)</span>, reused 0 <span class="o">(</span>delta 0<span class="o">)</span>, pack-reused 3168
Receiving objects: 100% <span class="o">(</span>3172/3172<span class="o">)</span>, 2.89 MiB | 146.00 KiB/s, <span class="k">done</span><span class="nb">.</span>
Resolving deltas: 100% <span class="o">(</span>1767/1767<span class="o">)</span>, <span class="k">done</span><span class="nb">.</span>
Checking connectivity... <span class="k">done</span><span class="nb">.</span>
A new Hex version is available <span class="o">(</span>v0.11.1<span class="o">)</span>, please update with <span class="sb">`</span>mix local.hex<span class="sb">`</span>
Running dependency resolution
Dependency resolution completed successfully
mochiweb: v2.12.2
<span class="k">*</span> Getting mochiweb <span class="o">(</span>Hex package<span class="o">)</span>
Checking package <span class="o">(</span>https://s3.amazonaws.com/s3.hex.pm/tarballs/mochiweb-2.12.2.tar<span class="o">)</span>
Fetched package
Unpacked package tarball <span class="o">(</span>/Users/scribb201/.hex/packages/mochiweb-2.12.2.tar<span class="o">)</span>
<span class="nv">$ </span>mix compile
<span class="o">==></span> mochiweb <span class="o">(</span>compile<span class="o">)</span>
Compiled src/reloader.erl
Compiled src/mochiweb_websocket.erl
Compiled src/mochiweb_util.erl
Compiled src/mochiweb_socket.erl
Compiled src/mochiweb_session.erl
Compiled src/mochiweb_response.erl
Compiled src/mochiweb_socket_server.erl
Compiled src/mochiweb_multipart.erl
Compiled src/mochiweb_io.erl
Compiled src/mochiweb_mime.erl
Compiled src/mochiweb_http.erl
Compiled src/mochiweb_headers.erl
Compiled src/mochiweb_request.erl
Compiled src/mochiweb_echo.erl
Compiled src/mochiweb_cover.erl
Compiled src/mochiweb_cookies.erl
Compiled src/mochiweb_base64url.erl
Compiled src/mochiweb_acceptor.erl
Compiled src/mochiweb.erl
Compiled src/mochiutf8.erl
Compiled src/mochiweb_html.erl
Compiled src/mochitemp.erl
Compiled src/mochilogfile2.erl
Compiled src/mochilists.erl
Compiled src/mochinum.erl
Compiled src/mochijson.erl
Compiled src/mochihex.erl
Compiled src/mochiglobal.erl
Compiled src/mochijson2.erl
Compiled src/mochifmt_std.erl
Compiled src/mochifmt_records.erl
Compiled src/mochifmt.erl
Compiled src/mochiweb_charref.erl
<span class="o">==></span> webmachine <span class="o">(</span>compile<span class="o">)</span>
Compiled src/wrq.erl
Compiled src/wmtrace_resource.erl
Compiled src/webmachine_util.erl
Compiled src/webmachine_sup.erl
Compiled src/webmachine_router.erl
Compiled src/webmachine_perf_log_handler.erl
Compiled src/webmachine_resource.erl
Compiled src/webmachine_mochiweb.erl
Compiled src/webmachine_multipart.erl
Compiled src/webmachine_logger_watcher_sup.erl
Compiled src/webmachine_logger_watcher.erl
Compiled src/webmachine_log.erl
Compiled src/webmachine_error_log_handler.erl
Compiled src/webmachine_error.erl
Compiled src/webmachine_error_handler.erl
Compiled src/webmachine_deps.erl
Compiled src/webmachine_dispatcher.erl
Compiled src/webmachine_app.erl
Compiled src/webmachine_access_log_handler.erl
Compiled src/webmachine.erl
Compiled src/webmachine_request.erl
Compiled src/webmachine_decision_core.erl
Compiled lib/tweeter.ex
Generated tweeter app
Consolidated List.Chars
Consolidated String.Chars
Consolidated Collectable
Consolidated Enumerable
Consolidated IEx.Info
Consolidated Inspect</code></pre></figure>
<h2 id="my-first-resource">My First Resource</h2>
<p>Now that we’ve verified we can build everything, let’s hook up the
webserver and a basic resource to serve some content. To do that, we
need to start the server as a child of our application
supervisor. Luckily, we generated a supervisor when we ran <code class="language-plaintext highlighter-rouge">mix
new</code>. Edit <code class="language-plaintext highlighter-rouge">lib/tweeter.ex</code> to look like this:</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="no">Tweeter</span> <span class="k">do</span>
<span class="kn">use</span> <span class="no">Application</span>
<span class="k">def</span> <span class="n">start</span><span class="p">(</span><span class="n">_type</span><span class="p">,</span> <span class="n">_args</span><span class="p">)</span> <span class="k">do</span>
<span class="kn">import</span> <span class="no">Supervisor</span><span class="o">.</span><span class="no">Spec</span><span class="p">,</span> <span class="ss">warn:</span> <span class="no">false</span>
<span class="c1"># Some configuration that Webmachine needs</span>
<span class="n">web_config</span> <span class="o">=</span> <span class="p">[</span><span class="ss">ip:</span> <span class="p">{</span><span class="mi">127</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">},</span>
<span class="ss">port:</span> <span class="mi">8080</span><span class="p">,</span>
<span class="ss">dispatch:</span> <span class="p">[]]</span>
<span class="c1"># Add the webmachine+mochiweb listener</span>
<span class="n">children</span> <span class="o">=</span> <span class="p">[</span>
<span class="n">worker</span><span class="p">(</span><span class="ss">:webmachine_mochiweb</span><span class="p">,</span> <span class="p">[</span><span class="n">web_config</span><span class="p">],</span>
<span class="ss">function:</span> <span class="ss">:start</span><span class="p">,</span>
<span class="ss">modules:</span> <span class="p">[</span><span class="ss">:mochiweb_socket_server</span><span class="p">])</span>
<span class="p">]</span>
<span class="n">opts</span> <span class="o">=</span> <span class="p">[</span><span class="ss">strategy:</span> <span class="ss">:one_for_one</span><span class="p">,</span> <span class="ss">name:</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Supervisor</span><span class="p">]</span>
<span class="no">Supervisor</span><span class="o">.</span><span class="n">start_link</span><span class="p">(</span><span class="n">children</span><span class="p">,</span> <span class="n">opts</span><span class="p">)</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>Now run the app and go to http://127.0.0.1:8080/.</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span>mix run <span class="nt">--no-halt</span></code></pre></figure>
<p>You should get a very “Web 1.0” 404 page. Hit <code class="language-plaintext highlighter-rouge">Ctrl-C a ENTER</code> in the
shell to exit the Mix process. Let’s add a “Hello, World” resource so
we can get something other than the 404 page. Following Elixir
conventions, we’ll build out our source tree to separate concerns.</p>
<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="nv">$ </span><span class="nb">mkdir</span> <span class="nt">-p</span> lib/tweeter/resources
<span class="nv">$ $EDITOR</span> lib/tweeter/resources/hello.ex</code></pre></figure>
<p>Below is what we’ll put into <code class="language-plaintext highlighter-rouge">hello.ex</code>, along with a little
explanation of why.</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"><span class="k">defmodule</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Hello</span> <span class="k">do</span>
<span class="c1"># Initializes the resource's state, which is nothing right now</span>
<span class="k">def</span> <span class="n">init</span><span class="p">(</span><span class="n">_</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="ss">:ok</span><span class="p">,</span> <span class="no">nil</span><span class="p">}</span>
<span class="k">end</span>
<span class="c1"># Required callback, but almost never overridden! Most people use</span>
<span class="c1"># service_available/2 instead. In Erlang, we'd do this to avoid it:</span>
<span class="c1">#</span>
<span class="c1"># -include_lib("webmachine/include/webmachine.hrl").</span>
<span class="c1">#</span>
<span class="c1"># Maybe later we'll make a macro that automatically includes it.</span>
<span class="k">def</span> <span class="n">ping</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="ss">:pong</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="c1"># Default body callback, producing HTML.</span>
<span class="k">def</span> <span class="n">to_html</span><span class="p">(</span><span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">)</span> <span class="k">do</span>
<span class="p">{</span><span class="s2">"<html><body>Hello, World!</body></html>"</span><span class="p">,</span> <span class="n">req_data</span><span class="p">,</span> <span class="n">state</span><span class="p">}</span>
<span class="k">end</span>
<span class="k">end</span></code></pre></figure>
<p>That isn’t too bad, right? You do have some strange 3-tuple return
values. What are they?</p>
<p>The 3-tuple return values thread the request/response data and the
resource state through the functions, while the first element of each
is the callback-specific return value. So, for <code class="language-plaintext highlighter-rouge">ping/2</code> we need to
return <code class="language-plaintext highlighter-rouge">:pong</code> for success, and <code class="language-plaintext highlighter-rouge">to_html/2</code> needs to return the
response body. This pattern will repeat as we add more functionality
to our resources.</p>
<p>Now let’s hook it up to the webserver in <code class="language-plaintext highlighter-rouge">tweeter.ex</code>:</p>
<figure class="highlight"><pre><code class="language-elixir" data-lang="elixir"> <span class="c1"># Some configuration that Webmachine needs</span>
<span class="n">web_config</span> <span class="o">=</span> <span class="p">[</span><span class="ss">ip:</span> <span class="p">{</span><span class="mi">127</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">},</span>
<span class="ss">port:</span> <span class="mi">8080</span><span class="p">,</span>
<span class="ss">dispatch:</span> <span class="p">[</span>
<span class="p">{[],</span> <span class="no">Tweeter</span><span class="o">.</span><span class="no">Resources</span><span class="o">.</span><span class="no">Hello</span><span class="p">,</span> <span class="p">[]}</span>
<span class="p">]]</span></code></pre></figure>
<p>What we’ve modified here is the “dispatch list”, or “routes” as they
are called in other frameworks. The <code class="language-plaintext highlighter-rouge">[]</code> in the first position says
we’ll match the root URL, or <code class="language-plaintext highlighter-rouge">/</code>. The second position is the module
name of our resource. The third position is any additional arguments
to initialize our resource (passed to <code class="language-plaintext highlighter-rouge">init/1</code> on startup). Now we can
try it out! Run <code class="language-plaintext highlighter-rouge">mix run --no-halt</code> again and refresh your
browser. You should see “Hello, World!” on the screen.</p>
<h2 id="up-next">Up next</h2>
<p>In the <a href="/tech/2016/07/14/webmachine-elixir-tutorial-2/">next installment</a>, we’ll learn some more parts of Webmachine
resources to serve up some static content.</p>
Sean Cribbs2014-03-13T00:00:00-05:00In Search of the Software Ursatz: Part 1 - Introductionhttp://seancribbs.com/tech/2014/03/13/in-search-of-the-software-ursatz-1/<p><em>This is the first in a series based on the talk I gave at
<a href="http://www.codepalousa.com/">Code PaLOUsa 2014</a> entitled
<a href="http://www.codepalousa.com/speakers/speaker-directory/itemlist/user/241-seancribbs">“In Search of the Software Ursatz”</a></em>.</p>
<p>It has been a long time since I wrote on this blog, but it has been an
equally long time (or more) that I have been thinking about the topic
of this series of posts.</p>
<p>What I want introduce to you, dear reader, is a set of musical
theories that have been influential in my thought process as a
musician and a programmer, in the hopes that they bring about deeper
insights to all of us. This has been a difficult thing to begin
writing and talking about. The concepts are very abstract, and
connecting them concretely to the work we do requires both a strong
gut feeling and occasional leaps of faith.</p>
<p>The ideas I will try to construct are also in their infancy; indeed, I
proposed the talk to Code PaLOUsa with very ambitious goals, but I
haven’t yet connected all of the dots. Luckily, the title of the talk
and these posts are “In Search of…” not “I Found It!”. The musical
theories I will discuss herein took the author <em>over thirty
years</em> – essentially his entire career – to develop; I am just in the
beginning stages of developing my theory.</p>
<p>That said, I apologize if it seemed through the title or the talk
abstract that I have a Grand Unified Theory of Software Design. That
is not the case. Instead, I will look at some important questions and
point in some directions I think we could pursue in the future.</p>
<h2 id="in-search-of">In Search of:</h2>
<p>To begin this discussion, I’d like to outline <em>what it is exactly</em>
that I am seeking.</p>
<p>First, there has been a trend in recent years to see code as
<em>craftsmanship</em>. This seems to mean not just the creation of code to
do a job, but a <em>skilled work</em> that also requires <em>aesthetics</em> beyond
the measurable aspects of the product. What does it mean to be a
<em>software craftsperson</em>? How does that quality reflect in the products
of the crafters? I’d like to know the answers, or at least better
understand the questions.</p>
<p>Second, I’d like an <em>intuitive technique</em> for analyzing the structure
of software. By <em>intuitive</em> I mean that its interpretation is obvious
to the experienced practitioner, and draws from a deep understanding
of software construction by the analyst.</p>
<p>Third, I’d like a <em>subjective means</em> of critical comparison of
software designs. That is, I’m not interested in performance
comparisions, SLOC counts, but what makes the software what it is. For
example, what are the defining aspects of the functional program
versus the object-oriented program and why is one <em>subjectively</em>
better in various circumstances. How do the surface features of a
program convey or obscure its meaning?</p>
<p>Fourth, I’m looking for the <em>why</em> not the <em>how</em>. We have many
Turing-complete languages and rich tools that can express essentially
the same computation. We have a wealth of information on the Internet
(e.g. stackoverflow) that can tell us how to accomplish things. Given
so many choices, what are the designs that win out and what makes them
better than others? What makes them tick?</p>
<h2 id="why-care">Why care?</h2>
<p>Those are great goals to achieve, but why do I care about them?</p>
<p>If we accept the idea that we should strive to be <em>software
craftspersons</em>, we need a <strong>framework for critical thought</strong> about our
craft that goes beyond surface details. We can only improve our craft
if we deeply understand the things we create and turn a critical eye
to our own work.</p>
<p>As Rich Hickey has so eloquently put it, there is a strong distinction
between <strong>simplicity</strong> and <strong>ease</strong>. Simple things require deep
understanding to wield, easy things are often canned solutions that
are quickly outgrown.</p>
<p>Finally, I believe that the <strong>software systems we build reflect</strong> –
and in some cases “leak” – <strong>the foundations on which they are
built</strong>. Understanding those foundations and the interaction between
different layers is essential to building successful, well-crafted
software.</p>
<h2 id="a-tale-of-two-pieces">A Tale of Two Pieces</h2>
<p>Below are two works from the Tonal period (an umbrella term
encompassing works from approximately 1600 to 1850). The first is
<em>Prelude no. 1 in C</em> from <em>The Well-Tempered Clavier, Book 1</em> by
Johann Sebastian Bach. Many of you will know this piece, even if you
are not a musician, as it has become very popular in weddings and
television spots in recent years.</p>
<iframe width="420" height="315" src="//www.youtube.com/embed/zlAic9aPoqs" frameborder="0">
</iframe>
<p>The second will be less familiar to most, except the pianists. It is
<em>Etude in F Major, Op. 10 no. 8</em> by Frederik Chopin.</p>
<iframe width="420" height="315" src="//www.youtube.com/embed/OlYMm9PJB2M" frameborder="0">
</iframe>
<p>Would you believe, aside from the fact that both of these pieces were
intended for keyboard instruments, that they have the same fundamental
structure? They sound very different on the surface, but use the same
techniques, in different combinations, for expounding upon the deep
structure of Tonal music.</p>
<p>That realization is the genius of the theories of Heinrich Schenker,
who I will discuss in more depth in the next post.</p>