Class: ReactOnRailsPro::RollingDeployAdapters::Http

Inherits:
Object
  • Object
show all
Defined in:
lib/react_on_rails_pro/rolling_deploy_adapters/http.rb

Overview

Built-in HTTP rolling-deploy adapter. Pairs with ReactOnRailsPro::RollingDeploy::BundlesController on the running Rails server: the controller exposes the current deployment's bundles and the adapter (running in the next deployment's build CI) fetches them.

The promise is "zero-infra default": no S3 bucket, no IAM, no extra gem. The currently-deployed Rails server already has the bundles + companion assets sitting on disk; this adapter pulls them via authenticated HTTP.

Configuration (see docs/pro/rolling-deploy-adapters.md):

ReactOnRailsPro.configure do |config|
config.rolling_deploy_adapter       = ReactOnRailsPro::RollingDeployAdapters::Http
config.rolling_deploy_token         = ENV.fetch("ROLLING_DEPLOY_TOKEN")
config.rolling_deploy_previous_urls = ENV["ROLLING_DEPLOY_PREVIOUS_URLS"]
end

Error contract matches the rolling_deploy_adapter protocol: every exception is caught and reported as a warning so a failed seed degrades to the runtime 410-retry fallback rather than failing the build. rubocop:disable Metrics/ClassLength

Constant Summary collapse

DEFAULT_OPEN_TIMEOUT_SECONDS =

Per-request HTTP timeouts. The outer Timeout.timeout in RollingDeployCacheStager bounds the total wall-clock budget (10s for discovery, 30s for fetch); these inner timeouts let a hung server fail before the outer wrapper interrupts mid-write, which is more reliable than relying on the thread-level Timeout.timeout that may interrupt at a random execution point.

5
DEFAULT_READ_TIMEOUT_SECONDS =
25
MANIFEST_READ_TIMEOUT_SECONDS =

Manifest discovery is wrapped in a 10s outer budget by RollingDeployCacheStager.

4
DISCOVERY_DEADLINE_SECONDS =
10
FETCH_DEADLINE_SECONDS =
30
DEFAULT_MAX_SIZE =

Maximum uncompressed payload accepted from /bundles/:hash. Mirrors the tarball helper default so a misbehaving or malicious server cannot exhaust disk via a zip-bomb-style response.

ReactOnRailsPro::RollingDeploy::Tarball::DEFAULT_MAX_SIZE
COMPRESSED_BODY_CAP =

Maximum compressed bytes accepted from /bundles/:hash before extract enforces DEFAULT_MAX_SIZE on the uncompressed tarball contents. Set near 1/4 of DEFAULT_MAX_SIZE: JS bundles typically decompress 3-5x, so a 50 MB wire payload that decompresses beyond 200 MB is anomalous.

50 * 1024 * 1024
LOG_PREFIX =
"[ReactOnRailsPro::RollingDeployAdapters::Http]"
BUNDLE_ENTRY_NAME =

Wire-format constant: must stay in sync with ReactOnRailsPro::RollingDeploy::BundlesController::BUNDLE_ENTRY_NAME. The controller serves the bundle file under this entry name; if the two ever diverge the client will fail to locate the bundle after extracting the tarball.

"bundle.js"

Class Method Summary collapse

Class Method Details

.fetch(bundle_hash) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/react_on_rails_pro/rolling_deploy_adapters/http.rb', line 106

def fetch(bundle_hash)
  bases = configured_previous_urls
  return nil if bases.empty?
  return nil if hash_invalid?(bundle_hash)

  if token_missing?
    return warn_and_return(
      "rolling_deploy_token is not configured; skipping fetch(#{bundle_hash.inspect})",
      nil
    )
  end

  candidates = fetch_candidates(bundle_hash, bases)
  return nil if candidates.empty?

  dir = bundle_dir(bundle_hash)
  result = fetch_from_candidates(bundle_hash, candidates, dir)
  return result if result

  cleanup_and_return(dir, nil)
rescue StandardError => e
  cleanup_and_return(dir, nil) if dir
  warn_and_return("fetch(#{bundle_hash.inspect}) failed: #{e.class}: #{e.message}", nil)
end

.previous_bundle_hashesObject



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
# File 'lib/react_on_rails_pro/rolling_deploy_adapters/http.rb', line 87

def previous_bundle_hashes
  bases = configured_previous_urls
  if bases.empty?
    @discovery_provenance = nil
    return []
  end
  if token_missing?
    @discovery_provenance = nil
    return warn_and_return("rolling_deploy_token is not configured; skipping manifest fetch", [])
  end

  provenance = discover_provenance(bases)
  @discovery_provenance = provenance
  provenance.keys.select { |hash| publishable_provenance?(hash, provenance.fetch(hash)) }
rescue StandardError => e
  @discovery_provenance = nil
  warn_and_return("previous_bundle_hashes failed: #{e.class}: #{e.message}", [])
end

.upload(_bundle_hash, bundle:, assets:) ⇒ Object

Intentional no-op. The running Rails server IS the artifact store — bundle + companion assets are already on local disk where the mountable BundlesController will serve them on the next deploy's build CI. Documented in docs/pro/rolling-deploy-adapters.md.



135
136
137
# File 'lib/react_on_rails_pro/rolling_deploy_adapters/http.rb', line 135

def upload(_bundle_hash, bundle:, assets:)
  # See class doc above.
end