ruby / http clients
I prefer code like this to a library specific to the API:
require "bundler/inline"
gemfile do
source "https://rubygems.org"
gem "http"
end
url = ENV["API_URL"]
if url.to_s.strip == ""
puts "err: API_URL environment variable is not set"
exit 1
end
begin
resp = HTTP.post(url, json: { text: "hi" })
rescue => e
puts "err: #{e.message}"
exit 1
end
puts resp.body.to_s
An SDK has costs. I must upgrade it for security patches and dependency conflicts. Each one is another interface to learn. When I replace an SDK, I rewrite only the endpoints the app calls.
The example uses the HTTP gem
because net/http has a worse interface.
The Go version uses only the standard library
(see go / http clients):
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"os"
)
func main() {
url := os.Getenv("API_URL")
if url == "" {
log.Fatalln("err: API_URL environment variable is not set")
}
reqBody, err := json.Marshal(map[string]string{
"text": "Hello, world!",
})
if err != nil {
log.Fatalf("err: %v\n", err)
}
resp, err := http.Post(url, "application/json", bytes.NewBuffer(reqBody))
if err != nil {
log.Fatalf("err: %v\n", err)
}
defer resp.Body.Close()
respBody, err := io.ReadAll(resp.Body)
if err != nil {
log.Fatalf("err: %v\n", err)
}
fmt.Println(string(respBody))
}
I add to a client as I need, for example retries with exponential backoff.
Case studies
Cloudflare Images: retries and untrusted input
The upload client retried 429/502/503/504 responses with a
short backoff (DELAYS = [1, 2, nil], under Rack's 15s request
timeout). Uploads are idempotent, so a retry is safe.
Uploads that fetch a user-supplied URL first passed an SSRF
guard. The guard resolved the host and rejected private,
loopback, link-local, and multicast addresses. That blocked
http://169.254.169.254/latest/meta-data/.
Content type came from the bytes, not the Content-Type header:
private def detect_content_type(body)
bytes = body.bytes.take(4)
if bytes[0] == 0xFF && bytes[1] == 0xD8 && bytes[2] == 0xFF
"image/jpeg"
elsif bytes == [0x89, 0x50, 0x4E, 0x47]
"image/png"
elsif bytes[0] == 0x47 && bytes[1] == 0x49 && bytes[2] == 0x46
"image/gif"
end
end
Postmark: authenticating an inbound webhook
Outbound email was one POST /email. Postmark posts inbound
emails to a webhook, which needed two layers of authentication.
Basic auth covered transport. I compared credentials with
Rack::Utils.secure_compare. I coerced them with .to_s, so a
malformed credential returned the same 401 as a wrong password.
DKIM authenticated the message:
ok = auth_results.any? do |v|
v.match?(/\bdkim=pass\b/i) &&
v.match?(/\b(?:header\.)?d=example\.com\b/i)
end
Postmark retries a failed webhook up to 10 times, so the handler recorded each message ID and rejected duplicates.
WorkOS: OAuth without the SDK
Four endpoints covered SSO, audit logs, and AuthKit. The jwt gem verified JWTs.
The login handler stored a CSRF state token in the session.
The callback rejected a mismatch. The AuthKit bridge added a
consent POST so an attacker could not bind their identity to a
logged-in victim. The handler checked the redirect URL from
WorkOS before use:
uri = URI.parse(redirect_uri.to_s)
if uri.scheme != "https" || !uri.host&.end_with?(".workos.com", ".authkit.app")
return redirect_to("/login")
end
Sentry: error capture without the SDK
The sentry-ruby gem includes tracing, profiling, breadcrumbs,
and many integrations. I used only error capture, which fits in
a few hundred lines:
Sentry.init(dsn: ENV["SENTRY_DSN"])
Sentry.capture_exception(err, extra: {...}, fingerprint: [...])
Sentry.capture_message("did something weird", extra: {...})
capture_* is a no-op until init runs with a DSN, so tests
need no stubs. The client parses the DSN
(https://{key}@{host}/{project_id}) once at boot to build the
envelope endpoint
and auth header.
Events go onto a bounded SizedQueue. A background thread POSTs
them with retries. A Process.pid check makes the worker
fork-safe under Puma. A full queue drops events so a Sentry
failure never blocks the request.
Each event carries request params, so a POST /login would send
the password. The caller filters params:
SENSITIVE_KEY_FRAGMENTS = %w(email password secret token).freeze
SENSITIVE_EXACT_KEYS = Set.new(%w(code)).freeze
Substring matching catches current_password and *_token.
Exact matching redacts the OAuth code and keeps country_code.
The gem's breadcrumbs (request params, SQL, headers) leak PII,
and this client never emits them.