Raccourcir une URL en Ruby tient en un seul POST. Envoyez le lien long à l'API d'un raccourcisseur avec Net::HTTP, transmettez votre clé API comme jeton Bearer et extrayez short_url du JSON renvoyé. Aucune gem n'est nécessaire, ce qui compte lorsque le script doit s'exécuter sur une machine que vous ne contrôlez pas.
La forme du point de terminaison et le modèle d'authentification ci-dessous sont documentés dans la présentation de l'API gratuite de raccourcissement d'URL ; cet article est la version propre à Ruby du guide général pour raccourcir une URL, qui couvre plutôt le parcours depuis le tableau de bord.
La même série, avec d'autres environnements d'exécution : Python, JavaScript, PHP et Go. Les noms de champs sont ceux d'Elido ; la structure s'adapte à la plupart des raccourcisseurs modernes.
La méthode la plus rapide : Net::HTTP.post
Deux require, un appel :
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
Choisir ENV.fetch plutôt que ENV[] est volontaire : une clé manquante provoque immédiatement une erreur au lieu d'envoyer Bearer et de revenir avec un 401 déroutant.
La ligne unless response.is_a?(Net::HTTPSuccess) est celle que beaucoup omettent. Net::HTTP lève une exception sur un socket fermé ou un délai d'expiration, et sur rien d'autre ; une réponse 401 avec un corps d'erreur arrive donc comme si elle indiquait un succès jusqu'à ce que JSON.parse vous renvoie un hash sans short_url.
Ajouter des délais d'expiration, des nouvelles tentatives et une clé d'idempotence
Net::HTTP.post présente un défaut impossible à corriger : il n'accepte aucun argument de délai d'expiration. Pour tout ce qui doit s'exécuter sans surveillance, utilisez Net::HTTP.start, qui le permet.
Profitez-en pour traiter l'échec qu'une nouvelle tentative naïve aggrave. Si le POST atteint l'API mais que la réponse se perd sur le chemin du retour, votre code arrive au délai d'expiration, effectue une nouvelle tentative et crée un second lien pour la même destination. Une Idempotency-Key stable comble cette lacune : hachez la destination et l'API renverra le lien d'origine au lieu d'en créer un nouveau.
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
Le case sur la classe de la réponse est plus lisible qu'une accumulation de comparaisons de codes d'état, et il rend la politique évidente : un 429 attend aussi longtemps que le demande le serveur, un 5xx applique un backoff exponentiel, et tout le reste, notamment un 401 ou un 422, lève une exception dès la première tentative, car une nouvelle tentative ne réparera rien. L'analyse approfondie des limites de débit et de l'idempotence détaille la sémantique des en-têtes.
Vous voulez l'exécuter tel quel ? Créez une clé avec le forfait gratuit, exportez-la sous ELIDO_API_KEY et chaque extrait de code de cette page fonctionnera sans modification.
La version Faraday pour une application existante
Dans une application Rails, la boucle écrite à la main n'est pas adaptée. Faraday vous fournit un objet de connexion unique avec l'en-tête d'authentification déjà attaché, du JSON dans les deux sens et une politique de nouvelles tentatives déclarée plutôt qu'écrite :
# 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
Deux choses à savoir avant de coller ce code. Le middleware de nouvelles tentatives a été déplacé dans sa propre gem faraday-retry avec Faraday 2, et nécessite donc une ligne distincte dans le Gemfile. De plus, raise_error inverse le comportement de Net::HTTP : les erreurs 4xx et 5xx lèvent désormais Faraday::ClientError et Faraday::ServerError, ce qui est souhaitable dans un job qui doit effectuer une nouvelle tentative plutôt que stocker une valeur nil.
Raccourcir une liste sans dépasser la limite de débit
Ruby libère le verrou global lorsqu'un thread attend une E/S, si bien que les threads sont réellement avantageux ici, même avec CRuby. Ce qu'il ne faut pas faire, c'est en créer un par URL : un CSV de 2 000 lignes devient 2 000 sockets et un mur de 429. Une Queue accompagnée d'un pool fixe maintient la concurrence à un niveau constant, quelle que soit la longueur de la liste.
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
Indexer les résultats avec l'URL d'origine rend visible un échec partiel et permet de relancer le lot. Le Mutex n'est pas facultatif : un Hash ordinaire écrit par huit threads constitue une course de données, qui vous le fera payer lors de l'exécution qui compte.
Dans Rails, encapsulez shorten dans un Active Job et laissez l'adaptateur de file d'attente gérer le backoff. Effectuer une nouvelle tentative dans une action de contrôleur monopolise un thread Puma pendant chaque sleep, et un import limité par le débit peut discrètement affamer le processus web. Si vous intégrez cela dans un flux de publication, l'article sur les webhooks pour les événements de liens explique comment récupérer les données de clic sans interrogation périodique.
Lequel choisir
Pour une tâche rake ou un script ponctuel : Net::HTTP.post, quatre lignes, et c'est terminé. Pour tout ce qui est planifié : la version avec Net::HTTP.start, délais d'expiration et clé d'idempotence. Pour une application Rails qui utilise déjà Faraday : l'objet de connexion, avec les nouvelles tentatives déclarées une fois puis réutilisées partout.
Le choix compte peu face aux trois habitudes sous-jacentes. Prenez la clé dans l'environnement, définissez un délai d'expiration explicite et vérifiez la classe de la réponse avant d'analyser le corps. La page API et SDK répertorie les clients générés si vous préférez ne rien gérer de tout cela, et les solutions pour les développeurs présentent les autres fonctions exposées par l'API une fois que les liens sont créés depuis du code.
Lire la série pilier
Cet article appartient au cluster engineering. Commencez par le guide de l'API gratuite de raccourcissement d'URL pour la forme du point de terminaison et l'authentification, puis consultez les limites de débit et l'idempotence pour bien vous comporter sous charge. La référence à jour se trouve dans la documentation de l'API, et les raccourcisseurs d'URL pour les développeurs indiquent les points à vérifier dans une API avant de construire dessus.
Articles associés sur le blog
Questions fréquentes
Comment raccourcir une URL en Ruby ?
Envoyez l'URL longue à l'API d'un raccourcisseur avec Net::HTTP, en transmettant votre clé API dans un en-tête Bearer et la destination dans un corps JSON, puis lisez short_url dans la réponse analysée. Net::HTTP.post tient sur une seule ligne dans la bibliothèque standard, donc rien n'est à installer pour la première version.
Ai-je besoin d'une gem pour raccourcir des URL en Ruby ?
Non. Net::HTTP et la bibliothèque json sont fournies avec Ruby et couvrent tout l'appel. Faraday devient utile lorsque vous voulez une connexion réutilisable, un middleware de nouvelles tentatives et un encodage JSON automatique, ce qui est généralement le cas dans une application Rails plutôt que dans un script autonome.
Comment définir un délai d'expiration avec Net::HTTP ?
Utilisez Net::HTTP.start avec open_timeout et read_timeout plutôt que le raccourci Net::HTTP.post, qui ne permet de définir ni l'un ni l'autre. Sans ces paramètres, une connexion bloquée peut immobiliser un worker pendant les 60 secondes du délai par défaut, ce qui signifie perdre un emplacement de job dans une tâche en arrière-plan.
Pourquoi Net::HTTP ne lève-t-il pas d'exception pour un 401 ?
Parce qu'un 401 est une réponse HTTP valide, et non une défaillance du transport. Net::HTTP ne lève des exceptions que pour les erreurs de socket et de délai d'expiration ; vous devez donc vérifier vous-même la classe de la réponse avec is_a?(Net::HTTPSuccess) ou lire response.code. Le middleware raise_error de Faraday s'en charge pour vous.
Comment raccourcir de nombreuses URL en une seule fois en Ruby ?
Placez les URL dans une Queue et exécutez un petit pool de threads, généralement autour de huit, pour les traiter. Ruby libère le verrou global pendant les E/S, les threads peuvent donc réellement superposer les attentes réseau, et un pool fixe vous maintient sous la limite de débit de l'API, qu'une boucle illimitée avec un thread par URL dépasserait immédiatement.
Où la boucle de nouvelles tentatives doit-elle se trouver dans une application Rails ?
Dans un job en arrière-plan, pas dans une action de contrôleur. Une nouvelle tentative qui attend quelques secondes monopolise un thread Puma pendant tout ce temps, si bien qu'un lot limité par le débit peut affamer le processus web. Active Job et un adaptateur de file d'attente gèrent le backoff et vous donnent gratuitement un historique des nouvelles tentatives.
Essayer Elido
Collez une URL, obtenez un lien court
Sans inscription. Lien actif 30 jours. Inscrivez-vous pour le garder pour toujours.
Gratuit, sans inscription · 2 par jour