お遊び proxy server を書いてみました。

Erlang で echo server と echo client は簡単に書けるのですが、
proxy server はあまり例が無いかなぁと思いまして、ちょこっと書いてみました。
UDP パケットなので、たいしたことはしていませんし例外処理もしていません。

お遊びコードですので、お気楽に。

まずどのような動きをするのか。
echo client はもちろん、ある文字列を投げてそれを受け取るだけです。
ここでは I love python. というメッセージを投げています。
もちろん echo server へ投げているワケですから返ってくるのは I love python です。

Eshell V5.6.4  (abort with ^G)
1> c(echo_client).
{ok,echo_client}
2> echo_client:start().
echo client [client -> proxy]: I love python.
echo client [proxy -> client]: I love python.

ここでは echo server へ行く前に proxy server を経由します。
proxy server では love という文字列を hate に変換して echo server へ送る機能を付けてみました。単に re モジュール使ってみたかっただけです。

Eshell V5.6.4  (abort with ^G)
1> c(proxy_server).
{ok,proxy_server}
2> proxy_server:start().
proxy server [client -> proxy]: I love python.
proxy server [proxy -> server]: I hate python.
proxy server [server -> proxy]: I hate python.
proxy server [proxy -> client]: I love python.

さて echo server はただ受け取った値をそのまま返すだけなので、 I hate Python を返します。しかし proxy server は受け取った値に hate が入っていた場合は hate を love に変換して echo client へ送ります。

Eshell V5.6.4  (abort with ^G)
1> c(echo_server).
{ok,echo_server}
2> echo_server:start().
echo server [proxy -> server]: I hate python.
echo server [server -> proxy]: I hate python.

Proxy サーバを実装するのが如何にだるいかを伝えるためのエントリです:-P

以下大してキレイでもないソース。

proxy.hrl

-define(ECHO_SERVER_HOST, "127.0.0.1").
-define(ECHO_SERVER_PORT, 12345).

-define(PROXY_SERVER_HOST, "127.0.0.1").
-define(PROXY_SERVER_PORT, 54321).

echo_client.erl

-module(echo_client).
-export([start/0]).

-include("proxy.hrl").

start() ->
  echo_client("I love python.").

echo_client(Packet) ->
  {ok, Socket} = gen_udp:open(0, [list]),
  io:format("echo client [client -> proxy]: ~s\n", [Packet]),
  gen_udp:send(Socket, ?PROXY_SERVER_HOST, ?PROXY_SERVER_PORT, Packet),
  receive
    {udp, Socket, _, _, Packet2} ->
      io:format("echo client [proxy -> client]: ~s\n", [Packet2])
  after 2000 ->
    error
  end.

echo_server.erl

-module(echo_server).
-export([start/0]).

-include("proxy.hrl").

start() ->
  echo_server(?ECHO_SERVER_PORT).

echo_server(Port) ->
  {ok, Socket} = gen_udp:open(Port, [list]),
  loop(Socket).

loop(Socket) ->
  receive
    {udp, Socket, Host, Port, Packet} ->
      spawn(fun() -> reply(Socket, Host, Port, Packet) end),
      loop(Socket)
  end.

reply(Socket, Host, Port, Packet) ->
  io:format("echo server [proxy -> server]: ~s\n", [Packet]),
  gen_udp:send(Socket, Host, Port, Packet),
  io:format("echo server [server -> proxy]: ~s\n", [Packet]).

proxy_server.erl

-module(proxy_server).
-export([start/0]).

-include("proxy.hrl").

start() ->
  proxy_server(?PROXY_SERVER_PORT).

proxy_server(Port) ->
  {ok, Socket} = gen_udp:open(Port, [list]),
  loop(Socket).

loop(Socket) ->
  receive
    {udp, Socket, Host, Port, Packet} ->
      spawn(fun() -> forward({Socket, Host, Port, Packet}) end),
      loop(Socket)
  end.

alter_packet(Packet, Subject, Replacement) ->
  {ok, MP} = re:compile(Subject, [caseless]),
  re:replace(Packet, MP, Replacement).

forward({Socket, Host, Port, Packet}) ->
  io:format("proxy server [client -> proxy]: ~s\n", [Packet]),
  {ok, Socket2} = gen_udp:open(0, [list]),
  AlterPacket = alter_packet(Packet, "love", "hate"),
  gen_udp:send(Socket2, ?ECHO_SERVER_HOST, ?ECHO_SERVER_PORT, AlterPacket),
  io:format("proxy server [proxy -> server]: ~s\n", [AlterPacket]),
  receive
    {udp, _, _, _, Packet2} ->
      io:format("proxy server [server -> proxy]: ~s\n", [Packet2]),
      AlterPacket2 = alter_packet(Packet2, "hate", "love"),
      gen_udp:send(Socket, Host, Port, AlterPacket2),
      io:format("proxy server [proxy -> client]: ~s\n", [AlterPacket2])
  after 2000 ->
    error
  end.

うわ、くだらねー。と思っていただけたら幸いです:-)

追記

re:compile 毎回やってるのかなり無駄な気がしてきた ... 。