Module: Conversant::V3::HttpClient
- Included in:
- Base
- Defined in:
- lib/conversant/v3/http_client.rb,
sig/conversant/v3.rbs
Overview
HTTP client module
Constant Summary collapse
- PORTAL_SESSION_REDIS_KEY =
Redis key for storing portal SESSION cookie
'CONVERSANT.V3.PORTAL.SESSION'- SSO_GW_SESSION2_REDIS_KEY =
Redis key for storing SSO_GW_SESSION2 cookie
'CONVERSANT.V3.PORTAL.SSO_GW_SESSION2'
Instance Method Summary collapse
-
#add_cookies_to_headers(headers) ⇒ Hash{String => String}
Adds cookies from cookie jar to request headers.
-
#authenticate ⇒ Hash{Symbol => String}?
Authenticates and returns cached SSO sessions.
-
#configuration ⇒ Conversant::Configuration
Returns the global Conversant configuration.
-
#cookie_jar ⇒ Hash{String => String}
Gets the thread-local cookie jar for storing cookies during authentication.
-
#cookie_jar=(value) ⇒ Hash?
Sets the thread-local cookie jar.
-
#debug_log(message) ⇒ void
Logs a debug message if debug mode is enabled.
-
#extract_form_action(html_body) ⇒ String?
Extracts form action URL from HTML login page.
-
#http_get(url) ⇒ Hash{Symbol => untyped}?
Performs an HTTP GET request with cookie management.
-
#http_post(url, payload, extra_headers = {}) ⇒ Hash{Symbol => untyped}?
Performs an HTTP POST request with cookie management.
-
#login_url ⇒ String
Returns the SSO login page URL (portal console root).
-
#parse_cookie_to_jar(cookie_string) ⇒ void
Parses a Set-Cookie header string and stores it in the cookie jar.
-
#redis ⇒ Redis
Returns the Redis client from configuration.
-
#request(method, url, payload, headers) ⇒ Array(Integer, RestClient::Response)
Makes an authenticated HTTP request.
-
#sso_login ⇒ Hash{Symbol => String}?
Performs SSO login to obtain root SESSION and SSO_GW_SESSION2 cookies.
-
#update_cookie_jar(response) ⇒ void
Updates cookie jar from HTTP response.
Instance Method Details
#add_cookies_to_headers(headers) ⇒ Hash{String => String}
Adds cookies from cookie jar to request headers
357 358 359 360 361 362 |
# File 'lib/conversant/v3/http_client.rb', line 357 def (headers) return headers if .nil? || .empty? = .map { |name, value| "#{name}=#{value}" }.join('; ') headers.merge('Cookie' => ) end |
#authenticate ⇒ Hash{Symbol => String}?
Authenticates and returns cached SSO sessions
Wrapper around sso_login that ensures sessions are cached in Redis. This is the primary method used by service classes for authentication.
187 188 189 190 191 192 193 194 195 |
# File 'lib/conversant/v3/http_client.rb', line 187 def authenticate result = sso_login return unless result && result[:session] redis.set(PORTAL_SESSION_REDIS_KEY, result[:session], ex: configuration.cache_ttl) redis.set(SSO_GW_SESSION2_REDIS_KEY, result[:sso_gw_session2], ex: configuration.cache_ttl) if result[:sso_gw_session2] result end |
#configuration ⇒ Conversant::Configuration
Returns the global Conversant configuration
442 443 444 |
# File 'lib/conversant/v3/http_client.rb', line 442 def configuration Conversant.configuration end |
#cookie_jar ⇒ Hash{String => String}
Gets the thread-local cookie jar for storing cookies during authentication
The cookie jar is used to maintain cookies across multiple HTTP requests during the SSO login flow. Each thread has its own isolated cookie jar.
41 42 43 |
# File 'lib/conversant/v3/http_client.rb', line 41 def Thread.current[:conversant_cookie_jar] ||= {} end |
#cookie_jar=(value) ⇒ Hash?
Sets the thread-local cookie jar
55 56 57 |
# File 'lib/conversant/v3/http_client.rb', line 55 def (value) Thread.current[:conversant_cookie_jar] = value end |
#debug_log(message) ⇒ void
This method returns an undefined value.
Logs a debug message if debug mode is enabled
68 69 70 71 72 |
# File 'lib/conversant/v3/http_client.rb', line 68 def debug_log() return unless configuration.debug_mode configuration.logger.debug "Conversant::V3 - #{}" end |
#extract_form_action(html_body) ⇒ String?
Extracts form action URL from HTML login page
Searches for the Keycloak login form action URL in the HTML.
415 416 417 418 419 420 421 422 423 424 425 426 427 |
# File 'lib/conversant/v3/http_client.rb', line 415 def extract_form_action(html_body) return nil unless html_body # Parse HTML to find form action URL match = html_body.match(/form[^>]*id=["']kc-form-login["'][^>]*action=["']([^"']*)["']/) return CGI.unescapeHTML(match[1]) if match # Fallback: look for any form with action match = html_body.match(/form[^>]*action=["']([^"']*)["']/) return CGI.unescapeHTML(match[1]) if match nil end |
#http_get(url) ⇒ Hash{Symbol => untyped}?
Performs an HTTP GET request with cookie management
Handles redirects automatically and updates the cookie jar from responses. Used internally during SSO login flow.
251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 |
# File 'lib/conversant/v3/http_client.rb', line 251 def http_get(url) headers = { 'User-Agent' => configuration.default_ua, 'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' } headers = (headers) response = RestClient::Request.execute( method: :get, url: url, headers: headers, verify_ssl: configuration.verify_ssl, max_redirects: 10 ) do |resp, _request, _result| (resp) case resp.code when 200..299 resp when 300..399 resp.follow_redirection else resp end end (response) { status: response.code, body: response.body, headers: response.headers } rescue StandardError => e configuration.logger.error "Conversant::V3 - HTTP GET error: #{e.}" nil end |
#http_post(url, payload, extra_headers = {}) ⇒ Hash{Symbol => untyped}?
Performs an HTTP POST request with cookie management
Handles redirects automatically and updates the cookie jar from responses. Used internally during SSO login flow for form submissions.
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 |
# File 'lib/conversant/v3/http_client.rb', line 299 def http_post(url, payload, extra_headers = {}) headers = { 'User-Agent' => configuration.default_ua, 'Accept' => '*/*' }.merge(extra_headers) headers = (headers) response = RestClient::Request.execute( method: :post, url: url, payload: payload, headers: headers, verify_ssl: configuration.verify_ssl, max_redirects: 10 ) do |resp, _request, _result| (resp) case resp.code when 200..299 resp when 300..399 location = resp.headers[:location] if location get_response = http_get(location) if get_response && get_response[:status] == 200 OpenStruct.new( code: get_response[:status], body: get_response[:body], headers: get_response[:headers] ) else resp.follow_redirection end else resp.follow_redirection end else resp end end (response) { status: response.code, body: response.body, headers: response.headers } rescue StandardError => e configuration.logger.error "Conversant::V3 - HTTP POST error: #{e.}" nil end |
#login_url ⇒ String
Returns the SSO login page URL (portal console root)
Resolved from configuration (ENV-driven) so the base domain is never hardcoded. Always normalized with a single trailing slash.
435 436 437 |
# File 'lib/conversant/v3/http_client.rb', line 435 def login_url "#{configuration.portal_endpoint.chomp('/')}/" end |
#parse_cookie_to_jar(cookie_string) ⇒ void
This method returns an undefined value.
Parses a Set-Cookie header string and stores it in the cookie jar
393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/conversant/v3/http_client.rb', line 393 def () return unless = .split(';') return if .empty? first_part = .first parts = first_part.split('=', 2) return unless parts && parts.length == 2 name, value = parts [name.strip] = value.strip debug_log "Parsed cookie: #{name.strip}" if configuration.debug_mode end |
#redis ⇒ Redis
Returns the Redis client from configuration
449 450 451 |
# File 'lib/conversant/v3/http_client.rb', line 449 def redis configuration.redis end |
#request(method, url, payload, headers) ⇒ Array(Integer, RestClient::Response)
Makes an authenticated HTTP request
Handles JSON payload serialization and SSL verification based on configuration. All service classes use this method for making API calls.
216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
# File 'lib/conversant/v3/http_client.rb', line 216 def request(method, url, payload, headers) debug_log "[Request] #{method} #{url}" debug_log "[Request] Headers: #{headers.inspect}" if configuration.debug_mode # Check Content-Type using string key (headers are now plain hash with string keys) payload = payload&.to_json if headers&.[]('Content-Type') == configuration.default_content_type request = RestClient::Request.new( method: method, url: url, payload: payload, headers: headers, verify_ssl: configuration.verify_ssl ) response = request.execute do |response| response end debug_log "[Response] Status: #{response.code}" if configuration.debug_mode [response.code, response] rescue StandardError => e configuration.logger.error "Conversant::V3 - Request exception: #{e.}" [500, nil] end |
#sso_login ⇒ Hash{Symbol => String}?
Performs SSO login to obtain root SESSION and SSO_GW_SESSION2 cookies
Executes a multi-step SSO authentication flow:
- Fetches the login page
- Extracts the form action URL
- Submits credentials
- Extracts and caches session cookies
Sessions are cached in Redis for reuse across all services and customers.
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 |
# File 'lib/conversant/v3/http_client.rb', line 94 def sso_login session_key = PORTAL_SESSION_REDIS_KEY sso_key = SSO_GW_SESSION2_REDIS_KEY # Check cache first cached_session = redis.get(session_key) cached_sso_gw = redis.get(sso_key) if cached_session && cached_sso_gw debug_log 'Using cached global SSO sessions' return { session: cached_session, sso_gw_session2: cached_sso_gw } end begin # Initialize thread-safe cookie jar self. = {} # Step 1: Get login page and extract form action debug_log "[SSO Login - Step 1] Fetching login page: #{login_url}" login_page_response = http_get(login_url) return nil unless login_page_response debug_log "Login page loaded, status: #{login_page_response[:status]}" # Step 2: Parse login form to get action URL form_action = extract_form_action(login_page_response[:body]) unless form_action debug_log 'Could not extract form action URL from login page' return nil end debug_log "[SSO Login - Step 2] Form action extracted: #{form_action}" # Step 3: Submit credentials form_data = URI.encode_www_form( username: configuration.swiftserve_identifier_id, password: configuration.swiftserve_identifier_hash, credentialId: '' ) debug_log '[SSO Login - Step 3] Submitting login form...' login_response = http_post(form_action, form_data, { 'Content-Type' => 'application/x-www-form-urlencoded' }) return nil unless login_response debug_log "Login completed, status: #{login_response[:status]}" # Step 4: Extract cookies = ['SESSION'] = ['SSO_GW_SESSION2'] if && # Cache the sessions globally redis.set(session_key, , ex: configuration.cache_ttl) redis.set(sso_key, , ex: configuration.cache_ttl) configuration.logger.info 'Conversant::V3 - SSO login successful, sessions cached' return { session: , sso_gw_session2: } end debug_log 'New SSO login failed - no valid sessions obtained' nil rescue StandardError => e configuration.logger.error "Conversant::V3 - SSO login error: #{e.}" nil ensure self. = nil end end |
#update_cookie_jar(response) ⇒ void
This method returns an undefined value.
Updates cookie jar from HTTP response
Extracts cookies from Set-Cookie headers and response.cookies hash.
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/conversant/v3/http_client.rb', line 370 def (response) return unless response&.headers = response.headers[:set_cookie] if if .is_a?(Array) .each { || () } elsif .is_a?(String) () end end return unless response.respond_to?(:cookies) && response..is_a?(Hash) response..each do |name, value| [name.to_s] = value.to_s end end |