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
merged 7 commits into from Apr 17, 2018

Conversation

@straight-shoota
Contributor

straight-shoota commented Mar 5, 2018

Until now, a HTTP::Server instance binds to exactly one IP address (host + port). This address was set in the constructor alongside several options for passing one or more handlers.

This resulted in quite a few constructors already, and adding support for more options (such as using a Unix socket) would multiply the numbers. Additionally, it should be possible to have the server listen on multiple addresses, as many other HTTP server implementations.

This PR changes the API of HTTP::Server:

  • The constructor only accepts handlers (in multiple variations, as before).
  • #bind is used to add one ore multiple addresses the server should listen on. It accepts the same arguments previously used in the constructor (port, host, port - each with optional reuse_port).
  • Additional overloads exist for Socket::IPAddress and Socker::Server.
  • You can bind to a Unix socket using #bind(Socket::UNIXAddress) or by supplying a UNIXServer (#5959 )
  • There is also a single-argument overload that accepts a string with either a host + port or a Unix socket (prefixed by unix:) and creates the appropriate server accordingly. See #2735 (issue-comment) for the reasoning behind this. (will be a subsequent PR => #6500)
  • #bind_unused_port is a shortcut for bind(0) and returns the port. This is particularly useful for the use case of binding to an unused port and printing the port (could maybe change the return type to Socket::IPAddress to include the address as well). #port was removed because there is no single port anymore that this method could return.
  • #listen starts accepting incoming requests, as before. However, there is also an catch-all overload which forwards all arguments to #bind and starts listening. This is a shortcut for the following:
server.bind 80
server.listen
# same as
server.listen 80

With a server listening on multiple interfaces, I figured it would be useful to be able to determine from which connection a client request originates, so I added a nilable (for stubbing) instance variable socket to HTTP::Server::Context.

This also includes a minor fix to copy the path of a UNIXServer to each socket. Otherwise you couldn't really tell them apart if (only by the file descriptor). (#5869)

Fixes one half of #2735 (#5959)

@RX14

I'd call them sockets not interfaces in the http server. You call them sockets in the http context.

def self.new(port, &handler : Context ->)
new("127.0.0.1", port, &handler)
# DEPRECATED: Create an instance and call `#bind` instead.

This comment has been minimized.

@RX14

RX14 Mar 6, 2018

Member

We don't deprecate before 1.0, we just remove. Unless it's super duper common and used in many places in one project (think MemoryIO), and even then we make the overloads print notices using macros otherwise nobody will notice.

def bind(address : String, reuse_port : Bool = false) : Socket::Server
if address.starts_with?("unix:")
{% if flag?(:unix) %}
bind(Socket::UNIXAddress.new(address[5..-1])).as(Socket::Server)

This comment has been minimized.

@RX14

RX14 Mar 6, 2018

Member

I'm not sure we want to continue spattering ifdefs around the codebase. If sockets aren't supported, we'll raise in UNIXAddress.new instead of everywhere it's used. Indent is wrong.

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

Yeah, it's probably better this way. The whole Socket API looks like it needs a major refactoring anyway.

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

Some duplication, here. Having different #bind overloads is already redundant, but introducing yet another one with a URI-like string, required for binding UNIX sockets, that feels too much and weirdly designed.

It should either be the only solution, or there should be explicitly named methods (ala puma), which IMHO feels better (an application may require a URI, parse it, and call the correct methods):

  • #bind_tcp_listener(host, port)
  • #bind_ssl_listener(host, port, ctx)
  • #bind_unix_listener(host, port)

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

The UNIX socket part is removed from this PR. Regarding method naming, I'd suggest to continue the discussion in the main thread.

# Creates a `Socket::Server` listenting to *address* and adds it as an interface.
#
# The *address* can either be `host:port` or a Unix socket prefixed by `unix:`.
# This obviously works only on systems supporting Unix sockets.

This comment has been minimized.

@RX14

RX14 Mar 6, 2018

Member

"obviously" is unneccesary.

end
end
# Creates the underlying `TCPServer` if the doesn't already exist.
{% if flag?(:unix) %}

This comment has been minimized.

@RX14

RX14 Mar 6, 2018

Member

Don't bother.

until @wants_close
spawn handle_client(server.accept?)
@interfaces.each do |interface|
spawn handle_client(interface.accept?)

This comment has been minimized.

@RX14

RX14 Mar 6, 2018

Member

This is completely wrong. It will round-robin each interface accepting one connection from each interface in order, instead of which interface is ready to accept. You need to spawn a fiber for each interface.

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

Darn. I'll put my shame hat on and hide in the basement... :(
The worst of it is that I had a relevant spec for this which somehow got lost during rebasing.

