Recently, I was working on a small Erlang script to fetch the content of a web page using the built-in httpc
module. Everything seemed fine at first until Erlang randomly threw this strange warning at me:
Received unexpected tcp data on #Port<0.4432>
At first glance, it looked like a low level TCP issue. But I eventually figured out what was really happening — and in this post, I’ll walk you through it so you don’t spend hours scratching your head like I did!
My Original Code
Here’s the initial function I wrote:
geturl(Url) ->
{ok, RequestId} = httpc:request(
get,
{Url, [{"User-Agent", "Opera/9.80 (Windows NT 6.1; U; ru) Presto/2.8.131 Version/11.10"}]},
[],
[{sync, false}]
),
M = receive
{http, {RequestId, {_HttpOk, _ResponseHeaders, _Body}}} ->
ok;
after 20000 ->
not_ok
end,
io:format("httprequest to ~p: ~p~n",[Url, M]).
Sometimes it printed:
httprequest to "http://example.com": ok
But other times, BAM:
Received unexpected tcp data on #Port<0.4432>
What Was Actually Going Wrong?
After digging deeper, I figured out the core issue:
Root Cause | Explanation |
---|---|
I used {sync, false} | This makes the HTTP request asynchronous |
I only consumed one response | But the TCP connection kept sending more data |
My receive block stopped listening | The remaining messages had nowhere to go |
httpc_manager logged it as “unexpected tcp data” | Because the port was still active |
So essentially… the HTTP process was still alive, but I stopped caring about it too early. Erlang politely warned me — in the loudest possible way.
Two Solid Fixes
Depending on your use case, you can avoid the warning in two ways:
Approach | Fix | Best When |
---|---|---|
Synchronous request | Use {sync, true} or simply omit it | Quick, one-off HTTP requests |
Consume all async messages | Keep listening in a loop | Long-running async logic |
Improved Version
geturl_sync(Url) ->
case httpc:request(get, {Url, []}, [], []) of
{ok, {{_HttpVersion, 200, _ReasonPhrase}, _Headers, Body}} ->
io:format("Success fetching ~p~n~s~n", [Url, Body]);
{ok, {{_HttpVersion, StatusCode, Reason}, _Headers, _Body}} ->
io:format("Request failed with status ~p: ~p~n", [StatusCode, Reason]);
{error, Reason} ->
io:format("Request error: ~p~n", [Reason])
end.
Simple. Clean. No weird TCP ghosts haunting your logs.
Improved Async Version
geturl_async(Url) ->
{ok, RequestId} = httpc:request(get, {Url, []}, [], [{sync, false}]),
wait_for_response(RequestId).
wait_for_response(RequestId) ->
receive
{http, {RequestId, {ok, _Headers, Body}}} ->
io:format("Success: ~s~n", [Body]),
wait_for_more();
{http, {RequestId, {error, Reason}}} ->
io:format("Error: ~p~n", [Reason]),
wait_for_more();
After ->
io:format("Unhandled message: ~p~n", [After]),
wait_for_more()
after 20000 ->
io:format("Timeout waiting for response~n")
end.
wait_for_more() ->
receive
{http, Msg} ->
io:format("Additional message: ~p~n", [Msg]),
wait_for_more()
after 1000 ->
io:format("Done processing async responses~n")
end.
This version properly flushes all TCP messages, so nothing is left dangling.
Final Thought
Erlang didn’t throw that cryptic TCP warning because something was broken it was actually warning me that I wasn’t finishing what I started. Once I understood that asynchronous httpc
calls can leave unread TCP messages behind, the fix became obvious: either go fully synchronous or handle async responses properly.