class UV::Http::Request

Constants

CONNECTION
CRLF

Attributes

method[R]
options[R]
path[R]

Public Class Methods

new(endpoint, options) click to toggle source
Calls superclass method
# File lib/uv-rays/http/request.rb, line 32
def initialize(endpoint, options)
    super(endpoint.thread, endpoint.thread.defer)

    @host = endpoint.host
    @port = endpoint.port
    @http_proxy = endpoint.http_proxy? ? endpoint.proxy : nil
    @encoded_host = endpoint.encoded_host

    path = options[:path]
    if path.is_a?(::URI)
        @path = path.to_s.split(@encoded_host)[1] || '/'
    else
        @path = path
    end

    @method = options[:method]
    @cookiejar = endpoint.cookiejar
    @middleware = endpoint.middleware
    @uri = "#{endpoint.scheme}://#{@encoded_host}#{@path}"
    @path = @uri if @http_proxy
    endpoint = nil

    @options = options
    @ntlm_creds = options[:ntlm]
    @digest_creds = options[:digest]
    @challenge_retries = 0

    # Don't hold references to vars we don't require anymore
    self.finally {
        @host = @port = nil
        @cookiejar = nil
        @middleware = nil
    }
    @error = proc { |reason| reject(reason) }
end

Public Instance Methods

cookies_hash() click to toggle source
# File lib/uv-rays/http/request.rb, line 23
def cookies_hash
    @cookiejar.get_hash(@uri)
end
execute(transport) click to toggle source
# File lib/uv-rays/http/request.rb, line 105
def execute(transport)
    # configure ntlm request headers
    if @options[:ntlm]
        @options[:headers] ||= {}
        @options[:headers][:Authorization] ||= ntlm_auth_header
    end

    head, body = build_request, @options[:body]
    @transport = transport

    @middleware.each do |m|
        begin
            head, body = m.request(self, head, body) if m.respond_to?(:request)
        rescue => e
            reject e
            return
        end
    end

    body = body.is_a?(Hash) ? form_encode_body(body) : body
    file = @options[:file]
    query = @options[:query]

    # Set the Content-Length if file is given
    head['content-length'] = File.size(file) if file

    # Set the Content-Length if body is given,
    # or we're doing an empty post or put
    if body
        head['content-length'] = body.bytesize
    elsif method == :post or method == :put
        # wont happen if body is set and we already set content-length above
        head['content-length'] ||= 0
    end

    # Set content-type header if missing and body is a Ruby hash
    if !head['content-type'] and @options[:body].is_a? Hash
        head['content-type'] = 'application/x-www-form-urlencoded'
    end

    request_header = encode_request(method, @path, query)
    if @http_proxy && (@http_proxy[:username] || @http_proxy[:password])
        request_header << encode_auth('Proxy-Authorization', [@http_proxy[:username], @http_proxy[:password]])
    end
    request_header << encode_headers(head)
    request_header << CRLF

    if body
        request_header << body
        transport.write(request_header).catch &@error
    elsif file
        transport.write(request_header).catch &@error

        # Send file
        fileRef = @reactor.file file, File::RDONLY do
            # File is open and available for reading
            pSend = fileRef.send_file(transport, using: :raw, wait: :promise)
            pSend.catch &@error
            pSend.finally do
                fileRef.close
            end
        end
        fileRef.catch &@error
    else
        transport.write(request_header).catch &@error
    end
end
notify(*args) click to toggle source
# File lib/uv-rays/http/request.rb, line 173
def notify(*args)
    @defer.notify(*args)
end
on_headers(&callback) click to toggle source
# File lib/uv-rays/http/request.rb, line 181
def on_headers(&callback)
    @headers_callback = callback
end
reject(reason) click to toggle source
# File lib/uv-rays/http/request.rb, line 101
def reject(reason)
    @defer.reject(reason)
