Class: Async::HTTP::Protocol::HTTP1::Client

Inherits:
Connection
  • Object
show all
Defined in:
lib/async/http/protocol/http1/client.rb

Instance Attribute Summary collapse

Attributes inherited from Connection

#count, #version

Instance Method Summary collapse

Methods inherited from Connection

#as_json, #concurrency, #http1?, #http2?, #peer, #reusable?, #to_json, #to_s, #viable?

Constructor Details

#initializeClient

Returns a new instance of Client.



13
14
15
16
17
# File 'lib/async/http/protocol/http1/client.rb', line 13

def initialize(...)
  super
  
  @pool = nil
end

Instance Attribute Details

#poolObject

Returns the value of attribute pool.



19
20
21
# File 'lib/async/http/protocol/http1/client.rb', line 19

def pool
  @pool
end

Instance Method Details

#call(request, task: Task.current) ⇒ Object

Used by the client to send requests to the remote server.



32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
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
# File 'lib/async/http/protocol/http1/client.rb', line 32

def call(request, task: Task.current)
  # Mark the start of the trailers:
  trailer = request.headers.trailer!
  
  # We carefully interpret https://tools.ietf.org/html/rfc7230#section-6.3.1 to implement this correctly.
  begin
    target = request.path
    authority = request.authority
    
    # If we are using a CONNECT request, we need to use the authority as the target:
    if request.connect?
      target = authority
      authority = nil
    end
    
    write_request(authority, request.method, target, @version, request.headers)
  rescue
    # If we fail to fully write the request and body, we can retry this request.
    raise RequestFailed
  end
  
  if request.body?
    body = request.body
    
    if protocol = request.protocol
      # This is a very tricky apect of handling HTTP/1 upgrade connections. In theory, this approach is a bit inefficient, because we spin up a task just to handle writing to the underlying stream when we could be writing to the stream directly. But we need to maintain some level of compatibility with HTTP/2. Additionally, we don't know if the upgrade request will be accepted, so starting to write the body at this point needs to be handled with care.
      task.async(annotation: "Upgrading request...") do
        # If this fails, this connection will be closed.
        write_upgrade_body(protocol, body)
      rescue => error
        self.close(error)
      end
    elsif request.connect?
      task.async(annotation: "Tunnneling request...") do
        write_tunnel_body(@version, body)
      rescue => error
        self.close(error)
      end
    else
      task.async(annotation: "Streaming request...") do
        # Once we start writing the body, we can't recover if the request fails. That's because the body might be generated dynamically, streaming, etc.
        write_body(@version, body, false, trailer)
      rescue => error
        self.close(error)
      end
    end
  elsif protocol = request.protocol
    write_upgrade_body(protocol)
  else
    write_body(@version, request.body, false, trailer)
  end
  
  return Response.read(self, request)
rescue => error
  self.close(error)
  raise
end

#closed(error = nil) ⇒ Object



21
22
23
24
25
26
27
28
29
# File 'lib/async/http/protocol/http1/client.rb', line 21

def closed(error = nil)
  super
  
  if pool = @pool
    @pool = nil
    # If the connection is not reusable, this will retire it from the connection pool and invoke `#close`.
    pool.release(self)
  end
end