Class: Ecoportal::API::Common::GraphQL::HttpClient
- Inherits:
-
Common::Client
- Object
- Common::Client
- Ecoportal::API::Common::GraphQL::HttpClient
- Includes:
- AuthService
- Defined in:
- lib/ecoportal/api/common/graphql/http_client.rb
Constant Summary collapse
- ENDPOINT_PATH =
'external/graphql'.freeze
- CONNECT_TIMEOUT =
30- READ_TIMEOUT =
90- WRITE_TIMEOUT =
90- KEEP_ALIVE_TIMEOUT =
5
Constants included from AuthService
AuthService::DEFAULT_SERVER, AuthService::TOKEN_AUTORENEW
Instance Attribute Summary collapse
-
#host ⇒ Object
readonly
Returns the value of attribute host.
-
#version ⇒ Object
readonly
Returns the value of attribute version.
Class Method Summary collapse
Instance Method Summary collapse
-
#base_request ⇒ HTTP
Creates a HTTP object adding the
X-ApiKeyorX-ECOPORTAL-API-KEYparam to the header, depending on the API version. -
#execute(query_string, variables: {}, operation_name: nil) ⇒ Hash
Executes a GraphQL query or mutation over HTTP.
-
#initialize(email: nil, pass: nil, org_id: nil, api_key: nil, version: 'graphql', host: 'live.ecoportal.com', logger: ::Logger.new(IO::NULL), deep_logging: false, no_schema: true) ⇒ HttpClient
constructor
A new instance of HttpClient.
- #org_id ⇒ Object
-
#url_for(path) ⇒ String
Full URl builder of the request.
Methods included from AuthService
Methods included from AuthService::InstanceMethods
#session_token, #session_token_renewed
Constructor Details
#initialize(email: nil, pass: nil, org_id: nil, api_key: nil, version: 'graphql', host: 'live.ecoportal.com', logger: ::Logger.new(IO::NULL), deep_logging: false, no_schema: true) ⇒ HttpClient
Returns a new instance of HttpClient.
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 115 def initialize( email: nil, pass: nil, org_id: nil, api_key: nil, version: 'graphql', host: 'live.ecoportal.com', logger: ::Logger.new(IO::NULL), deep_logging: false, no_schema: true ) @org_id = org_id @user_email = email @user_pass = pass @no_schema = no_schema @version = version super( api_key: api_key, host: host, logger: logger, deep_logging: deep_logging ) end |
Instance Attribute Details
#host ⇒ Object (readonly)
Returns the value of attribute host.
105 106 107 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 105 def host @host end |
#version ⇒ Object (readonly)
Returns the value of attribute version.
105 106 107 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 105 def version @version end |
Class Method Details
.base_url(host) ⇒ Object
90 91 92 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 90 def base_url(host) "#{protocol(host)}://#{host}" end |
.protocol(host) ⇒ Object
94 95 96 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 94 def protocol(host) host.match(/^localhost|^127\.0\.0\.1/)? 'http' : 'https' end |
Instance Method Details
#base_request ⇒ HTTP
It configures HTTP so it only allows body data in json format.
This is the ONLY place in the gem's dependency chain that talks to the
server via http.rb (require 'http', pulled in by Ecoportal::API::Common::Client
from the ecoportal-api gem) -- every live GraphQL query goes through #execute
below, called from Logic::BaseQuery#graphql_query via client.http_client.execute,
and AuthService#auth_http_client (version: 'http') reuses this same
base_request for the OAuth token POST. Common::GraphQL::Client (graphlient) is a
separate, unrelated path: it never reaches http.rb at all.
Without an explicit connect:, http.rb's HTTP::Timeout::PerOperation falls back
to its own default of 0.25s for CONNECT even though read/write were already given
explicitly (per_operation.rb: options.fetch(:connect_timeout, CONNECT_TIMEOUT),
CONNECT_TIMEOUT = 0.25). connect_ssl (http/timeout/per_operation.rb) uses
that same @connect_timeout for the TLS handshake via rescue_readable/
rescue_writable (http/timeout/null.rb) -- whose error text is unconditionally
"Read timed out after #@read_timeout seconds", so a slow TLS handshake on the EU
instance surfaced as HTTP::TimeoutError: Read timed out after 0.25 seconds
even though @read_timeout itself was correctly 90 the whole time. Live-confirmed
2026-09-08 via backtrace: connect_ssl -> start_tls (http/connection.rb) ->
Common::Client#post -> AuthService#session_token_data ->
Common::GraphQL::Client#initialize. Read/write were never the problem; only
connect was missing.
The client is made PERSISTENT (HTTP.persistent) to base_url, not a
plain one-shot client. Confirmed live 2026-09-08: eu.live.ecoportal.com
closes an HTTP/1.1 request carrying Connection: close -- what a
non-persistent http.rb client always sends -- without ever responding. A
persistent client sends Connection: keep-alive and reuses the TCP/TLS
connection instead, which the SAME Ruby stack proved fine against EU. This
also matches Cloudflare keep-alive hygiene; Sydney is fine either way. One
HttpClient is bound to one host, so HTTP::StateError (raised only on a
cross-origin request against a persistent client) cannot occur here.
Creates a HTTP object adding the X-ApiKey or X-ECOPORTAL-API-KEY param to the header, depending on the API version.
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 173 def base_request @base_request ||= case @version when NilClass, 'http' HTTP when 'v1', 'v0' HTTP.headers('X-ApiKey' => key_token) when 'v2', 'v3' HTTP.headers('X-ECOPORTAL-API-KEY' => key_token) when 'graphql' HTTP.headers('Authorization' => "Bearer #{session_token(host: host)}") end.then do |request| request ||= HTTP request.persistent(base_url, timeout: KEEP_ALIVE_TIMEOUT).accept(:json).timeout( connect: CONNECT_TIMEOUT, read: READ_TIMEOUT, write: WRITE_TIMEOUT ) end end |
#execute(query_string, variables: {}, operation_name: nil) ⇒ Hash
Executes a GraphQL query or mutation over HTTP.
205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 205 def execute(query_string, variables: {}, operation_name: nil) body = { query: query_string, variables: variables } body[:operationName] = operation_name if operation_name # Route through the inherited `Common::Client` pipeline # (`instrument` -> `with_retry` { `rate_throttling` }) so GraphQL requests # inherit the SAME battle-tested contingencies as the REST clients: # rate-limit backoff (429 / Cloudflare 1015), 5xx + connection-error retries, # timeouts, and instrumentation. Do NOT re-issue the request raw here — that # bypasses all of the above and is what let live runs die on the first 429. response = instrument('POST', ENDPOINT_PATH, body) do request { |http| http.post(url_for(''), json: body) } end handle_http_response(response) end |
#org_id ⇒ Object
194 195 196 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 194 def org_id @org_id || fetch_env_required('ORGANIZATION_ID') end |
#url_for(path) ⇒ String
Full URl builder of the request
225 226 227 |
# File 'lib/ecoportal/api/common/graphql/http_client.rb', line 225 def url_for(path) "#{api_url}#{path}" end |