Files
tailscale-custom/.agent/workflows/blazor_wasm_compression_optimization.md
T
huanld 2fb067ecbf
checklocks / checklocks (push) Has been cancelled
CodeQL / Analyze (go) (push) Has been cancelled
natlab-integrationtest / natlab-integrationtest (push) Has been cancelled
CI / gomod-cache (push) Has been cancelled
CI / race-root-integration (1/4) (push) Has been cancelled
CI / race-root-integration (2/4) (push) Has been cancelled
CI / race-root-integration (3/4) (push) Has been cancelled
CI / race-root-integration (4/4) (push) Has been cancelled
CI / test (-race, amd64, 1/3) (push) Has been cancelled
CI / test (-race, amd64, 2/3) (push) Has been cancelled
CI / test (-race, amd64, 3/3) (push) Has been cancelled
CI / test (386) (push) Has been cancelled
CI / test (amd64) (push) Has been cancelled
CI / Windows (benchmarks) (push) Has been cancelled
CI / Windows (1/2) (push) Has been cancelled
CI / Windows (2/2) (push) Has been cancelled
CI / macos (push) Has been cancelled
CI / privileged (push) Has been cancelled
CI / vm (push) Has been cancelled
CI / cross (386, linux) (push) Has been cancelled
CI / cross (amd64, darwin) (push) Has been cancelled
CI / cross (amd64, freebsd) (push) Has been cancelled
CI / cross (amd64, openbsd) (push) Has been cancelled
CI / cross (amd64, windows) (push) Has been cancelled
CI / cross (arm, 5, linux) (push) Has been cancelled
CI / cross (arm, 7, linux) (push) Has been cancelled
CI / cross (arm64, darwin) (push) Has been cancelled
CI / cross (arm64, linux) (push) Has been cancelled
CI / cross (arm64, windows) (push) Has been cancelled
CI / cross (loong64, linux) (push) Has been cancelled
CI / ios (push) Has been cancelled
CI / crossmin (amd64, illumos) (push) Has been cancelled
CI / crossmin (amd64, plan9) (push) Has been cancelled
CI / crossmin (amd64, solaris) (push) Has been cancelled
CI / crossmin (ppc64, aix) (push) Has been cancelled
CI / android (push) Has been cancelled
CI / wasm (push) Has been cancelled
CI / tailscale_go (push) Has been cancelled
CI / fuzz (push) Has been cancelled
CI / depaware (push) Has been cancelled
CI / go_generate (push) Has been cancelled
CI / make_tidy (push) Has been cancelled
CI / licenses (push) Has been cancelled
CI / staticcheck (macOS) (push) Has been cancelled
CI / staticcheck (Linux) (push) Has been cancelled
CI / staticcheck (Windows) (push) Has been cancelled
CI / staticcheck (Portable (1/4)) (push) Has been cancelled
CI / staticcheck (Portable (2/4)) (push) Has been cancelled
CI / staticcheck (Portable (3/4)) (push) Has been cancelled
CI / staticcheck (Portable (4/4)) (push) Has been cancelled
CI / notify_slack (push) Has been cancelled
CI / merge_blocker (push) Has been cancelled
CI / check_mergeability_strict (push) Has been cancelled
CI / check_mergeability (push) Has been cancelled
Dockerfile build / deploy (push) Has been cancelled
test installer.sh / test (curl, alpine:3.21) (push) Has been cancelled
test installer.sh / test (curl, alpine:edge) (push) Has been cancelled
test installer.sh / test (curl, alpine:latest) (push) Has been cancelled
test installer.sh / test (curl, amazonlinux:latest) (push) Has been cancelled
test installer.sh / test (curl, archlinux:latest) (push) Has been cancelled
test installer.sh / test (curl, debian:oldstable-slim) (push) Has been cancelled
test installer.sh / test (curl, debian:sid-slim) (push) Has been cancelled
test installer.sh / test (curl, debian:stable-slim, 1.80.0) (push) Has been cancelled
test installer.sh / test (curl, debian:testing-slim) (push) Has been cancelled
test installer.sh / test (curl, elementary/docker:stable) (push) Has been cancelled
test installer.sh / test (curl, elementary/docker:unstable) (push) Has been cancelled
test installer.sh / test (curl, fedora:latest, 1.80.0) (push) Has been cancelled
test installer.sh / test (curl, kalilinux/kali-dev) (push) Has been cancelled
test installer.sh / test (curl, kalilinux/kali-rolling) (push) Has been cancelled
test installer.sh / test (curl, opensuse/leap:latest) (push) Has been cancelled
test installer.sh / test (curl, opensuse/tumbleweed:latest) (push) Has been cancelled
test installer.sh / test (curl, oraclelinux:8) (push) Has been cancelled
test installer.sh / test (curl, oraclelinux:9) (push) Has been cancelled
test installer.sh / test (curl, parrotsec/core:latest) (push) Has been cancelled
test installer.sh / test (curl, rockylinux:8.7) (push) Has been cancelled
test installer.sh / test (curl, rockylinux:9) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:20.04) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:22.04) (push) Has been cancelled
test installer.sh / test (curl, ubuntu:24.04, 1.80.0) (push) Has been cancelled
test installer.sh / test (wget, debian:oldstable-slim) (push) Has been cancelled
test installer.sh / test (wget, debian:sid-slim) (push) Has been cancelled
update-flake / update-flake (push) Has been cancelled
tailscale.com/cmd/vet / vet (push) Has been cancelled
test installer.sh / notify-slack (push) Has been cancelled
feat: security hardening, production roadmap, admin panel v1
Client security fixes (cmd/tailscale-tray/main.go):
- SSRF protection in Add Server dialog (validateControlURL): reject
  private/loopback/link-local/cloud-metadata IPs via DNS resolution
