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

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

Instance Attribute Summary

Attributes inherited from Connection

#count, #version

Instance Method Summary collapse

Methods inherited from Connection

#concurrency, #http1?, #http2?, #initialize, #peer, #read_line, #read_line?, #reusable?, #viable?

Constructor Details

This class inherits a constructor from Async::HTTP::Protocol::HTTP1::Connection

Instance Method Details

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

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



31
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 31

def call(request, task: Task.current)
	# We need to keep track of connections which are not in the initial "ready" state.
	@ready = false
	
	Console.logger.debug(self) {"#{request.method} #{request.path} #{request.headers.inspect}"}
	
	# 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
		write_request(request.authority, request.method, request.path, @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 do |subtask|
				subtask.annotate("Upgrading request.")
				
				# If this fails, this connection will be closed.
				write_upgrade_body(protocol, body)
			end
		elsif request.connect?
			task.async do |subtask|
				subtask.annotate("Tunnelling body.")
				
				write_tunnel_body(@version, body)
			end
		else
			task.async do |subtask|
				subtask.annotate("Streaming body.")
				
				# 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)
			end
		end
	elsif protocol = request.protocol
		write_upgrade_body(protocol)
	else
		write_body(@version, body, false, trailer)
	end
	
	response = Response.read(self, request)
	@ready = true
	
	return response
rescue
	# This will ensure that #reusable? returns false.
	@stream.close
	
	raise
end