Class: LittleGhost::Support::HTTPClient
- Inherits:
-
Object
- Object
- LittleGhost::Support::HTTPClient
- Defined in:
- lib/little_ghost/support/http_client.rb
Overview
HTTPClient gives integrations a shared, bounded streaming HTTP layer. It applies cancellation, deadlines, timeouts, and response-size limits while yielding response chunks as they arrive.
Security and trust
HTTPS is required by default. Enabling allow_insecure_http can expose API
keys and model content in transit; use it only with a trusted local
development endpoint.
Constant Summary collapse
- DEFAULT_MAX_RESPONSE_BYTES =
Default upper bound for a complete provider response (50 MiB).
50 * 1024 * 1024
- DEFAULT_MAX_ERROR_BODY_BYTES =
:nodoc:
4 * 1024
- TRANSIENT_NETWORK_ERRORS =
[ Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, EOFError, SocketError, SystemCallError, IOError, OpenSSL::SSL::SSLError, Net::ProtocolError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError ].freeze
Instance Method Summary collapse
-
#each_chunk(uri:, method: :get, headers: {}, body: nil, deadline: nil, cancellation_token: nil, allow_insecure_http: false, label: "HTTP request") ⇒ Object
Executes a bounded request and yields response chunks as they arrive.
-
#initialize(base_url: nil, open_timeout: 10, read_timeout: 120, allow_insecure_http: false, max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES, max_error_body_bytes: DEFAULT_MAX_ERROR_BODY_BYTES) ⇒ HTTPClient
constructor
Configures
base_urlwith connection, read, and response size limits. -
#request(uri:, method: :get, headers: {}, body: nil, allow_insecure_http: false, cancellation_token: nil, deadline: nil) ⇒ Object
Executes a bounded request and returns the complete response body.
-
#stream(path:, headers:, body:, cancellation_token:, deadline: nil) ⇒ Object
Posts
bodyand yields response chunks until completion.
Constructor Details
#initialize(base_url: nil, open_timeout: 10, read_timeout: 120, allow_insecure_http: false, max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES, max_error_body_bytes: DEFAULT_MAX_ERROR_BODY_BYTES) ⇒ HTTPClient
Configures base_url with connection, read, and response
size limits.
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
# File 'lib/little_ghost/support/http_client.rb', line 38 def initialize( base_url: nil, open_timeout: 10, read_timeout: 120, allow_insecure_http: false, max_response_bytes: DEFAULT_MAX_RESPONSE_BYTES, max_error_body_bytes: DEFAULT_MAX_ERROR_BODY_BYTES ) if base_url @base_url = URI(base_url.end_with?("/") ? base_url : "#{base_url}/") validate_uri!(@base_url, allow_insecure_http:) end @open_timeout = open_timeout @read_timeout = read_timeout @allow_insecure_http = allow_insecure_http @max_response_bytes = positive_integer(max_response_bytes, :max_response_bytes) @max_error_body_bytes = positive_integer(max_error_body_bytes, :max_error_body_bytes) end |
Instance Method Details
#each_chunk(uri:, method: :get, headers: {}, body: nil, deadline: nil, cancellation_token: nil, allow_insecure_http: false, label: "HTTP request") ⇒ Object
Executes a bounded request and yields response chunks as they arrive.
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 |
# File 'lib/little_ghost/support/http_client.rb', line 98 def each_chunk(uri:, method: :get, headers: {}, body: nil, deadline: nil, cancellation_token: nil, allow_insecure_http: false, label: "HTTP request") unless block_given? return enum_for( __method__, uri:, method:, headers:, body:, deadline:, cancellation_token:, allow_insecure_http:, label: ) end uri = URI(uri.to_s) validate_uri!(uri, allow_insecure_http:) check_control!(cancellation_token, deadline) request_class = {get: Net::HTTP::Get, post: Net::HTTP::Post, put: Net::HTTP::Put}.fetch(method.to_sym) do raise ArgumentError, "unsupported HTTP method: #{method}" end request = request_class.new(uri) headers.each { |name, value| request[name] = value unless value.to_s.empty? } request.body = body if body http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = uri.scheme == "https" http.open_timeout = remaining_timeout(deadline, @open_timeout) http.read_timeout = remaining_timeout(deadline, @read_timeout) http.write_timeout = remaining_timeout(deadline, @read_timeout) http.request(request) do |response| unless response.is_a?(Net::HTTPSuccess) response_body = read_limited(response, @max_error_body_bytes) raise Providers::HTTPError.new( "#{label} failed with HTTP #{response.code}", status: response.code.to_i, body: response_body ) end bytes_read = 0 response.read_body do |chunk| check_control!(cancellation_token, deadline) bytes_read += chunk.bytesize raise ProtocolError, "#{label} response exceeded #{@max_response_bytes} bytes" if bytes_read > @max_response_bytes yield chunk end end rescue *TRANSIENT_NETWORK_ERRORS => error raise Providers::HTTPError, "#{label} failed (#{error.class})" end |
#request(uri:, method: :get, headers: {}, body: nil, allow_insecure_http: false, cancellation_token: nil, deadline: nil) ⇒ Object
Executes a bounded request and returns the complete response body.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/little_ghost/support/http_client.rb', line 82 def request(uri:, method: :get, headers: {}, body: nil, allow_insecure_http: false, cancellation_token: nil, deadline: nil) response_body = +"" each_chunk( uri:, method:, headers:, body:, cancellation_token:, deadline: deadline || Time.now + @read_timeout, allow_insecure_http: ) { |chunk| response_body << chunk } response_body end |
#stream(path:, headers:, body:, cancellation_token:, deadline: nil) ⇒ Object
Posts body and yields response chunks until completion.
Cancellation and deadline interrupt the request. Without a block, this
method returns an Enumerator.
62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 |
# File 'lib/little_ghost/support/http_client.rb', line 62 def stream(path:, headers:, body:, cancellation_token:, deadline: nil) return enum_for(__method__, path:, headers:, body:, cancellation_token:, deadline:) unless block_given? raise ConfigurationError, "HTTP client requires base_url for streaming" unless @base_url stream = Support::InterruptibleStream.new(cancellation_token:, deadline:) do |emit| uri = URI.join(@base_url.to_s, path.sub(%r{\A/}, "")) each_chunk( uri:, method: :post, headers:, body:, deadline:, allow_insecure_http: @allow_insecure_http, label: "Provider" ) { |chunk| emit.call(chunk) } end stream.each { |chunk| yield chunk } end |