From the MochiWeb library's README:
"MochiWeb is an Erlang library for building lightweight HTTP servers."
The following is guided tour of the MochiWeb source code. By the end, you should understand how MochiWeb works, and how MochiWeb uses OTP. Naturally, you should then have a well-educated guess on how to build applications using MochiWeb.
Please let me know if you have any errors to report or additions to make using the feedback form. I'll be very grateful to you for making this a more valuable resource, and I'll cite your contribution.
It is assumed that you:
Most of the following will probably apply to non-Linux systems, but I have not tested anything in those environments. YMMV.
For your reference, this walkthrough is based on git commit 9a53dbd7b2c52eb5b9d4e90088ab471cac7b8ae9, from July 24th, 2010.
This section covers the new_mochiweb.erl script. If you're not interested, feel free to skip to the next section, where we begin discussing MochiWeb's server internals.
Creating a new mochiweb application is made easy by the scripts/new_mochiweb.erl script. It essentially copies the contents of the priv/skel dir into a new project dir, renaming the files and their contents to match the project name you give it at the command line.
For example, if we want to create a new mochiweb project called demo_mochiweb in the ~/proj folder:
aj@fattie:~/proj/mochiweb$ ./scripts/new_mochiweb.erl demo_mochiweb ~/proj
proj/demo_mochiweb/
proj/demo_mochiweb/support/
include.mk
run_tests.escript
Makefile
proj/demo_mochiweb/src/
skel.erl
skel_app.erl
skel.app
skel.hrl
skel_deps.erl
Makefile
skel_sup.erl
skel_web.erl
start-dev.sh
proj/demo_mochiweb/priv/
proj/demo_mochiweb/priv/www/
index.html
start.sh
This simple script is written in escript. The script's execution, from beginning to end, looks something like:
scripts/new_mochiweb.erlmochiweb_skel:skelcopy(Dest, Name)mochiweb_skel:skelcopy/2priv/skel to Dest (the location you specified on the command line), while renaming files and replacing in each file skel with Name (the name you gave on the command line).deps/mochiweb-src to the actual MochiWeb source.cool erlang trick: In scripts/new_mochiweb.erl's main([Name, Dest]) function, the mochiweb source code is dynamically built and loaded without stopping/restarting the node itself. Look for code:rehash().
Assuming all went well, you now have a mochiweb project in ~/proj/demo_mochiweb, and we're ready to see it in action.
The skeleton application starts a MochiWeb server on port 8000, and does nothing but display "MochiWeb running." We're now going to find out exactly how it accomplishes that feat.
In your project folder, you have two scripts that start your sever: start-dev.sh and start.sh
start.sh*/ebin folders to the pathdemo_mochiweb:start/0start-dev.shstart.sh doesmake to compile the source before starting the applicationsreloader:start/0 before starting the demo_mochiweb applicationThe mochiweb skeleton comes with a simple Makefile that builds your erlang source and places it in the correct folder. If you're unfamiliar, you should read a bit about GNU's make tool sometime soon.
Excluding the make step, start-dev.sh accomplishes all of its goals using this one-liner:
exec erl -pa $PWD/ebin $PWD/deps/*/ebin -boot start_sasl -s reloader -s demo_mochiweb
Taking a few bits straight from The Erlang Emulator's man page, the options used are:
-pa Dir1 Dir2 ...-boot Filestart_sasl is a boot script that come with Erlang/OTP. From the System Principles Guide, using the -boot start_sasl option "[l]oads the code for and starts the applications
Kernel,
STDLIB and
SASL."
-s Mod [Func [Arg1, Arg2, ...]]-boot start_saslI don't fully understand the value of SASL yet. You're best off learning about it yourself ;) Here's the SASL User's Guide and Reference Manual @todo learn about sasl
-s reloaderThis calls reloader:start/0. Reloader is a gen_server whose stated goal is "automatically reloading modified modules during development." reloader:start/0 effectively starts the reloader, a gen_server outside any supervision tree, which polls for newly-compiled versions of every loaded module's source code, every 1 second, and loads new code if found. The code is very readable, and not complex; if you're interested, you should definitely read the code.
-s demo_mochiwebFinally, our code is getting executed! Specifically, demo_mochiweb:start/0 is called. The call stack looks roughly as follows:
demo_mochiweb:start/0demo_mochiweb_deps:ensure/0, which adds any ebin or include folders that are not already on the path to the VM's code path.crypto application is started (exits badly otherwise).demo_mochiweb application (our app!)demo_mochiweb.appdemo_mochiweb_app defined in demo_mochiweb_app.erldemo_mochiweb_app:start/2 (application)demo_mochiweb_sup Supervisordemo_mochiweb_sup:start_link/0 (supervisor)demo_mochiweb_web:start/0 (which is not a gen_server, as it turns out) as a permanent child process, one_for_one restart policy, with a treshold of 10 restarts in 10 seconds (exit time threshold of 5000ms before it is mercifully killed). This is also where the web server is configured, namely given the port and docroot to operate on.demo_mochiweb_web:start/1Loop for mochiweb. In our case, this loop function calls demo_mochiweb_erl:loop/2, which responds by serving files out of the docroot. Can you guess where "MochiWeb running" came from now? Answermochiweb_http:start/1 with our callback function, among other options.where we're at: Our application started the main supervisor, which then spawned demo_mochiweb_web:start/1 (which is not a gen_server, recall). Now, control is being passed to the MochiWeb source.
mochiweb_http:start/1mochiweb_http:loop(Socket, HttpLoop), where HttpLoop is our 'Loop' functionmochiweb_socket_server:start/1, called with the set of parsed options.mochiweb_socket_server:start/1 (gen_server)#mochiweb_socket_server record.:start_server/1, called with the #mochiweb_socket_server record it built from our optionsmochiweb_socket_server:start_server/1gen_server:start_link/3 that starts itself.mochiweb_socket_server:init/1:listen/3 (@todo fill in purpose of options):listen/3, and decides what to do/return based on its output.
@todo quickly describe the decision and its effects. unprivileged ports = stop gen_server.
mochiweb_socket_server:listen/3mochiweb_socket:listen/4mochiweb_socket:listen/4gen_tcp:listen/2 (or ssl:listen/2 if using SSL)mochiweb_socket_server:new_acceptor_pool/2mochiweb_acceptor:start_link/3, where N in this case is the default Size is set in the record definition as 16.acceptor_pool is the set of Pids of the newly created acceptorsmochiweb_acceptor:start_link/3spawn_links to :init/3 @todo why proc_lib:spawn_link instead of spawn_link or gen_server:start_link?mochiweb_acceptor:init/3gen_tcp:accept/1 (ssl:ssl_accept if ssl)gen_server from mochiweb_socket_server is cast the {accepted, ...} message, and the socket is then parsed into a request via :call_loop/2, which effectively returns the value from a call to our 'Loop' function.
Your MochiWeb Skeleton app consists of a single application, supervisor, and gen_server.
The gen_server spawns a bunch of little connection acceptors that live for around 2 seconds.
Once a connection acceptor finishes its job (or times out), it lets the gen_server know it's done, and then dies.
The gen_server listens for acceptor deaths and respawns new acceptors under normal circumstances.
Recall that the body of our application, the 'Loop' function, was wrapped in a call to mochiweb_http:loop/2. From this wrapper function, the stream of incoming data is parsed into manageable data structures, and finally our 'Loop' is called.
Essays 1743 (discovered via Mark Pilgrim's Dive Into HTML5), Bob Ippolito (author of MochiWeb), Linux, GNU, emacs, git, inkscape.