๐ ๋ฐ๋ชจ: https://u.lou2.kr
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
| ์์ญ | ๊ธฐ์ | ์ค๋ช |
|---|---|---|
| ์น ํ๋ ์์ํฌ | Axum 0.8 | ๋น๋๊ธฐ HTTP ์๋ฒ |
| ๋ฐ์ดํฐ๋ฒ ์ด์ค | PostgreSQL + SQLx | ํ์ ์์ ์ฟผ๋ฆฌ |
| ์บ์ | Redis + MessagePack | ๊ณ ์ ์ง๋ ฌํ ์บ์ฑ |
| ์ธ์ฆ | JWT | ํ ํฐ ๊ธฐ๋ฐ ์ธ์ฆ |
| ํด์ฑ | xxHash (xxh3_128) | ์ค๋ณต URL ๊ฐ์ง |
| ๋ฉ๋ชจ๋ฆฌ | mimalloc | ๊ณ ์ฑ๋ฅ ํ ๋น์ |
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" }
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
ํน์ง:
- DB ID ๊ธฐ๋ฐ์ผ๋ก ์ถฉ๋ ์์
- ๋๋ค ์ ๋์ฌ/์ ๋ฏธ์ฌ๋ก ์์ฐจ ์ถ์ธก ๋ฐฉ์ง
- ์ผ๊ด๋ ์ฑ๋ฅ (DB ํฌ๊ธฐ ๋ฌด๊ด)
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)
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
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[์๋ต]
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 --releasedocker 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 | ์ต๋ ๋์ ์นํ ์ |
์์ฒญ:
{
"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"
}๋จ์ถ URL์ ์๋ณธ URL๋ก ๋ฆฌ๋๋ ์ ํฉ๋๋ค.
src/
โโโ main.rs # ์ง์
์
โโโ error.rs # ์๋ฌ ์ฒ๋ฆฌ
โโโ api/ # HTTP ํธ๋ค๋ฌ, ๋ผ์ฐํธ, ๋ฏธ๋ค์จ์ด
โโโ config/ # ํ๊ฒฝ ์ค์ , DB/Redis ์ฐ๊ฒฐ
โโโ models/ # ๋ฐ์ดํฐ ๋ชจ๋ธ, ๋ฆฌํฌ์งํ ๋ฆฌ
โโโ utils/ # JWT, Base62, ๋๋ค ๋ฌธ์์ด
MIT License
