[erlang-questions] gen_tcp:recv problem

Matthias Lersch matthias.lersch@REDACTED
Tue Mar 10 22:33:48 CET 2015


Hi,

I have a problem with gen_tcp:recv...
The Body of the first call is missing but shows up at the second call 
causing a http_error. (full code is shown a the end of the mail)

I do a gen_tcp:connect/4 with the options
[list, {mode, list}, {reuseaddr, true}, {active, false}, {keepalive, 
true}, {packet, http}, {send_timeout, timer:seconds(2)}]

Then i send a HTTP POST request using gen_tcp:send/2 and go into a 
"receve loop" with gen_tcp:recv/3 until i get a {error,_}  (thats for 
debugging at the moment and recv timout is set to 10 seconds)

my problem is:
- On the first call (send) i get all the headers but no content.
- On the second call i get the content of the first call at the 
beginning of the response causing the http_error.


I'm sending the request to a node.js service, but the result is the same 
if i use a dummy apache service with the same response.
So i don't think its a problem with the Server I'm sending the request to.
The Body of the response (aka content) is always "OK"

The received data from the first call looks like this (there is no body 
with "OK", it ends with http_eoh and then a timeout after 10 seconds) :

  {ok,{http_response,{1,1},200,"OK"}}
  {ok,{http_header,0,"X-Powered-By",undefined,"Express"}}
  {ok,{http_header,42,'Content-Type',undefined,"text/html; charset=utf-8"}}
  {ok,{http_header,38,'Content-Length',undefined,"2"}}
  {ok,{http_header,3,'Date',undefined,"Tue, 10 Mar 2015 20:23:05 GMT"}}
  {ok,{http_header,2,'Connection',undefined,"keep-alive"}}
  {ok,http_eoh}
     ...after 10 seconds...
  {error,timeout}

The second call then returns this (notice the OKHTTP/1.1... in the first 
row, "OK" is the Body from the call before. It should be HTTP/1.1):

  {ok,{http_error,"OKHTTP/1.1 200 OK\r\n"}}
  {ok,{http_error,"X-Powered-By: Express\r\n"}}
  {ok,{http_error,"Content-Type: text/html; charset=utf-8\r\n"}}
  {ok,{http_error,"Content-Length: 2\r\n"}}
  {ok,{http_error,"Date: Tue, 10 Mar 2015 20:23:16 GMT\r\n"}}
  {ok,{http_error,"Connection: keep-alive\r\n"}}
  {ok,{http_error,"\r\n"}}
     ...after 10 seconds...
  {error,timeout}



here is the gen_server code im using for my tests.
i run the test like this from the erl command line
s:start_link().
s:work(1,1). % first call without body
s:work(1,1). % second call that fails

Thanky for your help!


%%%-------------------------------------------------------------------
%%% @author mlersch
%%% @copyright (C) 2015, <COMPANY>
%%% @doc
%%%
%%% @end
%%% Created : 20. Feb 2015 11:13
%%%-------------------------------------------------------------------
-module(s).
-author("mlersch").

%% API
-compile(export_all).

-behaviour(gen_server).

%% API
-export([
   start_link/0,
   send/2,
   reconnect/4]).

%% gen_server callbacks
-export([init/1,
   handle_call/3,
   handle_cast/2,
   handle_info/2,
   terminate/2,
   code_change/3]).

-define(SERVER, ?MODULE).

-record(state, {
   connection=false,
   host,
   port,
   timeout,
   opts
}).


%%%===================================================================
%%% API
%%%===================================================================

send(Processor, Data)->
   gen_server:call(?MODULE, {send, Processor, Data}).

info()->
   gen_server:call(?MODULE,info).

%%--------------------------------------------------------------------
%% @doc
%% Starts the server
%%
%% @end
%%--------------------------------------------------------------------
-spec(start_link() ->
   {ok, Pid :: pid()} | ignore | {error, Reason :: term()}).
start_link() ->
   process_flag(trap_exit, true),
   io:format("~p Start~n",[?MODULE]),
   gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%%===================================================================
%%% gen_server callbacks
%%%===================================================================

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Initializes the server
%%
%% @spec init(Args) -> {ok, State} |
%%                     {ok, State, Timeout} |
%%                     ignore |
%%                     {stop, Reason}
%% @end
%%--------------------------------------------------------------------
-spec(init(Args :: term()) ->
   {ok, State :: #state{}} | {ok, State :: #state{}, timeout() | 
hibernate} |
   {stop, Reason :: term()} | ignore).
