Skip to content

Commit adf827f

Browse files
authored
Merge pull request #43 from lpgauth/feature/max-requests
Land the HTTPS tests on master
2 parents 99811eb + 2d26784 commit adf827f

4 files changed

Lines changed: 241 additions & 117 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: 123 additions & 47 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() ->
@@ -17,7 +25,9 @@ start() ->
1725
Pid = spawn(fun () -> init(Self) end),
1826
receive
1927
{Pid, started} ->
20-
{ok, Pid}
28+
{ok, Pid};
29+
{Pid, {error, Reason}} ->
30+
{error, Reason}
2131
after 5000 ->
2232
{error, timeout}
2333
end.
@@ -27,82 +37,128 @@ stop() ->
2737
undefined ->
2838
ok;
2939
Pid ->
40+
Ref = monitor(process, Pid),
3041
exit(Pid, kill),
31-
ok
42+
receive
43+
{'DOWN', Ref, process, Pid, _} ->
44+
ok
45+
end
3246
end.
3347

3448
%% private
3549
init(Parent) ->
36-
register(?MODULE, self()),
37-
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-
]),
45-
Parent ! {self(), started},
46-
accept(LSocket).
50+
try
51+
register(?MODULE, self()),
52+
persistent_term:put({?MODULE, connections}, counters:new(1, [])),
53+
{ok, _} = application:ensure_all_started(ssl),
54+
{ok, LSocket} = listen(gen_tcp, ?PORT, ?LISTEN_OPTIONS),
55+
%% the default pkix_test_data key (secp112r2, sha1) is not
56+
%% negotiable by a modern TLS client
57+
KeyOpts = [{key, {namedCurve, secp256r1}}, {digest, sha256}],
58+
SslOptions = public_key:pkix_test_data(#{root => KeyOpts,
59+
peer => KeyOpts}),
60+
{ok, LSocketSsl} = listen(ssl, ?PORT_SSL,
61+
?LISTEN_OPTIONS ++ SslOptions),
62+
spawn_link(fun () -> accept_ssl(LSocketSsl) end),
63+
Parent ! {self(), started},
64+
accept(LSocket)
65+
catch
66+
Class:Error:Stacktrace ->
67+
Parent ! {self(), {error, {Class, Error, Stacktrace}}}
68+
end.
69+
70+
%% the previous fixture's ports can linger briefly after its death:
71+
%% ERTS releases them asynchronously once the DOWN signal fires
72+
listen(Transport, Port, Options) ->
73+
listen(Transport, Port, Options, 50).
74+
75+
listen(Transport, Port, Options, Retries) ->
76+
case Transport:listen(Port, Options) of
77+
{ok, LSocket} ->
78+
{ok, LSocket};
79+
{error, eaddrinuse} when Retries > 0 ->
80+
timer:sleep(10),
81+
listen(Transport, Port, Options, Retries - 1);
82+
{error, _} = E ->
83+
E
84+
end.
4785

4886
accept(LSocket) ->
4987
{ok, Socket} = gen_tcp:accept(LSocket),
5088
counters:add(persistent_term:get({?MODULE, connections}), 1, 1),
5189
Pid = spawn_link(fun () ->
52-
receive go -> connection(Socket) end
90+
receive go -> connection(gen_tcp, Socket) end
5391
end),
5492
ok = gen_tcp:controlling_process(Socket, Pid),
5593
Pid ! go,
5694
accept(LSocket).
5795

58-
connection(Socket) ->
59-
case gen_tcp:recv(Socket, 0) of
96+
accept_ssl(LSocket) ->
97+
{ok, TSocket} = ssl:transport_accept(LSocket),
98+
counters:add(persistent_term:get({?MODULE, connections}), 1, 1),
99+
Pid = spawn_link(fun () ->
100+
receive
101+
go ->
102+
case ssl:handshake(TSocket) of
103+
{ok, Socket} ->
104+
connection(ssl, Socket);
105+
{error, _} ->
106+
ok
107+
end
108+
end
109+
end),
110+
ok = ssl:controlling_process(TSocket, Pid),
111+
Pid ! go,
112+
accept_ssl(LSocket).
113+
114+
connection(Transport, Socket) ->
115+
case recv(Transport, Socket, 0) of
60116
{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);
117+
ContentLength = headers(Transport, Socket, 0),
118+
Body = body(Transport, Socket, ContentLength),
119+
respond(Transport, Socket, Method, Path, Body),
120+
connection(Transport, Socket);
65121
{ok, _} ->
66-
gen_tcp:close(Socket);
122+
close(Transport, Socket);
67123
{error, _} ->
68-
gen_tcp:close(Socket)
124+
close(Transport, Socket)
69125
end.
70126

