6 min czytaniaInżynieria

Jak skrócić adres URL w JavaScript za pomocą fetch i Node

Skróć adres URL w JavaScript jednym wywołaniem fetch, a następnie dodaj limit czasu, ponowienia i idempotencję - i zobacz, dlaczego przeglądarka nie jest właściwym miejscem na klucz.

Marius Voß
DevRel · edge infra
Jak skrócić adres URL w JavaScript: wywołanie fetch w Node wysyłające docelowy adres URL do API skracacza i odczytujące krótki link, przy czym przeglądarka nie może przechowywać klucza API

Skrócenie adresu URL w JavaScript to jedno żądanie HTTP POST. Wysyłasz długi link do API skracacza za pomocą fetch, przekazujesz klucz API jako token Bearer i odczytujesz krótki link z odpowiedzi JSON. Od Node 18 nie trzeba instalować żadnego pakietu - fetch jest globalny.

Ważne jest jednak to, gdzie ten kod działa. Klucz API skracacza to poświadczenie pozwalające zużywać limit, a JavaScript po stronie klienta jest publiczny, więc przeglądarka odpada. Wszystko poniżej działa po stronie serwera: skrypt Node, handler trasy albo funkcja serverless.

To wersja w JavaScript artykułu jak skrócić adres URL, który zamiast tego opisuje panel i przepływ w przeglądarce. Jeśli nie wybrano jeszcze usługi, przegląd darmowego API skracacza URL przedstawia kształt żądania, model uwierzytelniania i limity darmowego planu przyjęte w tym artykule. Nazwy pól poniżej pochodzą z Elido, ale ten schemat - wysłanie docelowego adresu i otrzymanie krótkiego adresu URL - działa w większości nowoczesnych skracaczy.

Najszybszy sposób: jedno wywołanie fetch w Node

Umieść klucz w zmiennej środowiskowej i wyślij docelowy adres do punktu końcowego links:

const res = await fetch("https://api.elido.app/v1/links", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    destination_url: "https://example.com/spring-sale?utm_source=newsletter",
  }),
  signal: AbortSignal.timeout(10_000),
});

if (!res.ok) throw new Error(`shorten failed: ${res.status}`);

const { short_url } = await res.json();
console.log(short_url); // -> https://s.elido.me/ab12cd

Uruchom go za pomocą node --env-file=.env shorten.js, a klucz nigdy nie trafi do kodu źródłowego.

Wiersz if (!res.ok) nie jest ozdobnikiem. fetch odrzuca obietnicę tylko przy błędzie sieci albo przerwaniu żądania, więc odpowiedź 401 albo 500 wraca jako poprawnie rozstrzygnięta obietnica z ok: false. Pomiń to sprawdzenie, a unieważniony klucz będzie wyglądał dokładnie jak sukces, dopóki coś w dalszej części nie spróbuje użyć undefined jako linku. Każdy, kto przychodzi z axios, raz się na tym potknie.

AbortSignal.timeout to drugi nawyk, który warto wyrobić. Bez niego zawieszone połączenie blokuje działanie, dopóki gniazdo samo nie umrze, co w zadaniu cron oznacza po prostu zniknięcie zadania.

Dlaczego przeglądarka nie jest właściwym miejscem

Wklej ten fragment do komponentu React, a zepsują się dwie rzeczy. Pierwsza to CORS: API uwierzytelniane kluczem nie wysyła Access-Control-Allow-Origin dla dowolnych witryn, więc przeglądarka blokuje odpowiedź. Lektura dokumentacji CORS na MDN zwykle wysyła ludzi na poszukiwanie proxy, które usunie błąd.

Błąd jest tylko objawem. Prawdziwy problem to druga rzecz: klucz w kodzie front-endu może odczytać każdy, kto otworzy DevTools albo przeszuka bundle. Bundlery go nie ukrywają, prefiksy NEXT_PUBLIC_ wręcz go reklamują, a publiczny klucz do płatnego API oznacza cudzy darmowy limit.

Wywołanie fetch z przeglądarki do API skracacza adresów URL zablokowane przez CORS przy kluczu API ujawnionym w bundlu, zestawione z właściwą ścieżką, w której przeglądarka wywołuje trasę serwerową przechowującą klucz i przekazującą żądanie dalej

Działający schemat umieszcza pośrodku własną trasę. Przeglądarka wysyła zwykły adres URL do twojego punktu końcowego, serwer dodaje klucz, a klucz pozostaje w zmiennej środowiskowej, której klient nie widzi:

// app/api/shorten/route.js  (Next.js App Router)
export async function POST(request) {
  const { url } = await request.json();

  const res = await fetch("https://api.elido.app/v1/links", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ destination_url: url }),
  });

  const { short_url } = await res.json();
  return Response.json({ short_url });
}

Sprawdź url przed przekazaniem go dalej. Otwarty punkt końcowy, który skraca wszystko, co ktokolwiek wyśle, to otwarte przekierowanie z dodatkowymi krokami, a artykuł o podatnościach typu open redirect opisuje, co atakujący robią z takim punktem.