@@ -7,8 +7,11 @@ class HTTP::Server
# The `HTTP::Server::Response` to configure and write to.
getter response : Response
# An optional `Socket` which holds the client connection of this context.
getter socket : Socket?

This comment has been minimized.

@RX14

RX14 Mar 6, 2018

Member

I'm not sure about this.

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

Same here. Hijacking the IO must be achieved within a request handler or response output (see WebSocket handler).

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

The websocket handler really hijacks the IO using upgrade. The problem I'm trying to solve is not about taking over the IO handling (including responsibility for calling #close). It's rather to get information about the connection, such as local_address. Don't know about other things... remote_address could be useful.
So maybe we could just expose those in Response?

This comment has been minimized.

@RX14

RX14 Mar 7, 2018

Member

I'd prefer to expose specific metadata regarding the socket, instead of an actual socket you can perform IO on. That seems to be more like work that should be done in another PR though.

@RX14

This comment has been minimized.

Member

RX14 commented Mar 6, 2018

Also I strongly recommend doing the UNIX socket stuff (new feature) in a later PR and leaving this just as a pure refactor.

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 6, 2018

Sure, I can split it up.

I'm not entirely sure about Context#socket but I'm not sure there is an alternative and I think the socket should be accessible from a handler when you can have different ones.

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch 2 times, most recently from a20b633 to 91b7a9a Mar 6, 2018

@straight-shoota straight-shoota changed the title from Refactor `HTTP::Server` to bind to multiple addresses and support Unix sockets to Refactor `HTTP::Server` to bind to multiple addresses Mar 6, 2018

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch from 91b7a9a to 2612dcd Mar 6, 2018

@ysbaddaden

Interesting, and going the right way, but:

  • the constructor methods should be dropped;
  • there are far too many #bind overloads inducing lots of duplication, a handful of them would be far enough; I'd prefer explicitly named methods, too (see comment below).
# You may set *reuse_port* to true to enable the `SO_REUSEPORT` socket option,
# which allows multiple processes to bind to the same port.
def bind(port : Int32, reuse_port : Bool = false) : TCPServer
bind "127.0.0.1", port, reuse_port

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

I was gonna say that binding to 127.0.0.1 makes the #bind(port) overload somewhat useless (invisible on network, doesn't bind ipv6 interfaces) but that's how it's currently done...

That's still weird, thought, since TCPServer.new(port) will bind on :: (all ipv4 & ipv6 interfaces) for example.

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

Yes, this should probably be improved. Although I'm not entirely sure if :: would be a sane default.
I'd suggest to keep the current behavior for now and open an issue about this to fix it once this is merged. WDYT?

This comment has been minimized.

@RX14

RX14 Mar 7, 2018

Member

I prefer to keep everything local-addressed by default, and force people to choose possibly insecure defaults (0.0.0.0, ::) by conscious decision.

This comment has been minimized.

@straight-shoota

straight-shoota Mar 7, 2018

Contributor

Still, we should make sure to take the IPv6 loopback ::1 into account as well. It would probably nice to have a constant or class method in Socket::IPAddress instead of using magic value here.

This comment has been minimized.

@straight-shoota

straight-shoota Mar 7, 2018

Contributor

Maybe we could have a method to listen on 0.0.0.0 and ::, like #bind_all(port : Int32)

def bind(address : String, reuse_port : Bool = false) : Socket::Server
if address.starts_with?("unix:")
{% if flag?(:unix) %}
bind(Socket::UNIXAddress.new(address[5..-1])).as(Socket::Server)

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

Some duplication, here. Having different #bind overloads is already redundant, but introducing yet another one with a URI-like string, required for binding UNIX sockets, that feels too much and weirdly designed.

It should either be the only solution, or there should be explicitly named methods (ala puma), which IMHO feels better (an application may require a URI, parse it, and call the correct methods):

  • #bind_tcp_listener(host, port)
  • #bind_ssl_listener(host, port, ctx)
  • #bind_unix_listener(host, port)
# server = HTTP::Server.new { }
# server.bind Socket::UNIXAddress.new("/tmp/my-socket")
# ```
def bind(address : Socket::UNIXAddress) : UNIXServer

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

Are the XAddress overloads really useful?

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

I guess we don't necessarily need them, it's just that the Socket API is not really nice in that regard, you currently can't directly create a UNIXSocket from a UNIXAddress. But this needs to be fixed there (see #5778) then we can probably do without the overloads. Or have only a general bind(address : Socket::Address) which delegates creating the appropriate server to Socket::Server.new(address : Socket::Address). That would probably be the best solution.

