Socket To Me January 8, 2009

Let’s continue our January 6 walk-through of Joe Armstrong’s Simple Web Server.

Story to date (eagle-eye view):

We started at level 1: web_server.erl

START UP PROCESS (Entry: web_server:start/1)

*** spawns PROCESS A; passes in the variable Port

PROCESS A (Entry: web_server:server/1)

*** sets trap_exit

*** drops down to Level 2: http_driver:start/3

*** spawns PROCESS B; passes in Port, Fun, Max) where Fun = webserver:server/2; Max = 15

*** let’s call this Fun FUNA

PROCESS B (Entry: http_driver:Server/3)

*** drops down to level 3: tcp_driver:start_raw_server/4

*** if port name not registered

******* spawns PROCESS C; passes in Port, Fun, Max, Length)

*** listen for two messages:

1) {Pid, ok}

*** registers port name

2} {Pid, Error}

*** return Error

PROCESS C (Entry: tcp_driver:cold_start/5)

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.

*** sets trap_exit

*** reports start up

*** listens for request

*** if request:

****** Passes ok back to Master (tcp_driver:start_raw_server/5 in PROCESS B which registers PROCESS C)

****** sets up socket: e.g. tcp_driver:socket_loop/5

****** passes following parameters into tcp_driver:socket_loop/5:

— Listen, passed back from gen_tcp:list/2

— New, anonymous function tcp_driver:start_accept/2

— empty list

— Fun, anonymous function; our old friend FUNA

— Max

Now, I hope I’ve got that right. If you spot errors or subtitles here that I’ve missed, please let me know.

We move on.

So how does Joe Armstrong’s Simple Web Server handle requests?

In tcp_driver:cold_start/5, we see call to tcp_driver:socket_loop/5

********** tcp_driver:socket_loop/5

tcp_driver:socket_loop(Listen, New, Active, Fun, Max) ->

receive

{istarted, New} ->

Active1 = [New|Active],

possibly_start_another(false, Listen, Active1, Fun, Max);

{‘EXIT’, New, Why} ->

%% io:format(“Child exit=~p~n”,[Why]),

possibly_start_another(false, Listen, Active, Fun, Max);

{‘EXIT’, Pid, Why} ->

%% io:format(“Child exit=~p~n”,[Why]),

Active1 = lists:delete(Pid, Active),

possibly_start_another(New, Listen, Active1, Fun, Max);

{children, From} ->

From ! {session_server, Active},

socket_loop(Listen, New, Active, Fun, Max);

Other ->

io:format(“Here in loop:~p~n”,[Other])

end.

*** Note variables:

****** New is the anonmyous function tcp_driver:start_accept/2

****** Active, in first pass, at least, is an empty list

*** receive loop listens for four messages:

1) {istarted, New}

*** sets Active1 = [New|Active]

*** calls tcp_driver:possibly_start_another/5

2) {‘EXIT’, New, Why}

*** calls tcp_driver:possibly_start_another/5

3) {‘EXIT’, Pid, Why}

*** sets Active1 to lists:delete(Pid, Active)

*** calls tcp_driver:possibly_start_another/5

4) {children, From}

*** sends message {session_server, Active}

Frankly, I’m puzzled here. Best I can tell tcp_driver:socket_loop/5 is listening to a child process spawned by gen_tcp:listen/2.

Erlang Wizard Alert: Am I correct?

If we follow the anonymous function New (tcp_driver:start_accept/2), we begin to see daylight.

********** tcp_driver:start_accept/2

tcp_driver:start_accept(Listen, Fun) ->

S = self(),

spawn_link(fun() -> start_child(S, Listen, Fun) end).

********** tcp_driver:start_child/3 — PROCESS D

start_child(Parent, Listen, Fun) ->

case gen_tcp:accept(Listen) of

{ok, Socket} ->

Parent ! {istarted,self()}, % tell the controller

inet:setopts(Socket, [{nodelay,true},

{active, true}]), % before we activate socket

%% io:format(“running the child:~p~n”,[Socket]),

Fun(Socket);

Other ->

exit(oops)

end.

*** calls gen_tcp:accept/1 (See: http://erlang.org/doc/man/gen_tcp.html)

***** Accepts an incoming connection request on a listen socket.

*** if gen_tcp:accept/1 returns {ok, Socket}

****** send message istarted to PROCESS C

*** call inet:setops/2 (See: http://erlang.org/doc/man/inet.html)

****** Sets one or more options for a socket

*** calls Fun(Socket), our old friend FUNA.

Recall that FUNA is fun(Client) from:

web_server:server(Port) ->

S = self(),

process_flag(trap_exit, true),

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

loop().

which is our old friend:

web_server:server(Client, Master) ->

receive

{Client, closed} ->

true;

{Client, Request} ->

Response = generate_response(Request),

Client ! {self(), Response},

server(Client, Master)

after 5000 ->

true

end.

And, at last, we come to the heart of the matter. web_server:server/2 is a receive loop listening for two messages:

1) {Client, closed}

*** drops out of the recieve loop

2} {Client, Request}

*** sets variable Response to function tcp_driver:generate_response/1

*** passes Response in message to Client

*** loops back to web_server:server/2 (tail recursion)

********** tcp_driver:generate_response/1

generate_response({_, Vsn, F, Args, Env}) ->

F1 = “.” ++ F,

case file:read_file(F1) of

{ok, Bin} ->

case classify(F) of

html ->

{header(html),[Bin]};

jpg ->

{header(jpg),[Bin]};

gif ->

{header(jpg),[Bin]};

_ ->

{header(text),[body(“white”),”

",Bin,"

“]}

end;

_ ->

show({no_such_file,F,args,Args,cwd,file:get_cwd()})

end.

And… I think this leg of our journey ends.

Next and final leg… how Joe Armstrong’s Simple Web Server prepares and launches a response.