end
resolve(response, parser = nil) click to toggle source
# File lib/uv-rays/http/request.rb, line 70
def resolve(response, parser = nil)
    if response.status == 401 && @challenge_retries == 0 && response[:"WWW-Authenticate"]
        challenge = Array(response[:"WWW-Authenticate"]).reject { |auth| auth.downcase == 'negotiate' }[0]

        begin
            if @ntlm_creds && challenge[0..3] == 'NTLM'
                @options[:headers] ||= {}
                @options[:headers][:Authorization] = ntlm_auth_header(challenge)
                @challenge_retries += 1

                execute(@transport)
                return false
            elsif @digest_creds && challenge[0..5] == 'Digest'
                @options[:headers] ||= {}
                @options[:headers][:Authorization] = digest_auth_header(challenge)
                @challenge_retries += 1

                execute(@transport)
                return false
            end
        rescue => e
            reject e
            true
        end
    end

    @transport = nil
    @defer.resolve(response)
    true
end
set_headers(head) click to toggle source
# File lib/uv-rays/http/request.rb, line 177
def set_headers(head)
    @headers_callback.call(head) if @headers_callback
end
streaming?() click to toggle source
# File lib/uv-rays/http/request.rb, line 185
def streaming?
    @options[:streaming]
end

Protected Instance Methods

build_request() click to toggle source
# File lib/uv-rays/http/request.rb, line 193
def build_request
    head = @options[:headers] ? munge_header_keys(@options[:headers]) : {}

    # Set the cookie header if provided
    @cookies = @cookiejar.get(@uri)
    if cookie = head[COOKIE]
        @cookies << encode_cookie(cookie)
    end
    head[COOKIE] = @cookies.compact.uniq.join("; ").squeeze(";") unless @cookies.empty?

    # Set connection close unless keep-alive
    if !@options[:keepalive]
        head['connection'] = 'close'
    end

    # Set the Host header if it hasn't been specified already
    head['host'] ||= @encoded_host

    # Set the User-Agent if it hasn't been specified
    if !head.key?('user-agent')
        head['user-agent'] = "UV HttpClient"
    elsif head['user-agent'].nil?
        head.delete('user-agent')
    end

    head
end
digest_auth_header(challenge) click to toggle source
# File lib/uv-rays/http/request.rb, line 250
def digest_auth_header(challenge)
    uri = URI.parse @uri
    uri.userinfo = "#{CGI::escape(@digest_creds[:user])}:#{CGI::escape(@digest_creds[:password])}"

    digest_auth = Net::HTTP::DigestAuth.new
    auth = digest_auth.auth_header uri, challenge, method.to_s.upcase
    auth
end
ntlm_auth_header(challenge = nil) click to toggle source
# File lib/uv-rays/http/request.rb, line 221
def ntlm_auth_header(challenge = nil)
    if @ntlm_auth && challenge.nil?
        return @ntlm_auth
    elsif challenge
        scheme, param_str = parse_ntlm_challenge_header(challenge)
        if param_str.nil?
            @ntlm_auth = nil
            return ntlm_auth_header(@ntlm_creds)
        else
            t2 = Net::NTLM::Message.decode64(param_str)
            t3 = t2.response(@ntlm_creds, ntlmv2: true)
            @ntlm_auth = "NTLM #{t3.encode64}"
            return @ntlm_auth
        end
    else
        domain = @ntlm_creds[:domain]
        t1 = Net::NTLM::Message::Type1.new()
        t1.domain = domain if domain
        @ntlm_auth = "NTLM #{t1.encode64}"
        return @ntlm_auth
    end
end
parse_ntlm_challenge_header(challenge) click to toggle source
# File lib/uv-rays/http/request.rb, line 244
def parse_ntlm_challenge_header(challenge)
    scheme, param_str = challenge.scan(/\A(\S+)(?:\s+(.*))?\z/)[0]
    return nil if scheme.nil?
    return scheme, param_str
end