Poking Around Joe Armstrong’s Simple Web Server – Part 2
December 30, 2008
Let’s see what more we can learn about Joe Armstong’s Simple Web Server by poking around the second layer, http_driver.erl.
In our previous post we learned that web_server:server/1 passes web_server:server/2 as a function down to level 2, e.g. http_driver:start/3. It also instantiates the Port variable in http_driver:start/3 and passes in an integer, 15.
server(Port) ->
S = self(),
process_flag(trap_exit, true),
http_driver:start(Port, fun(Client) -> server(Client, S) end, 15),
loop().
Thus web_server:server/2 gets called somewhere in the depths of http_driver.erl or maybe even in tcp_driver.erl.
When called, web_server/server/2 listens for a Request variable. When a request is received, it calls web_server:generate_response/1. generate_response/1 looks for a file name in the Request tuple and, if found, returns file type and content to the Response variable in web_server:server/2. server/2, in turn sends the response back to the client.
server(Client, Master) ->
receive
{Client, closed} ->
true;
{Client, Request} ->
Response = generate_response(Request),
Client ! {self(), Response},
server(Client, Master)
after 5000 ->
true
end.
What’s not clear so far is just when and how web_server:server/2 gets called. Seems we have to dig down into level 2, http_driver.erl, to find the answer.
Here goes:
http_driver.erl
First thing we note is that http_driver.erl exports three functions: start/3, classify/1, and header/1.
-export([start/3]).
-export([classify/1, header/1]).
Good. http_driver:start/3 is just where we wanted to start excavating.
start/3
start(Port, Fun, Max) ->
spawn(fun() -> server(Port, Fun, Max) end).
http_driver:start/3 spawns a new process running http_driver:server/3.
server/3
server(Port, Fun, Max) ->
tcp_server:start_raw_server(Port,
fun(Socket) -> input_handler(Socket, Fun) end,
Max,
0).
Recall that the function web_server:server/2 was passed into http_driver:start/3 with two parameters: the variables Client and S. The value of Client is still mysterious at this point. The value of S, however, is the PID of the master web server process.
What do you want to bet that the value of Client ends up being the PID of the client process? Seems that we’ll need to move down to level 3, tcp_server.erl, however, to check it out. Be patient. We’ll get there.
Based on the Max variable in the http_driver:server/3, I’m guessing that integer 15 passed down in web_server:server/1 is the maximum number of connections. Comments in the source at this point would definitely help us out.
It looks, however, like we’re setting up a process in tcp_server with a port number, a function to handle input, a variable to hold the master web server process, a variable that holds the maximum number of connections (just a guess), and a yet mysterious integer 0. Once again, we’ll need to dig down into level 3, tcp_server.erl, to confirm.
The input function, http_driver:input_handler/2, takes two parameters: the variable Socket, and a second function, in this case, our old friend web_server:server/2, now packing the variables Client and S where S is the PID of master web server process.
So what happens when http_driver:input_handler/2 gets called?
input_handler/2
input_handler(Socket, Fun) ->
%% When we get spawned we spawn an
%% additional process to handle the input
S = self(),
Server = spawn_link(fun() -> Fun(S) end),
process_flag(trap_exit, true),
relay(Socket, Server, {header, []}).
Whoa!
We spawn another process. And if my eyes aren’t deceiving me, it’s web_server:server/1. But, boy, am I confused! I definitely need big-time guru help here. Is Fun(S) truly web_server:server/1? I thought server/1 took a port address as a parameter. But here it looks like it’s taking a PID. How can this be?
You know what? It’s now 1:48 am and I’m totally whacked. I’m going to pick this up in the later am when my mind is more rested.
Meanwhile, I’ll post our exploration to date in hope that some guru angel will fly by in the night and leave a comment behind to set us back on the right track.
Night all.
December 30, 2008 at 10:31 am
I think that this anonymous Fun(S) takes a ClientPid self() argument and calls web_server:server(Client, S), where this second ‘S’ is the original server Pid. (I prefer to have variable names a little longer than one character to avoid this sort of confusion!) It does not call web_server:server/1 but rather web_server:server/2.
i.e. Fun/1 is really:
fun(ClientPid) ->
web_server:server(ClientPid, ServerPid)
end.
ServerPid was defined/set when the function was created, back in web_server.erl.
I am enjoying reading about your explorations.
Thanks,
Philip
December 30, 2008 at 10:31 pm
Many thanks again, Phillip.
If this keeps up I’ll have to come over and mow your lawn and wash your car.
And to think that I’ve even read all about Funs on pp 42 through 48 of Programming Erlang. Clearly Joe thinks they’re important to have devoted so many pages. Shows how long some of these concepts take to sink in.
Back to the book for me.
And, I’m delighted that you’re enjoying the blog. Thank you.
My fear is that these explorations may be a source of amusement in and around the Erlang community as much as anything. But I figure that it’s better to look stupid once while seeking expert information than to remain stupid for a life time.
LRP
December 31, 2008 at 12:45 am
“I figure that it’s better to look stupid once while seeking expert information than to remain stupid for a life time.”
Agreed! That is why I have blogged some of my own Erlang adventures.
I am not going to worry about what some random person on the internet thinks of me… as long as they tell me where I’ve screwed up and how to fix it.
Cheers,
Philip