Wednesday, February 15, 2012

Setting up a Yaws web server and Riak database

NOTE:  This is a rough draft for now.  I'm hoping to refine it but, wanted to get it up for those out there trying to get Yaws up and running.  It combines various bits and pieces I was able to piece together from other sources on the web.

This was done on OS X 10.7.  I assume something similar would work on Linux.  It doesn't look like Riak is available for other BSD systems.  If you're using Windows this may or may not work but I'm not interested in that OS.





I’m going to walk through setting up a Riak database cluster and a Yaws web server.  We’ll use Yaws to access the database as well as dynamically generate our HTML5 pages.  A mixture of Erlang, Yaws and Javascript code will be employed, where we’ll also use data from the Riak cluster to populate some of our Javascript files.  It’s pretty cool!

  • Create a directory called Programming in your home directory.
  • Create a directory called erlang in Programming.
  • Install Homebrew, Erlang and Yaws.
    • - NOTE: If Erlang is unpgraded by Homebrew you may need to uninstall and reinstall Yaws.
  • Download the new version of Riak from http://downloads.basho.com/riak/CURRENT/, current as of 8 FEB 11.  Create a directory called riak in the erlang directory.  Copy the Riak directory you just downloaded into there three time and rename them riak1, riak2 and riak3.  We’re following the directions from http://wiki.basho.com/Basic-Cluster-Setup.html.
    • Now we’ll edit each app.config and vm.args in the riakN/etc folder.
      • In app.config change the port numbers in http, https, handoff_port and pb_port to unique numbers.  For riak1 I have the following:

{http, [ {"127.0.0.1", 8091 } ]}
{https, [{ "127.0.0.1", 8091 }]}
{handoff_port, 8191 }
{pb_port, 8291 }

I increment the port numbers by 1 for riak2 and riak3
      • In vm.args edit the “-name” line.  I have the following for riak1:

      • with similar entries for riak2 and riak3.
    • You should be able to start and join the node:

./riak/riak1/bin/riak start
./riak/riak2/bin/riak start
./riak/riak3/bin/riak start
./riak/riak2/bin/riak-admin join riak1@127.0.0.1
./riak/riak3/bin/riak-admin join
riak1@127.0.0.1
Run the following command:
./riak/riak1/bin/riak-admin status
You should see the following line in your output:
ring_members : ['riak1@127.0.0.1','riak2@127.0.0.1','riak3@127.0.0.1']
ring_ownership : <<"[{'riak3@127.0.0.1',21},{'riak2@127.0.0.1',21},{'riak1@127.0.0.1',22}]">>
  • NOTE: riakc is included with riak so we don’t need to install riak-erlang-client.  I’ll show this a few steps below.  All of the commands here can be run inside an interactive Yaws session.  You can skip this part for now and run the commands once you have the Yaws server setup. Before you leave, at least create a directory called yaws in your erlang directory.

Install riak-erlang-client in the yaws directory you just created and follow the instructions: https://github.com/basho/riak-erlang-client.  To start Erlang use the command:
$ erl -pa ~/Programming/erlang/yaws/riak-erlang-client/ebin/ -pa ~/Programming/erlang/yaws/riak-erlang-client/deps/protobuffs/ebin/  -pa ~/Programminerlang/yaws/riak-erlang-client/deps/protobuffs/ebin/
Erlang R15B (erts-5.9) [source] [64-bit] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]

Eshell V5.9  (abort with ^G)
1> {ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 8291).
{ok,<0.33.0>}
2> riakc_pb_socket:ping(Pid).
pong
3> Object = riakc_obj:new(<<"groceries">>, <<"mine">>, <<"eggs & bacon">>).
{riakc_obj,<<"groceries">>,<<"mine">>,undefined,[],
          undefined,<<"eggs & bacon">>}