71-
headers(Socket, ContentLength) ->
72-
case gen_tcp:recv(Socket, 0) of
127+
headers(Transport, Socket, ContentLength) ->
128+
case recv(Transport, Socket, 0) of
73129
{ok, {http_header, _, 'Content-Length', _, Value}} ->
74-
headers(Socket, binary_to_integer(Value));
130+
headers(Transport, Socket, binary_to_integer(Value));
75131
{ok, {http_header, _, _, _, _}} ->
76-
headers(Socket, ContentLength);
132+
headers(Transport, Socket, ContentLength);
77133
{ok, http_eoh} ->
78134
ContentLength
79135
end.
80136

81-
body(_Socket, 0) ->
137+
body(_Transport, _Socket, 0) ->
82138
<<>>;
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}]),
139+
body(Transport, Socket, ContentLength) ->
140+
ok = setopts(Transport, Socket, [{packet, raw}]),
141+
{ok, Body} = recv(Transport, Socket, ContentLength),
142+
ok = setopts(Transport, Socket, [{packet, http_bin}]),
87143
Body.
88144

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)).
145+
respond(Transport, Socket, Method, <<"/1">>, _Body) ->
146+
reply(Transport, Socket, Method, <<"Hello world!">>);
147+
respond(Transport, Socket, Method, <<"/2">>, _Body) ->
148+
reply(Transport, Socket, Method, binary:copy(<<"Hello world!">>, 1000));
149+
respond(Transport, Socket, Method, <<"/3">>, Body) ->
150+
reply(Transport, Socket, Method, Body);
151+
respond(Transport, Socket, Method, <<"/4">>, _Body) ->
152+
chunked_reply(Transport, Socket, Method, [<<"Hello">>, <<" world!">>]);
153+
respond(Transport, Socket, Method, <<"/5">>, _Body) ->
154+
reply(Transport, Socket, Method, method(Method)).
99155

100156
method(Method) when is_atom(Method) ->
101157
atom_to_binary(Method, utf8);
102158
method(Method) when is_binary(Method) ->
103159
Method.
104160

105-
reply(Socket, Method, Body) ->
161+
reply(Transport, Socket, Method, Body) ->
106162
Headers = [
107163
<<"HTTP/1.1 200 OK\r\n">>,
108164
<<"Connection: Keep-Alive\r\n">>,
@@ -112,12 +168,12 @@ reply(Socket, Method, Body) ->
112168
],
113169
case Method of
114170
'HEAD' ->
115-
ok = gen_tcp:send(Socket, Headers);
171+
ok = send(Transport, Socket, Headers);
116172
_ ->
117-
ok = gen_tcp:send(Socket, [Headers, Body])
173+
ok = send(Transport, Socket, [Headers, Body])
118174
end.
119175

120-
chunked_reply(Socket, Method, Chunks) ->
176+
chunked_reply(Transport, Socket, Method, Chunks) ->
121177
Headers = [
122178
<<"HTTP/1.1 200 OK\r\n">>,
123179
<<"Connection: Keep-Alive\r\n">>,
@@ -126,9 +182,29 @@ chunked_reply(Socket, Method, Chunks) ->
126182
],
127183
case Method of
128184
'HEAD' ->
129-
ok = gen_tcp:send(Socket, Headers);
185+
ok = send(Transport, Socket, Headers);
130186
_ ->
131187
Encoded = [[integer_to_binary(byte_size(Chunk), 16), <<"\r\n">>,
132188
Chunk, <<"\r\n">>] || Chunk <- Chunks],
133-
ok = gen_tcp:send(Socket, [Headers, Encoded, <<"0\r\n\r\n">>])
189+
ok = send(Transport, Socket, [Headers, Encoded, <<"0\r\n\r\n">>])
134190
end.
191+
192+
close(gen_tcp, Socket) ->
193+
gen_tcp:close(Socket);
194+
close(ssl, Socket) ->
195+
ssl:close(Socket).
196+
197+
recv(gen_tcp, Socket, Length) ->
198+
gen_tcp:recv(Socket, Length);
199+
recv(ssl, Socket, Length) ->
200+
ssl:recv(Socket, Length).
201+
202+
send(gen_tcp, Socket, Data) ->
203+
gen_tcp:send(Socket, Data);
204+
send(ssl, Socket, Data) ->
205+
ssl:send(Socket, Data).
206+
207+
setopts(gen_tcp, Socket, Opts) ->
208+
inet:setopts(Socket, Opts);
209+
setopts(ssl, Socket, Opts) ->
210+
ssl:setopts(Socket, Opts).

0 commit comments

Comments
 (0)