Dodaj limit czasu, ponowienia i klucz idempotencji

Szczęśliwa ścieżka ma osiem wierszy. Zadanie działające bez nadzoru musi przetrwać odpowiedź 429, przejściowy błąd 5xx i gorszy przypadek, w którym POST się udaje, ale odpowiedź nigdy nie nadchodzi: kod przekracza limit czasu, ponawia żądanie, a teraz istnieją dwa krótkie linki do jednego celu.

Klucz idempotencji zamyka tę lukę. Wygeneruj jeden dla każdego adresu URL, używaj go ponownie przy każdym ponowieniu tego adresu URL, a API zwróci pierwotny link zamiast tworzyć drugi.

import { randomUUID } from "node:crypto";

const API = "https://api.elido.app/v1/links";
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

export async function shorten(destinationUrl, { retries = 3 } = {}) {
  const idempotencyKey = randomUUID(); // one per URL, not per attempt

  for (let attempt = 0; attempt < retries; attempt++) {
    const res = await fetch(API, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.ELIDO_API_KEY}`,
        "Content-Type": "application/json",
        "Idempotency-Key": idempotencyKey,
      },
      body: JSON.stringify({ destination_url: destinationUrl }),
      signal: AbortSignal.timeout(10_000),
    });

    if (res.status === 429) {
      await sleep(Number(res.headers.get("Retry-After") ?? 2) * 1000);
      continue;
    }
    if (res.status >= 500) {
      await sleep(2 ** attempt * 1000); // exponential backoff
      continue;
    }
    if (!res.ok) {
      throw new Error(`shorten failed: ${res.status} ${await res.text()}`);
    }

    return (await res.json()).short_url;
  }

  throw new Error(`shorten failed after ${retries} attempts`);
}

Odpowiedź 401 albo 403 nigdy nie naprawi się sama, więc te błędy są zgłaszane od razu zamiast zużywać trzy próby. Odpowiedź 429 czeka dokładnie tak długo, jak żąda tego nagłówek Retry-After. Tylko błędy 5xx otrzymują wykładniczy backoff. Szczegółowe omówienie limitów szybkości i idempotencji wyjaśnia, dlaczego naiwna pętla ponowień prowadzi do tego, że jedna awaria dostawcy tworzy dwa linki.

Chcesz uruchamiać fragmenty dokładnie tak, jak są zapisane? Utwórz klucz w darmowym planie, wyeksportuj go jako ELIDO_API_KEY, a każdy przykład na tej stronie zadziała bez zmian.

Skracanie adresów URL zbiorczo bez przekraczania limitu szybkości

Oczywista wersja zbiorcza to Promise.all(urls.map(shorten)). Uruchamia każde żądanie w tym samym takcie, więc lista 500 adresów URL oznacza 500 jednoczesnych połączeń, a API odpowiada na większość z nich kodem 429. Mała pula rozwiązuje ten problem: stała liczba workerów pobiera elementy ze współdzielonej kolejki, więc współbieżność pozostaje stała niezależnie od długości listy.

export async function shortenAll(urls, concurrency = 8) {
  const queue = [...urls];
  const results = new Map();

  const worker = async () => {
    while (queue.length) {
      const url = queue.pop();
      try {
        results.set(url, await shorten(url));
      } catch (err) {
        results.set(url, `ERROR: ${err.message}`); // one bad URL must not sink the batch
      }
    }
  };

  await Promise.all(Array.from({ length: concurrency }, worker));
  return results;
}
Promise.all uruchamiające wszystkie żądania skracania naraz i zbierające błędy limitu szybkości, zestawione z ograniczoną pulą workerów utrzymującą stałą liczbę aktywnych żądań podczas bezpiecznego zbiorczego skracania adresów URL

Kluczowanie wyników za pomocą oryginalnego adresu URL sprawia, że częściowa awaria jest widoczna i można ją ponowić, zamiast pozostawiać cichą lukę w wynikach. Ustaw concurrency zgodnie z dokumentacją planu; osiem to bezpieczny punkt wyjścia, a nagłówki odpowiedzi informują, kiedy trzeba zejść niżej.

Jeśli wolisz nie utrzymywać tego wszystkiego samodzielnie, API i SDK mają już podłączoną logikę ponowień i stronicowania, a krótki przewodnik po SDK pokazuje typowaną wersję tego samego wywołania.

Gdzie powinien znajdować się kod

Cztery miejsca pokrywają niemal wszystkie przypadki. Jednorazowy skrypt skracający CSV działa bez problemu jako zwykły Node z --env-file. Formularz na stronie potrzebuje opisanego wyżej handlera trasy, w którym klucz pozostaje na serwerze, a przeglądarka rozmawia tylko z twoim serwerem. Worker kolejki pasuje do partii na tyle dużej, że żądanie HTTP nie powinno na nią czekać. Z kolei krok budowania generujący linki kampanii należy umieścić w CI, a klucz przechowywać jako sekret repozytorium.

Jedna uwaga dotycząca środowisk edge: node:crypto nie jest tam dostępne, ale crypto.randomUUID() jest globalnym elementem Web Crypto w Vercel Edge, Cloudflare Workers i Deno, więc wystarczy zmienić import, a reszta kodu pozostaje bez zmian.

Miejsce się zmienia, ale lista kontrolna pozostaje taka sama: klucz w środowisku, limit czasu przy każdym żądaniu, odczyt res.ok przed treścią oraz klucz idempotencji przy wszystkim, co może uruchomić się dwa razy. Zespoły robiące to na dużą skalę zwykle zaczynają czytać co jeszcze udostępnia API - kliknięcia, tagi, wygasanie - i podłączają webhooki dla zdarzeń linków, zamiast odpytywać API.

Przeczytaj serię cornerstone

Ten tekst należy do klastra engineering. Zacznij od darmowego przewodnika po API skracacza URL, który wyjaśnia kształt punktu końcowego i uwierzytelnianie, a następnie przeczytaj tekst o limitach szybkości i idempotencji, żeby poprawnie zachowywać się pod obciążeniem. Aktualną dokumentację znajdziesz w dokumentacji API, a skracacze URL dla deweloperów opisują, czego szukać w API przed wyborem rozwiązania.

Powiązane wpisy na blogu

Najczęściej zadawane pytania

Jak skrócić adres URL w JavaScript?

Wyślij żądanie HTTP POST do API skracacza adresów URL za pomocą fetch, przekazując długi adres URL w treści JSON i klucz API jako token Bearer, a następnie odczytaj krótki link ze sparsowanej odpowiedzi. W Node 18 lub nowszym fetch jest wbudowany, więc nie trzeba instalować pakietu: zbuduj nagłówki, wyślij docelowy adres URL do punktu końcowego links i użyj pola short_url z otrzymanego JSON-a.

Czy mogę skrócić adres URL w przeglądarce za pomocą JavaScript?

Nie z kluczem API umieszczonym na stronie. Wszystko w JavaScript po stronie klienta można odczytać w DevTools, więc klucz wysłany do przeglądarki staje się publicznym kluczem, z którego każdy może zużyć twój limit. Zamiast tego wywołuj skracacz z trasy serwerowej albo funkcji serverless i pozwól, żeby przeglądarka wywoływała tę trasę.

Dlaczego moje żądanie fetch do skracacza adresów URL zwraca błąd CORS?

Ponieważ API skracacza nie wysyła nagłówka Access-Control-Allow-Origin dla twojej witryny, co jest celowe na punkcie końcowym uwierzytelnianym kluczem. Przeglądarka blokuje odpowiedź, nawet jeśli żądanie mogło się udać. Przeniesienie wywołania na stronę serwerową usuwa przeglądarkę ze ścieżki, a wraz z nią ten błąd.

Czy potrzebuję pakietu npm, aby skracać adresy URL w Node.js?

Nie. Node udostępnia globalny fetch od wersji 18, a wywołanie API skracacza to jedno żądanie POST z dwoma nagłówkami, więc zależność daje bardzo niewiele. Sięgnij po SDK, gdy potrzebujesz typowanych odpowiedzi, wbudowanych ponowień i pomocników do stronicowania w wielu punktach końcowych, a nie tylko jednego wywołania.

Jak skrócić wiele adresów URL naraz w Node.js?

Uruchom małą pulę workerów zamiast wykonywać Promise.all na całej liście, aby w danej chwili trwało tylko kilka żądań i aby nie przekroczyć limitu szybkości. Wysyłaj jeden Idempotency-Key dla każdego adresu URL, aby ponowione żądanie zwracało pierwotny link zamiast tworzyć duplikat.

Czy fetch zgłasza błąd przy odpowiedzi 401 albo 500?

Nie, i to zaskakuje osoby przechodzące z axios. fetch odrzuca obietnicę tylko przy błędzie sieci albo przerwanym żądaniu, więc odpowiedź 401 albo 500 przychodzi jako rozstrzygnięta odpowiedź z ok ustawionym na false. Samodzielnie sprawdź response.ok przed odczytaniem treści, bo inaczej nieudane wywołanie będzie wyglądać jak udane.

Wypróbuj Elido

Wklej URL, otrzymaj krótki link

Bez rejestracji. Link działa 30 dni. Zarejestruj się, aby zachować go na zawsze.

Za darmo, bez rejestracji · 2 dziennie

Wypróbuj Elido

Skracarka URL hostowana w UE: własne domeny, głęboka analityka i otwarte API. Darmowy plan - bez karty kredytowej.

Tagi
how to shorten a url in javascript
url shortener node js
shorten url javascript fetch
javascript url shortener api
bulk shorten urls node
node fetch post json

Czytaj dalej