Skip to content

Instantly share code, notes, and snippets.

@Asmod4n
Last active December 28, 2015 03:59
Show Gist options
  • Save Asmod4n/2a340d35da82a4ffda1b to your computer and use it in GitHub Desktop.
Save Asmod4n/2a340d35da82a4ffda1b to your computer and use it in GitHub Desktop.
module Reel
# The Reel HTTP server class
#
# This class is a Celluloid::IO actor which provides a barebones HTTP(S) server
class Server
include Celluloid::IO
# How many connections to backlog in the TCP accept queue
DEFAULT_BACKLOG = 100
execute_block_on_receiver :initialize
finalizer :shutdown
def initialize(host_or_url, port_or_type, options = {}, &callback)
@ssl_server = false
if options[:spy]
require 'reel/spy'
@spy = STDOUT
end
@callback = callback
setup_server(host_or_url, port_or_type, options)
async.run
end
def shutdown
@server.close if @server
end
def run
if @ssl_server
loop do
begin
socket = @server.accept
rescue OpenSSL::SSL::SSLError => ex
Logger.warn "Error accepting SSLSocket: #{ex.class}: #{ex.to_s}"
retry
end
async.handle_connection socket
end
else
loop { async.handle_connection @server.accept }
end
end
private
def setup_server(host_or_url, port_or_type, options)
if port_or_type == 'url'
require 'uri'
uri = URI.parse(host_or_url)
if uri.scheme == 'http'
if uri.path.length == 0 && uri.host != nil
@server = tcp(uri.host, uri.port, options)
elsif uri.path.length > 0
@server = unix(uri.path)
end
elsif uri.scheme == 'https'
@ssl_server = true
if uri.path.length == 0 && uri.host != nil
@server = ssl(tcp(uri.host, uri.port, options), options)
elsif uri.path.length > 0
@server = ssl(unix(uri.path), options)
end
end
else
@server = tcp(host_or_url, port_or_type, options)
end
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 [TCPServer] Celluloid::IO TCPServer
#
#
def tcp(host, port, options)
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)
server
end
# Create a Reel server over a UNIX socket.
#
# @param [String] socket_path path to the UNIX socket
#
def unix(socket_path)
File.delete(socket_path) if File.exists?(socket_path)
Celluloid::IO::UNIXServer.new(socket_path)
end
# Setup a new Reel HTTPS server
#
# @param [Socket] Socket to be wrapped around
# @option options [String] :cert the server's SSL certificate
# @option options [String] :key the server's SSL key
#
# @return [SSLServer] Reel HTTPS server socket
def ssl(socket, options)
# 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)
ssl_context = OpenSSL::SSL::SSLContext.new
ssl_context.cert = OpenSSL::X509::Certificate.new options.fetch(:cert)
ssl_context.key = OpenSSL::PKey::RSA.new options.fetch(:key)
ssl_context.ca_file = options[:ca_file]
ssl_context.ca_path = options[:ca_path]
# if verify_mode isn't explicitly set, verify peers if we've
# been provided CA information that would enable us to do so
ssl_context.verify_mode = case
when options.include?(:verify_mode) then options[:verify_mode]
when options.include?(:ca_file) then OpenSSL::SSL::VERIFY_PEER
when options.include?(:ca_path) then OpenSSL::SSL::VERIFY_PEER
else OpenSSL::SSL::VERIFY_NONE
end
# wrap an SSLServer around the Reel::Server we've been given
Celluloid::IO::SSLServer.new(socket, ssl_context)
end
def handle_connection(socket)
if @spy
socket = Reel::Spy.new(socket, @spy)
end
connection = Connection.new(socket)
begin
@callback.call(connection)
ensure
if connection.attached?
connection.close rescue nil
end
end
rescue RequestError, EOFError
# Client disconnected prematurely
# TODO: log this?
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment