Скорочення URL у Ruby - це один POST-запит. Надішліть довге посилання до API скорочувача за допомогою Net::HTTP, передайте свій API-ключ як Bearer-токен і розберіть short_url із отриманого JSON. Гем не потрібен, що важливо, коли скрипт має працювати на машині, яку ви не контролюєте.
Формат кінцевої точки та модель автентифікації нижче описано в огляді безкоштовного API скорочувача URL; цей матеріал є версією для Ruby загальної інструкції зі скорочення URL, де розглянуто шлях через панель керування.
Та сама серія для інших середовищ виконання: Python, JavaScript, PHP та Go. Назви полів належать Elido, але така структура підходить більшості сучасних скорочувачів.
Найшвидший спосіб: Net::HTTP.post
Два require, один виклик:
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, а не ENV[], використано навмисно: відсутній ключ одразу спричиняє помилку, замість того щоб надіслати Bearer і повернутися з незрозумілою відповіддю 401.
Рядок unless response.is_a?(Net::HTTPSuccess) часто пропускають. Net::HTTP створює виняток у разі непрацюючого сокета або тайм-ауту, але більше ні в яких випадках, тому відповідь 401 із тілом помилки надходить так, ніби це успіх, доки JSON.parse не поверне вам хеш без short_url.
Додайте тайм-аути, повторні спроби та ключ ідемпотентності
У Net::HTTP.post є вада, яку не можна виправити: він не приймає аргументів тайм-ауту. Для всього, що працює без нагляду, перейдіть на Net::HTTP.start, де вони доступні.
Поки ви це робите, обробіть помилку, яку наївна повторна спроба лише погіршує. Якщо POST досягає API, а відповідь губиться на зворотному шляху, код завершується через тайм-аут, повторює запит і створює друге посилання для того самого призначення. Стабільний Idempotency-Key усуває цю проблему: захешуйте призначення, і API поверне початкове посилання замість створення нового.
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
case за класом відповіді читається краще, ніж купа порівнянь кодів стану, і чітко показує політику: для 429 потрібно чекати стільки, скільки просить сервер, для 5xx застосовується експоненційна затримка, а все інше, зокрема 401 або 422, створює виняток із першої спроби, бо повторна спроба це не виправить. У детальному матеріалі про ліміти швидкості та ідемпотентність повністю описано семантику заголовків.
Хочете запустити це як є? Створіть ключ на безкоштовному плані, експортуйте його як ELIDO_API_KEY, і кожен фрагмент коду на цій сторінці працюватиме без змін.
Версія Faraday для вже наявного застосунку
У застосунку Rails саморобний цикл має неправильну структуру. Faraday надає один об'єкт з'єднання з підключеним заголовком автентифікації, JSON в обох напрямках і політику повторних спроб, яку оголошують, а не пишуть вручну:
# 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
Перед копіюванням зверніть увагу на дві речі. У Faraday 2 проміжне програмне забезпечення для повторних спроб перенесено до окремого гема faraday-retry, тому в Gemfile потрібен окремий рядок. А raise_error змінює поведінку Net::HTTP на протилежну: тепер 4xx і 5xx створюють Faraday::ClientError та Faraday::ServerError, що й потрібно для завдання, яке має повторювати спробу, а не зберігати nil.
Скорочення списку без перевищення ліміту швидкості
Ruby звільняє глобальне блокування, поки потік очікує на ввід-вивід, тому тут потоки справді корисні навіть у CRuby. Не можна лише створювати по одному потоку на URL: CSV із 2 000 рядків перетвориться на 2 000 сокетів і стіну відповідей 429. Queue у поєднанні з фіксованим пулом утримує рівень паралельності сталим незалежно від довжини списку.
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
Ключі результатів у вигляді початкових URL роблять часткову помилку видимою, а пакет - придатним до повторного запуску. Mutex не є необов'язковим: звичайний Hash, до якого одночасно записують вісім потоків, створює стан перегонів, і це дасть про себе знати саме під час важливого запуску.
У Rails обгорніть shorten в Active Job і доручіть адаптеру черги керувати затримкою. Повторна спроба всередині дії контролера утримує потік Puma під час кожного sleep, а імпорт, обмежений за швидкістю, може непомітно виснажити веб-процес. Якщо ви підключаєте це до процесу публікації, у матеріалі про вебхуки для подій посилань описано, як отримувати дані про кліки без опитування.
Що обрати
Завдання rake або одноразовий скрипт: Net::HTTP.post, чотири рядки - і готово. Заплановане виконання: версія Net::HTTP.start із тайм-аутами та ключем ідемпотентності. Застосунок Rails, у якому вже є Faraday: об'єкт з'єднання з повторними спробами, оголошеними один раз і повторно використаними всюди.
Вибір майже не має значення порівняно з трьома базовими звичками. Беріть ключ із середовища, встановлюйте явний тайм-аут і перевіряйте клас відповіді до розбору тіла. На сторінці API та SDK наведено список згенерованих клієнтів, якщо ви не хочете підтримувати все це самостійно, а рішення для розробників охоплюють інші можливості API після того, як посилання почнуть створюватися з коду.
Читайте основну серію
Цей матеріал належить до інженерного кластера. Почніть із посібника з безкоштовного API скорочувача URL, щоб розібратися зі структурою кінцевої точки й автентифікацією, а потім прочитайте матеріал про ліміти швидкості та ідемпотентність, щоб коректно працювати під навантаженням. Актуальний довідник - це документація API, а в матеріалі скорочувачі URL для розробників описано, що перевірити в API, перш ніж будувати на його основі.
У блозі також
Поширені запитання
Як скоротити URL у Ruby?
Надішліть довгий URL до API скорочувача методом POST через Net::HTTP, передавши API-ключ у заголовку Bearer, а URL призначення - у тілі JSON, після чого прочитайте short_url з розібраної відповіді. Net::HTTP.post - це однорядковий виклик стандартної бібліотеки, тому для першої версії нічого не потрібно встановлювати.
Чи потрібен мені гем, щоб скорочувати URL у Ruby?
Ні. Net::HTTP і бібліотека json постачаються з Ruby та охоплюють увесь виклик. Faraday виправдовує використання, коли вам потрібні повторно використовуване з'єднання, проміжне програмне забезпечення для повторних спроб і автоматичне кодування JSON, що зазвичай актуально в застосунку Rails, а не в окремому скрипті.
Як установити тайм-аут для Net::HTTP?
Використовуйте Net::HTTP.start з open_timeout і read_timeout замість скороченого Net::HTTP.post, де немає способу встановити жоден із них. Без цих параметрів зависле з'єднання може утримувати виконавця всі 60 секунд за замовчуванням, а у фоновому завданні це означає втрату одного доступного слота.
Чому Net::HTTP не створює виняток для 401?
Тому що 401 - це дійсна відповідь HTTP, а не помилка передавання. Net::HTTP створює винятки лише для помилок сокета й тайм-аутів, тому клас відповіді потрібно перевірити самостійно за допомогою is_a?(Net::HTTPSuccess) або прочитати response.code. Проміжне програмне забезпечення Faraday raise_error робить це за вас.
Як скоротити багато URL одночасно в Ruby?
Помістіть URL у Queue і запустіть невеликий пул потоків, зазвичай приблизно з восьми. Ruby звільняє глобальне блокування під час вводу-виводу, тому потоки справді перекривають очікування мережі, а фіксований пул утримує кількість запитів нижче ліміту швидкості API, який необмежений цикл із потоком на кожен URL одразу перевищив би.
Де має бути цикл повторних спроб у застосунку Rails?
У фоновому завданні, а не в дії контролера. Повторна спроба, яка присипляє потік на кілька секунд, увесь цей час утримує потік Puma, тому пакет, обмежений за швидкістю, може виснажити веб-процес. Active Job із адаптером черги обробляє затримку й без додаткової роботи надає історію повторних спроб.
Спробуйте Elido
Вставте URL - отримайте коротке посилання
Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.
Безкоштовно, без реєстрації · 2 на день