forked from httprb/http
/
response.rb
169 lines (137 loc) · 4.45 KB
/
response.rb
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
# frozen_string_literal: true
require "forwardable"
require "http/headers"
require "http/content_type"
require "http/mime_type"
require "http/response/status"
require "http/response/inflater"
require "http/uri"
require "http/cookie_jar"
require "time"
module HTTP
class Response
extend Forwardable
include HTTP::Headers::Mixin
# @return [Status]
attr_reader :status
# @return [String]
attr_reader :version
# @return [Body]
attr_reader :body
# @return [URI, nil]
attr_reader :uri
# @return [Request]
attr_reader :request
# @return [Hash]
attr_reader :proxy_headers
# Inits a new instance
#
# @option opts [Integer] :status Status code
# @option opts [String] :version HTTP version
# @option opts [Hash] :headers
# @option opts [Hash] :proxy_headers
# @option opts [HTTP::Connection] :connection
# @option opts [String] :encoding Encoding to use when reading body
# @option opts [String] :body
# @option opts [String] :uri
def initialize(opts)
@version = opts.fetch(:version)
@request = opts.fetch(:request)
@uri = HTTP::URI.parse(opts[:uri] || @request.uri)
@status = HTTP::Response::Status.new(opts.fetch(:status))
@headers = HTTP::Headers.coerce(opts[:headers] || {})
@proxy_headers = HTTP::Headers.coerce(opts[:proxy_headers] || {})
if opts.include?(:body)
@body = opts.fetch(:body)
else
connection = opts.fetch(:connection)
encoding = opts[:encoding] || charset || Encoding::BINARY
@body = Response::Body.new(connection, :encoding => encoding)
end
end
# @!method reason
# @return (see HTTP::Response::Status#reason)
def_delegator :status, :reason
# @!method code
# @return (see HTTP::Response::Status#code)
def_delegator :status, :code
# @!method to_s
# (see HTTP::Response::Body#to_s)
def_delegator :body, :to_s
alias to_str to_s
# @!method readpartial
# (see HTTP::Response::Body#readpartial)
def_delegator :body, :readpartial
# @!method connection
# (see HTTP::Response::Body#connection)
def_delegator :body, :connection
# Returns an Array ala Rack: `[status, headers, body]`
#
# @return [Array(Fixnum, Hash, String)]
def to_a
[status.to_i, headers.to_h, body.to_s]
end
# Flushes body and returns self-reference
#
# @return [Response]
def flush
body.to_s
self
end
# Value of the Content-Length header.
#
# @return [nil] if Content-Length was not given, or it's value was invalid
# (not an integer, e.g. empty string or string with non-digits).
# @return [Integer] otherwise
def content_length
# http://greenbytes.de/tech/webdav/rfc7230.html#rfc.section.3.3.3
# Clause 3: "If a message is received with both a Transfer-Encoding
# and a Content-Length header field, the Transfer-Encoding overrides the Content-Length.
return nil if @headers.include?(Headers::TRANSFER_ENCODING)
value = @headers[Headers::CONTENT_LENGTH]
return nil unless value
begin
Integer(value)
rescue ArgumentError
nil
end
end
# Parsed Content-Type header
#
# @return [HTTP::ContentType]
def content_type
@content_type ||= ContentType.parse headers[Headers::CONTENT_TYPE]
end
# @!method mime_type
# MIME type of response (if any)
# @return [String, nil]
def_delegator :content_type, :mime_type
# @!method charset
# Charset of response (if any)
# @return [String, nil]
def_delegator :content_type, :charset
def cookies
@cookies ||= headers.each_with_object CookieJar.new do |(k, v), jar|
jar.parse(v, uri) if k == Headers::SET_COOKIE
end
end
def chunked?
return false unless @headers.include?(Headers::TRANSFER_ENCODING)
encoding = @headers.get(Headers::TRANSFER_ENCODING)
# TODO: "chunked" is frozen in the request writer. How about making it accessible?
encoding.last == "chunked"
end
# Parse response body with corresponding MIME type adapter.
#
# @param type [#to_s] Parse as given MIME type.
# @raise (see MimeType.[])
# @return [Object]
def parse(type)
MimeType[type].decode to_s
end
# Inspect a response
def inspect
"#<#{self.class}/#{@version} #{code} #{reason} #{headers.to_h.inspect}>"
end
end
end