Encurtar uma URL em Ruby é um único POST. Envie o link longo para a API de um encurtador com Net::HTTP, passe sua chave de API como um token Bearer e extraia short_url do JSON retornado. Nenhuma gem é necessária, o que importa quando o script precisa rodar em uma máquina que você não controla.
O formato do endpoint e o modelo de autenticação abaixo estão documentados na visão geral da API gratuita de encurtador de URLs; este post é a versão específica para Ruby do guia geral sobre como encurtar uma URL, que aborda o caminho pelo dashboard.
Mesma série, outros runtimes: Python, JavaScript, PHP e Go. Os nomes dos campos são do Elido; o formato pode ser adaptado à maioria dos encurtadores modernos.
A Maneira Mais Rápida: Net::HTTP.post
Dois requires, uma chamada:
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 em vez de ENV[] é intencional: uma chave ausente falha imediatamente, em vez de enviar Bearer e voltar com um 401 confuso.
A linha unless response.is_a?(Net::HTTPSuccess) é a que as pessoas costumam ignorar. O Net::HTTP gera uma exceção em um socket inativo ou timeout, e em nenhuma outra situação, então uma resposta 401 com um corpo de erro chega parecendo exatamente um sucesso até que JSON.parse entregue um hash sem short_url.
Adicione Timeouts, Retries e uma Chave de Idempotência
Net::HTTP.post tem uma falha que você não pode corrigir: ele não aceita argumentos de timeout. Para qualquer coisa que rode sem supervisão, use Net::HTTP.start, que aceita esses argumentos.
Já que está fazendo isso, trate também da falha que um retry ingênuo piora. Se o POST chegar à API e a resposta se perder no caminho de volta, seu código atingirá o timeout, fará retry e criará um segundo link para o mesmo destino. Uma Idempotency-Key estável fecha essa brecha: faça o hash do destino e a API retornará o link original em vez de criar um novo.
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
O case sobre a classe da resposta é mais fácil de ler do que uma pilha de comparações de códigos de status e deixa a política evidente: um 429 espera o tempo solicitado pelo servidor, um 5xx usa backoff exponencial e qualquer outra coisa, como um 401 ou 422, gera uma exceção na primeira tentativa porque o retry não vai corrigi-la. O guia detalhado sobre limites de taxa e idempotência explica toda a semântica dos cabeçalhos.
Quer executar isso exatamente como está? Crie uma chave no plano gratuito, exporte-a como ELIDO_API_KEY e todos os snippets desta página funcionarão sem alterações.
A Versão com Faraday para um App que Você Já Tem
Em um app Rails, o loop feito à mão tem o formato errado. O Faraday fornece um único objeto de conexão com o cabeçalho de autenticação anexado, JSON nos dois sentidos e uma política de retry declarada em vez de escrita manualmente:
# 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
Duas coisas que você precisa saber antes de colar isso. O middleware de retry foi movido para uma gem própria, faraday-retry, no Faraday 2, então é necessária uma linha separada no Gemfile. E raise_error inverte o comportamento do Net::HTTP: agora 4xx e 5xx geram Faraday::ClientError e Faraday::ServerError, que é o que você quer em um job que deve fazer retry em vez de armazenar um nil.
Encurte uma Lista sem Estourar o Limite de Taxa
O Ruby libera o bloqueio global enquanto uma thread espera pela E/S, então as threads são uma vantagem real aqui até mesmo no CRuby. O que você não deve fazer é criar uma por URL: um CSV com 2.000 linhas vira 2.000 sockets e uma parede de 429s. Uma Queue mais um pool fixo mantém a concorrência constante, independentemente do tamanho da 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 a URL original como chave dos resultados mantém uma falha parcial visível e permite executar o lote novamente. O Mutex não é opcional: um Hash comum escrito por oito threads é uma condição de corrida, e isso vai atingir você justamente na execução importante.
No Rails, envolva shorten em um Active Job e deixe o adaptador de fila cuidar do backoff. Fazer retry dentro de uma ação de controller mantém uma thread do Puma ocupada durante cada sleep, e uma importação limitada por taxa pode esgotar silenciosamente o processo web. Se você estiver integrando isso a um fluxo de publicação, o post sobre webhooks para eventos de links explica como obter os dados de cliques de volta sem polling.
Qual Escolher
Uma tarefa do rake ou um script pontual: Net::HTTP.post, quatro linhas e pronto. Qualquer coisa agendada: a versão com Net::HTTP.start, timeouts e a chave de idempotência. Um app Rails que já usa Faraday: o objeto de conexão, com retries declarados uma vez e reutilizados em todos os lugares.
A escolha importa pouco em comparação com os três hábitos por baixo dela. Obter a chave do ambiente, definir um timeout explícito e verificar a classe da resposta antes de analisar o corpo. A página A API e os SDKs lista os clientes gerados caso você prefira não cuidar de nada disso, e soluções para desenvolvedores mostra o que mais a API oferece quando os links passam a ser criados pelo código.
Leia a Série Principal
Este artigo faz parte do cluster de engenharia. Comece pelo guia da API gratuita de encurtador de URLs para entender o formato do endpoint e a autenticação, depois leia limites de taxa e idempotência para agir corretamente sob carga. A referência ao vivo está na documentação da API, e encurtadores de URL para desenvolvedores explica o que verificar em uma API antes de construir sobre ela.
Relacionado no Blog
Perguntas frequentes
Como encurto uma URL em Ruby?
Faça um POST da URL longa para a API de um encurtador com Net::HTTP, enviando sua chave de API como um cabeçalho Bearer e o destino em um corpo JSON, depois leia short_url da resposta analisada. Net::HTTP.post é uma chamada de uma linha na biblioteca padrão, então nada precisa ser instalado na primeira versão.
Preciso de uma gem para encurtar URLs em Ruby?
Não. Net::HTTP e a biblioteca json vêm com o Ruby e cobrem toda a chamada. O Faraday vale a pena quando você quer uma conexão reutilizável, middleware de retry e codificação JSON automática, o que costuma acontecer dentro de um app Rails, e não em um script independente.
Como defino um timeout no Net::HTTP?
Use Net::HTTP.start com open_timeout e read_timeout em vez do atalho Net::HTTP.post, que não permite definir nenhum dos dois. Sem eles, uma conexão travada pode prender um worker pelo padrão completo de 60 segundos e, em um job em segundo plano, isso significa uma vaga de job perdida.
Por que o Net::HTTP não gera uma exceção para um 401?
Porque um 401 é uma resposta HTTP válida, não uma falha de transporte. O Net::HTTP só gera exceções para erros de socket e timeout, então você precisa verificar a classe da resposta por conta própria com is_a?(Net::HTTPSuccess) ou ler response.code. O middleware raise_error do Faraday faz isso por você.
Como encurto muitas URLs de uma vez em Ruby?
Coloque as URLs em uma Queue e execute um pequeno pool de threads sobre ela, geralmente com cerca de oito. O Ruby libera o bloqueio global durante a E/S, então as threads realmente se sobrepõem enquanto esperam a rede, e um pool fixo mantém você abaixo do limite de taxa da API, que um loop ilimitado com uma thread por URL ultrapassaria imediatamente.
Onde o loop de retry deve ficar em um app Rails?
Em um job em segundo plano, não em uma ação de controller. Um retry que dorme por alguns segundos mantém uma thread do Puma ocupada durante todo esse tempo, então um lote limitado por taxa pode esgotar o processo web. O Active Job com um adaptador de fila cuida do backoff e fornece um histórico de retry automaticamente.
Experimente Elido
Cole uma URL, obtenha um link curto
Sem cadastro. O link vive 30 dias. Cadastre-se para mantê-lo para sempre.
Grátis, sem necessidade de registo · 2 por dia