Files
Commilitia-Drop/desktop/platform/proxy.go
T
admin f21fa5b5e8 cdrop — 跨 OS 剪贴板与文件传输服务
Commilitia Drop:自托管的跨设备剪贴板同步与点对点文件传输。

- 后端 Go(chi / SQLite WAL / SSE Hub / WebRTC signaling + 状态机 / Relay ring buffer),编译进单个 distroless 镜像(前端 go:embed)。
- 前端 React + TanStack Router + Zustand,自实现 SSE + WebRTC P2P,NAT 受阻时回退服务端中继;聚珍(Juzhen)CJK 综合排版。
- 桌面端 Wails v2(macOS / Windows),瘦客户端复用 web。
- 鉴权 OIDC PKCE(自建 Casdoor 等),refresh_token 信封加密存系统密钥库;iOS Shortcut 用 HS256 scoped token。

架构文档与变更记录见 docs 分支(PROJECT_BRIEF / FRONTEND_DESIGN / CHANGELOG)。

本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
2026-06-15 21:38:28 +08:00

56 lines
2.0 KiB
Go

package platform
import (
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
)
// NewAPIProxy returns an http.Handler suitable for Wails' AssetServer.Handler.
// It reverse-proxies every /api/* request to the remote cdrop backend so the
// embedded web UI keeps using same-origin relative URLs — no CORS, no API-base
// rewrite, no backend change.
//
// Why a proxy instead of cross-origin fetch: the WebView serves the bundle from
// a custom scheme, so a relative /api request never reaches the backend, and a
// cross-origin fetch to https://drop.commilitia.net would need server-side CORS
// the backend doesn't send. Proxying keeps the request same-origin end to end.
//
// SSE (/api/hub/events) streams correctly: FlushInterval=-1 flushes each chunk,
// and the darwin WebView ResponseWriter forwards every Write to the
// WKURLSchemeTask immediately (didReceiveData per write), so frames arrive live.
//
// apiBase is the backend origin, e.g. https://drop.commilitia.net. Requests
// that are not under /api/ get a 404 — desktop navigation is client-side, so the
// asset FS already answers "/" and deep-link misses don't happen in normal use.
func NewAPIProxy(apiBase string) (http.Handler, error) {
target, err := url.Parse(apiBase)
if err != nil {
return nil, fmt.Errorf("proxy: parse api base %q: %w", apiBase, err)
}
if target.Scheme == "" || target.Host == "" {
return nil, fmt.Errorf("proxy: api base %q missing scheme or host", apiBase)
}
proxy := httputil.NewSingleHostReverseProxy(target)
base := proxy.Director
proxy.Director = func(r *http.Request) {
base(r)
// The backend is vhosted behind Caddy; the Host header drives TLS SNI
// and request routing, so it must be the backend host, not the WebView's
// synthetic origin.
r.Host = target.Host
}
proxy.FlushInterval = -1 // immediate flush — SSE frames / streamed relay bodies
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if strings.HasPrefix(r.URL.Path, "/api/") {
proxy.ServeHTTP(w, r)
return
}
http.NotFound(w, r)
}), nil
}