Module: Cloudflare::HTTP

Defined in:
lib/cloudflare_workers/http.rb

Constant Summary collapse

DEFAULT_HEADERS =
{}.freeze

Class Method Summary collapse

Class Method Details

.fetch(url, method: 'GET', headers: nil, body: nil) ⇒ Object

Issue an HTTP request via globalThis.fetch.

res = Cloudflare::HTTP.fetch('https://api.example.com/users',
        method: 'POST',
        headers: { 'content-type' => 'application/json' },
        body: { name: 'kazu' }.to_json).__await__
res.status   # => 200
res.json     # => { 'id' => 1, 'name' => 'kazu' }

The whole response body is awaited and returned as a String. Use ‘Cloudflare::HTTPResponse#body` to access raw text.



70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
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
# File 'lib/cloudflare_workers/http.rb', line 70

def self.fetch(url, method: 'GET', headers: nil, body: nil)
  hdrs = headers || DEFAULT_HEADERS
  method_str = method.to_s.upcase
  js_headers = ruby_headers_to_js(hdrs)
  js_body = body.nil? ? nil : body.to_s
  url_str = url.to_s
  response_klass = Cloudflare::HTTPResponse
  err_klass = Cloudflare::HTTPError
  headers_to_hash = method(:js_headers_to_hash)
  _ = headers_to_hash # silence opal lint

  # NOTE: the multi-line backtick below returns a Promise BECAUSE it
  # is assigned to `js_promise` (and `js_promise.__await__` awaits
  # it). Opal treats a multi-line x-string as a statement, not an
  # expression — safe when assigned, but a bare multi-line backtick
  # at end-of-method silently drops the Promise. Do NOT refactor
  # this into `def fetch ... end` with the backtick as the last
  # expression. See the single-line IIFE pattern used in
  # lib/cloudflare_workers/{cache,queue,durable_object}.rb#put for
  # the alternative that survives either position. (Phase 11B audit.)
  js_promise = `
    (async function() {
      var init = { method: #{method_str}, headers: #{js_headers}, redirect: 'follow' };
      if (#{js_body} !== nil && #{js_body} != null) { init.body = #{js_body}; }
      var resp;
      try {
        resp = await globalThis.fetch(#{url_str}, init);
      } catch (e) {
        #{Kernel}.$$raise(#{err_klass}.$new(e.message || String(e), Opal.hash({ url: #{url_str}, method: #{method_str} })));
      }
      var text = '';
      try {
        text = await resp.text();
      } catch (e) {
        text = '';
      }
      var hash_keys = [];
      var hash_vals = [];
      if (resp.headers && typeof resp.headers.forEach === 'function') {
        resp.headers.forEach(function(value, key) {
          hash_keys.push(String(key).toLowerCase());
          hash_vals.push(String(value));
        });
      }
      return { status: resp.status|0, text: text, hkeys: hash_keys, hvals: hash_vals };
    })()
  `

  js_result = js_promise.__await__
  hkeys = `#{js_result}.hkeys`
  hvals = `#{js_result}.hvals`
  h = {}
  i = 0
  len = `#{hkeys}.length`
  while i < len
    h[`#{hkeys}[#{i}]`] = `#{hvals}[#{i}]`
    i += 1
  end

  response_klass.new(
    status: `#{js_result}.status`,
    headers: h,
    body: `#{js_result}.text`,
    url: url_str
  )
end

.js_headers_to_hash(js_headers) ⇒ Object

Internal: convert JS Headers / iterable to Ruby Hash with lowercased string keys. Currently inlined in fetch() but exposed for completeness.



152
153
154
155
156
157
158
159
160
161
162
# File 'lib/cloudflare_workers/http.rb', line 152

def self.js_headers_to_hash(js_headers)
  h = {}
  `
    if (#{js_headers} && typeof #{js_headers}.forEach === 'function') {
      #{js_headers}.forEach(function(value, key) {
        #{h}.$$smap[String(key).toLowerCase()] = String(value);
      });
    }
  `
  h
end

.ruby_headers_to_js(hash) ⇒ Object

Convert a Ruby Hash<String, String> into a plain JS object usable as the ‘headers` init for fetch().



139
140
141
142
143
144
145
146
147
# File 'lib/cloudflare_workers/http.rb', line 139

def self.ruby_headers_to_js(hash)
  js_obj = `{}`
  hash.each do |k, v|
    ks = k.to_s
    vs = v.to_s
    `#{js_obj}[#{ks}] = #{vs}`
  end
  js_obj
end