Class: Revactor::HttpClient

Inherits:
Rev::HttpClient
  • Object
show all
Defined in:
lib/revactor/http_client.rb

Overview

A high performance HTTP client which wraps the asynchronous client in Rev

Constant Summary collapse

REQUEST_TIMEOUT =

Default timeout for HTTP requests (until the response header is received)

60
READ_TIMEOUT =

Read timeout for responses from the server

30
MAX_REDIRECTS =

Maximum number of HTTP redirects to follow

10
REDIRECT_STATUSES =

Statuses which indicate the request was redirected

[301, 302, 303, 307]

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(socket) ⇒ HttpClient

Returns a new instance of HttpClient.



104
105
106
107
# File 'lib/revactor/http_client.rb', line 104

def initialize(socket)
  super
  @controller = @receiver ||= Actor.current
end

Class Method Details

.connect(host, port = 80) ⇒ Object



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/revactor/http_client.rb', line 28

def connect(host, port = 80)        
  client = super
  client.instance_variable_set :@receiver, Actor.current
  client.attach Rev::Loop.default

  Actor.receive do |filter|
    filter.when(T[Object, client]) do |message, _|
      case message
      when :http_connected
        client.disable
        return client
      when :http_connect_failed
        raise TCP::ConnectError, "connection refused"
      when :http_resolve_failed
        raise TCP::ResolveError, "couldn't resolve #{host}"
      else raise "unexpected message for #{client.inspect}: #{message.inspect}"
      end              
    end

    filter.after(TCP::CONNECT_TIMEOUT) do
      client.close unless client.closed?
      raise TCP::ConnectError, "connection timed out"
    end
  end
end

.request(method, uri, options = {}) ⇒ Object

Perform an HTTP request for the given method and return a response object

Raises:



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# File 'lib/revactor/http_client.rb', line 55

def request(method, uri, options = {})
  follow_redirects = options.has_key?(:follow_redirects) ? options[:follow_redirects] : true
  uri = URI.parse(uri)
  
  MAX_REDIRECTS.times do
    raise URI::InvalidURIError, "invalid HTTP URI: #{uri}" unless uri.is_a? URI::HTTP
    request_options = uri.is_a?(URI::HTTPS) ? options.merge(:ssl => true) : options
  
    client = connect(uri.host, uri.port)
    response = client.request(method, uri.request_uri, request_options)
    
    # Request complete
    unless follow_redirects and REDIRECT_STATUSES.include? response.status
      return response unless block_given?
      
      begin
        yield response
      ensure
        response.close
      end
      
      return
    end
    
    response.close
    
    location = response.headers['location']
    raise "redirect with no location header: #{uri}" if location.nil?
    
    # Convert path-based redirects to URIs
    unless /^[a-z]+:\/\// === location
      location = "#{uri.scheme}://#{uri.host}" << File.expand_path(location, uri.path)
    end
    
    uri = URI.parse(location)
  end
  
  raise HttpClientError, "exceeded maximum of #{MAX_REDIRECTS} redirects"
end

Instance Method Details

#controller=(controller) ⇒ Object

Change the controlling Actor for active mode reception Set the controlling actor

Raises:

  • (ArgumentError)


111
112
113
114
115
116
# File 'lib/revactor/http_client.rb', line 111

def controller=(controller)
  raise ArgumentError, "controller must be an actor" unless controller.is_a? Actor
  
  @receiver = controller if @receiver == @controller
  @controller = controller
end

#request(method, path, options = {}) ⇒ Object

Initiate an HTTP request for the given path using the given method Supports the following options:

ssl: Boolean
  If true, an HTTPS request will be made

head: {Key: Value, Key2: Value2}
  Specify HTTP headers, e.g. {'Connection': 'close'}

query: {Key: Value}
  Specify query string parameters (auto-escaped)

cookies: {Key: Value}
  Specify hash of cookies (auto-escaped)

body: String
  Specify the request body (you must encode it for now)


136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/revactor/http_client.rb', line 136

def request(method, path, options = {})
  if options[:ssl]
    ssl_handshake
    
    Actor.receive do |filter|
      filter.when(T[:https_connected, self]) do
        disable
      end
      
      filter.when(T[:http_closed, self]) do
        raise EOFError, "SSL handshake failed"
      end
      
      filter.after(TCP::CONNECT_TIMEOUT) do
        close unless closed?
        raise TCP::ConnectError, "SSL handshake timed out"
      end
    end
  end
  
  super
  enable
  
  Actor.receive do |filter|
    filter.when(T[:http_response_header, self]) do |_, _, response_header|
      return HttpResponse.new(self, response_header)
    end
    
    filter.when(T[:http_error, self, Object]) do |_, _, reason|
      close unless closed?
      raise HttpClientError, reason
    end
    
    filter.when(T[:http_closed, self]) do
      raise EOFError, "connection closed unexpectedly"
    end

    filter.after(REQUEST_TIMEOUT) do
      @finished = true
      close unless closed?
      
      raise HttpClientError, "request timed out"
    end
  end
end