Teknisk dokumentation
Hur URL & 301 Checker processerar jobb, hanterar rate limiting och undviker att DDos:a målsajter — inklusive Cloudflare-skyddade webbplatser.
1. Jobbflöde — från CSV till resultat
Varje körning är ett asynkront bakgrundsjobb med en unik UUID-URL.
CSV-uppladdning
│
▼
┌─────────────┐ urls.json ┌──────────────┐
│ /api/upload │ ───────────────▶ │ jobStore.ts │ (disk + minnescache)
│ (POST) │ meta.json │ .jobs/[id]/ │
└─────────────┘ results.jsonl └──────────────┘
│ │
│ fire-and-forget │
▼ ▼
┌──────────────┐ ┌────────────────────┐
│ processor.ts │ │ /api/jobs/[id] │
│ (bakgrund) │ │ (GET) polling │
└──────────────┘ └────────────────────┘
│
├── Grupperar URLs per hostname
├── Startar en worker per domän (parallellt)
└── Varje worker: sekventiell, med delay| Steg | Vad händer | Fil |
|---|---|---|
| Upload | CSV parsas, URLs valideras, jobb skapas med UUID | api/upload/route.ts |
| Skapa jobb | Sparar meta.json, urls.json, tom results.jsonl | lib/jobStore.ts |
| Bakgrundsprocess | Startas direkt (fire-and-forget), blockerar inte HTTP-svaret | lib/processor.ts |
| Resultat-append | Varje kontrollerat URL appas till results.jsonl — O(1) per URL | lib/jobStore.ts |
| Polling | Frontend hämtar status + resultat var 2:a sekund | app/jobs/[id]/page.tsx |
2. Domain-aware processing — en worker per domän
Det viktigaste skyddet mot att DDos:a en sajt: alla URLs till samma domän körs strikt sekventiellt.
Input-URLs grupperas per hostname:
exempel.se → [url1, url2, url3, ...] ─┐
annan.se → [url4, url5, ...] ├─▶ körs parallellt
tredje.se → [url6, url7, ...] ─┘
Inom varje grupp:
url1 ──(300 ms gap)──▶ url2 ──(300 ms gap)──▶ url3 ...
└─ sekventiell, aldrig parallell
Globalt semaphore: max 5 requests i luften totalt
(sprider sig automatiskt över alla domäner)| Konstant | Värde | Innebär |
|---|---|---|
GLOBAL_CONCURRENCY | 5 | Max 5 HTTP-requests i luften totalt, oavsett antal domäner |
MAX_CONCURRENT_PER_DOMAIN | 1 (implicit) | Varje domän-worker är sekventiell — aldrig parallella requests till samma host |
MIN_DELAY_PER_DOMAIN_MS | 300 ms | Minst 300 ms mellan varje request till samma hostname ≈ max 3 req/s per domän |
Om en CSV innehåller 1 000 URLs till samma domän tar det ~5 minuter (1 000 × 300 ms). Med URLs spridda över 5 domäner tar det ~1 minut — domänerna arbetar parallellt.
3. Cloudflare-anpassad rate limiting
Cloudflare:s bot-management triggar primärt på burst-trafik — hög hastighet under kort tid — snarare än låg, jämn belastning. Dessa regler håller oss under radarn.
| Scenario | CF:s reaktion | Vår motåtgärd |
|---|---|---|
| Många parallella requests till samma IP/host | 429 Too Many Requests | Max 1 concurrent + 300 ms gap per domän |
| Bot-liknande beteende (ingen User-Agent, massa requests) | 403 / JS-challenge | Konfigurerbar User-Agent — välj rätt kategori i UI:t |
| Hög belastning på origin bakom CF | 503 Service Unavailable | Exponentiell backoff + retry (se sektion 4) |
| Tillfällig CF-spik eller origin-problem | 503 / 429 | Automatisk retry upp till 3 gånger per URL |
4. Exponentiell backoff vid 429 & 503
När en domän svarar med 429 (rate limited) eller 503 (överbelastad) pausar workern för den domänen och försöker igen — utan att blockera andra domäner.
Försök 1: request → 429 └─▶ vänta 30 s → försök igen Försök 2: request → 429 └─▶ vänta 60 s → försök igen Försök 3: request → 429 └─▶ vänta 120 s → försök igen Försök 4: request → 429 └─▶ ge upp, spara resultatet som 429 och gå vidare till nästa URL
| Konstant | Värde | Förklaring |
|---|---|---|
BACKOFF_BASE_MS | 30 000 ms (30 s) | Väntetid vid första retry — Cloudflare:s Retry-After är ofta 30–60 s |
BACKOFF_MAX_MS | 300 000 ms (5 min) | Taket för en enskild vänteperiod — backoff dubblas men aldrig mer än 5 min |
MAX_RETRIES | 3 | Maximalt 3 retries per URL — totalt 4 försök inklusive det första |
| Heartbeat | var 30 s under backoff | Uppdaterar lastActivityAt under lång väntan — förhindrar false-positive "Fastnad"-status |
Backoff-sleep påverkar bara den aktuella domänen. Övriga domäner fortsätter köra normalt under hela väntetiden.
5. Pause / Resume / Cancel & Auto-recovery
Jobb kan pausas, återupptas och avbrytas live. Kontrollsignaler skrivs till disk så att de fungerar korrekt oavsett vilken server-instans som hanterar anropet.
| Åtgärd | Vad händer server-side | Lämpligt när |
|---|---|---|
| ⏸ Pausa | Skriver paused: true till control.json. Varje domain-worker pollar filen var 500 ms och slutar ta nya URLs. Pågående request slutförs. | Du vill inspektera hittills-resultat, eller tillfälligt minska belastningen |
| ▶ Fortsätt | Rensar paused-flaggan. Alla workers vaknar inom 500 ms och fortsätter från nästa URL i sin kö. | Inspektionen är klar och du vill fortsätta körningen |
| ✕ Avbryt | Skriver cancelled: true till control.json. Workers avslutar efter nuvarande URL. Resultat hittills bevaras. | Du vill inte slutföra körningen — resultat sparas ändå |
| ↺ Återuppta | Läser results.jsonl för att se vilka URLs som redan är klara, beräknar resterande och startar en ny process från rätt offset. Anropas automatiskt efter 15 s om jobbet är fastnat. | Jobb fastnat vid serveromstart eller nätverksavbrott |
Tillståndsmaskinen:
pending ──▶ processing ──▶ done
│ ▲ ▲
▼ │ │
paused │ │
│ │ │
└───┘ │
│
stuck ────┘ (härledd — auto-recover efter 15 s)
│
cancelledKontroll-flaggorna lagras i control.json på disk — inte enbart i minnet. Det innebär att pause/cancel fungerar korrekt även när olika API-routes hanteras av separata Next.js-modulinstanser. Vid serveromstart visas jobb som "Fastnad" efter 3 minuters inaktivitet och återupptas automatiskt.
6. Stuck-detection — automatisk felidentifiering
Ett jobb som inte rapporterat aktivitet på länge markeras automatiskt som "Fastnad" utan att status behöver persisteras.
| Parameter | Värde | Logik |
|---|---|---|
STUCK_THRESHOLD_MS | 180 000 ms (3 min) | Om lastActivityAt är äldre än 3 min och status är processing returneras stuck — härledd, inte sparad |
| Heartbeat under backoff | var 30 s | Uppdaterar lastActivityAt under backoff-sleep så att 3-minutersgränsen aldrig nås vid normala retries |
| Paused / cancelled | N/A | Stuck-detection aktiveras inte för dessa statusar — den kontrollerar bara status === "processing" |
7. Lagring — filer och minnescache
Ingen databas krävs. All data lagras som filer på disk med en write-through cache i minnet.
| Fil | Format | Innehåll |
|---|---|---|
.jobs/[uuid]/meta.json | JSON | Status, antal URLs, tidsstämplar, User-Agent — läses alltid från disk för korrekthet |
.jobs/[uuid]/urls.json | JSON array | Alla inmatade URLs — läses en gång vid jobbstart |
.jobs/[uuid]/results.jsonl | JSONL (ett JSON-objekt per rad) | Resultat appas med appendFileSync — O(1) per URL, ingen risk för race condition |
.jobs/[uuid]/control.json | JSON | Kontrollsignaler: paused och cancelled — skrivs och läses från disk för att fungera korrekt mellan Next.js-modulinstanser |
getMeta läser alltid från disk (ej cache-first) för att garantera korrekt status oavsett vilken API-route som anropade. resultCache används däremot för snabb läsning av resultat under aktiv körning — cachad data skrivs om vid serveromstart.
URL & 301 Checker · Changelog