end
# Adds a `Socket::Server` *interface* to this server.
def bind(interface)

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

There should be a #bind(interface : Socket::Server) type constraint.

# Binds to the specified arguments and starts the server.
#
# See `#bind` and `#listen` for details

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

Is it really helpful? We're introducing and require explicit #bind but in the same time we mix its logic (bind an interface) with #listen (for incoming connections).

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

It's somewhat nice but not really necessary, I think. I guess we don't need it and it convolutes the API.

I will remove it. If desired, it can be added later.

@server = nil
@interfaces.each do |interface|
interface.close

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

Closing an interface can raise, which will prevent further interfaces from being closed.

This comment has been minimized.

@straight-shoota

straight-shoota Mar 6, 2018

Contributor

Will wrap it in rescue. We can probably ignore any exceptions, right?

@@ -7,8 +7,11 @@ class HTTP::Server
# The `HTTP::Server::Response` to configure and write to.
getter response : Response
# An optional `Socket` which holds the client connection of this context.
getter socket : Socket?

This comment has been minimized.

@ysbaddaden

ysbaddaden Mar 6, 2018

Member

Same here. Hijacking the IO must be achieved within a request handler or response output (see WebSocket handler).

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 6, 2018

@ysbaddaden Unfortunately, I have pushed new commits while you were reviewing and your comments have been hidden but not all of them are resolved.

The #bind overloads are reduced to a minimum. I don't like having different method names for this because it's just not necessary when the arguments clearly convey the meaning. I don't see much sense in using different names for the same thing.

In your examples, there was also a mention of bind_ssl_listener - do you think we need tls based on individual sockets? I'd favour having the entire server run either with TLS or not.

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch 2 times, most recently from f88addb to 83f40d0 Mar 6, 2018

@RX14

This comment has been minimized.

Member

RX14 commented Mar 7, 2018

I like the listen overload that forwards to bind. The difference between

server = HTTP::Server.new do |ctx|
  ctx.response.puts "Hello World!"
end

server.bind(80)
server.listen

and

server = HTTP::Server.new do |ctx|
  ctx.response.puts "Hello World!"
end

server.listen(80)

is actually quite large, as learning an ordering of methods - rather than simply a single method - is a fair bit more of a hurdle.

I do really want to keep the bind overloads to a minimum though.

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 7, 2018

It definitely has something to it. To me, the strongest point I see against it is the method signature. It would either delegate every single overload of #bind - which means a lot of overloads for #listen as well - or as I had it implemented, just catch the arguments and forward them to #bind. This is easier but #listen's method signature does no longer show what arguments it can accept.
But this might not be such a huge issue after all... hm. The docs can just list all possible overloads and/or reference #bind signatures.
In the end, it is a nice feature for about 90% of users who just need to listen on one socket.

@jhass

This comment has been minimized.

Member

jhass commented Mar 7, 2018

Not sure I'm entirely convinced of it myself, but just as another possibility, we could also have bind, listen without arguments and a bind_and_listen that forwards its argument to bind for the common case

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 7, 2018

Hm, that would make the name for the common case very bulky...

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch from 83f40d0 to 3d3b92c Mar 7, 2018

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 7, 2018

I've removed the commit exposing socket on HTTP::Server::Context and will open a new issue for that.

@jhass

This comment has been minimized.

Member

jhass commented Mar 7, 2018

Maybe we can find a less bulky but not too confusing name? start or serve would come to mind.

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 7, 2018

I doubt it would be helpful to introduce a different name. The main purpose is to listen - it just happens to also add a socket before that. We shouldn't have two method names for listening (which is also blocking).

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 7, 2018

What do you think about the return value of #bind_unused_port? It would make sense to return an IPAddress (ip + port) instead of only a port. That's especially true when called without a host argument, you shouldn't have to guess which IP is used. And the typical usecase will be printing the address (ip + port) like this:

address = server.bind_unused_port
puts "Listening on http://#{address}/"
server.listen

Even if the host is specified, it still makes sense to return the entire address (instead of having different return values depending on the presence of host argument).

And I guess it would be great to have access to the sockets ivar, or maybe only the local addresses of the sockets. If the server is listening on multiple sockets, the equivalent to the above sample could be:

# bind to a few sockets (maybe from config settings)

server.local_addresses.each do |address|
  puts "Listening on http://#{address}/"
end
server.listen

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch 2 times, most recently from c6306a4 to 013a078 Mar 14, 2018

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 14, 2018

