Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor HTTP::Server to bind to multiple addresses #5776

Merged
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})"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicated line?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually supposed to ensure that it doesn't simply alternate between addresses or something strange...

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