Accorciare un URL in Ruby richiede un solo POST. Invia il link lungo all'API di uno shortener con Net::HTTP, passa la tua chiave API come token Bearer ed estrai short_url dal JSON restituito. Non serve alcuna gem, un aspetto importante quando lo script deve essere eseguito su una macchina che non controlli.
La struttura dell'endpoint e il modello di autenticazione qui sotto sono documentati nella panoramica dell'API gratuita per URL shortener; questo articolo è la versione specifica per Ruby della guida generale su come accorciare un URL, che invece illustra il percorso dalla dashboard.
Stessa serie, altri runtime: Python, JavaScript, PHP e Go. I nomi dei campi sono quelli di Elido; la struttura si adatta alla maggior parte degli shortener moderni.
Il modo più rapido: Net::HTTP.post
Due require, una chiamata:
require "json"
require "net/http"
response = Net::HTTP.post(
URI("https://api.elido.app/v1/links"),
{ destination_url: "https://example.com/spring-sale?utm_source=newsletter" }.to_json,
"Authorization" => "Bearer #{ENV.fetch('ELIDO_API_KEY')}",
"Content-Type" => "application/json",
)
raise "shorten failed: #{response.code}" unless response.is_a?(Net::HTTPSuccess)
puts JSON.parse(response.body)["short_url"] # => https://s.elido.me/ab12cd
Usare ENV.fetch invece di ENV[] è una scelta deliberata: se manca la chiave, lo script si interrompe subito invece di inviare Bearer e tornare con un 401 difficile da interpretare.
La riga unless response.is_a?(Net::HTTPSuccess) è quella che si tende a saltare. Net::HTTP genera un'eccezione in caso di socket non disponibile o timeout, e in nessun altro caso, quindi un 401 con un corpo di errore arriva con l'aspetto di un successo finché JSON.parse non ti restituisce un hash privo di short_url.
Aggiungere timeout, nuovi tentativi e una chiave di idempotenza
Net::HTTP.post ha un difetto che non puoi correggere: non accetta argomenti per il timeout. Per qualsiasi esecuzione non sorvegliata, passa a Net::HTTP.start, che li supporta.
Già che ci sei, gestisci il problema che un nuovo tentativo ingenuo può peggiorare. Se il POST raggiunge l'API ma la risposta si perde durante il ritorno, il codice va in timeout, riprova e crea un secondo link per la stessa destinazione. Una Idempotency-Key stabile chiude questa falla: calcola l'hash della destinazione e l'API restituisce il link originale invece di crearne uno nuovo.
require "digest"
def shorten(destination, attempts: 3)
uri = URI("https://api.elido.app/v1/links")
key = Digest::SHA256.hexdigest(destination) # stable across retries and re-runs
attempts.times do |attempt|
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true,
open_timeout: 5, read_timeout: 10) do |http|
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer #{ENV.fetch('ELIDO_API_KEY')}"
request["Content-Type"] = "application/json"
request["Idempotency-Key"] = key
request.body = { destination_url: destination }.to_json
http.request(request)
end
case response
when Net::HTTPSuccess then return JSON.parse(response.body)["short_url"]
when Net::HTTPTooManyRequests then sleep(response["Retry-After"].to_i.clamp(1, 60))
when Net::HTTPServerError then sleep(2**attempt) # 1s, 2s, 4s
else raise "shorten failed: #{response.code} #{response.body}"
end
end
raise "shorten failed after #{attempts} attempts"
end
Il case sulla classe della risposta è più leggibile di una serie di confronti sui codici di stato e rende evidente la politica: un 429 attende quanto richiesto dal server, un 5xx aumenta progressivamente l'attesa e qualsiasi altra risposta, come un 401 o un 422, genera un'eccezione al primo tentativo perché riprovare non risolverebbe il problema. L'approfondimento su rate limit e idempotenza descrive in dettaglio la semantica degli header.
Vuoi eseguire il codice così com'è? Crea una chiave con il piano gratuito, esportala come ELIDO_API_KEY e tutti gli snippet di questa pagina funzioneranno senza modifiche.
La versione con Faraday per un'app che già possiedi
In un'app Rails, il ciclo scritto a mano ha la struttura sbagliata. Faraday ti offre un unico oggetto connessione con l'header di autenticazione associato, JSON in entrambe le direzioni e una politica di nuovi tentativi dichiarata invece che scritta:
# Gemfile: gem "faraday" and gem "faraday-retry"
ELIDO = Faraday.new(url: "https://api.elido.app") do |f|
f.request :json
f.request :retry, max: 3, interval: 0.5, backoff_factor: 2,
retry_statuses: [429, 500, 502, 503, 504]
f.response :json
f.response :raise_error
f.headers["Authorization"] = "Bearer #{ENV.fetch('ELIDO_API_KEY')}"
f.options.timeout = 10
end
def shorten(destination)
ELIDO.post("/v1/links",
{ destination_url: destination },
{ "Idempotency-Key" => Digest::SHA256.hexdigest(destination) })
.body["short_url"]
end
Prima di incollare il codice, devi sapere due cose. In Faraday 2 il middleware per i nuovi tentativi è stato spostato nella gem separata faraday-retry, quindi serve una riga distinta nel Gemfile. Inoltre raise_error inverte il comportamento di Net::HTTP: ora 4xx e 5xx generano Faraday::ClientError e Faraday::ServerError, esattamente ciò che serve in un job che dovrebbe riprovare invece di memorizzare un valore nil.
Accorciare un elenco senza superare il rate limit
Ruby rilascia il lock globale mentre un thread attende l'IO, quindi qui i thread sono davvero vantaggiosi anche su CRuby. Quello che non devi fare è crearne uno per ogni URL: un CSV di 2.000 righe diventa 2.000 socket e una parete di 429. Una Queue insieme a un pool fisso mantiene costante la concorrenza, indipendentemente dalla lunghezza dell'elenco.
def shorten_all(urls, concurrency: 8)
queue = Queue.new
results = {}
mutex = Mutex.new
urls.each { |u| queue << u }
concurrency.times { queue << :done }
workers = concurrency.times.map do
Thread.new do
while (url = queue.pop) != :done
value = begin
shorten(url)
rescue => e
"ERROR: #{e.message}" # one bad row must not sink the batch
end
mutex.synchronize { results[url] = value }
end
end
end
workers.each(&:join)
results
end
Indicizzare i risultati con l'URL originale mantiene visibile un errore parziale e consente di eseguire di nuovo il batch. Mutex non è opzionale: un semplice Hash scritto da otto thread costituisce una race condition e ti presenterà il conto proprio durante l'esecuzione che conta.
In Rails, inserisci shorten in un Active Job e lascia che sia l'adapter della coda a gestire l'attesa progressiva. Riprovare all'interno di un'azione del controller occupa un thread Puma durante ogni sleep e un'importazione soggetta a rate limit può saturare silenziosamente il processo web. Se stai integrando questo flusso in una pubblicazione, l'articolo sui webhook per gli eventi dei link spiega come recuperare i dati sui clic senza fare polling.
Quale scegliere
Un task rake o uno script occasionale: Net::HTTP.post, quattro righe e il gioco è fatto. Qualsiasi attività pianificata: la versione con Net::HTTP.start, timeout e chiave di idempotenza. Un'app Rails che usa già Faraday: l'oggetto connessione, con i nuovi tentativi dichiarati una volta e riutilizzati ovunque.
La scelta conta poco rispetto alle tre abitudini fondamentali. Leggere la chiave dall'ambiente, impostare un timeout esplicito e controllare la classe della risposta prima di analizzarne il corpo. La pagina API e SDK elenca i client generati se preferisci non occuparti di tutto questo, mentre le soluzioni per sviluppatori descrivono cos'altro espone l'API una volta che i link vengono creati dal codice.
Leggi la serie cornerstone
Questo articolo fa parte del cluster engineering. Inizia dalla guida all'API gratuita per URL shortener per conoscere struttura dell'endpoint e autenticazione, poi passa a rate limit e idempotenza per gestire correttamente il carico. Il riferimento aggiornato è la documentazione dell'API, mentre URL shortener per sviluppatori spiega cosa verificare in un'API prima di costruirci sopra.
Articoli correlati sul blog
Domande frequenti
Come posso accorciare un URL in Ruby?
Invia l'URL lungo all'API di uno shortener con Net::HTTP, passando la tua chiave API come header Bearer e la destinazione in un corpo JSON, poi leggi short_url dalla risposta analizzata. Net::HTTP.post è una chiamata su una sola riga nella libreria standard, quindi per la prima versione non devi installare nulla.
Mi serve una gem per accorciare gli URL in Ruby?
No. Net::HTTP e la libreria json sono incluse in Ruby e coprono l'intera chiamata. Faraday è utile quando vuoi una connessione riutilizzabile, un middleware per i nuovi tentativi e la codifica automatica in JSON, una situazione tipica all'interno di un'app Rails più che in uno script autonomo.
Come imposto un timeout su Net::HTTP?
Usa Net::HTTP.start con open_timeout e read_timeout invece della scorciatoia Net::HTTP.post, che non ti permette di impostare nessuno dei due. Senza questi valori, una connessione bloccata può tenere fermo un worker per tutti i 60 secondi predefiniti e, in un job in background, questo significa perdere uno slot di lavoro.
Perché Net::HTTP non genera un'eccezione per un 401?
Perché un 401 è una risposta HTTP valida, non un errore di trasporto. Net::HTTP genera eccezioni solo per errori di socket e timeout, quindi devi controllare personalmente la classe della risposta con is_a?(Net::HTTPSuccess) oppure leggere response.code. Il middleware raise_error di Faraday lo fa per te.
Come posso accorciare molti URL contemporaneamente in Ruby?
Inserisci gli URL in una Queue ed esegui un piccolo pool di thread che la elaborano, di solito circa otto. Ruby rilascia il lock globale durante l'IO, quindi i thread si sovrappongono davvero alle attese di rete, mentre un pool fisso ti mantiene sotto il rate limit dell'API che un ciclo senza limiti con un thread per URL supererebbe immediatamente.
Dove dovrebbe trovarsi il ciclo dei nuovi tentativi in un'app Rails?
In un job in background, non in un'azione del controller. Un nuovo tentativo che dorme per un paio di secondi occupa un thread Puma per tutto il tempo, quindi un batch soggetto a rate limit può saturare il processo web. Active Job con un adapter di coda gestisce l'attesa progressiva e ti offre gratuitamente la cronologia dei nuovi tentativi.
Prova Elido
Incolla un URL, ottieni un link breve
Senza registrazione. Il link vive 30 giorni. Iscriviti per conservarlo.
Gratis, nessuna registrazione richiesta · 2 al giorno