Skip to content

Commit 26dfd76

Browse files
GuyLewinpintsized
andauthored
Feature/connect improvements (#254)
* feat: pass ssl_reused_session into http_connect * style: don't redeclare variables * feat: return ssl_session upon successful SSL connection * doc: update README with changed APIs * chore(tests) update expired test cert * test(ssl_reused_session) add tests Co-authored-by: James Hurst <[email protected]>
1 parent 0ce55d6 commit 26dfd76

File tree

6 files changed

+228
-58
lines changed

6 files changed

+228
-58
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ local body = res.body
9191
local httpc = require("resty.http").new()
9292

9393
-- First establish a connection
94-
local ok, err = httpc:connect({
94+
local ok, err, ssl_session = httpc:connect({
9595
scheme = "https",
9696
host = "127.0.0.1",
9797
port = 8080,
@@ -153,7 +153,7 @@ Creates the HTTP connection object. In case of failures, returns `nil` and a str
153153

154154
## connect
155155

156-
`syntax: ok, err = httpc:connect(options)`
156+
`syntax: ok, err, ssl_session = httpc:connect(options)`
157157

158158
Attempts to connect to the web server while incorporating the following activities:
159159

@@ -172,6 +172,7 @@ The options table has the following fields:
172172
* `pool_size`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
173173
* `backlog`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsockconnect)
174174
* `proxy_opts`: sub-table, defaults to the global proxy options set, see [set\_proxy\_options](#set_proxy_options).
175+
* `ssl_reused_session`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
175176
* `ssl_verify`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake), except that it defaults to `true`.
176177
* `ssl_server_name`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)
177178
* `ssl_send_status_req`: option as per [OpenResty docs](https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake)

lib/resty/http.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ end
165165
do
166166
local aio_connect = require "resty.http_connect"
167167
-- Function signatures to support:
168-
-- ok, err = httpc:connect(options_table)
168+
-- ok, err, ssl_session = httpc:connect(options_table)
169169
-- ok, err = httpc:connect(host, port, options_table?)
170170
-- ok, err = httpc:connect("unix:/path/to/unix.sock", options_table?)
171171
function _M.connect(self, options, ...)

lib/resty/http_connect.lua

+11-6
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ client:connect {
2222
backlog = nil,
2323
2424
-- ssl options as per: https://github.com/openresty/lua-nginx-module#tcpsocksslhandshake
25+
ssl_reused_session = nil
2526
ssl_server_name = nil,
2627
ssl_send_status_req = nil,
2728
ssl_verify = true, -- NOTE: defaults to true
@@ -53,9 +54,10 @@ local function connect(self, options)
5354
end
5455

5556
-- ssl settings
56-
local ssl, ssl_server_name, ssl_verify, ssl_send_status_req
57+
local ssl, ssl_reused_session, ssl_server_name, ssl_verify, ssl_send_status_req
5758
if request_scheme == "https" then
5859
ssl = true
60+
ssl_reused_session = options.ssl_reused_session
5961
ssl_server_name = options.ssl_server_name
6062
ssl_send_status_req = options.ssl_send_status_req
6163
ssl_verify = true -- default
@@ -133,7 +135,8 @@ local function connect(self, options)
133135
end
134136

135137
if proxy then
136-
local proxy_uri_t, err = self:parse_uri(proxy_uri)
138+
local proxy_uri_t
139+
proxy_uri_t, err = self:parse_uri(proxy_uri)
137140
if not proxy_uri_t then
138141
return nil, err
139142
end
@@ -178,7 +181,8 @@ local function connect(self, options)
178181
-- authority-form of RFC 7230 Section 5.3.3. See also RFC 7231 Section
179182
-- 4.3.6 for more details about the CONNECT request
180183
local destination = request_host .. ":" .. request_port
181-
local res, err = self:request({
184+
local res
185+
res, err = self:request({
182186
method = "CONNECT",
183187
path = destination,
184188
headers = {
@@ -211,10 +215,11 @@ local function connect(self, options)
211215
end
212216
end
213217

218+
local ssl_session
214219
-- Now do the ssl handshake
215220
if ssl and sock:getreusedtimes() == 0 then
216-
local ok, err = sock:sslhandshake(nil, ssl_server_name, ssl_verify, ssl_send_status_req)
217-
if not ok then
221+
ssl_session, err = sock:sslhandshake(ssl_reused_session, ssl_server_name, ssl_verify, ssl_send_status_req)
222+
if not ssl_session then
218223
self:close()
219224
return nil, err
220225
end
@@ -228,7 +233,7 @@ local function connect(self, options)
228233
self.http_proxy_auth = request_scheme ~= "https" and proxy_authorization or nil
229234
self.path_prefix = path_prefix
230235

231-
return true
236+
return true, nil, ssl_session
232237
end
233238

234239
return connect

t/19-ssl_reused_session.t

+136
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
use Test::Nginx::Socket::Lua 'no_plan';
2+
use Cwd qw(abs_path realpath);
3+
use File::Basename;
4+
5+
$ENV{TEST_NGINX_HTML_DIR} ||= html_dir();
6+
7+
$ENV{TEST_NGINX_RESOLVER} ||= '8.8.8.8';
8+
$ENV{TEST_NGINX_CERT_DIR} ||= dirname(realpath(abs_path(__FILE__)));
9+
$ENV{TEST_COVERAGE} ||= 0;
10+
11+
my $realpath = realpath();
12+
13+
our $HttpConfig = qq{
14+
lua_package_path "$realpath/lib/?.lua;/usr/local/share/lua/5.1/?.lua;;";
15+
16+
init_by_lua_block {
17+
if $ENV{TEST_COVERAGE} == 1 then
18+
jit.off()
19+
require("luacov.runner").init()
20+
end
21+
22+
TEST_SERVER_SOCK = "unix:/$ENV{TEST_NGINX_HTML_DIR}/nginx.sock"
23+
24+
num_handshakes = 0
25+
}
26+
27+
server {
28+
listen unix:$ENV{TEST_NGINX_HTML_DIR}/nginx.sock ssl;
29+
server_name example.com;
30+
ssl_certificate $ENV{TEST_NGINX_CERT_DIR}/cert/test.crt;
31+
ssl_certificate_key $ENV{TEST_NGINX_CERT_DIR}/cert/test.key;
32+
ssl_session_tickets off;
33+
34+
server_tokens off;
35+
}
36+
};
37+
38+
no_long_string();
39+
#no_diff();
40+
41+
run_tests();
42+
43+
__DATA__
44+
45+
=== TEST 1: connect returns session userdata
46+
--- http_config eval: $::HttpConfig
47+
--- config
48+
server_tokens off;
49+
resolver $TEST_NGINX_RESOLVER ipv6=off;
50+
lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
51+
52+
location /t {
53+
content_by_lua_block {
54+
local httpc = assert(require("resty.http").new())
55+
local ok, err, session = assert(httpc:connect {
56+
scheme = "https",
57+
host = TEST_SERVER_SOCK,
58+
})
59+
60+
assert(type(session) == "userdata", "expected session to be userdata")
61+
assert(httpc:close())
62+
}
63+
}
64+
65+
--- request
66+
GET /t
67+
--- no_error_log
68+
[error]
69+
[alert]
70+
71+
72+
=== TEST 2: ssl_reused_session false does not return session userdata
73+
--- http_config eval: $::HttpConfig
74+
--- config
75+
server_tokens off;
76+
resolver $TEST_NGINX_RESOLVER ipv6=off;
77+
lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
78+
79+
location /t {
80+
content_by_lua_block {
81+
local httpc = assert(require("resty.http").new())
82+
local ok, err, session = assert(httpc:connect {
83+
scheme = "https",
84+
host = TEST_SERVER_SOCK,
85+
ssl_reused_session = false,
86+
})
87+
88+
assert(type(session) == "boolean", "expected session to be a boolean")
89+
assert(session == true, "expected session to be true")
90+
assert(httpc:close())
91+
}
92+
}
93+
94+
--- request
95+
GET /t
96+
--- no_error_log
97+
[error]
98+
[alert]
99+
100+
101+
=== TEST 3: ssl_reused_session accepts userdata
102+
--- http_config eval: $::HttpConfig
103+
--- config
104+
server_tokens off;
105+
resolver $TEST_NGINX_RESOLVER ipv6=off;
106+
lua_ssl_trusted_certificate $TEST_NGINX_CERT_DIR/cert/test.crt;
107+
108+
location /t {
109+
content_by_lua_block {
110+
local httpc = assert(require("resty.http").new())
111+
local ok, err, session = assert(httpc:connect {
112+
scheme = "https",
113+
host = TEST_SERVER_SOCK,
114+
})
115+
116+
assert(type(session) == "userdata", "expected session to be userdata")
117+
118+
local httpc2 = assert(require("resty.http").new())
119+
local ok, err, session2 = assert(httpc2:connect {
120+
scheme = "https",
121+
host = TEST_SERVER_SOCK,
122+
ssl_reused_session = session,
123+
})
124+
125+
assert(type(session2) == "userdata", "expected session2 to be userdata")
126+
127+
assert(httpc:close())
128+
assert(httpc2:close())
129+
}
130+
}
131+
132+
--- request
133+
GET /t
134+
--- no_error_log
135+
[error]
136+
[alert]

t/cert/test.crt

+25-22
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,27 @@
11
-----BEGIN CERTIFICATE-----
2-
MIID8DCCAtigAwIBAgIJALL9eJPZ6neGMA0GCSqGSIb3DQEBBQUAMFgxCzAJBgNV
3-
BAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYDVQQKEwRU
4-
ZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0MB4XDTE1MTAyMTE2MjQ1
5-
NloXDTE1MTEyMDE2MjQ1NlowWDELMAkGA1UEBhMCR0IxDTALBgNVBAgTBFRlc3Qx
6-
DTALBgNVBAcTBFRlc3QxDTALBgNVBAoTBFRlc3QxDTALBgNVBAsTBFRlc3QxDTAL
7-
BgNVBAMTBHRlc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDz/AoE
8-
c+TPdm+Aqcchq8fLNWksFQZqbsCBGnq8rUG1b6MsVlAOkDUQGRlNPs9v0/+pzgX7
9-
IYXPCFcV7YONNsTUfvBYTq43mfOycmAdb3SX6kBygxdhYsDRZR+vCAIkjoRmRB20
10-
meh1motqM58spq3IcT8VADTRJl1OI48VTnxmXdCtmkOymU948DcauMoxm03eL/hU
11-
6eniNEujbnbB305noNG0W5c3h6iz9CvqUAD1kwyjick+f1atB2YYn1bymA+db6YN
12-
3iTo0v2raWmIc7D+qqpkNaCRxgMb2HN6X3/SfkijtNJidjqHMbs2ftlKJ5/lODPZ
13-
rCPQOcYK6TT8MIZ1AgMBAAGjgbwwgbkwHQYDVR0OBBYEFFUC1GrAhUp7IvJH5iyf
14-
+fJQliEIMIGJBgNVHSMEgYEwf4AUVQLUasCFSnsi8kfmLJ/58lCWIQihXKRaMFgx
15-
CzAJBgNVBAYTAkdCMQ0wCwYDVQQIEwRUZXN0MQ0wCwYDVQQHEwRUZXN0MQ0wCwYD
16-
VQQKEwRUZXN0MQ0wCwYDVQQLEwRUZXN0MQ0wCwYDVQQDEwR0ZXN0ggkAsv14k9nq
17-
d4YwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOCAQEAtaUQOr3Qn87KXmmP
18-
GbSvCLSl+bScE09VYZsYaB6iq0pGN9y+Vh4/HjBUUsFexopw1dY25MEEJXEVi1xV
19-
2krLYAsfKCM6c1QBVmdqfVuxUvxpXwr+CNRNAlzz6PhjkeY/Ds/j4sg7EqN8hMmT
20-
gu8GuogX7+ZCgrzRSMMclWej+W8D1xSIuCC+rqv4w9SZdtVb3XGpCyizpTNsQAuV
21-
ACXvq9KXkEEj+XNvKrNdWd4zG715RdMnVm+WM53d9PLp63P+4/kwhwHULYhXygQ3
22-
DzzVPaojBBdw3VaHbbPHnv73FtAzOb7ky6zJ01DlmEPxEahCFpklMkY9T2uCdpj9
23-
oOzaNA==
2+
MIIEpDCCAowCCQDgS40Q0UNIKTANBgkqhkiG9w0BAQsFADAUMRIwEAYDVQQDDAls
3+
b2NhbGhvc3QwHhcNMjExMTIyMTQ0MDM2WhcNMjIxMTIyMTQ0MDM2WjAUMRIwEAYD
4+
VQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQC9
5+
OVsD7qyres7qkLZPY4MyC00/ByESpUVFgWiTU2nBeNC/kBvOOOVTvrVHxlTjZ9JG
6+
rCdYeIGkpkaJQ2QylHKRJolxh8x309/VkpKJYL357NuNeAk2l3tW4BMImXUGdVjD
7+
0qT8WTyApEE7lyfOXKYKbbEwfdks0eBcEZBBn4QTaZ9lVj7yScurto8jDYMkBfO/
8+
4QeuVe4ijgWU35Yd89Ya0HZr+1QBU1dMzbtf9mYPedRwRZgzKogZ6mZXs7GJ0ADM
9+
L+fg/djROd01eA3ynAfxJbRv1R7S73ljQJhD+oYf3rJaHU2Ko5heF9K9bd771Ze/
10+
sHGHZdLy3JZrPNdDPHG61H9m4zOUkNZvVkvHF9wg+uNs4VJxrkG6undOEyCyBefE
11+
XRknEBvMLlWF9c9KT5tZz/vI6/e5xrpNCdUjthn5YFEf43MVqWWcU8t9ViK+8+rG
12+
PQNKmxRiCOOmd6NhjWJInuMO3QiPJFqVs4UNpmE/4hi3LtwV7qH4jHWX5hRG27gz
13+
8VbfkBkIoy0Esm2Dv5lErZztPhycpFUodpBXGtQjXQLvYVm+yKwWlD6gTRH0LXj3
14+
3lp69m5/V9so3NsYWMxoNGvI4MqZuwGaiwxgkCWjUqQEX1y4i7Us01UDFpK2WuwG
15+
MUQNzGV2jX9urnjndFPHjszqo5BsmJuHwihGOFvbpwIDAQABMA0GCSqGSIb3DQEB
16+
CwUAA4ICAQAhXhf95xbkSw1fknoGhdgTAb3A+pKzcejZjqmQi3BJku8EILe06LGF
17+
Pli4WlN7KMk20A/TO/L46tsWztFl0vhSs+ed/Z3/0ugB+DO0InpwDxutPTrVjkmy
18+
W5HAKnW7MQAA9tcTcAPbiHIalwswQ1pab+kxl9fbsaC1CrSypa1eK6Hh/qJKxe2O
19+
06ovTTHuZ/yWJBaIwmvP8lCMfCrLGOCGToM4FWtDPc47VVxpk0Ks0kRL8Bqo4CsG
20+
rM+PpfsFBdPO/FftfVeMdzZCcK1CLWuE/uBdjDtU10qMVhQTxGG4eFv24xVQuCmH
21+
AB5hTt62DLxHAs6kgvafZz9ST6aVDu0kuPABC4z4JQVBCRbw0mL75R4ziEYH3CIe
22+
tmgYEXHfj3D3BDgX3ki6qqX7VtmkT0+VyrpQs+dhkCXlnqGmNM+gSsKve3ZH6a9J
23+
fFeKjiJH4YjlPmLlCxpnDoBgjGjcUmVEBcpZ7H8lNC3ldPVfDq3PDrHgFMH5lmth
24+
PZHbWROudoHVX5EBY2n08uzMWtl0xFwAfLG3Hk7xoBWBpebHxhw/yzm/iWHeBdi+
25+
8uSMLM3/TjmKhh+mF7KRKSYKetKKYiwB0/yIQJLMLxpUc3Aae7i0djH/kFz80HU/
26+
alsGmliI6PGyCt11DYS7Spl44pxl6N7pj2CJpyPOMwDI3VG097cYDQ==
2427
-----END CERTIFICATE-----

t/cert/test.key

+52-27
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,52 @@
1-
-----BEGIN RSA PRIVATE KEY-----
2-
MIIEowIBAAKCAQEA8/wKBHPkz3ZvgKnHIavHyzVpLBUGam7AgRp6vK1BtW+jLFZQ
3-
DpA1EBkZTT7Pb9P/qc4F+yGFzwhXFe2DjTbE1H7wWE6uN5nzsnJgHW90l+pAcoMX
4-
YWLA0WUfrwgCJI6EZkQdtJnodZqLajOfLKatyHE/FQA00SZdTiOPFU58Zl3QrZpD
5-
splPePA3GrjKMZtN3i/4VOnp4jRLo252wd9OZ6DRtFuXN4eos/Qr6lAA9ZMMo4nJ
6-
Pn9WrQdmGJ9W8pgPnW+mDd4k6NL9q2lpiHOw/qqqZDWgkcYDG9hzel9/0n5Io7TS
7-
YnY6hzG7Nn7ZSief5Tgz2awj0DnGCuk0/DCGdQIDAQABAoIBAGjKc7L94+SHRdTJ
8-
FtILacCJrCZW0W6dKulIajbnYzV+QWMlnzTiEyha31ciBw5My54u8rqt5z7Ioj60
9-
yK+6OkfaTXhgMsuGv/iAz29VE4q7/fow+7XEKHTHLhiLJAB3hb42u1t6TzFTs1Vl
10-
3pPa8wEIQsPOVuENzT1mYGoST7PW+LBIMr9ScMnRHfC0MNdV/ntQiXideOAd5PkA
11-
4O7fNgYZ8CTAZ8rOLYTMFF76/c/jLiqfeghqbIhqMykk36kd7Lud//FRykVsn1aJ
12-
REUva/SjVEth5kITot1hpMC4SIElWpha2YxiiZFoSXSaUbtHpymiUGV01cYtMWk0
13-
MZ5HN3ECgYEA/74U8DpwPxd4up9syKyNqOqrCrYnhEEC/tdU/W5wECi4y5kppjdd
14-
88lZzICVPzk2fezYXlCO9HiSHU1UfcEsY3u16qNCvylK7Qz1OqXV/Ncj59891Q5Z
15-
K0UBcbnrv+YD6muZuhlHEbyDPqYO091G9Gf/BbL5JIBDzg1qFO9Dh9cCgYEA9Drt
16-
O9PJ5Sjz3mXQVtVHpwyhOVnd7CUv8a1zkUQCK5uQeaiF5kal1FIo7pLOr3KAvG0C
17-
pXbm/TobwlfAfcERQN88aPN8Z/l1CB0oKV6ipBMD2/XLzDRtx8lpTeh/BB8jIhrz
18-
+FDJY54HCzLfW0P5kT+Cyw51ofjziPnFdO/Z6pMCgYEAon17gEchGnUnWCwDSl2Y
19-
hELV+jBSW02TQag/b+bDfQDiqTnfpKR5JXRBghYQveL0JH5f200EB4C0FboUfPJH
20-
6c2ogDTLK/poiMU66tCDbeqj/adx+fTr4votOL0QdRUIV+GWAxAcf8BvA1cvBJ4L
21-
fy60ckKM2gxFCJ6tUC/VkHECgYBoMDNAUItSnXPbrmeAg5/7naGxy6qmsP6RBUPF
22-
9tNOMyEhJUlqAT2BJEOd8zcFFb3hpEd6uwyzfnSVJcZSX2iy2gj1ZNnvqTXJ7lZR
23-
v7N2dz4wOd1lEgC7OCsaN1LoOThNtl3Z0uz2+FVc66jpUEhJNGThpxt7q66JArS/
24-
vAqkzQKBgFkzqA6QpnH5KhOCoZcuLQ4MtvnNHOx1xSm2B0gKDVJzGkHexTmOJvwM
25-
ZhHXRl9txS4icejS+AGUXNBzCWEusfhDaZpZqS6zt6UxEjMsLj/Te7z++2KQn4t/
26-
aI77jClydW1pJvICtqm5v+sukVZvQTTJza9ujta6fj7u2s671np9
27-
-----END RSA PRIVATE KEY-----
1+
-----BEGIN PRIVATE KEY-----
2+
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
51+
xmyWhwxzeZdweoZDR6um1BRcJVJzdA==
52+
-----END PRIVATE KEY-----

0 commit comments

Comments
 (0)