I've changed the return type of all bind and bind_unused_port method to Socket::IPAddress. That's what's typically interesting (to get the actual adress if port is zero) and there doesn't need to be direct access to the TCPServer. When this should really be necessary, it is possible to bind a custom TCPServer instance.

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 15, 2018

I'm not sure if bind(server : TCPServer) should maybe be renamed to add(server : TCPServer) or <<(server : TCPServer).

@ysbaddaden

This comment has been minimized.

Member

ysbaddaden commented Mar 15, 2018

Starting individual TCP and SSL servers is half the point of having multiple listeners to me. Without it, your patch would be pointless in Prax for example (binds a single HTTP::Server to both HTTP and HTTPS).

@ysbaddaden

Why drop the binding to UNIX sockets? Since we're refactoring this, I'd introduce new bindings, for example selectively binding SSL and binding UNIX sockets, or at least have a defined design for namings.

If we only bind TCPSocket then #bind is enough, but what if we want to bind a UNIXSocket? or selectively have a SSL context for different servers? I disagree that #bind and overloads convey enough meaning over proper naming (#bind_unix(path), #bind_tcp(host, port) and #bind_ssl(host, port, ctx)) with maybe a URI-like generic (#bind("tcp://host:port")).

Last pedantic: #bind_unused_port doesn't bring much benefit over #bind_tcp(port: 0).

@ysbaddaden

This comment has been minimized.

Member

ysbaddaden commented Mar 15, 2018

Here is a confusing example:

server.bind("localhost")
server.bind("/tmp/app.sock")

A "but binding a TCP server requires to specify a port" is a bad answer, with a #bind(path : String) overload to bind an UNIXSocket, we'd introduce a confusing API, because the compiler won't complain, neither at compile time or runtime: it will bind an UNIXSocket when we expected a TCPSocket to some default port (e.g. 80).

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch from 013a078 to 9c0ea8e Mar 15, 2018

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Mar 15, 2018

I'd wan't to keep it a simple as possible. Keeping track of which sockets need to use TLS inside HTTP::Server seems unnecessarily tedious to me.

What about if we just use a special SSL server socket for this? It would essentially wrap another Socket::Server (TCPServer) and accepts returns a OpenSSL::SSL::Socket::Server wrapping a Socket instead of the TCPSocket directly.
This would make the use of TLS completely transparent from the perspective of the HTTP::Server and would be reusable as a drop-in replacement for a plain TCPServer (of course there should be a bind overload for setting this up which receives a SSL context).

@RX14 asked to introduce Unix sockets in a subsequent PR and I agree. This let's us focus on the general API first (obviously having upcoming Unix sockets in mind) and worry about Unix socket specifics separately.

Your confusing example doesn't work because you can't bind to a host without a port. It would be:

server.bind("localhost", 80)
server.bind("/tmp/app.sock")

The only overlap would be an overload accepting a String as generic URI like tcp://localhost:80, unix:///tmp/app.sock or ssl://0.0.0.0:442 vs. an overload that expects a String as path for a Unix socket.
So, yeah maybe this calls for differently named methods. The downside of this is a more complicated API (a few different method names instead of semantically matching overloads) and it wouldn't be as easy to forward calls from listen which equally helps to provide a simple API as per @RX14's comment.

I'd probably rather think about having a different overload for generic URI strings, only accepting URI type without a named argument or delegate this entirely to Socket::Address:

server.bind Socket::Address.parse "tcp://127.0.0.1:80"
server.bind Socket::Address.parse "unix:///var/run/socket"

You're right, #bind_unused_port doesn't add much over specifying port: 0 anymore. I still like it for it's explicit meaning instead of relying on a magic number.

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})"

This comment has been minimized.

@Sija

Sija Apr 16, 2018

Contributor

Duplicated line?

This comment has been minimized.

@straight-shoota

straight-shoota Apr 16, 2018

Contributor

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

# Starts the server. Blocks until the server is closed.
def listen
raise "Can't start server with not sockets to listen to" if @sockets.empty?

This comment has been minimized.

@bcardiff

bcardiff Apr 16, 2018

Member

Maybe we should include something like "Use HTTP::Server#bind" in the error so we can guide the user a bit. WDYT?

This comment has been minimized.

@straight-shoota

This comment has been minimized.

@straight-shoota

straight-shoota Apr 16, 2018

Contributor

Another issue, which should probably also raise, is when bind is called after listen:

server.bind socket
spawn { server.listen }
server.bind socket2

For this we'd need a ivar to track if the server is listening. It could probably be implemented to retroactively start listening on the additional socket as well, but that's more complicated and probably not that useful without having service objects.

This comment has been minimized.

@bcardiff

bcardiff Apr 16, 2018

Member

