f21fa5b5e8
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)。 本次为公开发布初始提交:完整开发历史(含部署细节)留存于私有归档,公开仓库自此干净起步。
56 lines
2.0 KiB
Go
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
|
|
}
|