init([]) ->

   State = #state{
     host = "localhost",
     port = 9010,
     timeout = timer:seconds(2),
     opts = [list, {mode, list}, {reuseaddr, true}, {active, true}, 
{keepalive, true}, {packet, http}, {send_timeout, timer:seconds(2)}]
   },

   reconnect(State),
   {ok, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling call messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_call(Request :: term(), From :: {pid(), Tag :: term()},
     State :: #state{}) ->
   {reply, Reply :: term(), NewState :: #state{}} |
   {reply, Reply :: term(), NewState :: #state{}, timeout() | hibernate} |
   {noreply, NewState :: #state{}} |
   {noreply, NewState :: #state{}, timeout() | hibernate} |
   {stop, Reason :: term(), Reply :: term(), NewState :: #state{}} |
   {stop, Reason :: term(), NewState :: #state{}}).

handle_call({send, Processor, Data}, From, State) ->

   % If we are offline, notify the caller
   case State#state.connection of

     {ok,Con}->

       Request =
         ["POST /" ++ Processor ++ " HTTP/1.1\r\n"
           ++ "Content-Length: " ++ 
integer_to_list(lists:flatlength(Data)) ++ "\r\n"
           ++ "User-Agent: Erlang Client\r\n"
           ++ "Content-Type: application/json\r\n"
           ++ "Host: " ++ State#state.host ++ "\r\n"
           ++ "Accept: */*\r\n"
           ++ "\r\n", [Data]],

         % Try sending the data
         case send_and_receive(Con, Request) of
           ok -> {reply, ok, State};

           % If sending fails...
           Error ->
             % ...Answer the Client with a error
             gen_server:reply(From, error),
             io:format("~p Error: ~p~n", [?MODULE, Error]),

             % ...Close the connection
             gen_tcp:close(Con),
             timer:sleep(500),

             % ...Spawn a Process that trys to reconnect.
             reconnect(State),

             % ...Update the connection state
             {reply, error, State#state{connection = false}}

         end;

       _ -> {reply, offline, State}
   end;

handle_call(info, _From, State) ->
   {reply, State, State};

handle_call(Request, _From, State) ->
   io:format("~p CALL: ~p~n", [?MODULE, Request]),
   {reply, ok, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling cast messages
%%
%% @end
%%--------------------------------------------------------------------
-spec(handle_cast(Request :: term(), State :: #state{}) ->
   {noreply, NewState :: #state{}} |
   {noreply, NewState :: #state{}, timeout() | hibernate} |
   {stop, Reason :: term(), NewState :: #state{}}).
handle_cast({connected, Connection}, State) ->
   io:format("~p Connected: ~p~n", [?MODULE, Connection]),
   {noreply, State#state{connection = Connection}};

handle_cast(Request, State) ->
   io:format("~p CAST: ~p~n", [?MODULE, Request]),
   {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Handling all non call/cast messages
%%
%% @spec handle_info(Info, State) -> {noreply, State} |
%%                                   {noreply, State, Timeout} |
%%                                   {stop, Reason, State}
%% @end
%%--------------------------------------------------------------------
-spec(handle_info(Info :: timeout() | term(), State :: #state{}) ->
   {noreply, NewState :: #state{}} |
   {noreply, NewState :: #state{}, timeout() | hibernate} |
   {stop, Reason :: term(), NewState :: #state{}}).

handle_info({tcp_closed, _}, State) ->
   io:format("~p Connection closed!~n", [?MODULE]),
   reconnect(State),
   {noreply, State#state{connection = false}};

handle_info(Info, State) ->
   io:format("~p INFO: ~p~n", [?MODULE, Info]),
   {noreply, State}.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any
%% necessary cleaning up. When it returns, the gen_server terminates
%% with Reason. The return value is ignored.
%%
%% @spec terminate(Reason, State) -> void()
%% @end
%%--------------------------------------------------------------------
-spec(terminate(Reason :: (normal | shutdown | {shutdown, term()} | 
term()),
     State :: #state{}) -> term()).
terminate(Reason, _State) ->
   io:format("~p TERMINATE: ~p~n", [?MODULE, Reason]),
   ok.

%%--------------------------------------------------------------------
%% @private
%% @doc
%% Convert process state when code is changed
%%
%% @spec code_change(OldVsn, State, Extra) -> {ok, NewState}
%% @end
%%--------------------------------------------------------------------
-spec(code_change(OldVsn :: term() | {down, term()}, State :: #state{},
     Extra :: term()) ->
   {ok, NewState :: #state{}} | {error, Reason :: term()}).
code_change(_OldVsn, State, _Extra) ->
   {ok, State}.

%%%===================================================================
%%% Internal functions
%%%===================================================================



%% This helper function sends the request to the server and parses the 
response
%% to look in it for a status 200 code. If it finds a 200 we return ok 
otherwise error.
%% by default we use active connections, when we are in idle to get info 
messages about
%% deconnections into handle_info/2
%% but here we must change to passive mode, so we are able to parse the 
response of this
%% send operations. disconnect messages wont show up in passive mode but 
will pop up when
%% we switch bach to active mode again (even if the ocured during 
passive mode).
send_and_receive(Con, Request)->
   inet:setopts(Con, [{active, false}]),
   Response =
   case gen_tcp:send(Con, Request) of
     ok -> parse_response(Con);
     E -> E
   end,
   inet:setopts(Con, [{active, true}]),
   Response.


parse_response(Con)->parse_response(Con, error).
parse_response(Con, State)->
   R = gen_tcp:recv(Con, 0, timer:seconds(10)),
   io:format("PR: ~p~n",[R]),
   case R of
     {ok,{http_response,_,200,_}} -> parse_response(Con, ok);
     {ok,http_eoh} ->
       io:format("PRX: ~p~n",[gen_tcp:recv(Con, 0, timer:seconds(10))]),
       State;
     {error, E} -> {error, E};
     _ -> parse_response(Con, State)
   end.

reconnect(State)->
   spawn_link(?MODULE, reconnect, [self(), State, calendar:local_time(), 
1]).

reconnect(Pid, State, Started, Tries)->
   io:format("~p Connecting to host ~p on port ~p~n",[?MODULE, 
State#state.host, State#state.port]),
   case gen_tcp:connect(State#state.host, State#state.port, 
State#state.opts, State#state.timeout) of
     {ok, Connection} ->
       io:format("~p Reconnect changing controlling process: 
~p~n",[?MODULE, gen_tcp:controlling_process(Connection, Pid)]),
       gen_server:cast(?MODULE, {connected, {ok, Connection}});
     E -> io:format("~p Reconnection failed ~p (Tried it ~p times since 
~p). Waiting 2sec. till retry!~n", [?MODULE, E, Tries, Started]),
          timer:sleep(timer:seconds(2)),
          reconnect(Pid, State, Started, Tries+1)
   end.



%%%===================================================================
%%% Debug stuff
%%%===================================================================
loop(Max)->
   lists:foreach(fun(_)->
     timer:sleep(100),
       spawn(fun()->
         send("processgaac", 
"{\"_id\":{\"$oid\":\"53B382F4BE53EE3946137430\"},\"sid\":\"07101a57ce294750bb36fa72e2ffab5d\",\"eid\":1566,\"cd\":{\"$date\":1404273396000},\"t\":\"gaac\",\"p\":{\"ua\":\"Mozilla/5.0 
(Windows NT 6.3; WOW64; rv:30.0) Gecko/20100101 
Firefox/30.0\",\"pt\":\"search_result\",\"rt\":\"internal\",\"gs\":[{\"g\":\"08006309\",\"p\":1650},{\"g\":\"04431464\",\"p\":1429},{\"g\":\"03932773\",\"p\":696},{\"g\":\"07563999\",\"p\":825},{\"g\":\"09100275\",\"p\":5089},{\"g\":\"06426800\",\"p\":2240},{\"g\":\"10417853\",\"p\":1180},{\"g\":\"02096458\",\"p\":1365},{\"g\":\"08798173\",\"p\":3897},{\"g\":\"03531850\",\"p\":1243},{\"g\":\"00188357\",\"p\":412},{\"g\":\"09296203\",\"p\":2495},{\"g\":\"05566143\",\"p\":1498},{\"g\":\"08006456\",\"p\":1467},{\"g\":\"04347717\",\"p\":2199},{\"g\":\"08006410\",\"p\":4499},{\"g\":\"09374110\",\"p\":3897},{\"g\":\"04187656\",\"p\":691},{\"g\":\"05010397\",\"p\":1075},{\"g\":\"07727923\",\"p\":493}],\"cats\":[\"1100068\",\"1100068\",\"1100068\",\"1100071\",\"1100043\",\"1100015\",\"1100015\",\"1100029\",\"1100355\",\"1100355\",\"1100196\",\"1100114\",\"1100013\",\"1100021\",\"1100021\",\"1100021\",\"1100021\",\"1100021\",\"1100170\",\"1571\",\"1100014\",\"1100117\",\"1100233\",\"1100107\",\"1100096\",\"1100467\",\"1100094\",\"1100094\",\"1100013\",\"1100014\",\"1100014\",\"1100029\",\"1100015\",\"1100015\",\"1100017\",\"1100463\",\"1100034\",\"1100109\",\"1440\",\"1100114\",\"1100109\",\"1440\",\"1100177\",\"1100013\",\"1100191\",\"1100094\",\"1100191\",\"1100029\",\"1100029\",\"1100048\",\"1100043\",\"1100016\",\"1100210\",\"1100016\",\"1100463\",\"1226\"],\"catsc\":[\"AR02AA16\",\"79680\",\"22172\",\"30365\",\"AR01AC03\",\"24009\",\"79658\",\"66403\",\"30365\",\"22294\",\"BX01\",\"71445\",\"81085\",\"24510\",\"30365\",\"22294\",\"AD11AX01\",\"82323\",\"30365\",\"22294\",\"30365\",\"22294\",\"BB03Z\",\"81644\",\"81629\",\"25123\",\"22183\",\"22182\",\"30365\",\"30365\",\"BB01A\",\"24577\",\"66407\",\"30365\",\"AD01AE16\",\"30365\",\"AD08AC\",\"24512\",\"22206\",\"30365\",\"AN05CX\",\"81736\",\"22192\",\"30365\",\"BB03Z\",\"77677\",\"66409\",\"66407\",\"30365\",\"BX01\",\"71446\",\"79609\",\"22171\",\"30365\",\"BC01D\",\"30365\",\"BB03D\",\"82323\",\"25125\",\"22204\",\"30365\",\"22294\",\"AD11AX01\",\"82997\",\"82323\",\"25125\",\"22204\",\"32455\",\"30365\",\"22294\",\"BB03Z\",\"24783\",\"81736\",\"81083\",\"81082\",\"81087\",\"39604\",\"30365\",\"AS01GX07\",\"24009\",\"22220\",\"22196\",\"79610\",\"30365\",\"22294\",\"BC01D\",\"24728\",\"30365\",\"23432\",\"30365\"],\"catsx\":[\"30365\",\"30365\",\"22294\",\"30365\",\"22294\",\"82323\",\"30365\",\"22294\",\"30365\",\"22294\",\"30365\",\"30365\",\"30365\",\"30365\",\"30365\",\"30365\",\"77677\",\"30365\",\"30365\",\"30365\",\"82323\",\"30365\",\"22294\",\"82997\",\"82323\",\"30365\",\"22294\",\"39604\",\"30365\",\"30365\",\"22294\",\"30365\",\"23432\",\"30365\"],\"fpid\":\"/aktionsartikel\",\"lt\":\"list\",\"lp\":1},\"r\":{\"s\":[{\"slid\":\"high\",\"cid\":2498,\"scr\":100000000,\"f\":1}]},\"ti\":200000000,\"to\":false,\"at\":53596,\"dv\":8,\"agg\":[],\"pad\":\"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\"}")
       end)
     end,
     lists:seq(1,Max)).

work(Loops, Max)->
   lists:foreach(fun(_)-> spawn(s,loop,[Max]) end,lists:seq(1,Loops)).

-- 
________________________________________
Matthias Lersch, Senior Software Architect
Tel.: +49 69 972 69 197; Fax: +49 69 972 69 204; Email: 
matthias.lersch@REDACTED
Kairion GmbH, Gutleutstraße 30, D-60329 Frankfurt
Internet: http://www.kairion.de


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://erlang.org/pipermail/erlang-questions/attachments/20150310/693ee4cf/attachment.htm>


More information about the erlang-questions mailing list