Skip to content
This repository has been archived by the owner on Dec 7, 2018. It is now read-only.

Refactor Reel::Server, refactor/retrofit/abandon SocketMixin #121

Merged
merged 49 commits into from
Dec 12, 2013
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
169a7fd
Add SocketMixin for de/optimization per-server.
Nov 13, 2013
f19c2d1
Additional clarity on optimizations possible
Nov 13, 2013
6fa45f3
De/optimize at start and shutdown; style tweaks, and added SocketMixin
Nov 13, 2013
d1fab02
Deoptimize before close
Nov 13, 2013
9f68968
Change to TODO versus implied TODO
Nov 13, 2013
c5c34cb
Stylistic tweaks, and reshuffled including location to extend self ( …
Nov 13, 2013
41f0289
Style tweaks, and reshuffled at inclusion point to extend self
Nov 13, 2013
f5ed50a
SocketMixin module extends self
Nov 13, 2013
0546ecc
.is_a? TCPSocket vs. TCPSocket ===
Nov 13, 2013
38dfccf
Splitting out servers
Nov 13, 2013
c1f0a7f
Refactor servers into one foundation and three protocols.
Nov 13, 2013
fe9f290
Finish refactor of Server #120 and retrofit of SocketMixin #114
Nov 13, 2013
6975e29
Space made
Nov 13, 2013
516bf12
Update usage examples, references in docs, and limited tests.
Nov 13, 2013
5337bdf
Minor change to verbiage.
Nov 13, 2013
3d1fdd2
Trim down SocketMixin and make style changes per request
Nov 13, 2013
5eb4ebf
Abandon SocketMixin
Nov 13, 2013
c7a05d8
remove SocketMixin and bring in #optimize instead, then use it.
Nov 13, 2013
39fce89
Edit to class documentation
Nov 13, 2013
cc5368c
Change to Server::PROTOCOL naming.
Nov 13, 2013
38a7a4a
Consistency of spacing
Nov 13, 2013
aabd214
Change Server naming to Reel::Server::PROTOCOL
Nov 13, 2013
1f83b88
Reinsert < Server
Nov 13, 2013
ce2d90d
UNIXServer -> Server::UNIX
Nov 13, 2013
f1a8ecf
Pull out execute_block_on_receiver from subclasses
Nov 13, 2013
fab05f8
Fix calls to Celluloid::IO::SSLServer, and tweak doc.
Nov 13, 2013
0d47f52
Fixed incorrectly named class variable
Nov 13, 2013
a64e4ba
Fixed UNIX sockets test
Nov 13, 2013
081d989
Fixing UNIX server per test failures
Nov 13, 2013
75bc958
Pieced out server specs, ready for UNIX test.
Nov 13, 2013
9dc37aa
specs did not come across in last commit
Nov 13, 2013
8bb67b9
Fix broken UNIX server test
stouset Nov 13, 2013
e985d41
Whitespace fixes
stouset Nov 13, 2013
fd1f40c
Merge pull request #2 from stouset/refactor-servers
digitalextremist Nov 13, 2013
c4b0f6d
Ensure UNIX server receives correct HTTP body
stouset Nov 13, 2013
d6891a6
Avoid race condition with readpartial
stouset Nov 13, 2013
4d3d033
Merge pull request #3 from stouset/refactor-servers
digitalextremist Nov 13, 2013
9501d5b
Checking theory if placement outside ensure fixes jRuby
Nov 13, 2013
eb461c8
Cleaned out Server::UNIX code after moving it to its own branch on ce…
Dec 11, 2013
f426928
Remove unix server for now, until #123
Dec 11, 2013
511ca32
Remove spacing in parans
Dec 11, 2013
fd8c8dc
Remove spaces inside parens
Dec 11, 2013
4659b4e
Tweak to wrapping within 80 characters
Dec 12, 2013
37dd5fc
tweaks I did not save in my editor before commit
Dec 12, 2013
99c2f7a
Another attempt at proper text formatting
Dec 12, 2013
e977bcd
No more 1.9 mode: https://github.com/travis-ci/travis-ci/issues/1641
Dec 12, 2013
18f32f3
Suggested per https://github.com/travis-ci/travis-ci/issues/1641
Dec 12, 2013
feae2f7
Fixes to rbx coverage.
Dec 12, 2013
a91f046
End of massaging rbx into actual success. Failure accepted.
Dec 12, 2013
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ traditional multithreaded blocking I/O support too.
[nio4r]: https://github.com/celluloid/nio4r

Connections to Reel can be either non-blocking and handled entirely within
the Reel::Server thread, or the same connections can be dispatched to worker
the Reel::Server thread ( handling HTTP, SSL, or UNIX sockets ),
Copy link
Member

Choose a reason for hiding this comment

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

nit: no spaces inside parens please

or the same connections can be dispatched to worker
Copy link
Member

Choose a reason for hiding this comment

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

nit: word wrapping (please wrap to ~80 columns)

Copy link
Member Author

Choose a reason for hiding this comment

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

within80

Already within 80 here.

Copy link
Member

Choose a reason for hiding this comment

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

It's within 80, yes, but it's underindented relative to the rest of the text.

threads where they will perform ordinary blocking IO. Reel provides no
built-in thread pool, however you can build one yourself using Celluloid.pool,
or because Celluloid already pools threads to begin with, you can simply use
Expand Down Expand Up @@ -132,7 +133,7 @@ Reel lets you pass a block to initialize which receives connections:
```ruby
require 'reel'

Reel::Server.supervise("0.0.0.0", 3000) do |connection|
Reel::HTTPServer.supervise("0.0.0.0", 3000) do |connection|
# Support multiple keep-alive requests per connection
connection.each_request do |request|
# WebSocket support
Expand Down Expand Up @@ -163,7 +164,7 @@ You can also subclass Reel, which allows additional customizations:
```ruby
require 'reel'

class MyServer < Reel::Server
class MyServer < Reel::HTTPServer
def initialize(host = "127.0.0.1", port = 3000)
super(host, port, &method(:on_connection))
end
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/hello_reel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
addr, port = '127.0.0.1', 1234

puts "*** Starting server on #{addr}:#{port}"
Reel::Server.new(addr, port) do |connection|
Reel::HTTPServer.new(addr, port) do |connection|
connection.respond :ok, "Hello World"
end

Expand Down
2 changes: 1 addition & 1 deletion benchmarks/reel_pool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def handle_request(request)

connectionPool = MyConnectionHandler.pool

Reel::Server.run('127.0.0.1', 3000) do |connection|
Reel::HTTPServer.run('127.0.0.1', 3000) do |connection|
# We're handing this connection off to another actor, so
# we detach it first before handing it off
connection.detach
Expand Down
25 changes: 0 additions & 25 deletions examples/chunked.rb

This file was deleted.

2 changes: 1 addition & 1 deletion examples/hello_world.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
addr, port = '127.0.0.1', 1234

puts "*** Starting server on http://#{addr}:#{port}"
Reel::Server.run(addr, port) do |connection|
Reel::HTTPServer.run(addr, port) do |connection|
# For keep-alive support
connection.each_request do |request|
# Ordinarily we'd route the request here, e.g.
Expand Down
2 changes: 1 addition & 1 deletion examples/roundtrip.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def new_message(topic)
end
end

class WebServer < Reel::Server
class WebServer < Reel::HTTPServer
include Celluloid::Logger

def initialize(host = "0.0.0.0", port = 9000)
Expand Down
2 changes: 1 addition & 1 deletion examples/server_sent_events.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
require 'reel'


class ServerSentEvents < Reel::Server
class ServerSentEvents < Reel::HTTPServer
include Celluloid::Logger

def initialize(ip = '127.0.0.1', port = 63310)
Expand Down
2 changes: 1 addition & 1 deletion examples/spy_hello_world.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
addr, port = '127.0.0.1', 1234

puts "*** Starting server on http://#{addr}:#{port}"
Reel::Server.run(addr, port, spy: true) do |connection|
Reel::HTTPServer.run(addr, port, spy: true) do |connection|
# For keep-alive support
connection.each_request do |request|
# Ordinarily we'd route the request here, e.g.
Expand Down
6 changes: 3 additions & 3 deletions examples/websockets.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ def notify_time_change(topic, new_time)
end
end

class WebServer < Reel::Server
class WebServer < Reel::HTTPServer
include Celluloid::Logger

def initialize(host = "127.0.0.1", port = 1234)
Expand All @@ -53,11 +53,11 @@ def on_connection(connection)

# We're going to hand off this connection to another actor (TimeClient)
# However, initially Reel::Connections are "attached" to the
# Reel::Server actor, meaning that the server manages the connection
# Reel::HTTPServer actor, meaning that the server manages the connection
# lifecycle (e.g. error handling) for us.
#
# If we want to hand this connection off to another actor, we first
# need to detach it from the Reel::Server
# need to detach it from the Reel::Server ( in this case, Reel::HTTPServer )
connection.detach

route_websocket request.websocket
Expand Down
6 changes: 5 additions & 1 deletion lib/reel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
require 'reel/logger'
require 'reel/request'
require 'reel/response'

require 'reel/server'
require 'reel/ssl_server'
require 'reel/server/http'
require 'reel/server/ssl'
require 'reel/server/unix'

require 'reel/websocket'
require 'reel/stream'

Expand Down
48 changes: 48 additions & 0 deletions lib/reel/mixins.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def remote_host
# NOTE: Celluloid::IO does not yet support non-blocking reverse DNS
socket.peeraddr(true)[2]
end

end

module RequestMixin
Expand Down Expand Up @@ -60,4 +61,51 @@ def fragment
end

end

module SocketMixin

# Optimizations possible, depending on OS:

# TCP_NODELAY: prevent TCP packets from being buffered
# TCP_CORK: TODO: tersely describe
Copy link
Member

Choose a reason for hiding this comment

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

Cork is not being used correctly. Can you please remove it?

# SO_REUSEADDR: TODO: tersely describe

if RUBY_PLATFORM =~ /linux/
Copy link
Member

Choose a reason for hiding this comment

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

Can you leave this out for the time being?


# Only Linux supports the mix of socket behaviors given in these optimizations.
# Beaware, certain optimizations may work individually off Linux; not together.
def optimize_socket socket
Copy link
Member

Choose a reason for hiding this comment

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

No "Seattle style" parameters please. Please add parens around socket

if socket.is_a? TCPSocket
socket.setsockopt( Socket::IPPROTO_TCP, :TCP_NODELAY, 1 )
Copy link
Member

Choose a reason for hiding this comment

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

Can you remove the spaces around the parameters?

socket.setsockopt( Socket::IPPROTO_TCP, 3, 1 ) # TCP_CORK
Copy link
Member

Choose a reason for hiding this comment

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

Please remove CORK

socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
end
end

def deoptimize_socket socket
Copy link
Member

Choose a reason for hiding this comment

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

Ditto

if socket.is_a? TCPSocket
socket.setsockopt( Socket::IPPROTO_TCP, :TCP_NODELAY, 1 )
socket.setsockopt( Socket::IPPROTO_TCP, 3, 1 ) # TCP_CORK
Copy link
Member

Choose a reason for hiding this comment

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

Ditto

socket.setsockopt( Socket::SOL_SOCKET, Socket::SO_REUSEADDR, 1 )
end
end

else

# If the underying OS is not Linux, apply the remaining available optimizations.
def optimize_socket socket
if socket.is_a? TCPSocket
socket.setsockopt( Socket::IPPROTO_TCP, :TCP_NODELAY, 1 )
end
end

def deoptimize_socket socket
if socket.is_a? TCPSocket
socket.setsockopt( Socket::IPPROTO_TCP, :TCP_NODELAY, 0 )
end
end
end

end

end
71 changes: 18 additions & 53 deletions lib/reel/server.rb
Original file line number Diff line number Diff line change
@@ -1,71 +1,36 @@
module Reel
# The Reel HTTP server class
# The Reel server ( foundation ) class
Copy link
Member

Choose a reason for hiding this comment

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

You sure like those ( spaced out ) parens don't you? ;)

I'd probably say this here:

Base class for Reel servers

Copy link
Member Author

Choose a reason for hiding this comment

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

Roger, and yes, but I can keep in mind the convention in use and be less spaced out.

#
# This class is a Celluloid::IO actor which provides a bareboens HTTP server
# For HTTPS support, use Reel::SSLServer
# This class is a Celluloid::IO actor which provides a barebones server
# which does not open a socket itself, it just begin handling connections once
# initialized with a specific kind of protocol-based server.

# For specific protocol support, use:

# Reel::HTTPServer
# Reel::SSLServer
# Reel::UNIXServer
Copy link
Member

Choose a reason for hiding this comment

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

Should be Reel::Server::UNIX


class Server
include Celluloid::IO
include SocketMixin

# How many connections to backlog in the TCP accept queue
DEFAULT_BACKLOG = 100

execute_block_on_receiver :initialize
finalizer :shutdown

# Allow the existing `new` to be called, even though we will
# replace it with a default version that creates HTTP servers over
# TCP sockets.
#
class << self
alias_method :_new, :new
protected :_new
end

# Create a new Reel HTTP server
#
# @param [String] host address to bind to
# @param [Fixnum] port to bind to
# @option options [Fixnum] backlog of requests to accept
# @option options [true] spy on the request
#
# @return [Reel::SSLServer] Reel HTTPS server actor
#
# ::new was overridden for backwards compatibility. The underlying
# #initialize method now accepts a `server` param that is
# responsible for having established the bi-directional
# communication channel. ::new uses the existing (sane) default of
# setting up the TCP channel for the user.
#
def self.new(host, port, options = {} , &callback)
server = Celluloid::IO::TCPServer.new(host, port)
backlog = options.fetch(:backlog, DEFAULT_BACKLOG)

# prevent TCP packets from being buffered
server.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
server.listen(backlog)

self._new(server, options, &callback)
end

# Create a Reel HTTP server over a UNIX socket.
#
# @param [String] socket_path path to the UNIX socket
# @option options [true] spy on the request
#
def self.unix(socket_path, options = {}, &callback)
server = Celluloid::IO::UNIXServer.new(socket_path)

self._new(server, options, &callback)
end

def initialize(server, options = {}, &callback)
def initialize(server, options={}, &callback)
@spy = STDOUT if options[:spy]
@server = server
@options = options
@callback = callback
@server = server

@server.listen(options.fetch(:backlog, DEFAULT_BACKLOG))

async.run
end

end

def shutdown
@server.close if @server
Expand Down
25 changes: 25 additions & 0 deletions lib/reel/server/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Reel
class HTTPServer < Server
Copy link
Member

Choose a reason for hiding this comment

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

This breaks the mapping between class names and file names. I'd suggest reel/server/http.rb => Reel::Server::HTTP or reel/http_server.rb => Reel::HTTPServer

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok we were waiting for this confirmation, I will refactor based on that.

Copy link
Member Author

Choose a reason for hiding this comment

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

Changed naming and will change all other references to HTTPServer and SSLServer after testing.


execute_block_on_receiver :initialize

# Create a new Reel HTTPS server
#
# @param [String] host address to bind to
# @param [Fixnum] port to bind to
# @option options [Fixnum] backlog of requests to accept
#
# @return [Reel::HTTPServer] Reel HTTP server actor
def initialize(host, port, options={}, &callback)
optimize_socket server = Celluloid::IO::TCPServer.new(host, port)
options.merge!({ :host => host, :port => port })
super(server, options, &callback)
end

def shutdown
deoptimize_socket @server
super
end

end
end
Copy link
Contributor

Choose a reason for hiding this comment

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

HTTPServer looks like it can no longer be composed with SSLServer, which was the whole point of the original approach. :(

18 changes: 13 additions & 5 deletions lib/reel/ssl_server.rb → lib/reel/server/ssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ class SSLServer < Server
# @option options [String] :key the server's SSL key
#
# @return [Reel::SSLServer] Reel HTTPS server actor
def initialize(server, options = {}, &callback)
def initialize(host, port, options={}, &callback)

# Ideally we can encapsulate this rather than making Ruby OpenSSL a
# mandatory part of the Reel API. It would be nice to support
# alternatives (e.g. Puma's MiniSSL)
Expand All @@ -31,16 +32,23 @@ def initialize(server, options = {}, &callback)
else OpenSSL::SSL::VERIFY_NONE
end

# wrap an SSLServer around the Reel::Server we've been given
ssl_server = Celluloid::IO::SSLServer.new(server, ssl_context)
optimize_socket @tcpserver = Celluloid::IO::TCPServer.new(host, port)

server = Celluloid::IO::SSLServer.new(@tcpserver, ssl_context)
options.merge!({ :host => host, :port => port })
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this necessary if the TCPServer has already been established on the 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.

That is where the TCPServer is created though, which is then wrapped in IO::SSLServer and then the wrapped server is sent to Reel::Server at super(server, options, &callback)

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, I mean the merging of host and port into the options hash.

Copy link
Member Author

Choose a reason for hiding this comment

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

@stouset I do that so the Server can be asked for what port it is on, and what host it is listening on. It is not necessary, per se, but I saved @options in Server and you'll see in UNIXServer I also save @options[:socket_path]

In what I've seen, implementations sometimes need to know the underlying options the server is configured for to know how to setup environments properly request by request, or connection by connection.

Copy link
Member

Choose a reason for hiding this comment

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

Style nit: this (and the others) should be:

options.merge!(host: host, port: 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.

got it //


super(server, options, &callback)
end

super(ssl_server, options, &callback)
def shutdown
deoptimize_socket @tcpserver
super
end

def run
loop do
begin
socket = @server.accept
socket = @handler.accept
rescue OpenSSL::SSL::SSLError => ex
Logger.warn "Error accepting SSLSocket: #{ex.class}: #{ex.to_s}"
retry
Expand Down
19 changes: 19 additions & 0 deletions lib/reel/server/unix.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
module Reel
class UNIXServer < Server

execute_block_on_receiver :initialize

# Create a new Reel HTTPS server
#
# @option options [String] socket path to bind to
# @option options [Fixnum] backlog of requests to accept
#
# @return [Reel::UNIXServer] Reel UNIX server actor
def initialize(socket_path, options={}, &callback)
server = Celluloid::IO::UNIXServer.new(socket_path)
options[:socket_path] = socket_path
super(server, options, &callback)
end

end
end
2 changes: 1 addition & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ def example_path; "/example"; end
def example_url; "http://#{example_addr}:#{example_port}#{example_path}"; end

def with_reel(handler)
server = Reel::Server.new(example_addr, example_port, &handler)
server = Reel::HTTPServer.new(example_addr, example_port, &handler)
yield server
ensure
server.terminate if server && server.alive?
Expand Down