4> riakc_pb_socket:put(Pid, Object).
ok
5> {ok, O} = riakc_pb_socket:get(Pid, <<"groceries">>, <<"mine">>).
{ok,{riakc_obj,<<"groceries">>,<<"mine">>,
              <<107,206,97,96,96,96,204,96,202,5,82,28,7,47,197,115,250,
                27,85,78,201,96,74,...>>,
              [{{dict,2,16,16,8,80,48,
                      {[],[],[],[],[],[],[],[],[],[],[],[],...},
                      {{[],[],[],[],[],[],[],[],[],[],...}}},
                <<"eggs & bacon">>}],
              undefined,undefined}}
6>
Notice that 8291 was used as the port above.
  • Copy yaws.conf into ~/Programming/erlang/yaws.  Now we’ll need to edit this file.  Before that we’ll want to setup our file structure (some of these will be used later):

$ mkdir yaws
$ cd yaws
$ mkdir ebin
$ mkdir include
$ mkdir log
$ cd ../
$ mkdir www
$ cd www
$ mkdir test
$ cd test
$ mkdir src www
$ cd src
$ mkdir erlang js
$ cd erlang && mkdir ebin && ../../
$ cd www
$ mkdir html
  • Here’s the changes you’ll make in the your yaws.conf in ~/Programming/erlang/yaws:

#logdir = /usr/local/Cellar/yaws/1.92/var/log/yaws
logdir = yaws/log

#ebin_dir = /usr/local/Cellar/yaws/1.92/lib/yaws/examples/ebin
#ebin_dir = /usr/local/Cellar/yaws/1.92/var/yaws/ebin
ebin_dir = www/test/src/erlang/ebin

#include_dir = /usr/local/Cellar/yaws/1.92/lib/yaws/examples/include
include_dir = yaws/include

#<server Your-Servers-Name.local>
#        port = 8181
#        listen = 127.0.0.1
#        docroot = /usr/local/Cellar/yaws/1.92/var/yaws/www
#        auth_log = true
#        appmods = <cgi-bin, yaws_appmod_cgi>
#</server>

<server localhost>
       port = 8181
       listen = 127.0.0.1
       docroot = www
#        docroot = /tmp
#        dir_listings = true
#        auth_log = true
#        statistics = true
#        <auth>
#                realm = foobar
#                dir = /
#                user = foo:bar
#                user = baz:bar
#        </auth>
</server>

#<server Your-Servers-Name.local>
#        port = 8181
#        docroot = /tmp
#        listen = 127.0.0.1
#        dir_listings = true
#        auth_log = true
#        <ssl>
#                keyfile = /usr/local/Cellar/yaws/1.92/etc/yaws/yaws-key.pem
#                certfile = /usr/local/Cellar/yaws/1.92/etc/yaws/yaws-cert.pem
#                depth = 0
#        </ssl>
#</server>
Note that all of the paths are relative to the current directory (~/Programming/erlang/yaws).  My server insists on using /usr/local/Cellar/yaws/1.92/lib/yaws/examples/ebin as an ebin path also -- don’t know why.  You can fire things up now with:
$ yaws -i --pa \ /Users/daviddreisigmeyer/Programming/erlang/riak/riak1/lib/riakc-1.2.0/ebin/ \
--pa /Users/daviddreisigmeyer/Programming/erlang/riak/riak1/lib/protobuffs-0.6.0/ebin
If you go to http://localhost:8181 in the browser you’re server should be running.  We haven’t created any web pages yet so it’s not going to show anything.  Now on to that.  Oh, and we can create new Riak records from this interactive Yaws server.  Hit enter to get an Erlang prompt.  Then run something like:
1> {ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 8291).
{ok,<0.60.0>}
2> Object = riakc_obj:new(<<"groceries">>, <<"no-more">>, <<"MORE eggs & bacon!">>).
{riakc_obj,<<"groceries">>,<<"no-more">>,undefined,[],
          undefined,<<"MORE eggs & bacon!">>}
3> riakc_pb_socket:put(Pid, Object).
ok
4> {ok, O} = riakc_pb_socket:get(Pid, <<"groceries">>, <<"no-more">>).
{ok,{riakc_obj,<<"groceries">>,<<"no-more">>,
              <<107,206,97,96,96,96,204,96,202,5,82,28,17,6,150,28,254,
                242,140,123,50,152,18,...>>,
              [{{dict,2,16,16,8,80,48,
                      {[],[],[],[],[],[],[],[],[],[],[],[],...},
                      {{[],[],[],[],[],[],[],[],[],[],...}}},
                <<"MORE eggs & bacon!">>}],
              undefined,undefined}}