- RCE gate on AuthURL/BrowseToURL exec paths (validateAuthURL)
- Sanitized URL logging (sanitizeURLForLog drops query auth tokens)
- Error handling on exec.Command with user-facing showError()

Admin panel security (web-admin):
- Bcrypt password hashing (replaces SHA256)
- Rate limiting: 5 failed logins → 15-min lockout
- Session + login attempt cleanup goroutine (hourly)
- url.QueryEscape / encodeURIComponent for all API params
- Fail-hard startup when no TLS and non-loopback bind
- ADMIN_PASSWORD required (no default), password min 12 chars
- Username regex whitelist

Installer hardening (Setup.wxs):
- util:PermissionEx restricts SCM access: only Administrators +
  SYSTEM can start/stop/reconfigure service. Authenticated Users
  limited to QueryStatus/QueryConfig/Interrogate
- Vital="yes" on ServiceInstall

Docs & roadmap:
- PRODUCTION_ROADMAP.md: 5-milestone plan (security + features +
  distribution + ops) with granular tasks, effort, done-when
- CLIENT_SECURITY_AUDIT.md, SECURITY_FIXES.md, DEPLOYMENT.md
- AI assistant rules (.cursorrules, .antigravityrules, etc.)

Build & distribution:
- build-msi.ps1, deploy-and-sign.ps1, sign-release.ps1
- redeploy.ps1, tray-deploy.ps1, test-msi.ps1
- installer/msi/ alternative WXS setup
- Restored .github/workflows/ removed in mirror cleanup

.gitignore hardened: *.pfx, *.p12, *.key, *.pem, .env*
2026-04-22 15:18:11 +07:00

4.9 KiB

Blazor WASM Deployment Compression Optimization

Bối Cảnh

Khi deploy ứng dụng Blazor WebAssembly lên Docker/VPS, payload ban đầu rất nặng (~20MB uncompressed, ~15MB compressed). Ứng dụng phải tải hơn 80 file .wasm.dll trước khi khởi động được. Nếu không tối ưu đúng cách, thời gian load trang có thể lên tới 3+ phút.

Các Tối Ưu Đã Áp Dụng

1. Conditional Response Compression (Tránh Double-Compression)

Vấn đề: Blazor WASM publish tự động tạo ra các file pre-compressed .br (Brotli) trong thư mục _framework/. Khi thêm middleware ResponseCompression vào pipeline, nó nén thêm lần nữa lên file đã nén → trình duyệt giải mã thất bại → TypeError: Failed to fetch.

Giải pháp: Dùng app.UseWhen() để áp dụng compression cho MỌI đường dẫn NGOẠI TRỪ /_framework:

