Acortar una URL en Ruby es un solo POST. Envía el enlace largo a la API de un acortador con Net::HTTP, pasa tu clave de API como token Bearer y extrae short_url del JSON recibido. No hace falta ninguna gem, algo importante cuando el script tiene que ejecutarse en una máquina que no controlas.
La estructura del endpoint y el modelo de autenticación están documentados en la guía general de la API gratuita de acortamiento de URL; este artículo es la versión específica para Ruby del recorrido general sobre cómo acortar una URL, que cubre la ruta desde el panel.
La misma serie, otros entornos: Python, JavaScript, PHP y Go. Los nombres de los campos son los de Elido; la estructura se puede trasladar a la mayoría de los acortadores modernos.
La forma más rápida: Net::HTTP.post
Dos requires, una llamada:
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
ENV.fetch en lugar de ENV[] es una decisión deliberada: si falta la clave, falla al instante en vez de enviar Bearer y volver con un 401 confuso.
La línea unless response.is_a?(Net::HTTPSuccess) es la que mucha gente omite. Net::HTTP lanza una excepción si el socket está caído o se agota el tiempo de espera, y por ninguna otra cosa, así que un 401 con un cuerpo de error llega con el mismo aspecto que un éxito hasta que JSON.parse te devuelve un hash sin short_url.
Añade tiempos de espera, reintentos y una clave de idempotencia
Net::HTTP.post tiene un defecto que no puedes corregir: no acepta argumentos de tiempo de espera. Para cualquier proceso desatendido, pasa a Net::HTTP.start, que sí los acepta.
Ya que estás ahí, gestiona el fallo que un reintento ingenuo empeora. Si el POST llega a la API y la respuesta se pierde al regresar, tu código agota el tiempo de espera, reintenta y crea un segundo enlace para el mismo destino. Una Idempotency-Key estable cierra ese hueco: aplica un hash al destino y la API devuelve el enlace original en lugar de crear uno nuevo.
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
El case sobre la clase de la respuesta se lee mejor que un montón de comparaciones de códigos de estado y deja clara la política: un 429 espera el tiempo que indica el servidor, un 5xx aumenta la espera de forma exponencial y cualquier otra cosa, como un 401 o un 422, lanza una excepción en el primer intento porque reintentar no lo arreglará. El análisis detallado de los límites de solicitudes y la idempotencia explica toda la semántica de las cabeceras.
¿Quieres ejecutar esto tal cual? Crea una clave en el plan gratuito, expórtala como ELIDO_API_KEY y todos los fragmentos de esta página funcionarán sin cambios.
La versión con Faraday para una app que ya tienes
En una app de Rails, el bucle escrito a mano tiene una estructura equivocada. Faraday te da un único objeto de conexión con la cabecera de autenticación incorporada, JSON en ambas direcciones y una política de reintentos declarada en lugar de escrita:
# 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
Hay dos cosas que debes saber antes de pegarlo. El middleware de reintentos pasó a su propia gem, faraday-retry, en Faraday 2, así que necesita una línea separada en el Gemfile. Y raise_error invierte el comportamiento de Net::HTTP: ahora 4xx y 5xx lanzan Faraday::ClientError y Faraday::ServerError, que es lo que quieres en un trabajo que debe reintentarse en lugar de guardar un valor nil.
Acorta una lista sin superar el límite de solicitudes
Ruby libera el bloqueo global mientras un hilo espera durante la E/S, así que aquí los hilos son realmente útiles incluso en CRuby. Lo que no debes hacer es crear uno por URL: un CSV de 2.000 filas se convierte en 2.000 sockets y una pared de respuestas 429. Una Queue más un grupo fijo mantiene constante la simultaneidad, por larga que sea la lista.
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
Usar la URL original como clave de los resultados mantiene visible un fallo parcial y permite volver a ejecutar el lote. El Mutex no es opcional: un Hash normal escrito desde ocho hilos provoca una condición de carrera, y te afectará justo en la ejecución importante.
En Rails, envuelve shorten en un Active Job y deja que el adaptador de colas gestione la espera progresiva. Reintentar dentro de una acción del controlador mantiene ocupado un hilo de Puma durante cada sleep, y una importación limitada por solicitudes puede dejar sin recursos al proceso web en silencio. Si estás integrando esto en un flujo de publicación, el artículo sobre webhooks para eventos de enlaces explica cómo recuperar los datos de clics sin hacer polling.
Por cuál decantarse
Una tarea de rake o un script puntual: Net::HTTP.post, cuatro líneas y listo. Cualquier proceso programado: la versión de Net::HTTP.start con tiempos de espera y la clave de idempotencia. Una app de Rails que ya usa Faraday: el objeto de conexión, con los reintentos declarados una vez y reutilizados en todas partes.
La elección importa poco frente a los tres hábitos que hay debajo: obtener la clave del entorno, establecer un tiempo de espera explícito y comprobar la clase de la respuesta antes de analizar el cuerpo. La página de la API y los SDK enumera los clientes generados si prefieres no encargarte de nada de esto, y soluciones para desarrolladores explica qué más expone la API cuando ya se crean enlaces desde el código.
Lee la serie insignia
Esto forma parte del clúster de ingeniería. Empieza con la guía de la API gratuita de acortamiento de URL para conocer la estructura del endpoint y la autenticación, y continúa con límites de solicitudes e idempotencia para comportarte correctamente bajo carga. La referencia activa está en la documentación de la API, y acortadores de URL para desarrolladores explica qué debes comprobar en una API antes de basarte en ella.
Relacionado en el blog
Preguntas frecuentes
¿Cómo acorto una URL en Ruby?
Envía la URL larga mediante POST a la API de un acortador con Net::HTTP, incluye tu clave de API como cabecera Bearer y el destino en un cuerpo JSON, y después lee short_url de la respuesta analizada. Net::HTTP.post es una sola línea en la biblioteca estándar, así que no hace falta instalar nada para la primera versión.
¿Necesito una gem para acortar URL en Ruby?
No. Net::HTTP y la biblioteca json vienen con Ruby y cubren toda la llamada. Faraday merece la pena cuando quieres una conexión reutilizable, middleware de reintentos y codificación JSON automática, que suele ser el caso dentro de una app de Rails y no en un script independiente.
¿Cómo configuro un tiempo de espera en Net::HTTP?
Usa Net::HTTP.start con open_timeout y read_timeout en lugar del atajo Net::HTTP.post, que no permite configurar ninguno de los dos. Sin ellos, una conexión bloqueada puede mantener un worker detenido durante los 60 segundos predeterminados completos, y en un trabajo en segundo plano eso significa perder una ranura de trabajo.
¿Por qué Net::HTTP no lanza una excepción con un 401?
Porque un 401 es una respuesta HTTP válida, no un fallo de transporte. Net::HTTP solo lanza excepciones por errores de socket y de tiempo de espera, así que tienes que comprobar tú mismo la clase de la respuesta con is_a?(Net::HTTPSuccess) o leer response.code. El middleware raise_error de Faraday lo hace por ti.
¿Cómo acorto muchas URL a la vez en Ruby?
Introduce las URL en una Queue y ejecuta un grupo pequeño de hilos contra ella, normalmente unos ocho. Ruby libera el bloqueo global durante la E/S, así que los hilos se solapan de verdad mientras esperan a la red, y un grupo fijo te mantiene por debajo del límite de solicitudes de la API que un bucle sin límite con un hilo por URL superaría enseguida.
¿Dónde debería estar el bucle de reintento en una app de Rails?
En un trabajo en segundo plano, no en una acción del controlador. Un reintento que duerme un par de segundos mantiene ocupado un hilo de Puma todo ese tiempo, así que un lote limitado por solicitudes puede dejar sin recursos al proceso web. Active Job con un adaptador de colas gestiona la espera progresiva y te da un historial de reintentos sin trabajo adicional.
Prueba Elido
Pega una URL, obtén un enlace corto
Sin registro. El enlace vive 30 días. Crea una cuenta para conservarlo.
Gratis, sin registro · 2 por día