So we don’t need the riak-erlang-client that was installed earlier.  I didn’t know that when I first did this so you can remove that if you wish.  You should now run the Erlang session we had above where we put some data on the Riak database -- we'll use that in our web pages below. Notice when we started Yaws that we used the client that comes with Riak, not the one we installed separately.  You can start Erlang like this also.
  • Our main web page is given by the following code:


<!DOCTYPE html>
<erl>
out (Arg) ->
   Holder = riakfuncs:connect(),
   {ehtml, {html, [{lang,"en"}], [
{head, [], [
   htmlfuncs:title("Yaws and Riak"),
   {meta, [{charset, "utf-8"}]}
  ]},
{body, [], [
   htmlfuncs:header(),
   htmlfuncs:aside("Here comes a nice popup...", "At least I think it is!"),
   {script, [], {ssi,"../js/ssi_ex1.js","%",[{"a",Holder}]}},
   htmlfuncs:paragraph("And even an HTML5 canvas element!"),
   {canvas, [{id, "myCanvas"}, {width, "300"}, {height, "150"}]},
   {script, [], {ssi,"../js/myCanvas.js","%", []}},
   htmlfuncs:paragraph("Isn't that cool!"),
   {nav, [], [
      {a, [{href, "nextpage.yaws"}], "The Next Page"}
     ]}
  ]}
      ]
   }}.

</erl>

which is saved in the directory ~/Programming/erlang/yaws/www/test/src/erlang as testyaws.yaws.  We’ll have other .erl files here but the yaws server is going to look in the ./ebin directory for the .beam files it will use to actually run our website.  So, when you create or modify a .erl file you’ll want to run

