Class: X::StreamResponse

Inherits:
Object
  • Object
show all
Includes:
ResponseHeaders
Defined in:
x-core/lib/x/core/stream_response.rb

Overview

The response of a request whose body is read as it arrives, which Client#get_stream passes to its block

It is passed to the block before its body is read, so it reads the status and headers the API answered with, and the block reads the body with #read_body, for as long as it likes. It is x-core's own, rather than the response of the library the client sends its requests with, so that a block written for one release of 1.x reads the response of every other, whatever sends the request.

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#http_response ⇒ Net::HTTPResponse (readonly)

The response itself, as the client received it, with its body not yet read

It is an escape hatch, for what this does not read: the status is #status, the headers are #headers, and the body is read with #read_body. It is the response of the transport the client sends its requests with, a Net::HTTPResponse today, and its class is not covered by the compatibility promise of 1.x: a later release of 1.x may send its requests with another library, whose response this then returns. A body read from it, rather than with #read_body, raises the errors of its socket as they are, rather than as a NetworkError, which Client#get_stream raises as the block's own, and is not known to #read_body, which counts the reads of its own alone: once the body was read from it, in part or whole, #read_body reads what is left of it, returns the body it read whole, or raises an Error for a body it left none of.

Examples:

Read the reason phrase of the status line

response.http_response.message # => "OK"

Returns:

  • (Net::HTTPResponse) —

    the response of the transport



99
100
101
# File 'x-core/lib/x/core/stream_response.rb', line 99

def http_response
  @http_response
end

#uri ⇒ URI::Generic (readonly)

The URI of the request

Examples:

Get the path of the request

response.uri.path # => "/2/tweets/sample/stream"

Returns:

  • (URI::Generic) —

    the request URI



82
83
84
# File 'x-core/lib/x/core/stream_response.rb', line 82

def uri
  @uri
end

Instance Method Details

#headers ⇒ Hash{String => String}

The headers of the response

The names are lowercase, and a field the API sent more than once is joined with a comma.

Examples:

Read the content type of a stream

response.headers["content-type"]

Returns:

  • (Hash{String => String}) —

    the headers, frozen



# File 'x-core/lib/x/core/stream_response.rb', line 68

#rate_limit ⇒ RateLimit?

The 15-minute rate limit of the endpoint

For a stream, it counts the connections that may be opened in the window.

Examples:

Read how many times a stream may connect again before the limit resets

response.rate_limit&.remaining # => 49

Returns:

  • (RateLimit, nil) —

    the rate limit, or nil if the response reports none



143
# File 'x-core/lib/x/core/stream_response.rb', line 143

def rate_limit = rate_limits.find { |limit| limit.type.eql?(RateLimit::RATE_LIMIT_TYPE) }

#rate_limits ⇒ Array<RateLimit>

The rate limits the response reports in its headers

Examples:

Print how many connections remain in each window

response.rate_limits.each { |limit| puts "#{limit.type}: #{limit.remaining}" }

Returns:

  • (Array<RateLimit>) —

    the 15-minute limit, and the 24-hour app and user limits when reported



133
# File 'x-core/lib/x/core/stream_response.rb', line 133

def rate_limits = RateLimit.__send__(:all_from, http_response)

#read_body {|chunk| ... } ⇒ String?

Read the body, a chunk at a time as it arrives, or whole

Given a block, each chunk of the body is passed to it as it arrives, until the body ends, which the body of a stream does only when its connection closes. A chunk is whatever arrived, in binary, since it may end within a line, or within a character, which no encoding but binary reads half of. Without a block, the body is read to its end, and returned whole, tagged UTF-8, the encoding of the JSON the API sends, as the body of a Response is: a body that is not valid UTF-8 keeps its bytes, so valid_encoding? tells it apart, and scrub replaces what is not UTF-8. A response without a body, such as a 204, has none to return.

The body is read once, inside the block of Client#get_stream, since it is read from a connection that is closed once that block returns. A body read whole, without a block, is returned again by each read without a block that follows, as a String of its own. Any other second read raises an Error that says so, with or without a block, after a read that passed the chunks to a block, even one the block left partway through, with break, throw, or an error, or after a read that failed; so does any read once the block of Client#get_stream has returned. The Error is the caller's, not the socket's, so it is neither a NetworkError nor retried. Only the reads of this method are counted: a body read from #http_response is not known to it, and a read of a body that was read to its end there, a chunk at a time, raises the same Error, for there is none left. A dup or clone of the response reads the same body, so its reads are counted with those of the response, and a response that was frozen is read as any other is. A response that was frozen deeply, as Ractor.make_shareable freezes one on a Ruby before 4.1, which refuses to share the socket of one, is not: its connection is frozen with it, so a read of a body not yet read raises an Error that says so, while a body read whole before is returned again.

An error of the socket the body is read from, such as a connection that dropped, or a read that timed out, raises a NetworkError here, inside the block of Client#get_stream, which may rescue it, and which Client#get_stream raises as it is otherwise. It names the request, and its cause is the error of the socket. An error the block raises is raised as it was, whatever its class.

Examples:

Print the body of a stream as it arrives

response.read_body { |chunk| print chunk }

Read a body that ends whole

body = response.read_body

Flush what was read of a stream that dropped

begin
  response.read_body { |chunk| file.write(chunk) }
rescue X::NetworkError
  file.flush
  raise
end

Yield Parameters:

  • chunk (String) —

    each chunk of the body, as it arrives, in binary

Returns:

  • (String, nil) —

    the body, read whole and tagged UTF-8, or nil for a response without a body, or once a block was passed each chunk of it

Raises:

  • (Error) —

    if the body was read before, unless both reads are whole, or the block of Client#get_stream has returned, or the body was read from #http_response, or the response was frozen deeply

  • (NetworkError) —

    if the socket the body is read from fails, as when the connection drops or a read times out

  • (StandardError) —

    whatever the block raises



193
194
195
196
197
198
199
200
201
202
203
204
# File 'x-core/lib/x/core/stream_response.rb', line 193

def read_body(&block)
  raise Error, READ_OUTSIDE_BLOCK if @state.left
  return whole_body if @state.whole && !block
  note_read
  failed = [] #: Array[Exception]
  body = StreamBody.reading(uri, failed) { transport_body(block, failed) }
  return if block

  @state.body = body
  @state.whole = true
  whole_body
end

#status ⇒ Integer

The HTTP status code

Examples:

Get the status code

response.status # => 200

Returns:

  • (Integer) —

    the status code



125
# File 'x-core/lib/x/core/stream_response.rb', line 125

def status = Integer(http_response.code)