Middle men wanted
Joe Armstrong
joe@REDACTED
Wed May 5 14:46:24 CEST 2004
I've now made gen_middle_man (appended)
The plugins are easy (see middle_man_eterm - appended)
and middle_man_http.
A middle man must export
start(Args, Bin) -> {[Msg], Cont}
Args is ignored (for now)
Bin = initial data sent to the port
Msg = message to sent to the client
Cont = continuation to be called in received/2
received(Bin, Cont) -> {[Msg], Cont'}
return values as above
term_to_bin(Term) -> IOList
serialise Term. IOList is sent to the socket
With this structure handing sockets in a generic way is easy.
Open a socket and call gen_middle_man(.....,Mod) to handle the input
With this abstraction *all* servers connected to TCP ports see streams of
Erlang terms.
Now we can do some fun things. Here is an example:
Here is some server code:
handler0(Client) ->
receive
{Client, {fac, N}} when integer(N) ->
J = fac(N),
reply(Client, J),
handler0(Client);
...
Now here are three client dialogues:
Try number one:
telnet localhost 1288
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
erl. <--- this says we're to use the
eterm middle man
{fac,25}. <--- the query
15511210043330985984000000. <--- response
Number two:
telnet localhost 1288 <---- same port
Trying 127.0.0.1...
Connected to localhost.localdomain.
Escape character is '^]'.
<?xml version="1.0"?> <---- this says use XML
<tuple> <--- the query starts here
<element><atom>fac</atom></element>
<element><int>10</int></element>
</tuple>
<tuple> <--- the response starts here
<element>
<atom>reply</atom>
</element>
<element>
<int>3628800</int>
</element>
</tuple>
Number three (uses estream and an erlang client)
$ erl
Erlang (BEAM) emulator version 5.3 [source] [hipe]
1> {ok, S} = session:start("localhost", 1288).
{ok,<0.33.0>}
2> session:rpc(S, {fac,10}).
3628800
So the *same* server is polylingual - ie the port understands erlang
terms, XML expressions or Erlang binaries - and it's really easy to
write.
I'll be adding soap and xmlrpc real soon :-)
Cheers
/Joe
-module(middle_man_http).
%% 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.
-export([start/2, received/2, term_to_bin/1]).
-import(lists, [map/2, reverse/1]).
start(Vsn, Bin) ->
received(Bin, {header, []}).
received(Bin, Cont) ->
received(binary_to_list(Bin), Cont, []).
received(Data, {header, Buff}, L) ->
case scan_header(Data, Buff) of
{no, Buff1} ->
{reverse(L), {header, Buff1}};
{yes, Header, After} ->
got_header(Header, After, L)
end;
received(Data, {post, Buff, Len, X}, L) ->
case collect_chunk(Len, Data, Buff) of
{yes,PostData,After} ->
Args2 = parse_uri_args(PostData),
{Op,Vsn,URI,Args1,Env} = X,
Request = {Op,Vsn,URI,Args1++Args2,Env},
received(After, {header,[]}, [X|L]);
{no,Buff1, Len1} ->
State = {post, Buff1, Len1, X},
{reverse(L), State}
end.
got_header(Header, After, L) ->
%% We've got the header - parse it
case parse_header(Header) of
Result = {Op, ContentLen, Vsn, URI, Args, Env} ->
case ContentLen of
0 ->
received(After, {header, []}, [{Op,Vsn,URI,Args,Env}|L]);
_ ->
State = {post, [], ContentLen, {Op,Vsn,URI,Args,Env}},
received(After, State, L)
end;
Other ->
io:format("Oops ~p ~n", [Other]),
exit(debug)
end.
collect_chunk(0,New,Buf) -> {yes, reverse(Buf), New};
collect_chunk(N, [H|T], Buff) -> collect_chunk(N-1,T,[H|Buff]);
collect_chunk(N, [], Buff) -> {no, Buff, N}.
scan_header([$\n|T], [$\r,$\n,$\r|L]) -> {yes, reverse(L), T};
scan_header([H|T], L) -> scan_header(T, [H|L]);
scan_header([], L) -> {no, L}.
%%----------------------------------------------------------------------
parse_header(Str) ->
{ok, Fields} = regexp:split(Str, "\r\n"),
PRequest = parse_request(hd(Fields)),
%% Args = "KeyWord: Str" ..
PArgs = map(fun isolate_arg/1, tl(Fields)),
make_return_value({PRequest, PArgs}).
make_return_value({{Op,Vsn,{URI,Args}}, Env}) ->
{Op, content_length(Env), Vsn, URI, Args, Env}.
content_length([{"content-length",Str}|T]) ->
list_to_integer(Str);
content_length([_|T]) ->
content_length(T);
content_length([]) ->
0.
urlencoded2str([$%,Hi,Lo|T]) -> [decode_hex(Hi, Lo)|urlencoded2str(T)];
urlencoded2str([$+|T]) -> [$ |urlencoded2str(T)];
urlencoded2str([H|T]) -> [H|urlencoded2str(T)];
urlencoded2str([]) -> [].
isolate_arg(Str) -> isolate_arg(Str, []).
isolate_arg([$:,$ |T], L) -> {httpd_util:to_lower(reverse(L)), T};
isolate_arg([H|T], L) -> isolate_arg(T, [H|L]).
%% decode_hex %%
decode_hex(Hex1, Hex2) ->
hex2dec(Hex1)*16 + hex2dec(Hex2).
hex2dec(X) when X >=$0, X =<$9 -> X-$0;
hex2dec($A) -> 10;
hex2dec($B) -> 11;
hex2dec($C) -> 12;
hex2dec($D) -> 13;
hex2dec($E) -> 14;
hex2dec($F) -> 15;
hex2dec($a) -> 10;
hex2dec($b) -> 11;
hex2dec($c) -> 12;
hex2dec($d) -> 13;
hex2dec($e) -> 14;
hex2dec($f) -> 15.
parse_request(Str) ->
{ok, Args} = regexp:split(Str, " "),
case Args of
["POST", URI, Vsn] ->
{post, parse_vsn(Vsn) ,parse_uri(URI)};
["GET", URI, Vsn] ->
{get, parse_vsn(Vsn), parse_uri(URI)};
_ ->
oops
end.
parse_vsn("HTTP/1.0") -> {1,0};
parse_vsn(X) -> X.
%% A typical URI looks
%% like
%% URI = "/a/b/c?password=aaa&invisible=A+hidden+value"
parse_uri(URI) ->
case string:tokens(URI, "?") of
[Root] ->
{Root, []};
[Root, Args] ->
{Root, parse_uri_args(Args)}
end.
parse_uri_args(Args) ->
Args1 = string:tokens(Args, "&;"),
map(fun(KeyVal) ->
case string:tokens(KeyVal, "=") of
[Key, Val] ->
{urlencoded2str(Key), urlencoded2str(Val)};
[Key] ->
{urlencoded2str(Key), ""};
_ ->
io:format("Invalid str:~p~n",[KeyVal]),
{"error", "error"}
end
end, Args1).
term_to_bin({Headers, Data}) ->
B1 = list_to_binary(Data),
Len = size(B1),
Headers1 = [Headers,"Content-Length ",integer_to_list(Len),"\r\n\r\n"],
[Headers1, B1].
-module(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.
-export([start/2, received/2, term_to_bin/1]).
start(_, <<"erl.\r\n",B/binary>>) -> received(B, []);
start(_, <<"erl.\r",B/binary>>) -> received(B, []);
start(_, <<"erl.\n",B/binary>>) -> received(B, []);
start(_, _) -> exit(protocol).
received(Bin, Cont) ->
received(binary_to_list(Bin), Cont, []).
received(Str, Cont, L) ->
case erl_scan:tokens(Cont, Str, 1) of
{more, C1} ->
{lists:reverse(L), C1};
{done, Result, Rest} ->
case Result of
{ok, Toks, _} ->
case erl_parse:parse_term(Toks) of
{ok, Term} ->
io:format("sending:~p~n",[Term]),
received(Rest, [], [Term|L]);
_ ->
exit(bad_term)
end;
E ->
exit(bad_term)
end
end.
term_to_bin(Term) ->
io_lib:format("~p.\n",[Term]).
---- gen_middle man ---
-module(gen_middle_man).
%% 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.
-export([start/5]).
start(Args, Client, Socket, Bin, Mod) ->
erlang:monitor(process, Client),
{Msgs, Cont} = Mod:start(Args, Bin),
send_messages(Client, Msgs),
loop(Client, Socket, Cont, Mod).
send_messages(Client, []) -> void;
send_messages(Client, [H|T]) -> Client ! {self(), H}, send_messages(Client, T).
%% ---> {tcp,Socket,Bin}
%% ---> {tcp_closed, Socket}
%% ---> close
%% ---> {send, X}
%% ---> {'DOWN',_,_,_,_} (from client)
loop(Client, Socket, Cont, Mod) ->
receive
{tcp, Socket, Bin} ->
case Mod:received(Bin, Cont) of
{Msgs, Cont1} ->
send_messages(Client, Msgs),
loop(Client, Socket, Cont1, Mod)
end;
{tcp_closed, Socket} ->
%% revese the order (sneaky)
Client ! {closed, self()};
close ->
gen_tcp:close(Socket);
{send, Term} ->
B = Mod:term_to_bin(Term),
gen_tcp:send(Socket ,B),
loop(Client, Socket, Cont, Mod);
{'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