rm -f ./ebin/*.beam && erlc *.erl && mv *.beam ebin/

to update the site.

The first thing to notice is that this will be an HTML5 page: <!DOCTYPE html>.  Also, everything between <erl> and </erl> will be a Server Side Include (SSI), so Yaws will change it into HTML5.  Next, a function out(Arg) is defined, which we’ll now pick apart.  Oh, just so you can see what the result is this is the HTML5 output:

<!DOCTYPE html>

<html lang="en">
<head>
<title>Yaws and Riak</title>
<meta charset="utf-8"></meta></head>
<body>
<header>here | there | everywhere</header>
<article>
<p>Here comes a nice popup...</p>
<aside>
<p>At least I think it is!</p></aside></article>
<script>alert("This is from Riak through Yaws : " + "eggs & bacon");
</script>
<p>And even an HTML5 canvas element!</p>
<canvas id="myCanvas" width="300" height="150"></canvas>
<script>// Opera example
var elem = document.getElementById('myCanvas');
if (elem && elem.getContext) {
   var context = elem.getContext('2d');
   if (context) {
context.fillStyle   = '#00f';
context.strokeStyle = '#f00';
context.lineWidth   = 4;
context.fillRect  (0,   0, 150, 50);
context.strokeRect(0,  60, 150, 50);
context.clearRect (30, 25,  90, 60);
context.strokeRect(30, 25,  90, 60);
   }
}
</script>
<p>Isn't that cool!</p>
<nav><a href="nextpage.yaws">The Next Page</a></nav></body></html>

We want to actually use the Riak databases we’ve set up locally.  In order to do that we’ll first connect to them and put the returned values in a variable: Holder = riakfuncs:connect().  For this call to the databases I created a separate Erlang file riakfuncs.erl in the same directory named :

-module(riakfuncs).
-export([connect/0]).

connect () ->
   {ok, Pid} = riakc_pb_socket:start_link("127.0.0.1", 8291),
   {ok, O} = riakc_pb_socket:get(Pid, <<"groceries">>, <<"mine">>),
   Out_var = riakc_obj:get_values(O),
   Out_var.

Compile this and put the .beam file into the ./ebin directory.

Now we begin the ehtml part of our function that Yaws will convert into an HTML5 page.  The form for this is: {ehtml, {}}.  After this the syntax will be the following:

{keyword, [{arg1,val2},{arg2,val2},...],[stuff]}

This is expanded by Yaws into:

<keword arg1=val1 arg2=val2 …>
stuff
</keyword>
Awesome.

I wanted to put all of the HTML functions my main page so I created another file htmlfuncs.erl in the same directory:

-module(htmlfuncs).
-export([article/1, aside/2, header/0, paragraph/1, title/1]).

article (Words) ->
   {article, [], paragraph(Words)}.

aside (Article_Words, Aside_Words) ->
   {article, [], [paragraph(Article_Words), in_aside(Aside_Words)]}.
in_aside (Words) ->
   {aside, [], paragraph(Words)}.

header () ->
   {header, [], "here | there | everywhere"}.

paragraph (Words) ->
   {p, [], Words}.

title (Words) ->
   {title, [], Words}.

At the bottom of testyaws.yaws we’ve put an href to the HTML page nextpage.yaws.  Again, in the same directory you’ll have this file as nextpage.yaws:

<!DOCTYPE html>
<erl>
out (Arg) ->
   {ehtml, {html, [{lang,"en"}], [
{head, [], [
   htmlfuncs:title("Another Yaws Page"),
   {meta, [{charset, "utf-8"}]}
  ]},
{body, [], [
   htmlfuncs:header(),
   htmlfuncs:paragraph("Hello from the next page.")
  ]}
      ]
   }}.

</erl>

After we go through testpage.yaws you’ll understand this file also.  That’s it for the Erlang code.

Yaws takes this:

{head, [], [
htmlfuncs:title("Yaws and Riak"),
{meta, [{charset, "utf-8"}]}
]}

and converts it into this:

<head>
<title>Yaws and Riak</title>
<meta charset="utf-8"></meta></head>

Everything else is similar.  The next interesting thing to notice is the line

{script, [], {ssi,"../js/ssi_ex1.js","%",[{"a",Holder}]}}

This calls the function ssi_ex1.js in the ~/Programming/erlang/yaws/www/test/src/js directory.  The code is simply:

alert("This is from Riak through Yaws : " + "%a%");

The output of this is:

<script>alert("This is from Riak through Yaws : " + "eggs & bacon");
</script>

What happened is that the ‘%a%’ placeholder is replaced with the values in Holder, which holds what the call to the Riak database contains.  We tell Yaws to replace “a” with Holder in ssi_ex1.js and that the character ‘%’ is marking where we’ll include the Holder variable.  We’re also saying that this is and SSI (server-side include).

The next thing is a HTML5 canvas element:

{canvas, [{id, "myCanvas"}, {width, "300"}, {height, "150"}]},
{script, [], {ssi,"../js/myCanvas.js","%", []}}

with the Javascript code in myCanvas.js being

// Opera example
var elem = document.getElementById('myCanvas');
if (elem && elem.getContext) {
   var context = elem.getContext('2d');
   if (context) {
context.fillStyle   = '#00f';
context.strokeStyle = '#f00';
context.lineWidth   = 4;
context.fillRect  (0,   0, 150, 50);
context.strokeRect(0,  60, 150, 50);
context.clearRect (30, 25,  90, 60);
context.strokeRect(30, 25,  90, 60);
   }
}

located in ~/Programming/erlang/yaws/www/test/src/js.  

The final thing is a link to the nextpage.yaws page we created earlier

{nav, [], [
{a, [{href, "nextpage.yaws"}], "The Next Page"}
]}

which is converted to the HTML code

<nav><a href="nextpage.yaws">The Next Page</a></nav>

And that’s it.  Some things that could be done from here would be to use an HTML5 form to put data into the Riak database.  There also wasn’t any CSS, or plain HTML, files used here, though that should be straightforward.  I’m also going to look into using curl to do some web scraping.

Good luck - I hope you enjoy setting up your own Yaws / Riak combination.