From a20b6332ed9ae9a0a7cf2d61ef43eaa86c0851ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20M=C3=BCller?= Date: Mon, 5 Mar 2018 19:43:54 +0000 Subject: [PATCH] Add support for Unix sockets to `HTTP::Server` --- spec/std/http/server/server_spec.cr | 55 +++++++++++++++++++++++++++++ src/http/server.cr | 39 +++++++++++++++++++- 2 files changed, 93 insertions(+), 1 deletion(-) diff --git a/spec/std/http/server/server_spec.cr b/spec/std/http/server/server_spec.cr index 6d55f8e1d9ac..1dfd18ee4908 100644 --- a/spec/std/http/server/server_spec.cr +++ b/spec/std/http/server/server_spec.cr @@ -40,6 +40,23 @@ private class ReverseResponseOutput < IO end end +# TODO: replace with `HTTP::Client` once it supports connecting to Unix socket. +private def unix_request(path) + UNIXSocket.open(path) do |io| + io << "GET / HTTP/1.1" + io << "\r\n\r\n" + io.flush + + loop do + line = io.gets || break + + if line.empty? + return io.gets + end + end + end +end + module HTTP class Server describe Response do @@ -263,6 +280,44 @@ module HTTP HTTP::Client.get("http://127.0.0.1:#{port1}/").body.should eq "Test Server (127.0.0.1:#{port1})" HTTP::Client.get("http://127.0.0.1:#{port1}/").body.should eq "Test Server (127.0.0.1:#{port1})" end + + {% if flag?(:unix) %} + it "binds to different unix sockets" do + path1 = "/tmp/socket1" + path2 = "/tmp/socket2" + + begin + server = Server.new do |context| + socket = context.socket + + socket.should be_a(UNIXSocket) + if socket.is_a? UNIXSocket + context.response.puts "Test Server (#{socket.local_address})" + end + end + + socket1 = server.bind UNIXServer.new(path1) + socket2 = server.bind "unix:#{path2}" + + spawn do + server.listen + end + + Fiber.yield + + unix_request(path1).should eq "Test Server (#{path1})" + unix_request(path2).should eq "Test Server (#{path2})" + + server.close + + File.exists?(path1).should be_false + File.exists?(path2).should be_false + ensure + File.delete(path1) if File.exists?(path1) + File.delete(path2) if File.exists?(path2) + end + end + {% end %} end describe HTTP::Server::RequestProcessor do diff --git a/src/http/server.cr b/src/http/server.cr index e131fe09191f..bb21e68650fa 100644 --- a/src/http/server.cr +++ b/src/http/server.cr @@ -156,7 +156,44 @@ class HTTP::Server tcp_server.local_address.port end - # Creates a `TCPServer` bound to *address* and adds it as a socket. + # Creates a `Socket::Server` listenting to *address* and adds it as a socket. + # + # The *address* can either be `host:port` or a Unix socket prefixed by `unix:`. + # This works only on systems supporting Unix sockets. + # + # ``` + # server = HTTP::Server.new { } + # server.bind "127.0.0.1:8080" + # server.bind "unix:/tmp/my-socket" + # ``` + def bind(address : String, reuse_port : Bool = false) : Socket::Server + if address.starts_with?("unix:") + bind(Socket::UNIXAddress.new(address[5..-1])).as(Socket::Server) + else + host, _, port = address.partition(':') + if host == "*" || host.empty? + host = "0.0.0.0" + end + if port.empty? + port = 80 + else + port = port.to_i + end + bind(host, port, reuse_port).as(Socket::Server) + end + end + + # Creates a `UNIXServer` bound to *address* and adds it as an interface. + # + # ``` + # server = HTTP::Server.new { } + # server.bind Socket::UNIXAddress.new("/tmp/my-socket") + # ``` + def bind(address : Socket::UNIXAddress) : UNIXServer + bind UNIXServer.new(address.path) + end + + # Creates a `TCPServer` bound to *address* and adds it as an interface. # # ``` # server = HTTP::Server.new { }