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
|
# frozen_string_literal: true
module Seahorse
module Client
module Http
class Response
# @option options [Integer] :status_code (0)
# @option options [Headers] :headers (Headers.new)
# @option options [IO] :body (StringIO.new)
def initialize(options = {})
@status_code = options[:status_code] || 0
@headers = options[:headers] || Headers.new
@body = options[:body] || StringIO.new
@listeners = Hash.new { |h,k| h[k] = [] }
@complete = false
@done = nil
@error = nil
end
# @return [Integer] Returns `0` if the request failed to generate
# any response.
attr_accessor :status_code
# @return [Headers]
attr_accessor :headers
# @return [StandardError, nil]
attr_reader :error
# @return [IO]
def body
@body
end
# @param [#read, #size, #rewind] io
def body=(io)
@body = case io
when nil then StringIO.new('')
when String then StringIO.new(io)
else io
end
end
# @return [String|Array]
def body_contents
if body.is_a?(Array)
# an array of parsed events
body
else
body.rewind
contents = body.read
body.rewind
contents
end
end
# @param [Integer] status_code
# @param [Hash<String,String>] headers
def signal_headers(status_code, headers)
@status_code = status_code
@headers = Headers.new(headers)
emit(:headers, @status_code, @headers)
end
# @param [string] chunk
def signal_data(chunk)
unless chunk == ''
emit(:data, chunk)
@body.write(chunk)
end
end
# Completes the http response.
#
# @example Completing the response in a single call
#
# http_response.signal_done(
# status_code: 200,
# headers: {},
# body: ''
# )
#
# @example Complete the response in parts
#
# # signal headers straight-way
# http_response.signal_headers(200, {})
#
# # signal data as it is received from the socket
# http_response.signal_data("...")
# http_response.signal_data("...")
# http_response.signal_data("...")
#
# # signal done once the body data is all written
# http_response.signal_done
#
# @overload signal_done()
#
# @overload signal_done(options = {})
# @option options [required, Integer] :status_code
# @option options [required, Hash] :headers
# @option options [required, String] :body
#
def signal_done(options = {})
if options.keys.sort == [:body, :headers, :status_code]
signal_headers(options[:status_code], options[:headers])
signal_data(options[:body])
signal_done
elsif options.empty?
@body.rewind if @body.respond_to?(:rewind)
@done = true
emit(:done)
else
msg = 'options must be empty or must contain :status_code, :headers, '\
'and :body'
raise ArgumentError, msg
end
end
# @param [StandardError] networking_error
def signal_error(networking_error)
@error = networking_error
signal_done
end
def on_headers(status_code_range = nil, &block)
@listeners[:headers] << listener(status_code_range, block)
end
def on_data(&callback)
@listeners[:data] << callback
end
def on_done(status_code_range = nil, &callback)
listener = listener(status_code_range, callback)
if @done
listener.call
else
@listeners[:done] << listener
end
end
def on_success(status_code_range = 200..599, &callback)
on_done(status_code_range) do
unless @error
yield
end
end
end
def on_error(&callback)
on_done(0..599) do
if @error
yield(@error)
end
end
end
def reset
@status_code = 0
@headers.clear
@body.truncate(0)
@error = nil
end
private
def listener(range, callback)
range = range..range if Integer === range
if range
lambda do |*args|
if range.include?(@status_code)
callback.call(*args)
end
end
else
callback
end
end
def emit(event_name, *args)
@listeners[event_name].each { |listener| listener.call(*args) }
end
end
end
end
end
|