Skip to content

lee-lou2/url-shortener

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

9 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

URL ๋‹จ์ถ• ์„œ๋น„์Šค

ํ•œ๊ตญ์–ด | English

๐Ÿš€ ๋ฐ๋ชจ: https://u.lou2.kr

demo site

Rust๋กœ ๊ฐœ๋ฐœ๋œ ๊ณ ์„ฑ๋Šฅ URL ๋‹จ์ถ• ์„œ๋น„์Šค์ž…๋‹ˆ๋‹ค. ๋”ฅ ๋งํฌ ์ฒ˜๋ฆฌ, ํ”Œ๋žซํผ๋ณ„ ๋ฆฌ๋””๋ ‰์…˜, JWT ์ธ์ฆ, ์›นํ›… ์•Œ๋ฆผ์„ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค.

์•„ํ‚คํ…์ฒ˜

flowchart TB
    subgraph Client["๐ŸŒ ํด๋ผ์ด์–ธํŠธ"]
        Browser[๋ธŒ๋ผ์šฐ์ €]
        Mobile[๋ชจ๋ฐ”์ผ ์•ฑ]
    end

    subgraph Server["โšก API ์„œ๋ฒ„ (Axum)"]
        Router[๋ผ์šฐํ„ฐ]
        Auth[JWT ์ธ์ฆ]
        RateLimit[๋ ˆ์ดํŠธ ๋ฆฌ๋ฏธํ„ฐ]
        Handler[ํ•ธ๋“ค๋Ÿฌ]
    end

    subgraph Storage["๐Ÿ’พ ์ €์žฅ์†Œ"]
        Redis[(Redis Cache)]
        PostgreSQL[(PostgreSQL)]
    end

    subgraph External["๐Ÿ”” ์™ธ๋ถ€"]
        Webhook[์›นํ›… ์—”๋“œํฌ์ธํŠธ]
    end

    Browser --> Router
    Mobile --> Router
    Router --> RateLimit
    RateLimit --> Auth
    Auth --> Handler
    Handler <--> Redis
    Handler <--> PostgreSQL
    Handler -.->|๋น„๋™๊ธฐ| Webhook
Loading

ํ•ต์‹ฌ ๊ธฐ์ˆ 

์˜์—ญ ๊ธฐ์ˆ  ์„ค๋ช…
์›น ํ”„๋ ˆ์ž„์›Œํฌ Axum 0.8 ๋น„๋™๊ธฐ HTTP ์„œ๋ฒ„
๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค PostgreSQL + SQLx ํƒ€์ž… ์•ˆ์ „ ์ฟผ๋ฆฌ
์บ์‹œ Redis + MessagePack ๊ณ ์† ์ง๋ ฌํ™” ์บ์‹ฑ
์ธ์ฆ JWT ํ† ํฐ ๊ธฐ๋ฐ˜ ์ธ์ฆ
ํ•ด์‹ฑ xxHash (xxh3_128) ์ค‘๋ณต URL ๊ฐ์ง€
๋ฉ”๋ชจ๋ฆฌ mimalloc ๊ณ ์„ฑ๋Šฅ ํ• ๋‹น์ž

URL ์ƒ์„ฑ ํ”Œ๋กœ์šฐ

sequenceDiagram
    participant C as ํด๋ผ์ด์–ธํŠธ
    participant S as API ์„œ๋ฒ„
    participant R as Redis
    participant DB as PostgreSQL

    C->>S: POST /v1/urls (URL ๋ฐ์ดํ„ฐ + JWT)
    S->>S: JWT ๊ฒ€์ฆ
    S->>S: ์ž…๋ ฅ๊ฐ’ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
    S->>S: xxHash ์ƒ์„ฑ (์ค‘๋ณต ์ฒดํฌ์šฉ)
    S->>DB: INSERT ... ON CONFLICT
    
    alt ์ƒˆ URL
        DB-->>S: ์ƒ์„ฑ๋œ URL (id, random_key)
        S->>S: Base62 ์ธ์ฝ”๋”ฉ (short_key ์ƒ์„ฑ)
    else ๊ธฐ์กด URL
        DB-->>S: ๊ธฐ์กด URL ๋ฐ˜ํ™˜
    end
    
    S-->>C: { short_key: "Ab3D7Xy" }
Loading

๋‹จ์ถ•ํ‚ค ์ƒ์„ฑ ๋ฐฉ์‹

