class HTTP::Client
Clients make requests and receive responses
def finish_response
def finish_response @socket.close if @socket && !@socket.closed? @parser.reset @socket = nil end
def initialize(default_options = {})
def initialize(default_options = {}) @default_options = HTTP::Options.new(default_options) @parser = HTTP::Response::Parser.new @socket = nil end
def make_request_body(opts, headers)
def make_request_body(opts, headers) if opts.body opts.body elsif opts.form headers['Content-Type'] ||= 'application/x-www-form-urlencoded' URI.encode_www_form(opts.form) elsif opts.json headers['Content-Type'] ||= 'application/json' MimeType[:json].encode opts.json end end
def make_request_uri(uri, options)
def make_request_uri(uri, options) uri = URI uri.to_s unless uri.is_a? URI if options.params && !options.params.empty? params = CGI.parse(uri.query.to_s).merge(options.params || {}) uri.query = URI.encode_www_form params end uri end
def perform(req, options)
def perform(req, options) # finish previous response if client was re-used # TODO: this is pretty wrong, as socket shoud be part of response # connection, so that re-use of client will not break multiple # chunked responses finish_response uri = req.uri # TODO: keep-alive support @socket = options[:socket_class].open(req.socket_host, req.socket_port) @socket = start_tls(@socket, options) if uri.is_a?(URI::HTTPS) req.stream @socket begin read_more BUFFER_SIZE until @parser.headers rescue IOError, Errno::ECONNRESET, Errno::EPIPE => ex raise IOError, "problem making HTTP request: #{ex}" end body = Response::Body.new(self) res = Response.new(@parser.status_code, @parser.http_version, @parser.headers, body, uri) finish_response if :head == req.verb res end
def read_more(size)
def read_more(size) @parser << @socket.readpartial(size) unless @parser.finished? true rescue EOFError false end
def readpartial(size = BUFFER_SIZE)
def readpartial(size = BUFFER_SIZE) return unless @socket read_more size chunk = @parser.chunk finish_response if @parser.finished? chunk end
def request(verb, uri, opts = {})
def request(verb, uri, opts = {}) opts = @default_options.merge(opts) uri = make_request_uri(uri, opts) headers = opts.headers proxy = opts.proxy body = make_request_body(opts, headers) req = HTTP::Request.new(verb, uri, headers, proxy, body) res = perform req, opts if opts.follow res = Redirector.new(opts.follow).perform req, res do |request| perform request, opts end end res end
def start_tls(socket, options)
def start_tls(socket, options) # TODO: abstract away SSLContexts so we can use other TLS libraries context = options[:ssl_context] || OpenSSL::SSL::SSLContext.new socket = options[:ssl_socket_class].new(socket, context) socket.connect socket end