Joe Armstrong’s Simple Web Server – Again January 6, 2009

Ok, armed with my new understanding of anonymous functions, here’s my latest walk-through of Joe Armstrong’s Simple Web Server.

As you’ll recall JA’s Simple Web Server is factored into three levels:

Level 1: web_server.erl

Level 2: http_driver.erl

Level 3: tcp_driver.erl

We start at Level 1:

********** web_server:start/1

web_server:start([A]) ->

% START-UP PROCESS

start_on_port(list_to_integer(atom_to_list(A))).

*** Let’s call this START-UP PROCESS

*** takes a list containing atom A, which represents a port number

*** calls web_server:start_on_port/1

*** passes in list_to_integer(atom_to_list(A))

*** list_to_integer(atom_to_list(A)) transforms atom A into an integer.

********** web_server:start_on_port/1

web_server:start_on_port(Port) ->

spawn_link(fun() -> server(Port) end).

*** START-UP PROCESS spawns process web_server:server/1

*** Let’s call this PROCESS A

NOW We’re entering PROCESS A

********** web_server:server/1

web_server:server(Port) ->

% PROCESS A

S = self(),

process_flag(trap_exit, true),

http_driver:start(Port, fun(Client) -> server(Client, S) end, 15),

loop().

*** Assigns PID of PROCESS A to variable S

*** Sets trap-exit

*** Calls http_driver:start/3

*** Passes in Port, anonymous function web_server:server/2, 15

*** Calls web_server:loop()

********** http_driver:start/3

*** Drops down to Level 2 and spawns another process.

********** web_server:loop()

loop() ->

receive

Any ->

io:format(“server:~p~n”,[Any]),

loop()

end.

*** listens for any message

*** prints message

At this point START-UP PROCESS has spawned PROCESS A which calls a function in level 2, then goes into a receive loop that outputs any message coming into PROCESS A.

Now we’re dropping down to Level 2

********** http_driver:start/3 (cont.)

http_driver:start(Port, Fun, Max) ->

spawn(fun() -> server(Port, Fun, Max) end).

*** Spawns process web_server:server/3

*** Let’s call this PROCESS B

*** PROCESS takes three parameters: Port, Fun, Max

*** Fun is web_server:server/2

*** Let’s call Fun FUNA

Now we’re entering PROCESS B

********** http_driver:server/3

http_driver:server(Port, Fun, Max) ->

tcp_server:start_raw_server(Port,

fun(Socket) -> input_handler(Socket, Fun) end,

Max,

0).

*** Drops us down to Level 3

Now we’re entering Level 3

********** tcp_server:start_raw_server/4

tcp_driver:start_raw_server(Port, Fun, Max, Length) ->

%% This server accepts up to Max connections on Port

%% The *first* time a connection is made to Port

%% Then Fun(Socket) is called.

%% Thereafter messages to the socket result in messsages to the handler.

Name = port_name(Port),

case whereis(Name) of

undefined ->

Self = self(),

Pid = spawn_link(fun() ->

cold_start(Self, Port, Fun, Max, Length)

end),

receive

{Pid, ok} ->

register(Name, Pid),

{ok, self()};

{Pid, Error} ->

Error

end;

Pid ->

{error, already_started}

end.

*** Calls tcp_driver:port_name/1

*** Looks to see if port_name is registered (See: http://www.nabble.com/Registered-names-and-pids-td17222546.html)

*** If not, assigns PID of PROCESS B to the variable Self

*** Spawns a new process tcp_driver:cold_start/5

*** Let’s call this PROCESS C

*** Then listens for {Pid, ok}

*** If {Pid, ok}, it registers Pid under Name

*** Otherwise returns an Error

*** whereis(Name) returns a Pid, then reports error, process already started

********** tcp_driver:port_name/1

port_name(Port) when integer(Port) ->

list_to_atom(“portServer” ++ integer_to_list(Port)).

*** Transforms integer value of Port into a the atom postServerXXXX, where XXXX is port number

Now we’re entering PROCESS C

********** tcp_driver:cold_start/4

tcp_driver:cold_start(Master, Port, Fun, Max, Length) ->

process_flag(trap_exit, true),

io:format(“Starting a port server on ~p…~n”,[Port]),

case gen_tcp:listen(Port, [binary,

%% {dontroute, true},

{nodelay,true},

{packet, Length},

{reuseaddr, true},

{active, false}]) of

{ok, Listen} ->

%% io:format(“Listening on:~p~n”,[Listen]),

Master ! {self(), ok},

New = start_accept(Listen, Fun),

%% Now we’re ready to run

socket_loop(Listen, New, [], Fun, Max);

Error ->

Master ! {self(), Error}

end.

*** Set trap_exit

*** Report start up of new server

*** Listen to gen_tcp:listen/2 (http://erlang.org/doc/man/gen_tcp.html)

Note: gen_tcp:listen/2 “Sets up a socket to listen on the port Port on the local host.”

Also Note: FUNA has been passed all the down to tcp_driver:socket_loop/5

By all means review the cited gen-tcp man page.

Whew! I’m approaching brain death. But at this point we’ve plunged down through three levels of Joe Armstrong’s Simple Web Server to discover how he sets up a socket to listen for a request.

Oh yeah, simple for him.

Hope it hasn’t been too tedious. Also, please, please if I’ve added to the confusion or disseminated nonsense, set me straight.

Next episode we’ll take a look at how Joe’s web server processes requests and responses.

‘Til then.