Class: Async::HTTP::Protocol::HTTP11

Inherits:
IO::Protocol::Line
  • Object
show all
Defined in:
lib/async/http/protocol/http11.rb

Overview

Implements basic HTTP/1.1 request/response.

Direct Known Subclasses

HTTP10

Defined Under Namespace

Classes: Request

Constant Summary collapse

CRLF =
"\r\n".freeze
CONNECTION =
'connection'.freeze
HOST =
'host'.freeze
CLOSE =
'close'.freeze
VERSION =
"HTTP/1.1".freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(stream) ⇒ HTTP11

Returns a new instance of HTTP11.



48
49
50
51
52
53
# File 'lib/async/http/protocol/http11.rb', line 48

def initialize(stream)
	super(stream, CRLF)
	
	@persistent = true
	@count = 0
end

Instance Attribute Details

#countObject (readonly)

Returns the value of attribute count.



55
56
57
# File 'lib/async/http/protocol/http11.rb', line 55

def count
  @count
end

Instance Method Details

#call(request) ⇒ Object



147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/async/http/protocol/http11.rb', line 147

def call(request)
	request.version ||= self.version
	
	Async.logger.debug(self) {"#{request.method} #{request.path} #{request.headers.inspect}"}
	
	# 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, request.version, request.headers)
	rescue
		# If we fail to fully write the request and body, we can retry this request.
		raise RequestFailed.new
	end
	
	# 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(request.body)
	
	return Response.new(*read_response(request))
rescue
	# This will ensure that #reusable? returns false.
	@stream.close
	
	raise
end

#good?Boolean

Can we use this connection to make requests?

Returns:

  • (Boolean)


63
64
65
# File 'lib/async/http/protocol/http11.rb', line 63

def good?
	@stream.connected?
end

#hijackObject



88
89
90
91
92
93
94
# File 'lib/async/http/protocol/http11.rb', line 88

def hijack
	@persistent = false
	
	@stream.flush
	
	return @stream.io
end

#multiplexObject

Only one simultaneous connection at a time.



58
59
60
# File 'lib/async/http/protocol/http11.rb', line 58

def multiplex
	1
end

#next_requestObject



114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/async/http/protocol/http11.rb', line 114

def next_request
	# The default is true.
	return nil unless @persistent
	
	request = Request.new(self)
	
	unless persistent?(request.headers)
		@persistent = false
	end
	
	return request
rescue
	# Bad Request
	write_response(self.version, 400, {}, nil)
	
	raise
end

#persistent?(headers) ⇒ Boolean

Returns:

  • (Boolean)


80
81
82
83
84
85
86
# File 'lib/async/http/protocol/http11.rb', line 80

def persistent?(headers)
	if connection = headers[CONNECTION]
		return !connection.include?(CLOSE)
	else
		return true
	end
end

#read_requestObject



194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/async/http/protocol/http11.rb', line 194

def read_request
	method, path, version = read_line.split(/\s+/, 3)
	headers = read_headers
	
	@persistent = persistent?(headers)
	
	body = read_request_body(headers)
	
	@count += 1
	
	return headers.delete(HOST), method, path, version, headers, body
end

#read_response(request) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/async/http/protocol/http11.rb', line 179

def read_response(request)
	version, status, reason = read_line.split(/\s+/, 3)
	Async.logger.debug(self) {"#{version} #{status} #{reason}"}
	
	headers = read_headers
	
	@persistent = persistent?(headers)
	
	body = read_response_body(request, status, headers)
	
	@count += 1
	
	return version, Integer(status), reason, headers, body
end

#receive_requests(task: Task.current) ⇒ Object

Server loop.



133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/async/http/protocol/http11.rb', line 133

def receive_requests(task: Task.current)
	while request = next_request
		if response = yield(request, self)
			write_response(response.version || self.version, response.status, response.headers, response.body)
			request.finish
			
			# This ensures we yield at least once every iteration of the loop and allow other fibers to execute.
			task.yield
		else
			break
		end
	end
end

#reusable?Boolean

Returns:

  • (Boolean)


67
68
69
# File 'lib/async/http/protocol/http11.rb', line 67

def reusable?
	@persistent && !@stream.closed?
end

#versionObject



76
77
78
# File 'lib/async/http/protocol/http11.rb', line 76

def version
	VERSION
end

#write_request(authority, method, path, version, headers) ⇒ Object



171
172
173
174
175
176
177
# File 'lib/async/http/protocol/http11.rb', line 171

def write_request(authority, method, path, version, headers)
	@stream.write("#{method} #{path} #{version}\r\n")
	@stream.write("host: #{authority}\r\n")
	write_headers(headers)
	
	@stream.flush
end

#write_response(version, status, headers, body) ⇒ Object



207
208
209
210
211
212
213
# File 'lib/async/http/protocol/http11.rb', line 207

def write_response(version, status, headers, body)
	@stream.write("#{version} #{status}\r\n")
	write_headers(headers)
	write_body(body)
	
	@stream.flush
end