refactor: undo feature cover every single part of editor
This commit is contained in:
@@ -4,8 +4,8 @@ Tài liệu này mô tả:
|
||||
|
||||
- luồng request thật của frontend hiện tại
|
||||
- backend cần proxy chỗ nào
|
||||
- backend cần rewrite chỗ nào
|
||||
- trade-off hiệu suất nếu proxy/rewrite toàn bộ Goong
|
||||
- backend cần sanitize/rewrite chỗ nào
|
||||
- trade-off hiệu suất nếu proxy toàn bộ Goong
|
||||
- khuyến nghị triển khai thực dụng cho team BE
|
||||
|
||||
Tài liệu liên quan:
|
||||
@@ -26,21 +26,23 @@ Frontend hiện tại không `setStyle(goongStyle)` trực tiếp cho MapLibre.
|
||||
|
||||
Thay vào đó:
|
||||
|
||||
1. FE tự `fetch()` style JSON của Goong
|
||||
1. FE gọi style JSON qua `buildGoongProxyUrl(...)`
|
||||
2. FE parse style JSON
|
||||
3. FE lấy ra:
|
||||
- raster source cho satellite
|
||||
- selected vector sources/layers cho borders, labels, rivers
|
||||
4. FE `addSource()` và `addLayer()` thủ công
|
||||
5. MapLibre tự request tiếp `source.url`
|
||||
6. Từ source manifest, MapLibre tự request tiếp các tile URLs trong `tiles[]`
|
||||
4. FE gọi source manifest qua `buildGoongProxyUrl(...)` nếu style source có `url`
|
||||
5. FE rewrite `tiles[]` về proxy URL rồi `addSource()` và `addLayer()` thủ công
|
||||
6. MapLibre request tile/font URLs đã là URL proxy
|
||||
|
||||
Điểm quan trọng:
|
||||
|
||||
- browser có thể không chỉ gọi `assets/*.json`
|
||||
- browser sẽ đi sâu thêm ít nhất 2 tầng:
|
||||
- browser không được gọi trực tiếp `tiles.goong.io`
|
||||
- browser vẫn sẽ đi qua backend proxy ở các tầng:
|
||||
- `assets/*.json`
|
||||
- `sources/*.json`
|
||||
- tile URLs trong `tiles[]`
|
||||
- `fonts/{fontstack}/{range}.pbf`
|
||||
|
||||
## 2. Luồng request hiện tại
|
||||
|
||||
@@ -48,20 +50,29 @@ Thay vào đó:
|
||||
sequenceDiagram
|
||||
participant FE as Frontend
|
||||
participant GL as MapLibre
|
||||
participant BE as Backend Proxy
|
||||
participant GO as Goong
|
||||
|
||||
FE->>GO: GET assets/goong_satellite.json?api_key=...
|
||||
FE->>GO: GET assets/goong_map_web.json?api_key=...
|
||||
FE->>BE: GET /proxy/tiles.goong.io/assets/goong_satellite.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/assets/goong_map_web.json
|
||||
BE->>GO: fetch upstream style JSON with server-side key
|
||||
GO-->>BE: style JSON
|
||||
BE-->>FE: sanitized style JSON
|
||||
|
||||
FE->>GL: addSource(raster/vector) + addLayer(...)
|
||||
FE->>BE: GET /proxy/tiles.goong.io/sources/satellite.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/sources/base.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/sources/goong.json
|
||||
BE->>GO: fetch upstream source manifests with server-side key
|
||||
GO-->>BE: source manifests
|
||||
BE-->>FE: sanitized source manifests
|
||||
|
||||
GL->>GO: GET sources/satellite.json?api_key=...
|
||||
GL->>GO: GET sources/base.json?api_key=...
|
||||
GL->>GO: GET sources/goong.json?api_key=...
|
||||
FE->>GL: addSource(proxy tile URLs) + addLayer(...)
|
||||
|
||||
GL->>GO: GET raster tile URLs from satellite tiles[]
|
||||
GL->>GO: GET vector tile URLs from base tiles[]
|
||||
GL->>GO: GET vector tile URLs from goong tiles[]
|
||||
GL->>BE: GET /proxy/tiles.goong.io/...tile...
|
||||
GL->>BE: GET /proxy/tiles.goong.io/fonts/{fontstack}/{range}.pbf
|
||||
BE->>GO: fetch upstream tile/font bytes
|
||||
GO-->>BE: bytes
|
||||
BE-->>GL: bytes
|
||||
```
|
||||
|
||||
## 3. Mục tiêu của backend proxy
|
||||
@@ -75,35 +86,42 @@ thì backend phải đảm bảo:
|
||||
|
||||
1. browser chỉ gọi domain BE
|
||||
2. BE gọi Goong bằng key server-side
|
||||
3. mọi URL Goong lồng bên trong JSON đều được rewrite về domain BE
|
||||
3. mọi URL Goong lồng bên trong JSON đều được sanitize để không chứa `api_key`
|
||||
4. frontend nhận URL upstream/relative sạch để tự wrap qua `buildGoongProxyUrl(...)`
|
||||
|
||||
Nếu thiếu bước 3:
|
||||
|
||||
- `api_key` vẫn có thể lộ ở request tầng sau
|
||||
- `api_key` có thể lộ ngay trong response JSON ở browser devtools
|
||||
|
||||
## 4. Những gì cần rewrite
|
||||
## 4. Những gì cần sanitize/rewrite
|
||||
|
||||
### 4.1. Style JSON
|
||||
|
||||
Trong `goong_satellite.json` và `goong_map_web.json`, BE cần rewrite:
|
||||
Trong `goong_satellite.json` và `goong_map_web.json`, BE cần sanitize:
|
||||
|
||||
- `sources.*.url`
|
||||
- `glyphs`
|
||||
- `sprite`
|
||||
|
||||
Ví dụ:
|
||||
|
||||
- từ `https://tiles.goong.io/sources/base.json?api_key=...`
|
||||
- thành `/proxy/goong/sources/base.json`
|
||||
- thành `https://tiles.goong.io/sources/base.json`
|
||||
|
||||
Không rewrite sẵn thành `/proxy/...` với frontend hiện tại, vì `tiles.ts` đang tự gọi `buildGoongProxyUrl(...)`.
|
||||
|
||||
### 4.2. Source manifests
|
||||
|
||||
Trong `sources/satellite.json`, `sources/base.json`, `sources/goong.json`, BE cần rewrite:
|
||||
Trong `sources/satellite.json`, `sources/base.json`, `sources/goong.json`, BE cần sanitize:
|
||||
|
||||
- mọi phần tử trong `tiles[]`
|
||||
|
||||
Ví dụ:
|
||||
|
||||
- từ `https://.../{z}/{x}/{y}...api_key=...`
|
||||
- thành `/proxy/goong/tiles/...`
|
||||
- thành `https://.../{z}/{x}/{y}...`
|
||||
|
||||
Sau đó frontend rewrite URL sạch này về `${API_BASE_URL}/proxy/tiles.goong.io/...`.
|
||||
|
||||
### 4.3. Những field còn phải để ý cho flow hiện tại
|
||||
|
||||
@@ -123,27 +141,28 @@ Nếu sau này FE chuyển sang `map.setStyle(goongStyleJson)` trực tiếp th
|
||||
|
||||
### 5.1. Style endpoints
|
||||
|
||||
- `GET /proxy/goong/assets/goong_satellite.json`
|
||||
- `GET /proxy/goong/assets/goong_map_web.json`
|
||||
- `GET /proxy/tiles.goong.io/assets/goong_satellite.json`
|
||||
- `GET /proxy/tiles.goong.io/assets/goong_map_web.json`
|
||||
|
||||
Nhiệm vụ:
|
||||
|
||||
- gọi upstream Goong bằng key server-side
|
||||
- parse JSON
|
||||
- rewrite `sources.*.url`
|
||||
- trả JSON đã rewrite
|
||||
- strip `api_key` khỏi nested URL
|
||||
- trả JSON đã sanitize, chưa rewrite nested URL sang `/proxy/...`
|
||||
|
||||
### 5.2. Source endpoints
|
||||
|
||||
- `GET /proxy/goong/sources/satellite.json`
|
||||
- `GET /proxy/goong/sources/base.json`
|
||||
- `GET /proxy/goong/sources/goong.json`
|
||||
- `GET /proxy/tiles.goong.io/sources/satellite.json`
|
||||
- `GET /proxy/tiles.goong.io/sources/base.json`
|
||||
- `GET /proxy/tiles.goong.io/sources/goong.json`
|
||||
|
||||
Nhiệm vụ:
|
||||
|
||||
- gọi upstream Goong bằng key server-side
|
||||
- parse JSON
|
||||
- rewrite `tiles[]`
|
||||
- strip `api_key` khỏi `tiles[]`
|
||||
- giữ URL upstream/relative để frontend tự wrap bằng `buildGoongProxyUrl(...)`
|
||||
- giữ nguyên:
|
||||
- `bounds`
|
||||
- `minzoom`
|
||||
@@ -154,9 +173,9 @@ Nhiệm vụ:
|
||||
|
||||
### 5.3. Tile endpoint
|
||||
|
||||
Gợi ý route generic:
|
||||
Route generic frontend hiện build:
|
||||
|
||||
- `GET /proxy/goong/tiles/*`
|
||||
- `GET /proxy/tiles.goong.io/...`
|
||||
|
||||
Nhiệm vụ:
|
||||
|
||||
@@ -180,24 +199,25 @@ sequenceDiagram
|
||||
participant BE as Backend Proxy
|
||||
participant GO as Goong
|
||||
|
||||
FE->>BE: GET /proxy/goong/assets/goong_satellite.json
|
||||
FE->>BE: GET /proxy/goong/assets/goong_map_web.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/assets/goong_satellite.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/assets/goong_map_web.json
|
||||
|
||||
BE->>GO: fetch upstream style JSON
|
||||
GO-->>BE: style JSON
|
||||
BE-->>FE: rewritten style JSON
|
||||
BE-->>FE: sanitized style JSON
|
||||
|
||||
FE->>GL: addSource(raster/vector) + addLayer(...)
|
||||
|
||||
GL->>BE: GET /proxy/goong/sources/satellite.json
|
||||
GL->>BE: GET /proxy/goong/sources/base.json
|
||||
GL->>BE: GET /proxy/goong/sources/goong.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/sources/satellite.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/sources/base.json
|
||||
FE->>BE: GET /proxy/tiles.goong.io/sources/goong.json
|
||||
|
||||
BE->>GO: fetch upstream source manifests
|
||||
GO-->>BE: source manifests
|
||||
BE-->>GL: rewritten source manifests
|
||||
BE-->>FE: sanitized source manifests
|
||||
|
||||
GL->>BE: GET /proxy/goong/tiles/...
|
||||
FE->>GL: addSource(proxy tile URLs) + addLayer(...)
|
||||
|
||||
GL->>BE: GET /proxy/tiles.goong.io/...tile...
|
||||
GL->>BE: GET /proxy/tiles.goong.io/fonts/...
|
||||
BE->>GO: fetch upstream tile
|
||||
GO-->>BE: tile bytes
|
||||
BE-->>GL: tile bytes
|
||||
@@ -205,11 +225,11 @@ sequenceDiagram
|
||||
|
||||
## 7. Trade-off hiệu suất
|
||||
|
||||
### 7.1. Rewrite JSON có chậm không?
|
||||
### 7.1. Sanitize JSON có chậm không?
|
||||
|
||||
Có overhead, nhưng **rất nhỏ** so với tile traffic.
|
||||
|
||||
JSON cần rewrite hiện tại chỉ gồm:
|
||||
JSON cần sanitize hiện tại chỉ gồm:
|
||||
|
||||
- 2 style JSON
|
||||
- 3 source manifests
|
||||
@@ -218,7 +238,7 @@ Những file này nhỏ, số lượng ít, và có thể cache rất mạnh.
|
||||
|
||||
Kết luận:
|
||||
|
||||
- rewrite JSON không phải bottleneck chính
|
||||
- sanitize JSON không phải bottleneck chính
|
||||
|
||||
### 7.2. Tile proxy mới là chỗ đắt
|
||||
|
||||
@@ -235,21 +255,20 @@ Các ảnh hưởng có thể thấy:
|
||||
- tăng CPU/memory nếu BE buffer response thay vì stream
|
||||
- tăng load connection pool tới Goong
|
||||
|
||||
### 7.3. Nếu không rewrite tile URL
|
||||
### 7.3. Nếu không proxy tile/font URL
|
||||
|
||||
Nếu BE chỉ rewrite style/source JSON nhưng không rewrite `tiles[]`:
|
||||
Nếu BE chỉ proxy style/source JSON nhưng thiếu tile/font route:
|
||||
|
||||
- browser vẫn gọi Goong trực tiếp ở bước tile
|
||||
- `api_key` vẫn có thể lộ
|
||||
- MapLibre request tile/font proxy URL sẽ lỗi
|
||||
- hoặc nếu FE bị đổi để dùng URL upstream trực tiếp thì browser sẽ gọi Goong và có thể lộ key
|
||||
|
||||
Tức là:
|
||||
|
||||
- hiệu suất tốt hơn
|
||||
- nhưng mục tiêu bảo mật key không đạt
|
||||
- tile/font route vẫn là phần bắt buộc nếu muốn giữ kiến trúc hiện tại
|
||||
|
||||
## 8. Cách giảm thiểu impact hiệu suất
|
||||
|
||||
### 8.1. Cache rewritten JSON ở BE
|
||||
### 8.1. Cache sanitized JSON ở BE
|
||||
|
||||
Khuyến nghị:
|
||||
|
||||
@@ -266,7 +285,7 @@ TTL có thể dài vì:
|
||||
|
||||
Tối ưu:
|
||||
|
||||
- chỉ rewrite một lần rồi reuse
|
||||
- chỉ sanitize một lần rồi reuse
|
||||
|
||||
### 8.2. Stream tile response
|
||||
|
||||
@@ -292,18 +311,18 @@ Nếu BE/ngược phía CDN có cache tốt, impact sẽ giảm rất nhiều.
|
||||
Nếu production có CDN/nginx/edge cache:
|
||||
|
||||
- cache mạnh cho:
|
||||
- rewritten style JSON
|
||||
- rewritten source manifests
|
||||
- sanitized style JSON
|
||||
- sanitized source manifests
|
||||
- tile responses
|
||||
|
||||
Điều này quan trọng hơn tối ưu code rewrite.
|
||||
Điều này quan trọng hơn tối ưu code sanitize.
|
||||
|
||||
### 8.5. Đừng rewrite tile mỗi request theo kiểu string building phức tạp
|
||||
### 8.5. Đừng parse manifest ở mỗi tile request
|
||||
|
||||
Nên:
|
||||
|
||||
- rewrite `tiles[]` một lần ở source manifest
|
||||
- tile route chỉ resolve path đơn giản và forward
|
||||
- sanitize source manifest một lần rồi cache
|
||||
- tile route chỉ resolve target path đơn giản và forward
|
||||
|
||||
Không nên:
|
||||
|
||||
@@ -313,18 +332,19 @@ Không nên:
|
||||
|
||||
Nếu team BE muốn giải pháp cân bằng giữa bảo mật và hiệu suất:
|
||||
|
||||
### Option A. Full proxy, full rewrite
|
||||
### Option A. Full proxy, sanitize JSON
|
||||
|
||||
BE cover:
|
||||
|
||||
1. style JSON
|
||||
2. source manifests
|
||||
3. tiles
|
||||
4. fonts/glyphs
|
||||
|
||||
Ưu điểm:
|
||||
|
||||
- key không lộ ra browser
|
||||
- FE không cần biết upstream Goong
|
||||
- FE vẫn dùng upstream target path sạch rồi tự wrap proxy URL
|
||||
|
||||
Nhược điểm:
|
||||
|
||||
@@ -337,7 +357,7 @@ BE cover:
|
||||
1. style JSON
|
||||
2. source manifests
|
||||
|
||||
Nhưng không rewrite `tiles[]`
|
||||
Nhưng để tile/font đi trực tiếp upstream.
|
||||
|
||||
Ưu điểm:
|
||||
|
||||
@@ -346,28 +366,27 @@ Nhưng không rewrite `tiles[]`
|
||||
Nhược điểm:
|
||||
|
||||
- key vẫn lộ ở tile request
|
||||
- không khớp với code hiện tại nếu `buildGoongProxyUrl(...)` vẫn được dùng cho tile/font
|
||||
|
||||
Kết luận:
|
||||
|
||||
- nếu ưu tiên bảo mật key thật sự: dùng **Option A**
|
||||
- nếu ưu tiên hiệu suất hơn và chấp nhận domain restrictions của Goong: dùng **Option B**
|
||||
- nếu ưu tiên hiệu suất hơn và chấp nhận domain restrictions của Goong: **Option B cần đổi frontend**
|
||||
|
||||
## 10. Recommendation cho codebase hiện tại
|
||||
|
||||
Với frontend hiện tại, hướng hợp lý nhất là:
|
||||
|
||||
1. giữ nguyên FE logic parse style/source như hiện nay
|
||||
2. chuyển các URL Goong ở `config.ts` sang endpoint nội bộ BE
|
||||
3. để BE rewrite:
|
||||
- `sources.*.url`
|
||||
- `tiles[]`
|
||||
4. để BE stream tile response
|
||||
5. cache rewritten JSON ở BE
|
||||
2. giữ `config.ts` dùng upstream URL sạch rồi để `buildGoongProxyUrl(...)` wrap thành `${API_BASE_URL}/proxy/tiles.goong.io/...`
|
||||
3. để BE sanitize nested `api_key` trong style/source JSON, nhưng không rewrite nested URL thành `/proxy/...`
|
||||
4. để BE stream tile/font response
|
||||
5. cache sanitized JSON ở BE
|
||||
|
||||
Nói ngắn:
|
||||
|
||||
- rewrite JSON: nên làm
|
||||
- rewrite tile URLs: bắt buộc nếu muốn giấu key
|
||||
- sanitize JSON: bắt buộc để không lộ key trong response
|
||||
- FE rewrite tile URLs bằng `buildGoongProxyUrl(...)`
|
||||
- proxy tile: phần tốn hiệu suất nhất
|
||||
- muốn bù hiệu suất: phải dùng cache/stream/CDN tốt
|
||||
|
||||
@@ -375,10 +394,11 @@ Nói ngắn:
|
||||
|
||||
1. Tạo route proxy cho 2 style JSON
|
||||
2. Tạo route proxy cho 3 source manifests
|
||||
3. Rewrite `sources.*.url` trong style JSON
|
||||
4. Rewrite `tiles[]` trong source manifests
|
||||
3. Strip `api_key` khỏi nested URL trong style JSON
|
||||
4. Strip `api_key` khỏi `tiles[]` trong source manifests
|
||||
5. Tạo route proxy tile generic
|
||||
6. Stream tile response
|
||||
7. Preserve cache headers
|
||||
8. Cache rewritten JSON
|
||||
9. Kiểm tra browser không còn request trực tiếp `tiles.goong.io`
|
||||
6. Tạo route proxy fonts/glyphs
|
||||
7. Stream tile/font response
|
||||
8. Preserve cache headers
|
||||
9. Cache sanitized JSON
|
||||
10. Kiểm tra browser không còn request trực tiếp `tiles.goong.io`
|
||||
|
||||
Reference in New Issue
Block a user