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

Conversation

straight-shoota
Copy link
Member

@straight-shoota 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 (Add HTTP::Server#bind_unix #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 => Add HTTP::Server#bind(URI) #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)

Copy link
Contributor

@RX14 RX14 left a comment

Choose a reason for hiding this comment

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

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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

Copy link
Contributor

Choose a reason for hiding this comment

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

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)

Copy link
Member Author

Choose a reason for hiding this comment

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

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

"obviously" is unneccesary.

end
end

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

Choose a reason for hiding this comment

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

Don't bother.

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

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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?
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure about this.

Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

@RX14 RX14 Mar 7, 2018

Choose a reason for hiding this comment

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

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
Copy link
Contributor

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
Copy link
Member Author

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 jm/feature/http-server-interfaces branch 2 times, most recently from a20b633 to 91b7a9a Compare March 6, 2018 13:50
@straight-shoota straight-shoota changed the title Refactor HTTP::Server to bind to multiple addresses and support Unix sockets Refactor HTTP::Server to bind to multiple addresses Mar 6, 2018
@straight-shoota straight-shoota force-pushed the jm/feature/http-server-interfaces branch from 91b7a9a to 2612dcd Compare March 6, 2018 14:11
Copy link
Contributor

@ysbaddaden ysbaddaden left a comment

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

Are the XAddress overloads really useful?

Copy link
Member Author

Choose a reason for hiding this comment

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

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

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

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).

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 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
Copy link
Contributor

Choose a reason for hiding this comment

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

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?
Copy link
Contributor

Choose a reason for hiding this comment

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

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

@straight-shoota
Copy link
Member Author

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 jm/feature/http-server-interfaces branch 2 times, most recently from f88addb to 83f40d0 Compare March 6, 2018 15:56
@RX14
Copy link
Contributor

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
Copy link
Member Author

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
Copy link
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
Copy link
Member Author

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

@straight-shoota straight-shoota force-pushed the jm/feature/http-server-interfaces branch from 83f40d0 to 3d3b92c Compare March 7, 2018 12:15
@straight-shoota
Copy link
Member Author

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

@jhass
Copy link
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
Copy link
Member Author

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
Copy link
Member Author

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 jm/feature/http-server-interfaces branch 2 times, most recently from c6306a4 to 013a078 Compare March 14, 2018 15:14
@straight-shoota
Copy link
Member Author

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
Copy link
Member Author

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

@ysbaddaden
Copy link
Contributor

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).

Copy link
Contributor

@ysbaddaden ysbaddaden left a comment

Choose a reason for hiding this comment

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

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
Copy link
Contributor

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 jm/feature/http-server-interfaces branch from 013a078 to 9c0ea8e Compare March 15, 2018 12:08
@straight-shoota
Copy link
Member Author

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.


# 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?
Copy link
Member

Choose a reason for hiding this comment

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

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

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure.

Copy link
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

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

@bcardiff
Copy link
Member

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
Copy link
Member Author

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
Copy link
Member

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 jm/feature/http-server-interfaces branch from 96544b6 to b3bd139 Compare April 16, 2018 20:45
@straight-shoota
Copy link
Member Author

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
Copy link
Member

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 😉 )

Copy link
Contributor

@RX14 RX14 left a comment

Choose a reason for hiding this comment

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

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
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

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.

Copy link
Contributor

Choose a reason for hiding this comment

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

having getter? closed would be nice too.

Copy link
Member Author

Choose a reason for hiding this comment

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

Already in there ;)

@straight-shoota straight-shoota force-pushed the jm/feature/http-server-interfaces branch from b3bd139 to 8e17445 Compare April 17, 2018 08:10
@RX14 RX14 added this to the Next milestone Apr 17, 2018
@RX14 RX14 merged commit 01a5126 into crystal-lang:master Apr 17, 2018
@straight-shoota straight-shoota deleted the jm/feature/http-server-interfaces branch April 17, 2018 11:15
@straight-shoota
Copy link
Member Author

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 pushed a commit to chris-huxtable/crystal that referenced this pull request Jun 6, 2018
)

* 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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

7 participants