lib/hatetepe/client.rb in hatetepe-0.5.1 vs lib/hatetepe/client.rb in hatetepe-0.5.2
- old
+ new
@@ -5,10 +5,17 @@
require "hatetepe/connection"
require "hatetepe/parser"
require "hatetepe/request"
require "hatetepe/version"
+module Hatetepe
+ HatetepeError = Class.new(StandardError)
+ RequestError = Class.new(HatetepeError)
+ ClientError = Class.new(RequestError)
+ ServerError = Class.new(RequestError)
+end
+
module Hatetepe::Client
include Hatetepe::Connection
# @api private
Job = Struct.new(:fiber, :request, :sent, :response)
@@ -37,16 +44,15 @@
# Configuration values that overwrite the defaults.
#
# @api semipublic
def initialize(config)
@config = CONFIG_DEFAULTS.merge(config)
+ @ssl_handshake_completed = EM::DefaultDeferrable.new
end
# Initializes the parser, request queue, and middleware pipe.
#
- # TODO: Use +Rack::Builder+ for building the app pipe.
- #
# @see EM::Connection#post_init
#
# @api semipublic
def post_init
@builder, @parser = Hatetepe::Builder.new, Hatetepe::Parser.new
@@ -58,12 +64,18 @@
@app = proc {|request| send_request(request) }
self.comm_inactivity_timeout = config[:timeout]
self.pending_connect_timeout = config[:connect_timeout]
+
+ start_tls if config[:ssl]
end
+ def ssl_handshake_completed
+ EM::Synchrony.next_tick { @ssl_handshake_completed.succeed }
+ end
+
# Feeds response data into the parser.
#
# @see EM::Connection#receive_data
#
# @param [String] data
@@ -99,18 +111,20 @@
# should automatically be closed.
#
# @api public
def <<(request)
Fiber.new do
+ EM::Synchrony.sync(@ssl_handshake_completed) if config[:ssl]
+
response = @app.call(request)
if response && (request.verb == "HEAD" || response.status == 204)
response.body.close_write
end
if !response
- request.fail(nil, self)
+ request.fail
elsif response.failure?
request.fail(response)
else
request.succeed(response)
end
@@ -131,15 +145,56 @@
#
# @return [Hatetepe::Response, nil]
#
# @api public
def request(verb, uri, headers = {}, body = [])
- request = Hatetepe::Request.new(verb, URI(uri), headers, body)
+ uri = URI(uri)
+ uri.scheme ||= @config[:ssl] ? 'http' : 'https'
+ uri.host ||= @config[:host]
+ uri.port ||= @config[:port]
+
+ headers['Host'] ||= "#{uri.host}:#{uri.port}"
+
+ request = Hatetepe::Request.new(verb, URI(uri.to_s), headers, body)
self << request
EM::Synchrony.sync(request)
end
+ # Like +#request+, but raises errors for 4xx and 5xx responses.
+ #
+ # @param [Symbol, String] verb
+ # The HTTP method verb, e.g. +:get+ or +"PUT"+.
+ # @param [String, URI] uri
+ # The request URI.
+ # @param [Hash] headers (optional)
+ # The request headers.
+ # @param [#each] body (optional)
+ # A request body object whose +#each+ method yields objects that respond
+ # to +#to_s+.
+ #
+ # @return [Hatetepe]::Response, nil]
+ #
+ # @raise [Hatetepe::ClientError]
+ # If the server responded with a 4xx status code.
+ # @raise [Hatetepe::ServerError]
+ # If the server responded with a 5xx status code.
+ # @raise [Hatetepe::RequestError]
+ # If the client failed to receive any response at all.
+ def request!(verb, uri, headers = {}, body = [])
+ response = request(verb, uri, headers, body)
+
+ if response.nil?
+ raise Hatetepe::RequestError
+ elsif response.status >= 500
+ raise Hatetepe::ServerError
+ elsif response.status >= 400
+ raise Hatetepe::ClientError
+ end
+
+ response
+ end
+
# Gracefully stops the client.
#
# Waits for all requests to finish and then stops the client.
#
# @api public
@@ -186,13 +241,11 @@
end
# @api public
def self.request(verb, uri, headers = {}, body = [])
uri = URI(uri)
- client = start(host: uri.host, port: uri.port)
+ client = start(host: uri.host, port: uri.port, ssl: uri.scheme == 'https')
client.request(verb, uri, headers, body)
- ensure
- client.stop
end
# Feeds the request into the builder and blocks while waiting for the
# response to arrive.
#