-
-
Save Asmod4n/2a340d35da82a4ffda1b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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