Skip to content

Commit

Permalink
Merge branch 'keepalive'
Browse files Browse the repository at this point in the history
Conflicts:
	lib/goliath.rb
  • Loading branch information
igrigorik committed Feb 24, 2011
2 parents 4e7a025 + 0e6bf34 commit 310323b
Show file tree
Hide file tree
Showing 20 changed files with 444 additions and 419 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ Gemfile.lock
.yardoc
.livereload
*.watchr
*.rbc
2 changes: 1 addition & 1 deletion goliath.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ Gem::Specification.new do |s|

s.add_development_dependency 'rspec', '>2.0'
s.add_development_dependency 'nokogiri'
s.add_development_dependency 'em-http-request'
s.add_development_dependency 'em-http-request', '>= 1.0.0.beta.1'
s.add_development_dependency 'yajl-ruby'

s.add_development_dependency 'yard'
Expand Down
8 changes: 7 additions & 1 deletion lib/goliath.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
require 'eventmachine'
require 'http/parser'
require 'async_rack'
require 'stringio'

require 'goliath/version'
require 'goliath/goliath'
require 'goliath/runner'
require 'goliath/server'
require 'goliath/constants'
require 'goliath/connection'
require 'goliath/request'
require 'goliath/response'
Expand All @@ -28,4 +34,4 @@

require 'goliath/api'

require 'goliath/application'
require 'goliath/application'
12 changes: 5 additions & 7 deletions lib/goliath/api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ module Goliath
# end
#
class API
GOLIATH_ENV = 'goliath.env'

class << self
# Retrieves the middlewares defined by this API server
#
Expand Down Expand Up @@ -99,7 +97,7 @@ def options_parser(opts, options)
#
# @return [Goliath::Env] The current environment data for the request
def env
Thread.current[GOLIATH_ENV]
Thread.current[Goliath::Constants::GOLIATH_ENV]
end

# The API will proxy missing calls to the env object if possible.
Expand Down Expand Up @@ -130,20 +128,20 @@ def method_missing(name, *args, &blk)
def call(env)
Fiber.new {
begin
Thread.current[GOLIATH_ENV] = env
Thread.current[Goliath::Constants::GOLIATH_ENV] = env
status, headers, body = response(env)

if body == Goliath::Response::STREAMING
env[Goliath::Request::STREAM_START].call(status, headers)
env[Goliath::Constants::STREAM_START].call(status, headers)
else
env[Goliath::Request::ASYNC_CALLBACK].call([status, headers, body])
env[Goliath::Constants::ASYNC_CALLBACK].call([status, headers, body])
end

rescue Exception => e
env.logger.error(e.message)
env.logger.error(e.backtrace.join("\n"))

env[Goliath::Request::ASYNC_CALLBACK].call([400, {}, {:error => e.message}])
env[Goliath::Constants::ASYNC_CALLBACK].call([400, {}, {:error => e.message}])
end
}.resume

Expand Down
157 changes: 54 additions & 103 deletions lib/goliath/connection.rb
Original file line number Diff line number Diff line change
@@ -1,108 +1,78 @@
require 'goliath/request'
require 'goliath/response'
require 'http/parser'
require 'goliath/env'

module Goliath
# @private
class Connection < EM::Connection
attr_accessor :app, :request, :response, :port
attr_reader :logger, :status, :config, :options
include Constants

attr_accessor :app, :port, :logger, :status, :config, :options
attr_reader :parser

AsyncResponse = [-1, {}, []].freeze

def post_init
@request = Goliath::Request.new
@response = Goliath::Response.new
@current = nil
@requests = []
@pending = []

@parser = Http::Parser.new
@parser.on_headers_complete = proc do |h|

env = Goliath::Env.new
env[OPTIONS] = options
env[SERVER_PORT] = port.to_s
env[LOGGER] = logger
env[OPTIONS] = options
env[STATUS] = status
env[CONFIG] = config
env[REMOTE_ADDR] = remote_address

r = Goliath::Request.new(@app, self, env)
r.parse_header(h, @parser)

@requests.push r
end

@parser.on_body = proc do |data|
@requests.first.parse(data)
end

@parser.on_message_complete = proc do
req = @requests.shift

@request.remote_address = remote_address
@request.async_callback = method(:async_process)
if @current.nil?
@current = req
@current.succeed
else
@pending.push req
end

@request.stream_start = method(:stream_start)
@request.stream_send = method(:stream_send)
@request.stream_close = method(:stream_close)
req.process
end
end

def receive_data(data)
begin
request.parse(data)
process if request.finished?
@parser << data
rescue HTTP::Parser::Error => e
terminate_request
terminate_request(false)
end
end

def process
@request.port = port.to_s
response.send_close = request.env[Goliath::Request::HEADERS]['Connection'] rescue nil
post_process(@app.call(@request.env))

rescue Exception => e
logger.error("#{e.message}\n#{e.backtrace.join("\n")}")
post_process([500, {}, 'An error happened'])
end

def async_process(results)
@response.status, @response.headers, @response.body = *results

log_request(:async, @response)
send_response
terminate_request
end

def stream_start(status, headers)
send_data(@response.head)
send_data(@response.headers_output)
end

def stream_send(data)
send_data(data)
end

def stream_close
terminate_request
end

def log_request(type, response)
logger.info("#{type} status: #{@response.status}, " +
"Content-Length: #{@response.headers['Content-Length']}, " +
"Response Time: #{"%.2f" % ((Time.now.to_f - request.env[:start_time]) * 1000)}ms")
end

def post_process(results)
results = results.to_a
return if async_response?(results)

@response.status, @response.headers, @response.body = *results
log_request(:sync, @response)
send_response

rescue Exception => e
logger.error("#{e.message}\n#{e.backtrace.join("\n")}")

ensure
terminate_request if not async_response?(results)
end

def send_response
@response.each { |chunk| send_data(chunk) }
end

def async_response?(results)
results && results.first == AsyncResponse.first
end

def terminate_request
close_connection_after_writing rescue nil
close_request_response
def unbind
@requests.map {|r| r.close }
end

def close_request_response
@request.async_close.succeed
@response.close rescue nil
end
def terminate_request(keep_alive)
if req = @pending.shift
@current = req
@current.succeed
else
@current = nil
end

def unbind
@request.async_close.succeed unless @request.async_close.nil?
@response.body.fail if @response.body.respond_to?(:fail)
close_connection_after_writing rescue nil if !keep_alive
end

def remote_address
Expand All @@ -111,24 +81,5 @@ def remote_address
nil
end

def logger=(logger)
@logger = logger
@request.logger = logger
end

def status=(status)
@status = status
@request.status = status
end

def config=(config)
@config = config
@request.config = config
end

def options=(options)
@options = options
@request.options = options
end
end
end
50 changes: 50 additions & 0 deletions lib/goliath/constants.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Goliath
module Constants
INITIAL_BODY = ''
# Force external_encoding of request's body to ASCII_8BIT
INITIAL_BODY.encode!(Encoding::ASCII_8BIT) if INITIAL_BODY.respond_to?(:encode)

SERVER_SOFTWARE = 'SERVER_SOFTWARE'
SERVER = 'Goliath'

HTTP_PREFIX = 'HTTP_'
LOCALHOST = 'localhost'
LOGGER = 'logger'
STATUS = 'status'
CONFIG = 'config'
OPTIONS = 'options'

RACK_INPUT = 'rack.input'
RACK_VERSION = 'rack.version'
RACK_ERRORS = 'rack.errors'
RACK_MULTITHREAD = 'rack.multithread'
RACK_MULTIPROCESS = 'rack.multiprocess'
RACK_RUN_ONCE = 'rack.run_once'
RACK_VERSION_NUM = [1, 0]

ASYNC_CALLBACK = 'async.callback'
ASYNC_CLOSE = 'async.close'

STREAM_START = 'stream.start'
STREAM_SEND = 'stream.send'
STREAM_CLOSE = 'stream.close'

SERVER_NAME = 'SERVER_NAME'
SERVER_PORT = 'SERVER_PORT'
SCRIPT_NAME = 'SCRIPT_NAME'
REMOTE_ADDR = 'REMOTE_ADDR'
CONTENT_LENGTH = 'CONTENT_LENGTH'
REQUEST_METHOD = 'REQUEST_METHOD'
REQUEST_URI = 'REQUEST_URI'
QUERY_STRING = 'QUERY_STRING'
HTTP_VERSION = 'HTTP_VERSION'
REQUEST_PATH = 'REQUEST_PATH'
PATH_INFO = 'PATH_INFO'
FRAGMENT = 'FRAGMENT'
CONNECTION = 'CONNECTION'

GOLIATH_ENV = 'goliath.env'

HOST_PORT_REGEXP = /(?<host>.*?):(?<port>.*)/
end
end
18 changes: 15 additions & 3 deletions lib/goliath/env.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
require 'goliath/constants'

module Goliath
# Holds information for the current request.
#
# Goliath::Env also provides access to the logger, configuration information
# and anything else set into the config data during initialization.
class Env < Hash
include Constants

# Create a new Goliath::Env object
#
# @return [Goliath::Env] The Goliath::Env object
def initialize
self[SERVER_SOFTWARE] = SERVER
self[SERVER_NAME] = LOCALHOST
self[RACK_VERSION] = RACK_VERSION_NUM
self[RACK_ERRORS] = STDERR
self[RACK_MULTITHREAD] = false
self[RACK_MULTIPROCESS] = false
self[RACK_RUN_ONCE] = false

self[:start_time] = Time.now.to_f
self[:time] = Time.now.to_f
self[:trace] = []
Expand Down Expand Up @@ -51,7 +63,7 @@ def trace_stats
#
# @param blk The block to execute.
def on_close(&blk)
self[Goliath::Request::ASYNC_CLOSE].callback &blk
self[ASYNC_CLOSE].callback &blk
end

# If the API is a streaming API this will send the provided data to the client.
Expand All @@ -60,13 +72,13 @@ def on_close(&blk)
#
# @param data [String] The data to send to the client.
def stream_send(data)
self[Goliath::Request::STREAM_SEND].call(data)
self[STREAM_SEND].call(data)
end

# If the API is a streaming API this will be executed by the API to signal that
# the stream is complete. This will close the connection with the client.
def stream_close
self[Goliath::Request::STREAM_CLOSE].call
self[STREAM_CLOSE].call
end

# @param name [Symbol] The method to check if we respond to it.
Expand Down
8 changes: 3 additions & 5 deletions lib/goliath/rack/formatters/html.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,10 @@ def call(env)
end

def post_process(status, headers, body)
if html_response?(headers)
body = StringIO.new(to_html(body, false))
end
body = to_html(body, false) if html_response?(headers)
[status, headers, body]
end

def html_response?(headers)
headers['Content-Type'] =~ %r{^text/html}
end
Expand All @@ -49,7 +47,7 @@ def to_html(content, fragment=true)
when "String" then string_to_html(content)
else string_to_html(content)
end

html_string += html_footer unless fragment
html_string
end
Expand Down
Loading

0 comments on commit 310323b

Please sign in to comment.