Wednesday, April 27, 2016

Erlang http response emulator

Recently I had to implement a "transparent" proxy between Tibco and Blackberry. I thought I'd share the erlang module I wrote to emulate responses from BB as you can't really expect from BB server to give you error responses or timeouts so you can test your proxy part. Here it is:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
-module(httpemulator).
-author("stanislavdvoychenko").

-export([client/0, server/0,start/0,accept/1,enter_loop/1,loop/1]).

client() ->
  {ok, Socket} =  gen_tcp:connect("localhost", 4001,[list, {packet, 0}]),
  ok = gen_tcp:send(Socket, "packet"),
  receive
    {tcp,Socket,String} ->
      io:format("Client received = ~p~n",[String]),
      io:format("Client result = ~p~n",[String]),
      gen_tcp:close(Socket)
  after 1000 ->
    exit
  end.

server() ->
  Pid = spawn(fun()-> start() end),
  Pid.

start() ->
  io:format("Started Server:~n"),
  {ok, Socket} = gen_tcp:listen(4001, [binary, {packet, 0},{reuseaddr, true},{active, false}]),
  accept(Socket).

accept(ListenSocket) ->
  io:format("Accept Server:~n"),
  case gen_tcp:accept(ListenSocket) of
    {ok, Socket} ->
      Pid = spawn(fun() ->
        io:format("Connection accepted ~n", []),
        enter_loop(Socket)
                  end),
      io:format("Pid ~p~n",[Pid]),
      gen_tcp:controlling_process(Socket, Pid),
      Pid ! ack,
      accept(ListenSocket);
    Error ->
      exit(Error)
  end.

enter_loop(Socket) ->
  %% make sure to acknowledge owner rights transmission finished
  receive ack -> ok end,
  loop(Socket).

loop(Socket) ->
  io:format("Loop Server:~n"),
  case gen_tcp:recv(Socket, 0) of
    {ok, Data} ->
          io:format("Server got data = ~s~n", [Data]),
          Match = re:run(Data, ".*Emulator-Command:\s(.+)\r", [{capture, [1],list}]),
      case Match of
        {match, ["timeout"]} -> Response = timeout("timeout");
        {match, ["protocolviolation"]} -> Response = protocol_violation("Omitting carriage return, this should cause the protocol violation");
        {match, ["httperror"]} -> Response = http_error("500", "Internal Server Error");
        _ -> Response = response("testing")
      end,

io:format("Match: ~p~n", [Match]),
          gen_tcp:send(Socket, Response);
          %%loop(Socket);
    {error, Reason} ->
      io:format("Error on socket ~p reason: ~p~n", [Socket, Reason]),
      gen_tcp:close(Socket)
  end.

response(Str) ->
  B = iolist_to_binary(Str),
  iolist_to_binary(
    io_lib:fwrite(
      "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nContent-Length: ~p\r\n\r\n~s",
      [size(B), B])).

timeout(Str) ->
  timer:sleep(1000000000),
  B = iolist_to_binary(Str),
  iolist_to_binary(
    io_lib:fwrite(
      "HTTP/1.0 200 OK\r\nContent-Type: text/html\r\nContent-Length: ~p\r\n\r\n~s",
      [size(B), B])).

protocol_violation(Str) ->
  B = iolist_to_binary(Str),
  iolist_to_binary(
    io_lib:fwrite(
      "HTTP/1.0 200 OK\nContent-Type: text/html\nContent-Length: ~p\n\n~s",
      [size(B), B])).

http_error(Code, Str) ->
  B = iolist_to_binary(Str),
  iolist_to_binary(
    io_lib:fwrite(
      "HTTP/1.0 ~s ~s\r\nContent-Type: text/html\r\nContent-Length: ~p\r\n\r\n~s",
      [Code, B, size(B), B])).

This Erlang module expects you to sent an extra http header Emulator-Command: * where * can be:

httperror -> returns 500 and Internal Server Error
timeout -> runs a long sleep on Erlang side, causes timeout on a proxy side
protocolviolation -> As I built the proxy in ,net WCF it was funny to see that MS takes only \n in the http headers as a protocol violation, so \r\n is required to keep MS happy.

anything else there or omitting this header will just return proper http response with "testing" body.

Disclaimer. I'm only starting to learn Erlang so please take the code with caution of course!

To compile in the erlang shell:

c(httpemulator).

To start emulator server:

httpemulator:server().

To end the emulator process:

exit(pid(0,96,0), old).

Pid is returned at the server start.

Hope it can be useful :).

No comments: