File: response.rb

package info (click to toggle)
ruby-protocol-http 0.55.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 840 kB
  • sloc: ruby: 6,904; makefile: 4
file content (190 lines) | stat: -rw-r--r-- 5,945 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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
181
182
183
184
185
186
187
188
189
190
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2019-2025, by Samuel Williams.

require_relative "body/buffered"
require_relative "body/reader"
require_relative "headers"

module Protocol
	module HTTP
		# Represents an HTTP response which can be used both server and client-side.
		#
		# ~~~ ruby
		# require 'protocol/http'
		# 
		# # Long form:
		# Protocol::HTTP::Response.new("http/1.1", 200, Protocol::HTTP::Headers[["content-type", "text/html"]], Protocol::HTTP::Body::Buffered.wrap("Hello, World!"))
		# 
		# # Short form:
		# Protocol::HTTP::Response[200, {"content-type" => "text/html"}, ["Hello, World!"]]
		# ~~~
		class Response
			prepend Body::Reader
			
			# Create a new response.
			#
			# @parameter version [String | Nil] The HTTP version, e.g. `"HTTP/1.1"`. If `nil`, the version may be provided by the server sending the response.
			# @parameter status [Integer] The HTTP status code, e.g. `200`, `404`, etc.
			# @parameter headers [Hash] The headers, e.g. `{"content-type" => "text/html"}`, etc.
			# @parameter body [Body::Readable] The body, e.g. `"Hello, World!"`, etc.
			# @parameter protocol [String | Array(String)] The protocol, e.g. `"websocket"`, etc.
			def initialize(version = nil, status = 200, headers = Headers.new, body = nil, protocol = nil)
				@version = version
				@status = status
				@headers = headers
				@body = body
				@protocol = protocol
			end
			
			# @attribute [String | Nil] The HTTP version, usually one of `"HTTP/1.1"`, `"HTTP/2"`, etc.
			attr_accessor :version
			
			# @attribute [Integer] The HTTP status code, e.g. `200`, `404`, etc.
			attr_accessor :status
			
			# @attribute [Hash] The headers, e.g. `{"content-type" => "text/html"}`, etc.
			attr_accessor :headers
			
			# @attribute [Body::Readable] The body, e.g. `"Hello, World!"`, etc.
			attr_accessor :body
			
			# @attribute [String | Array(String) | Nil] The protocol, e.g. `"websocket"`, etc.
			attr_accessor :protocol
			
			# A response that is generated by a client, may choose to include the peer (address) associated with the response. It should be implemented by a sub-class.
			#
			# @returns [Peer | Nil] The peer (address) associated with the response.
			def peer
				nil
			end
			
			# Whether the response is considered a hijack: the connection has been taken over by the application and the server should not send any more data.
			def hijack?
				false
			end
			
			# Whether the status is 100 (continue).
			def continue?
				@status == 100
			end
			
			# Whether the status is considered informational.
			def informational?
				@status and @status >= 100 && @status < 200
			end
			
			# Whether the status is considered final. Note that 101 is considered final.
			def final?
				# 101 is effectively a final status.
				@status and @status >= 200 || @status == 101
			end
			
			# Whether the status is 200 (ok).
			def ok?
				@status == 200
			end
			
			# Whether the status is considered successful.
			def success?
				@status and @status >= 200 && @status < 300
			end
			
			# Whether the status is 206 (partial content).
			def partial?
				@status == 206
			end
			
			# Whether the status is considered a redirection.
			def redirection?
				@status and @status >= 300 && @status < 400
			end
			
			# Whether the status is 304 (not modified).
			def not_modified?
				@status == 304
			end
			
			# Whether the status is 307 (temporary redirect) and should preserve the method of the request when following the redirect.
			def preserve_method?
				@status == 307 || @status == 308
			end
			
			# Whether the status is considered a failure.
			def failure?
				@status and @status >= 400 && @status < 600
			end
			
			# Whether the status is 400 (bad request).
			def bad_request?
				@status == 400
			end
			
			# Whether the status is 500 (internal server error).
			def internal_server_error?
				@status == 500
			end
			
			# @deprecated Use {#internal_server_error?} instead.
			alias server_failure? internal_server_error?
			
			# A short-cut method which exposes the main response variables that you'd typically care about. It follows the same order as the `Rack` response tuple, but also includes the protocol.
			#
			# ~~~ ruby
			# 	Response[200, {"content-type" => "text/html"}, ["Hello, World!"]]
			# ~~~
			#
			# @parameter status [Integer] The HTTP status code, e.g. `200`, `404`, etc.
			# @parameter headers [Hash] The headers, e.g. `{"content-type" => "text/html"}`, etc.
			# @parameter body [String | Array(String) | Body::Readable] The body, e.g. `"Hello, World!"`, etc. See {Body::Buffered.wrap} for more information about .
			def self.[](status, _headers = nil, _body = nil, headers: _headers, body: _body, protocol: nil)
				body = Body::Buffered.wrap(body)
				headers = Headers[headers]
				
				self.new(nil, status, headers, body, protocol)
			end
			
			# Create a response for the given exception.
			#
			# @parameter exception [Exception] The exception to generate the response for.
			def self.for_exception(exception)
				Response[500, Headers["content-type" => "text/plain"], ["#{exception.class}: #{exception.message}"]]
			end
			
			# Convert the response to a hash suitable for serialization.
			#
			# @returns [Hash] The response as a hash.
			def as_json(...)
				{
					version: @version,
					status: @status,
					headers: @headers&.as_json,
					body: @body&.as_json,
					protocol: @protocol
				}
			end
			
			# Convert the response to JSON.
			#
			# @returns [String] The response as JSON.
			def to_json(...)
				as_json.to_json(...)
			end
			
			# Summarise the response as a string.
			#
			# @returns [String] The response as a string.
			def to_s
				"#{@status} #{@version}"
			end
			
			# Implicit conversion to an array.
			#
			# @returns [Array] The response as an array, e.g. `[status, headers, body]`.
			def to_ary
				return @status, @headers, @body
			end
		end
	end
end