Middle men wanted

Joe Armstrong joe@REDACTED
Wed May 5 11:25:18 CEST 2004



   I'm collecting middle men - let me explain.

   Cheers

/Joe

  (warning long text)


   What is a middle man?
   =====================

  TCP application protocols (as defined in RFCs) are a pain to program
- I want therefore a number of device drivers (I call them middle men)
to  isolate the  protocols  from  the programs  which  respond to  the
protocols.


  A middle  man sits between  a TCP socket  and an erlang  program. It
converts  the protocol  packets -  into Erlang  terms in  a transparnt
manner. ie for every protocl  messgae there is exactly one Erlang term
and vice. versa.

  Note: the same middle_man is used for both the client and the server
software :-)
                   

		    +-----------------+
      TCP packets   |    Middle man   | Erlang terms
    ---->-----------|                 |----->------
                    |                 |
      TCP packets   |                 | Erlang terms
    ----<-----------|                 |-----<------
                    +-----------------+


   The middle man does packet re-assembly etc for TCP packets and parsing.

   So for example: if a HTTP middle man recives a  


	GET .....

   request, it will parse the entire request and send a {get," ..."} message
to it's output.


   What do I want?
   ===============

	I have written the following middle men

	eterm   (erlang terms)
	http
	estream (erlang binary representaion of terms)
	xml     (generic)
	
	I am writing:

	xmlrpc
	soap

	I want:

	IRC, NNTP, POP2, ....

   Why do I want middle men and where is this leading?
   ===================================================

  I have made a erlang deamon that acts as a "polyport" - ie just open
port 1234  (say) on the server  and start talking in  any protocol you
feel like  - the  port analyses the  input and starts  the appropriate
middle man.

  Using the esteam protocol a client can then do the following:

		{ok, S} = session:start("host", Port, estream)

  This opens a estream connection to Host,Port

		session:rpc(S, {apply, [M,F,A]})

	do an rpc on the server.

  Now the server code is like this:

	loop(Client) ->
	    receive
		{From, {apply,{M,F,A}} ->
		    Val = (catch apply(M,F,A)),
		    From ! {send, Val}
	    end.

  This is *very* powerful (see security later)

  So suppose I want to transfer a file from a clien to a server. The client code is

	{ok, S} = session:start("host", Port, estream),
	{ok, B} = file:read_file(File),
	session:rpc(S, {apply, {file,write_file,[Bin]}}),
	session:close(S)

  Which transfers a file from the client to the server - this works despite the fact
there is no file server (ie like FTP) running on the server.

   <<security>> I don't allow applies until the client has authenticated itself <<>>

  Soon - all nodes in planetlab will offer polyport servers :-) 

    Writing a server
    ================

   Middle men make writing servers very easy.

   For example, my HTTP server now looks like this:

server(Client) ->
    receive
	{Client, closed} ->
	    exit(handler_closed);
	{Client, {msg, Request}} ->
	    Response = generate_response(Request),
	    Client ! {msg, Response},
	    server(Client);
	Other ->
	    io:format("Mod http:unexpected message:~p~n",[Other])
    end.

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"),"<pre>",Bin,"</pre>"]}
            end;
        _ ->
            show({no_such_file,F,args,Args,cwd,file:get_cwd()})
    end.


    Middle men API
    ==============

The middle man is started with a call like this:

	M:start(Args, Client, Socket, Bin)

(Args is ignored)

	Client is a Pid to whom all decoded messages should be sent.
	Socket is the TCP socket
	Bin is the first data packet that was sent to the socket
        then the connection was extablised.

The middle man protocol is handled in a control loop like this:

The loop handles the following messages
	
	{tcp, Socket, Bin}          -- data from the socket
	{tcp_closed, Socket}        -- socket closed
	close                       -- request to close the socket
	{send, Term}                -- request to send the term Term
	{'DOWN',_,process,Client,_} -- the Client dies
	

And is something like this

loop(Client, Socket, Cont) ->
    receive
	{tcp, Socket, Bin} ->
	    received(Client, Socket, Bin, Cont);
	{tcp_closed, Socket} ->
	    Client ! {self(), closed};
	close ->
	    gen_tcp:close(Socket);
	{send, Term} ->
	    ... format the term ...
	    B   = format_term(Term),
	    gen_tcp:send(Socket ,B),
	    loop(Client, Socket, Cont);
	{'DOWN',_,process,Client,_} ->
	    gen_tcp:close(Socket);
	Other ->
	    ed_log:error({middle_man_eterm,funny,msg,Other})
    end.

At the end I have included a middle man that converts between the textual
and internal form of Erlang terms.



Middle man example
==================
 
-module(ed_middle_man_eterm).
 

%% Copyright (C) 2004 by Joe Armstrong (joe@REDACTED)
%% All rights reserved.
%% The copyright holder hereby grants the rights of usage, distribution
%% and modification of this software to everyone and for any purpose, as
%% long as this license and the copyright notice above are preserved and
%% not modified. There is no warranty for this software.

-compile(export_all).

start(Args, Client, Socket, <<"erl\r\n",B/binary>>) ->
    erlang:monitor(process, Client),
    received(Client, Socket, binary_to_list(B), []);
start(Args, Client, Socket, <<"erl\r",B/binary>>) ->
    erlang:monitor(process, Client),
    received(Client, Socket, binary_to_list(B), []);
start(Args, Client, Socket, <<"erl\n",B/binary>>) ->
    erlang:monitor(process, Client),
    received(Client, Socket, binary_to_list(B), []).

received(Client, Socket, Str, Cont) ->
    case erl_scan:tokens(Cont, Str, 1) of
	{more, C1} ->
	    loop(Client, Socket, C1);
	{done, Result, Rest} ->
	    case Result of 
		{ok, Toks, _} ->
		    case  erl_parse:parse_term(Toks) of
			{ok, Term} ->
			    Client ! {self(), Term};
			_ ->
			    exit(bad_term)
		    end;
		E ->
		    exit(bad_term)
	    end,
	    received(Client, Socket, Rest, [])
    end.

%% ---> {tcp,Socket,Bin}
%% ---> {tcp_closed, Socket}
%% ---> close
%% ---> {send, X}
%% ---> {'DOWN',_,_,_,_}       (from client)

loop(Client, Socket, Cont) ->
    receive
	{tcp, Socket, Bin1} ->
	    received(Client, Socket, binary_to_list(Bin1), Cont);
	{tcp_closed, Socket} ->
	    Client ! {self(), closed};
	close ->
	    gen_tcp:close(Socket);
	{send, Term} ->
	    B   = io_lib:format("~p",[Term]),
	    io:format("Sending:~p~n",[Term]),
	    gen_tcp:send(Socket ,[B,".\n"]),
	    loop(Client, Socket, Cont);
	{'DOWN',_,process,Client,_} ->
	    gen_tcp:close(Socket);
	Other ->
	    ed_log:error({middle_man_eterm,funny,msg,Other})
    end.








More information about the erlang-questions mailing list