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. #