flowchart LR
    subgraph Input["์ž…๋ ฅ"]
        ID["DB ID: 12345"]
        RK["๋žœ๋คํ‚ค: AbXy"]
    end

    subgraph Process["์ฒ˜๋ฆฌ"]
        B62["Base62 ์ธ์ฝ”๋”ฉ"]
        Split["๋žœ๋คํ‚ค ๋ถ„๋ฆฌ"]
    end

    subgraph Output["์ถœ๋ ฅ"]
        SK["๋‹จ์ถ•ํ‚ค: Ab3D7Xy"]
    end

    ID --> B62
    B62 --> |"3D7"| Merge
    RK --> Split
    Split --> |"์ ‘๋‘์‚ฌ: Ab"| Merge
    Split --> |"์ ‘๋ฏธ์‚ฌ: Xy"| Merge
    Merge["๊ฒฐํ•ฉ"] --> SK
Loading

ํŠน์ง•:

  • DB ID ๊ธฐ๋ฐ˜์œผ๋กœ ์ถฉ๋Œ ์—†์Œ
  • ๋žœ๋ค ์ ‘๋‘์‚ฌ/์ ‘๋ฏธ์‚ฌ๋กœ ์ˆœ์ฐจ ์ถ”์ธก ๋ฐฉ์ง€
  • ์ผ๊ด€๋œ ์„ฑ๋Šฅ (DB ํฌ๊ธฐ ๋ฌด๊ด€)

URL ๋ฆฌ๋””๋ ‰์…˜ ํ”Œ๋กœ์šฐ

sequenceDiagram
    participant C as ํด๋ผ์ด์–ธํŠธ
    participant S as API ์„œ๋ฒ„
    participant R as Redis
    participant DB as PostgreSQL
    participant W as ์›นํ›…

    C->>S: GET /Ab3D7Xy
    S->>S: short_key ํŒŒ์‹ฑ (id + random_key ์ถ”์ถœ)
    
    S->>R: GET url:{id}
    alt ์บ์‹œ ํžˆํŠธ
        R-->>S: MessagePack ๋ฐ์ดํ„ฐ
    else ์บ์‹œ ๋ฏธ์Šค
        R-->>S: null
        S->>DB: SELECT * FROM urls WHERE id = ?
        DB-->>S: URL ๋ฐ์ดํ„ฐ
        S->>R: SETEX url:{id} (TTL: 1์‹œ๊ฐ„)
    end

    S->>S: random_key ๊ฒ€์ฆ
    S->>S: ํ”Œ๋žซํผ ๊ฐ์ง€ (iOS/Android/๊ธฐํƒ€)
    
    par ๋น„๋™๊ธฐ ์›นํ›… ํ˜ธ์ถœ
        S--)W: POST (short_key, user_agent, timestamp)
    end

    S-->>C: HTML (๋”ฅ๋งํฌ + ํด๋ฐฑ URL)
Loading

ํ”Œ๋žซํผ๋ณ„ ๋ฆฌ๋””๋ ‰์…˜

flowchart TD
    Request[์š”์ฒญ ์ˆ˜์‹ ] --> Detect{User-Agent ๋ถ„์„}
    
    Detect -->|iOS| iOS{๋”ฅ๋งํฌ ์„ค์ •?}
    Detect -->|Android| Android{๋”ฅ๋งํฌ ์„ค์ •?}
    Detect -->|๊ธฐํƒ€| Default[๊ธฐ๋ณธ ํด๋ฐฑ URL]
    
    iOS -->|์žˆ์Œ| iOSDeep[iOS ๋”ฅ๋งํฌ ์‹œ๋„]
    iOS -->|์—†์Œ| iOSFallback[iOS ํด๋ฐฑ URL]
    iOSDeep -->|์‹คํŒจ์‹œ| iOSFallback
    
    Android -->|์žˆ์Œ| AndroidDeep[Android ๋”ฅ๋งํฌ ์‹œ๋„]
    Android -->|์—†์Œ| AndroidFallback[Android ํด๋ฐฑ URL]
    AndroidDeep -->|์‹คํŒจ์‹œ| AndroidFallback

    iOSFallback --> Response[๋ฆฌ๋””๋ ‰์…˜]
    AndroidFallback --> Response
    Default --> Response
Loading

์บ์‹ฑ ์ „๋žต

