diff --git a/doc/index.md b/doc/index.md index 6e8649cf..f21961b8 100644 --- a/doc/index.md +++ b/doc/index.md @@ -186,6 +186,8 @@ Creates a new connection to an HTTP server. - `port` (string|integer): port to connect to in numeric form e.g. `"80"` or `80` + - `path` (string): path to connect to (UNIX sockets) + - `sendname` (string|boolean, optional): the [TLS SNI](https://en.wikipedia.org/wiki/Server_Name_Indication) host to send. defaults to `true` - `true` indicates to copy the `host` field diff --git a/http/client.lua b/http/client.lua index ebb0f568..4cb35959 100644 --- a/http/client.lua +++ b/http/client.lua @@ -80,6 +80,7 @@ local function connect(options, timeout) family = options.family; host = options.host; port = options.port; + path = options.path; sendname = options.sendname; v6only = options.v6only; nodelay = true; diff --git a/http/server.lua b/http/server.lua index 7b77c0b9..4bbfcd9b 100644 --- a/http/server.lua +++ b/http/server.lua @@ -162,8 +162,9 @@ local server_mt = { --[[ Starts listening on the given socket Takes a table of options: - - `.host`: address to bind to (required) + - `.host`: address to bind to (required if not `.path`) - `.port`: port to bind to (optional if tls isn't `nil`, in which case defaults to 80 for `.tls == false` or 443 if `.tls == true`) + - `.path`: path to UNIX socket (required if not `.host`) - `.v6only`: allow ipv6 only (no ipv4-mapped-ipv6) - `.reuseaddr`: turn on SO_REUSEADDR flag? - `.reuseport`: turn on SO_REUSEPORT flag? @@ -177,9 +178,11 @@ Takes a table of options: ]] local function listen(tbl) local tls = tbl.tls - local host = assert(tbl.host, "need host") + local host = tbl.host + local path = tbl.path + assert(host or path, "need host or path") local port = tbl.port - if port == nil then + if host and port == nil then if tls == true then port = "443" elseif tls == false then @@ -190,11 +193,16 @@ local function listen(tbl) end local ctx = tbl.ctx if ctx == nil and tls ~= false then - ctx = new_ctx(host) + if host then + ctx = new_ctx(host) + else + error("Custom OpenSSL context required when using a UNIX domain socket") + end end local s = assert(cs.listen{ host = host; port = port; + path = path; v6only = tbl.v6only; reuseaddr = tbl.reuseaddr; reuseport = tbl.reuseport; diff --git a/spec/server_spec.lua b/spec/server_spec.lua index 98548b0a..e6c77cdb 100644 --- a/spec/server_spec.lua +++ b/spec/server_spec.lua @@ -4,14 +4,33 @@ describe("http.server module", function() local new_headers = require "http.headers".new local cqueues = require "cqueues" local cs = require "cqueues.socket" - local function simple_test(tls, version) + local function assert_loop(cq, timeout) + local ok, err, _, thd = cq:loop(timeout) + if not ok then + if thd then + err = debug.traceback(thd, err) + end + error(err, 2) + end + end + local function simple_test(tls, version, path) local cq = cqueues.new() - local s = server.listen { - host = "localhost"; - port = 0; - } + local options = {} + if path then + options.path = path + else + options.host = "localhost" + options.port = 0 + end + options.version = version + options.tls = tls + local s = server.listen(options) assert(s:listen()) - local _, host, port = s:localname() + local host, port + if not path then + local _ + _, host, port = s:localname() + end local on_stream = spy.new(function(stream) stream:get_headers() stream:shutdown() @@ -22,12 +41,16 @@ describe("http.server module", function() s:close() end) cq:wrap(function() - local conn = client.connect { - host = host; - port = port; - tls = tls; - version = version; - } + local client_options = {} + if path then + client_options.path = path + else + client_options.host = host + client_options.port = port + end + client_options.tls = tls + client_options.version = version + local conn = client.connect(client_options) local stream = conn:new_stream() local headers = new_headers() headers:append(":method", "GET") @@ -41,16 +64,16 @@ describe("http.server module", function() assert.truthy(cq:empty()) assert.spy(on_stream).was.called() end - it("works with plain http 1.1", function() + it("works with plain http 1.1 using IP", function() simple_test(false, 1.1) end) - it("works with https 1.1", function() + it("works with https 1.1 using IP", function() simple_test(true, 1.1) end) - it("works with plain http 2.0", function() + it("works with plain http 2.0 using IP", function() simple_test(false, 2.0) end); - (require "http.tls".has_alpn and it or pending)("works with https 2.0", function() + (require "http.tls".has_alpn and it or pending)("works with https 2.0 using IP", function() simple_test(true, 2.0) end) it("taking socket from underlying connection is handled well by server", function() @@ -84,4 +107,38 @@ describe("http.server module", function() assert.truthy(cq:empty()) assert.spy(on_stream).was.called() end) + --[[ + -- + -- Until there is a way to generate OpenSSL contexts in this file for + -- UNIX domain sockets, there is no way to use TLS with this. Because + -- of this, the status for using TLS with UNIX domain sockets is + -- pending. + -- + --]] + local socket_path = os.tmpname() + os.remove(socket_path) -- in case it was generated automatically + it("works with plain http 1.1 using UNIX socket domain", function() + simple_test(false, 1.1, socket_path) + finally(function() + os.remove(socket_path) + end) + end) + pending("works with https 1.1 using UNIX socket domain", function() + simple_test(true, 1.1, socket_path) + finally(function() + os.remove(socket_path) + end) + end) + it("works with plain http 2.0 using UNIX socket domain", function() + simple_test(false, 2.0, socket_path) + finally(function() + os.remove(socket_path) + end) + end); + pending("works with https 2.0 using UNIX socket domain", function() + simple_test(true, 2.0, socket_path) + finally(function() + os.remove(socket_path) + end) + end) end)