Yeap, I would say to add an ivar to track that too.

@bcardiff

This comment has been minimized.

Member

bcardiff commented Apr 16, 2018

Given that argless #listen is still there, wasn't the api of constructing the server with an initial/single host/port and then calling listen comfortable enough?

I fail to see the reason why to get rid of that constructor.

I think that easier/shorter interfaces for common use case are important. And add more fine grained ones to allow more rare scenarios, not at the expense of losing the easier ones.

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Apr 16, 2018

@bcardiff It's comfortable, but such an additional signature for new would add four additional overloads. I'd like to avoid that. Using the listen short cut should be equally comfortable, more intuitive and easier to transform to bind calls:

server = HTTP::Server.new(host, port) { }
server.listen
# vs.
server = HTTP::Server.new { }
server.listen(host, port)
@bcardiff

This comment has been minimized.

Member

bcardiff commented Apr 16, 2018

I get the api is not that different. But when creating a http server is usually with a host and port bind.
And the usually here means that only one overload is needed.

Having a initialize(host : String, port : Int32, &handler) even without the default ip will allow simpler introduction to the http server IMO.

I don't care that much the api is breaking, but the fact that is a nice simpler api for lot's of simple use case where a framework is not used.

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch from 96544b6 to b3bd139 Apr 16, 2018

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Apr 16, 2018

You'd need to have these four additional overloads, even if you just want to support host : String, port : Int32. otherwise the API would be completely arbitrary.

  • self.new(host : String, port : Int32, &handler : HTTP::Handler::Proc)
  • self.new(host : String, port : Int32, handlers : Array(HTTP::Handler), &handler : HTTP::Handler::Proc)
  • self.new(host : String, port : Int32, handlers : Array(HTTP::Handler))
  • initialize(host : String, port : Int32, handler : HTTP::Handler | HTTP::Handler::Proc)

I don't really see how specifying the host and port in the constructor is a simpler introduction to the http server than in the #listen method (see my previous post). I rather think it's the other way around.

@bcardiff

This comment has been minimized.

Member

bcardiff commented Apr 16, 2018

I was thinking just in the first overload. I haven't seen the other used that much in presentations and smalls projects. But, ok. Let's keep the proposal (and update website homepage sample on release 😉 )

@RX14

Also I think that the standard exception style is without a full-stop.

@@ -159,6 +162,9 @@ class HTTP::Server
# Adds a `Socket::Server` *socket* to this server.
def bind(socket : Socket::Server) : Nil
raise "Can't add socket to running server." if listening?
raise "Can't add socket to closed server." if @wants_close

This comment has been minimized.

@RX14

RX14 Apr 16, 2018

Member

Just disallow #close unless listening?. I'd also just rename this variable to @closed because the state persists after it's fully shut down - not just when it's in some "closing down" state that "wants close" implies.

This comment has been minimized.

@straight-shoota

straight-shoota Apr 17, 2018

Contributor

You need to be able to call #close even if the server is not listening because sockets may already be bound and need to be released.

This comment has been minimized.

@Sija

Sija Apr 17, 2018

Contributor

having getter? closed would be nice too.

This comment has been minimized.

@straight-shoota

straight-shoota Apr 17, 2018

Contributor

Already in there ;)

@straight-shoota straight-shoota force-pushed the straight-shoota:jm/feature/http-server-interfaces branch from b3bd139 to 8e17445 Apr 17, 2018

@RX14

RX14 approved these changes Apr 17, 2018

@RX14 RX14 added the kind:feature label Apr 17, 2018

@RX14 RX14 added this to the Next milestone Apr 17, 2018

@RX14 RX14 merged commit 01a5126 into crystal-lang:master Apr 17, 2018

3 of 4 checks passed

continuous-integration/travis-ci/pr The Travis CI build failed
Details
ci/circleci: test_darwin Your tests passed on CircleCI!
Details
ci/circleci: test_linux Your tests passed on CircleCI!
Details
ci/circleci: test_linux32 Your tests passed on CircleCI!
Details

@straight-shoota straight-shoota deleted the straight-shoota:jm/feature/http-server-interfaces branch Apr 17, 2018

@straight-shoota

This comment has been minimized.

Contributor

straight-shoota commented Apr 17, 2018

@RX14 The squashed commit message of 01a5126 just joined all individual commit messages together, including the fixups. It's not a big deal but it would be preferable if at least the fixups were removed as they don't add anything to the message. (for future merges)

chris-huxtable added a commit to chris-huxtable/crystal that referenced this pull request Jun 6, 2018

Refactor `HTTP::Server` to bind to multiple addresses (crystal-lang#5776
)

* 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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment