Skip to content

Commit 6cce819

Browse files
committed
Add end-to-end HTTPS tests
The eunit fixture gains a TLS listener next to the TCP one, sharing the request handling via a transport parameter. Certs are generated at startup with public_key:pkix_test_data/1 (secp256r1/sha256 -- the helper's default secp112r2/sha1 key is not negotiable by a modern TLS client), so no cert files or test deps are needed. The subtests are parameterized by base URL and now also run over https on shackle_ssl, plus shackle_ssl_socket gated on OTP 28+, mirroring the shackle_socket gate. The client connects with verify_none since the cert is self-signed. The option() type and README only admitted shackle_socket and shackle_tcp for protocol, even though https pools honor the option since bf913db; both now list all four transports. Closes #28
1 parent 30e62be commit 6cce819

4 files changed

Lines changed: 207 additions & 111 deletions

File tree

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,11 +93,11 @@ ok
9393
</tr>
9494
<tr>
9595
<td>protocol</td>
96-
<td>shackle_socket | shackle_tcp</td>
97-
<td>shackle_tcp</td>
98-
<td>shackle transport for http pools (shackle_socket runs on the
99-
socket NIF and needs OTP 28+); https pools always use
100-
shackle_ssl</td>
96+
<td>shackle_socket | shackle_ssl | shackle_ssl_socket |
97+
shackle_tcp</td>
98+
<td>shackle_tcp (http) / shackle_ssl (https)</td>
99+
<td>shackle transport; the socket variants run on the socket NIF
100+
and need OTP 28+</td>
101101
</tr>
102102
<tr>
103103
<td>reconnect</td>

include/buoy.hrl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
{max_requests, pos_integer() | infinity} |
4949
{pool_size, pos_integer()} |
5050
{pool_strategy, random | round_robin} |
51-
{protocol, shackle_socket | shackle_tcp} |
51+
{protocol, shackle_socket | shackle_ssl |
52+
shackle_ssl_socket | shackle_tcp} |
5253
{reconnect, boolean()} |
5354
{reconnect_time_max, pos_integer() | infinity} |
5455
{reconnect_time_min, pos_integer()} |

test/buoy_http_server.erl

Lines changed: 89 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77
]).
88

99
-define(PORT, 8080).
10+
-define(PORT_SSL, 8443).
11+
-define(LISTEN_OPTIONS, [
12+
binary,
13+
{active, false},
14+
{backlog, 128},
15+
{packet, http_bin},
16+
{reuseaddr, true}
17+
]).
1018

1119
%% public
1220
connection_count() ->
@@ -35,74 +43,94 @@ stop() ->
3543
init(Parent) ->
3644
register(?MODULE, self()),
3745
persistent_term:put({?MODULE, connections}, counters:new(1, [])),
38-
{ok, LSocket} = gen_tcp:listen(?PORT, [
39-
binary,
40-
{active, false},
41-
{backlog, 128},
42-
{packet, http_bin},
43-
{reuseaddr, true}
44-
]),
46+
{ok, _} = application:ensure_all_started(ssl),
47+
{ok, LSocket} = gen_tcp:listen(?PORT, ?LISTEN_OPTIONS),
48+
%% the default pkix_test_data key (secp112r2, sha1) is not
49+
%% negotiable by a modern TLS client
50+
KeyOpts = [{key, {namedCurve, secp256r1}}, {digest, sha256}],
51+
SslOptions = public_key:pkix_test_data(#{root => KeyOpts,
52+
peer => KeyOpts}),
53+
{ok, LSocketSsl} = ssl:listen(?PORT_SSL, ?LISTEN_OPTIONS ++ SslOptions),
54+
spawn_link(fun () -> accept_ssl(LSocketSsl) end),
4555
Parent ! {self(), started},
4656
accept(LSocket).
4757

4858
accept(LSocket) ->
4959
{ok, Socket} = gen_tcp:accept(LSocket),
5060
counters:add(persistent_term:get({?MODULE, connections}), 1, 1),
5161
Pid = spawn_link(fun () ->
52-
receive go -> connection(Socket) end
62+
receive go -> connection(gen_tcp, Socket) end
5363
end),
5464
ok = gen_tcp:controlling_process(Socket, Pid),
5565
Pid ! go,
5666
accept(LSocket).
5767

58-
connection(Socket) ->
59-
case gen_tcp:recv(Socket, 0) of
68+
accept_ssl(LSocket) ->
69+
{ok, TSocket} = ssl:transport_accept(LSocket),
70+
counters:add(persistent_term:get({?MODULE, connections}), 1, 1),
71+
Pid = spawn_link(fun () ->
72+
receive
73+
go ->
74+
case ssl:handshake(TSocket) of
75+
{ok, Socket} ->
76+
connection(ssl, Socket);
77+
{error, _} ->
78+
ok
79+
end
80+
end
81+
end),
82+
ok = ssl:controlling_process(TSocket, Pid),
83+
Pid ! go,
84+
accept_ssl(LSocket).
85+
86+
connection(Transport, Socket) ->
87+
case recv(Transport, Socket, 0) of
6088
{ok, {http_request, Method, {abs_path, Path}, _Version}} ->
61-
ContentLength = headers(Socket, 0),
62-
Body = body(Socket, ContentLength),
63-
respond(Socket, Method, Path, Body),
64-
connection(Socket);
89+
ContentLength = headers(Transport, Socket, 0),
90+
Body = body(Transport, Socket, ContentLength),
91+
respond(Transport, Socket, Method, Path, Body),
92+
connection(Transport, Socket);
6593
{ok, _} ->
66-
gen_tcp:close(Socket);
94+
close(Transport, Socket);
6795
{error, _} ->
68-
gen_tcp:close(Socket)
96+
close(Transport, Socket)
6997
end.
7098

71-
headers(Socket, ContentLength) ->
72-
case gen_tcp:recv(Socket, 0) of
99+
headers(Transport, Socket, ContentLength) ->
100+
case recv(Transport, Socket, 0) of
73101
{ok, {http_header, _, 'Content-Length', _, Value}} ->
74-
headers(Socket, binary_to_integer(Value));
102+
headers(Transport, Socket, binary_to_integer(Value));
75103
{ok, {http_header, _, _, _, _}} ->
76-
headers(Socket, ContentLength);
104+
headers(Transport, Socket, ContentLength);
77105
{ok, http_eoh} ->
78106
ContentLength
79107
end.
80108

81-
body(_Socket, 0) ->
109+
body(_Transport, _Socket, 0) ->
82110
<<>>;
83-
body(Socket, ContentLength) ->
84-
ok = inet:setopts(Socket, [{packet, raw}]),
85-
{ok, Body} = gen_tcp:recv(Socket, ContentLength),
86-
ok = inet:setopts(Socket, [{packet, http_bin}]),
111+
body(Transport, Socket, ContentLength) ->
112+
ok = setopts(Transport, Socket, [{packet, raw}]),
113+
{ok, Body} = recv(Transport, Socket, ContentLength),
114+
ok = setopts(Transport, Socket, [{packet, http_bin}]),
87115
Body.
88116

89-
respond(Socket, Method, <<"/1">>, _Body) ->
90-
reply(Socket, Method, <<"Hello world!">>);
91-
respond(Socket, Method, <<"/2">>, _Body) ->
92-
reply(Socket, Method, binary:copy(<<"Hello world!">>, 1000));
93-
respond(Socket, Method, <<"/3">>, Body) ->
94-
reply(Socket, Method, Body);
95-
respond(Socket, Method, <<"/4">>, _Body) ->
96-
chunked_reply(Socket, Method, [<<"Hello">>, <<" world!">>]);
97-
respond(Socket, Method, <<"/5">>, _Body) ->
98-
reply(Socket, Method, method(Method)).
117+
respond(Transport, Socket, Method, <<"/1">>, _Body) ->
118+
reply(Transport, Socket, Method, <<"Hello world!">>);
119+
respond(Transport, Socket, Method, <<"/2">>, _Body) ->
120+
reply(Transport, Socket, Method, binary:copy(<<"Hello world!">>, 1000));
121+
respond(Transport, Socket, Method, <<"/3">>, Body) ->
122+
reply(Transport, Socket, Method, Body);
123+
respond(Transport, Socket, Method, <<"/4">>, _Body) ->
124+
chunked_reply(Transport, Socket, Method, [<<"Hello">>, <<" world!">>]);
125+
respond(Transport, Socket, Method, <<"/5">>, _Body) ->
126+
reply(Transport, Socket, Method, method(Method)).
99127

100128
method(Method) when is_atom(Method) ->
101129
atom_to_binary(Method, utf8);
102130
method(Method) when is_binary(Method) ->
103131
Method.
104132

105-
reply(Socket, Method, Body) ->
133+
reply(Transport, Socket, Method, Body) ->
106134
Headers = [
107135
<<"HTTP/1.1 200 OK\r\n">>,
108136
<<"Connection: Keep-Alive\r\n">>,
@@ -112,12 +140,12 @@ reply(Socket, Method, Body) ->
112140
],
113141
case Method of
114142
'HEAD' ->
115-
ok = gen_tcp:send(Socket, Headers);
143+
ok = send(Transport, Socket, Headers);
116144
_ ->
117-
ok = gen_tcp:send(Socket, [Headers, Body])
145+
ok = send(Transport, Socket, [Headers, Body])
118146
end.
119147

120-
chunked_reply(Socket, Method, Chunks) ->
148+
chunked_reply(Transport, Socket, Method, Chunks) ->
121149
Headers = [
122150
<<"HTTP/1.1 200 OK\r\n">>,
123151
<<"Connection: Keep-Alive\r\n">>,
@@ -126,9 +154,29 @@ chunked_reply(Socket, Method, Chunks) ->
126154
],
127155
case Method of
128156
'HEAD' ->
129-
ok = gen_tcp:send(Socket, Headers);
157+
ok = send(Transport, Socket, Headers);
130158
_ ->
131159
Encoded = [[integer_to_binary(byte_size(Chunk), 16), <<"\r\n">>,
132160
Chunk, <<"\r\n">>] || Chunk <- Chunks],
133-
ok = gen_tcp:send(Socket, [Headers, Encoded, <<"0\r\n\r\n">>])
161+
ok = send(Transport, Socket, [Headers, Encoded, <<"0\r\n\r\n">>])
134162
end.
163+
164+
close(gen_tcp, Socket) ->
165+
gen_tcp:close(Socket);
166+
close(ssl, Socket) ->
167+
ssl:close(Socket).
168+
169+
recv(gen_tcp, Socket, Length) ->
170+
gen_tcp:recv(Socket, Length);
171+
recv(ssl, Socket, Length) ->
172+
ssl:recv(Socket, Length).
173+
174+
send(gen_tcp, Socket, Data) ->
175+
gen_tcp:send(Socket, Data);
176+
send(ssl, Socket, Data) ->
177+
ssl:send(Socket, Data).
178+
179+
setopts(gen_tcp, Socket, Opts) ->
180+
inet:setopts(Socket, Opts);
181+
setopts(ssl, Socket, Opts) ->
182+
ssl:setopts(Socket, Opts).

0 commit comments

Comments
 (0)