Skip to content

Commit

Permalink
Refactor HTTP::Server to bind to multiple addresses (crystal-lang#5776
Browse files Browse the repository at this point in the history
)

* Refactor HTTP::Server to use multiple interfaces

* Remove HTTP::Server#port and add HTTP::Server#bind_unused_port

The return type is now an Socket::IPAddress instead of only the port number.

* Rename `HTTP::Server#bind` to `#bind_tcp`, add overloads to `#listen`

* Add HTTP::Server#addresses

* fixup! Refactor HTTP::Server to use multiple interfaces

* fixup! Refactor HTTP::Server to use multiple interfaces

* Add HTTP::Server.listening? and raise errors when running or closed
  • Loading branch information
straight-shoota authored and chris-huxtable committed Jun 6, 2018
1 parent b718278 commit d4b8e20
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 100 deletions.
4 changes: 2 additions & 2 deletions samples/http_server.cr
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
require "http/server"

server = HTTP::Server.new "0.0.0.0", 8080 do |context|
server = HTTP::Server.new do |context|
context.response.headers["Content-Type"] = "text/plain"
context.response.print("Hello world!")
end

puts "Listening on http://0.0.0.0:8080"
server.listen
server.listen "0.0.0.0", 8080
132 changes: 110 additions & 22 deletions spec/std/http/server/server_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -194,21 +194,19 @@ module HTTP
end

describe HTTP::Server do
it "re-sets special port zero after bind" do
server = Server.new(0) { |ctx| }
server.bind
server.port.should_not eq(0)
end

it "re-sets port to zero after close" do
server = Server.new(0) { |ctx| }
server.bind
server.close
server.port.should eq(0)
it "binds to unused port" do
server = Server.new { |ctx| }
address = server.bind_unused_port
address.port.should_not eq(0)

server = Server.new { |ctx| }
port = server.bind_tcp(0).port
port.should_not eq(0)
end

it "doesn't raise on accept after close #2692" do
server = Server.new("0.0.0.0", 0) { }
server = Server.new { }
server.bind_unused_port

spawn do
server.close
Expand All @@ -219,15 +217,99 @@ module HTTP
end

it "reuses the TCP port (SO_REUSEPORT)" do
s1 = Server.new(0) { |ctx| }
s1.bind(reuse_port: true)
s1 = Server.new { |ctx| }
address = s1.bind_unused_port(reuse_port: true)

s2 = Server.new(s1.port) { |ctx| }
s2.bind(reuse_port: true)
s2 = Server.new { |ctx| }
s2.bind_tcp(address.port, reuse_port: true)

s1.close
s2.close
end

it "binds to different ports" do
server = Server.new do |context|
context.response.print "Test Server (#{context.request.headers["Host"]?})"
end

tcp_server = TCPServer.new("127.0.0.1", 0)
server.bind tcp_server
address1 = tcp_server.local_address

address2 = server.bind_unused_port

address1.should_not eq address2

spawn { server.listen }

Fiber.yield

HTTP::Client.get("http://#{address2}/").body.should eq "Test Server (#{address2})"
HTTP::Client.get("http://#{address1}/").body.should eq "Test Server (#{address1})"
HTTP::Client.get("http://#{address1}/").body.should eq "Test Server (#{address1})"
end

it "lists addresses" do
server = Server.new { }

tcp_server = TCPServer.new("127.0.0.1", 0)
addresses = [server.bind_unused_port, server.bind_unused_port, tcp_server.local_address]
server.bind tcp_server

server.addresses.should eq addresses
end

describe "#bind" do
it "fails after listen" do
server = Server.new { }
server.bind_unused_port
spawn { server.listen }
Fiber.yield
expect_raises(Exception, "Can't add socket to running server") do
server.bind_unused_port
end
server.close
end

it "fails after close" do
server = Server.new { }
server.bind_unused_port
spawn { server.listen }
Fiber.yield
server.close
expect_raises(Exception, "Can't add socket to closed server") do
server.bind_unused_port
end
server.close unless server.closed?
end
end

describe "#listen" do
it "fails after listen" do
server = Server.new { }
server.bind_unused_port
spawn { server.listen }
Fiber.yield
server.listening?.should be_true
expect_raises(Exception, "Can't start running server") do
server.listen
end
server.close
end

it "fails after close" do
server = Server.new { }
server.bind_unused_port
spawn { server.listen }
Fiber.yield
server.listening?.should be_true
server.close
server.listening?.should be_false
expect_raises(Exception, "Can't re-start closed server") do
server.listen
end
end
end
end

describe HTTP::Server::RequestProcessor do
Expand Down Expand Up @@ -310,40 +392,46 @@ module HTTP

typeof(begin
# Initialize with custom host
server = Server.new("0.0.0.0", 0) { |ctx| }
server = Server.new { |ctx| }
server.bind_tcp "0.0.0.0", 0
server.listen
server.close

server = Server.new("0.0.0.0", 0, [
server = Server.new([
ErrorHandler.new,
LogHandler.new,
CompressHandler.new,
StaticFileHandler.new("."),
]
)
server.bind_tcp "0.0.0.0", 0
server.listen
server.close

server = Server.new("0.0.0.0", 0, [StaticFileHandler.new(".")]) { |ctx| }
server = Server.new([StaticFileHandler.new(".")]) { |ctx| }
server.bind_tcp "0.0.0.0", 0
server.listen
server.close

# Initialize with default host
server = Server.new(0) { |ctx| }
server = Server.new { |ctx| }
server.bind_tcp 0
server.listen
server.close

server = Server.new(0, [
server = Server.new([
ErrorHandler.new,
LogHandler.new,
CompressHandler.new,
StaticFileHandler.new("."),
]
)
server.bind_tcp 0
server.listen
server.close

server = Server.new(0, [StaticFileHandler.new(".")]) { |ctx| }
server = Server.new([StaticFileHandler.new(".")]) { |ctx| }
server.bind_tcp 0
server.listen
server.close
end)
Expand Down
24 changes: 12 additions & 12 deletions spec/std/http/web_socket_spec.cr
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ describe HTTP::WebSocket do
end

it "negotiates over HTTP correctly" do
port_chan = Channel(Int32).new
address_chan = Channel(Socket::IPAddress).new

spawn do
http_ref = nil
Expand All @@ -313,15 +313,15 @@ describe HTTP::WebSocket do
end
end

http_server = http_ref = HTTP::Server.new(0, [ws_handler])
http_server.bind
port_chan.send(http_server.port)
http_server = http_ref = HTTP::Server.new([ws_handler])
address = http_server.bind_unused_port
address_chan.send(address)
http_server.listen
end

listen_port = port_chan.receive
listen_address = address_chan.receive

ws2 = HTTP::WebSocket.new("ws://127.0.0.1:#{listen_port}/foo/bar?query=arg&yes=please")
ws2 = HTTP::WebSocket.new("ws://#{listen_address}/foo/bar?query=arg&yes=please")

random = Random::Secure.hex
ws2.on_message do |str|
Expand All @@ -334,7 +334,7 @@ describe HTTP::WebSocket do
end

it "negotiates over HTTPS correctly" do
port_chan = Channel(Int32).new
address_chan = Channel(Socket::IPAddress).new

spawn do
http_ref = nil
Expand All @@ -350,19 +350,19 @@ describe HTTP::WebSocket do
end
end

http_server = http_ref = HTTP::Server.new(0, [ws_handler])
http_server = http_ref = HTTP::Server.new([ws_handler])
tls = http_server.tls = OpenSSL::SSL::Context::Server.new
tls.certificate_chain = File.join(__DIR__, "../openssl/ssl/openssl.crt")
tls.private_key = File.join(__DIR__, "../openssl/ssl/openssl.key")
http_server.bind
port_chan.send(http_server.port)
address = http_server.bind_unused_port
address_chan.send(address)
http_server.listen
end

listen_port = port_chan.receive
listen_address = address_chan.receive

client_context = OpenSSL::SSL::Context::Client.insecure
ws2 = HTTP::WebSocket.new("127.0.0.1", port: listen_port, path: "/", tls: client_context)
ws2 = HTTP::WebSocket.new(listen_address.address, port: listen_address.port, path: "/", tls: client_context)

random = Random::Secure.hex
ws2.on_message do |str|
Expand Down
10 changes: 6 additions & 4 deletions src/compiler/crystal/tools/playground/server.cr
Original file line number Diff line number Diff line change
Expand Up @@ -503,15 +503,17 @@ module Crystal::Playground
HTTP::StaticFileHandler.new(public_dir),
]

server = HTTP::Server.new handlers

host = @host
if host
server = HTTP::Server.new host, @port, handlers
address = server.bind_tcp host, @port
else
server = HTTP::Server.new @port, handlers
host = "localhost"
address = server.bind_tcp @port
end
@port = address.port

puts "Listening on http://#{host}:#{@port}"
puts "Listening on http://#{address}"
if host == "0.0.0.0"
puts "WARNING running playground with 0.0.0.0 is unsecure."
end
Expand Down
3 changes: 2 additions & 1 deletion src/compiler/crystal/tools/playground/views/_about.html
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,12 @@ <h1>Usage</h1>
<pre class="playground">
require "http/server"

server = HTTP::Server.new "0.0.0.0", 5678 do |context|
server = HTTP::Server.new do |context|
context.request.path
context.response.headers["Content-Type"] = "text/plain"
context.response.print("Hello world!")
end
server.bind "0.0.0.0", 5678

puts "Listening on http://0.0.0.0:5678"
server.listen</pre>
Expand Down
3 changes: 2 additions & 1 deletion src/http/formdata.cr
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ require "./formdata/**"
# require "http"
# require "tempfile"
#
# server = HTTP::Server.new(8085) do |context|
# server = HTTP::Server.new do |context|
# name = nil
# file = nil
# HTTP::FormData.parse(context.request) do |part|
Expand All @@ -34,6 +34,7 @@ require "./formdata/**"
# context.response << file.path
# end
#
# server.bind 8085
# server.listen
# ```
#
Expand Down
Loading

0 comments on commit d4b8e20

Please sign in to comment.