Skip to content

Commit bdfea6d

Browse files
committed
Merge pull request #1370 from pguyot/w47/http-server-fixes
Fix issues in http_server * Fix a bug where http_server would fail to send a reply with integers. * Fix another bug where http_server crashed with badmatch if connection was closed, including because of previous bug. * Also fix socket_driver to return `{gen_tcp, closed}` when socket is closed on Linux instead of `{gen_tcp, {recv, 104}}` Fixes #1366 These changes are made under both the "Apache 2.0" and the "GNU Lesser General Public License 2.1 or later" license terms (dual license). SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
2 parents 11b9c1b + 652c074 commit bdfea6d

File tree

6 files changed

+76
-6
lines changed

6 files changed

+76
-6
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
- ESP32: fix `gpio:init/1` on GPIO >= 32
1313
- Adding missing check, passing a non numeric argument to a function expecting a floating point
1414
might lead to a crash in certain situations.
15+
- Fixed several bugs in `http_server` (#1366)
16+
- Fixed generic\_unix `socket_driver` to return `{gen_tcp, closed}` when socket is closed on Linux instead of `{gen_tcp, {recv, 104}}`
1517

1618
## [0.6.5] - 2024-10-15
1719

libs/eavmlib/src/http_server.erl

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,16 @@ find_route(Method, Path, [{Target, Mod, _Opts} | T]) ->
7373
end.
7474

7575
reply(StatusCode, ReplyBody, Conn) ->
76-
{ok, Conn} = reply(
76+
{ok, NewConn} = reply(
7777
StatusCode, ReplyBody, [<<"Content-Type: text/html\r\nConnection: close\r\n">>], Conn
7878
),
79-
Socket = proplists:get_value(socket, Conn),
79+
Socket = proplists:get_value(socket, NewConn),
8080
gen_tcp:close(Socket),
81-
ClosedConn = [{closed, true} | Conn],
81+
ClosedConn =
82+
case proplists:get_value(closed, NewConn) of
83+
undefined -> [{closed, true} | NewConn];
84+
true -> NewConn
85+
end,
8286
{ok, ClosedConn}.
8387

8488
reply(StatusCode, ReplyBody, ReplyHeaders, Conn) ->
@@ -107,13 +111,15 @@ reply(StatusCode, ReplyBody, ReplyHeaders, Conn) ->
107111
{ok, ClosedConn}
108112
end.
109113

110-
send_reply(Socket, [Chunk | Tail]) ->
114+
send_reply(Socket, [Chunk | Tail]) when is_list(Chunk) orelse is_binary(Chunk) ->
111115
case gen_tcp:send(Socket, Chunk) of
112116
ok -> send_reply(Socket, Tail);
113117
{error, _} = ErrorTuple -> ErrorTuple
114118
end;
115119
send_reply(_Socket, []) ->
116-
ok.
120+
ok;
121+
send_reply(Socket, IOData) ->
122+
gen_tcp:send(Socket, IOData).
117123

118124
code_to_status_string(200) ->
119125
<<"200 OK">>;

src/platforms/generic_unix/lib/socket_driver.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -790,7 +790,7 @@ static EventListener *passive_recv_callback(GlobalContext *glb, EventListener *b
790790
return NULL;
791791
}
792792
SocketDriverData *socket_data = (SocketDriverData *) ctx->platform_data;
793-
if (len == 0) {
793+
if (len == 0 || (len < 0 && errno == ECONNRESET)) {
794794
// {Ref, {error, closed}}
795795
BEGIN_WITH_STACK_HEAP(12, heap);
796796
term pid = listener->pid;

tests/libs/eavmlib/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ set(ERLANG_MODULES
2626
test_dir
2727
test_file
2828
test_ahttp_client
29+
test_http_server
2930
test_port
3031
test_timer_manager
3132
)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
%
2+
% This file is part of AtomVM.
3+
%
4+
% Copyright 2024 Paul Guyot <[email protected]>
5+
%
6+
% Licensed under the Apache License, Version 2.0 (the "License");
7+
% you may not use this file except in compliance with the License.
8+
% You may obtain a copy of the License at
9+
%
10+
% http://www.apache.org/licenses/LICENSE-2.0
11+
%
12+
% Unless required by applicable law or agreed to in writing, software
13+
% distributed under the License is distributed on an "AS IS" BASIS,
14+
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
% See the License for the specific language governing permissions and
16+
% limitations under the License.
17+
%
18+
% SPDX-License-Identifier: Apache-2.0 OR LGPL-2.1-or-later
19+
%
20+
21+
-module(test_http_server).
22+
-export([test/0, handle_req/3]).
23+
24+
test() ->
25+
ok = test_chunk().
26+
27+
test_chunk() ->
28+
Router = [
29+
{"*", ?MODULE, []}
30+
],
31+
Pid = http_server:start_server(8080, Router),
32+
{ok, Conn} = connect_client(5),
33+
{ok, Conn2, _Ref} = ahttp_client:request(Conn, <<"GET">>, <<"/">>, [], undefined),
34+
ok = loop_passive(Conn2, []),
35+
exit(Pid, kill),
36+
ok.
37+
38+
connect_client(Retries) when Retries > 0 ->
39+
ConnectResult = ahttp_client:connect(http, "localhost", 8080, [{active, false}]),
40+
case ConnectResult of
41+
{ok, Conn} ->
42+
{ok, Conn};
43+
{error, _} = ConnectError ->
44+
io:format("Request failed: ~p~n", [ConnectError]),
45+
connect_client(Retries - 1)
46+
end.
47+
48+
handle_req("GET", [], Conn) ->
49+
Body = [34, <<"hello">>],
50+
http_server:reply(200, Body, Conn).
51+
52+
% http_server doesn't send Content-Length, so ahttp_client doesn't know when it's done and reports closed connection
53+
loop_passive(Conn, AccResp) ->
54+
case ahttp_client:recv(Conn, 0) of
55+
{ok, UpdatedConn, Responses} ->
56+
loop_passive(UpdatedConn, lists:reverse(Responses, AccResp));
57+
{error, {gen_tcp, closed}} ->
58+
[{data, _DataRef, <<"\"hello">>}, {status, _StatusRef, 200}] = AccResp,
59+
ok
60+
end.

tests/libs/eavmlib/tests.erl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ start() ->
2626
etest:test([
2727
test_dir,
2828
test_file,
29+
test_http_server,
2930
test_port,
3031
test_timer_manager,
3132
test_ahttp_client

0 commit comments

Comments
 (0)