flowchart LR
    subgraph Request["์š”์ฒญ"]
        R1[URL ์กฐํšŒ]
    end

    subgraph Cache["Redis ์บ์‹œ"]
        Check{์บ์‹œ ํ™•์ธ}
        Hit[์บ์‹œ ํžˆํŠธ]
        Miss[์บ์‹œ ๋ฏธ์Šค]
        Update[์บ์‹œ ๊ฐฑ์‹ ]
    end

    subgraph DB["PostgreSQL"]
        Query[DB ์กฐํšŒ]
    end

    subgraph Serialize["์ง๋ ฌํ™”"]
        MP[MessagePack]
    end

    R1 --> Check
    Check -->|์กด์žฌ| Hit
    Check -->|์—†์Œ| Miss
    Miss --> Query
    Query --> MP
    MP --> Update
    Update --> Hit
    Hit --> Response[์‘๋‹ต]
Loading

MessagePack ์‚ฌ์šฉ ์ด์œ :

  • JSON ๋Œ€๋น„ 30-50% ์ž‘์€ ํฌ๊ธฐ
  • ๋น ๋ฅธ ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™”
  • ๋ฐ”์ด๋„ˆ๋ฆฌ ํฌ๋งท์œผ๋กœ Redis ์ €์žฅ ํšจ์œจ์ 

์‹œ์ž‘ํ•˜๊ธฐ

์‚ฌ์ „ ์ค€๋น„

  • Rust 1.75+
  • PostgreSQL
  • Redis

์‹คํ–‰

# ์ €์žฅ์†Œ ๋ณต์ œ
git clone https://github.com/lee-lou2/url-shortener.git
cd url-shortener

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
cp .env.example .env

# ์‹คํ–‰
cargo run --release

Docker

docker build -t url-shortener .
docker run -p 3000:3000 --env-file .env url-shortener

์ฃผ์š” ํ™˜๊ฒฝ ๋ณ€์ˆ˜

๋ณ€์ˆ˜ ๊ธฐ๋ณธ๊ฐ’ ์„ค๋ช…
SERVER_PORT 3000 ์„œ๋ฒ„ ํฌํŠธ
DB_HOST localhost PostgreSQL ํ˜ธ์ŠคํŠธ
REDIS_HOST localhost Redis ํ˜ธ์ŠคํŠธ
JWT_SECRET - JWT ์‹œํฌ๋ฆฟ (ํ”„๋กœ๋•์…˜ ํ•„์ˆ˜)
CACHE_TTL_SECS 3600 ์บ์‹œ TTL (์ดˆ)
RATE_LIMIT_PER_SECOND 10 ์ดˆ๋‹น ์š”์ฒญ ์ œํ•œ
WEBHOOK_MAX_CONCURRENT 100 ์ตœ๋Œ€ ๋™์‹œ ์›นํ›… ์ˆ˜

API

POST /v1/urls - URL ์ƒ์„ฑ

์š”์ฒญ:

{
  "defaultFallbackUrl": "https://example.com",
  "iosDeepLink": "myapp://path",
  "iosFallbackUrl": "https://apps.apple.com/app/myapp",
  "androidDeepLink": "myapp://path",
  "androidFallbackUrl": "https://play.google.com/store/apps/details?id=com.myapp",
  "webhookUrl": "https://webhook.example.com",
  "ogTitle": "์ œ๋ชฉ",
  "ogDescription": "์„ค๋ช…",
  "ogImageUrl": "https://example.com/image.jpg"
}

์‘๋‹ต:

{
  "message": "URL created successfully",
  "short_key": "Ab3D7Xy"
}

GET /{short_key} - ๋ฆฌ๋””๋ ‰์…˜

๋‹จ์ถ• URL์„ ์›๋ณธ URL๋กœ ๋ฆฌ๋””๋ ‰์…˜ํ•ฉ๋‹ˆ๋‹ค.

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

src/
โ”œโ”€โ”€ main.rs           # ์ง„์ž…์ 
โ”œโ”€โ”€ error.rs          # ์—๋Ÿฌ ์ฒ˜๋ฆฌ
โ”œโ”€โ”€ api/              # HTTP ํ•ธ๋“ค๋Ÿฌ, ๋ผ์šฐํŠธ, ๋ฏธ๋“ค์›จ์–ด
โ”œโ”€โ”€ config/           # ํ™˜๊ฒฝ ์„ค์ •, DB/Redis ์—ฐ๊ฒฐ
โ”œโ”€โ”€ models/           # ๋ฐ์ดํ„ฐ ๋ชจ๋ธ, ๋ฆฌํฌ์ง€ํ† ๋ฆฌ
โ””โ”€โ”€ utils/            # JWT, Base62, ๋žœ๋ค ๋ฌธ์ž์—ด

๋ผ์ด์„ ์Šค

MIT License

Languages