// Program.cs — Services
builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<BrotliCompressionProvider>();
    options.Providers.Add<GzipCompressionProvider>();
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[]
    {
        "image/svg+xml", "application/javascript", "text/css"
    });
});
builder.Services.Configure<BrotliCompressionProviderOptions>(options => 
{
    options.Level = CompressionLevel.Fastest;
});

// Program.cs — Middleware (CONDITIONAL)
app.UseWhen(
    context => !context.Request.Path.StartsWithSegments("/_framework"), 
    appBuilder => { appBuilder.UseResponseCompression(); }
);

Quy tắc vàng: KHÔNG BAO GIỜ nén /_framework/* bằng middleware. Blazor đã tự xử lý qua UseBlazorFrameworkFiles().

2. Aggressive Static File Caching (365 ngày)

Vấn đề: Mỗi lần truy cập, trình duyệt tải lại toàn bộ 80+ file static (CSS, JS, WASM) vì không có Cache header.

Giải pháp: Set Cache-Control: public,max-age=31536000 (1 năm) cho tất cả static files:

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        ctx.Context.Response.Headers.Append("Cache-Control", "public,max-age=31536000");
    }
});

Lần đầu load chậm, nhưng từ lần 2 trở đi load gần như tức thì vì mọi thứ đã cached.

3. Assembly Lazy Loading (Giảm 4MB payload)

Vấn đề: DevExpress PDF và Printing DLLs chiếm tổng ~4MB compressed nhưng chỉ dùng khi xuất file/in ấn.

Giải pháp: Thêm BlazorWebAssemblyLazyLoad vào .csproj:

<ItemGroup>
    <BlazorWebAssemblyLazyLoad Include="DevExpress.Pdf.v24.2.Core.wasm" />
    <BlazorWebAssemblyLazyLoad Include="DevExpress.Printing.v24.2.Core.wasm" />
</ItemGroup>

Kết quả: Giảm initial payload từ ~15MB → ~11MB compressed.

4. HTTP/1.1 vs HTTP/2 (Nút thắt cổ chai lớn nhất)

Vấn đề CRITICAL: Khi truy cập qua http://IP:port (HTTP thuần), trình duyệt bắt buộc dùng HTTP/1.1 với giới hạn tối đa 6 kết nối đồng thời cho mỗi domain. Với 80+ file WASM, trình duyệt phải xếp hàng tải từng batch 6 file → cực kỳ chậm (2-3 phút).

Giải pháp: Truy cập qua HTTPS domain (ví dụ: https://sso.instratech.net/hrm/). HTTPS tự động kích hoạt HTTP/2 Multiplexing — tải tất cả 80 file cùng lúc trên 1 kết nối duy nhất.

Phương thức Protocol Song song tối đa Thời gian load
http://IP:8010 HTTP/1.1 6 files ~2.5 phút
https://domain/hrm/ HTTP/2 Unlimited ~10-15 giây

Bài học: LUÔN deploy Blazor WASM đằng sau reverse proxy có HTTPS (Nginx, Cloudflare, etc.) để tận dụng HTTP/2.

Thứ Tự Middleware Đúng Trong Pipeline

UseResponseCompression()  ← CHỈ cho non-framework paths
UseBlazorFrameworkFiles() ← Serve pre-compressed _framework/*.br files
UseStaticFiles()          ← Serve CSS/JS/images với cache headers

Kiểm Tra Nhanh

# Verify Brotli compression đang hoạt động cho _framework files:
curl -I -H "Accept-Encoding: br" http://localhost:8010/_framework/blazor.webassembly.js
# Phải thấy: Content-Encoding: br

# Verify non-framework files cũng được nén:
curl -I -H "Accept-Encoding: br" http://localhost:8010/_content/DevExpress.Blazor/modules/dx-blazor-all.js
# Phải thấy: Content-Encoding: br (từ middleware)

# Verify cache headers:
curl -I http://localhost:8010/app.css
# Phải thấy: Cache-Control: public,max-age=31536000

Tổng Hợp Kết Quả

Tối ưu Tiết kiệm
Conditional Brotli Compression ~60-70% kích thước CSS/JS
Lazy Loading (PDF + Printing) ~4MB compressed
Cache 365 ngày Lần 2+ load tức thì
HTTP/2 Multiplexing Từ 2.5 phút → 10-15s