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