v1.3.5: add CORS whitelist support (f.CORS) covering /cell and all custom routes

This commit is contained in:
2026-09-09 11:31:22 +08:00
parent cfecbff8f8
commit a4993502bc
6 changed files with 236 additions and 6 deletions
+134
View File
@@ -0,0 +1,134 @@
package fun
import (
"bytes"
"net/http"
"strings"
"testing"
"time"
)
func startCorsServer(t *testing.T, port uint16, withCors bool) *Fun {
t.Helper()
f := New()
if err := f.BindService(&TestSvc{}); err != nil {
t.Fatal(err)
}
if withCors {
f.CORS("https://a.com", "HTTPS://B.Com") // 大小写归一化
}
f.BindRoute("GET", "/ping", func(ctx *RouteCtx) error {
ctx.RequestCtx.WriteString("pong")
return nil
})
go f.Start(port)
time.Sleep(300 * time.Millisecond)
return f
}
func corsGet(t *testing.T, url, origin string) *http.Response {
t.Helper()
req, _ := http.NewRequest("GET", url, nil)
if origin != "" {
req.Header.Set("Origin", origin)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
return resp
}
func TestCorsWhitelistEcho(t *testing.T) {
startCorsServer(t, 39201, true)
resp := corsGet(t, "http://127.0.0.1:39201/ping", "https://a.com")
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d", resp.StatusCode)
}
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://a.com" {
t.Fatalf("allow-origin = %q, want echo", got)
}
if got := resp.Header.Get("Access-Control-Allow-Credentials"); got != "true" {
t.Fatalf("allow-credentials = %q", got)
}
if got := resp.Header.Get("Vary"); !strings.Contains(got, "Origin") {
t.Fatalf("vary = %q, want Origin", got)
}
// 白名单大小写不敏感
resp2 := corsGet(t, "http://127.0.0.1:39201/ping", "https://B.COM")
defer resp2.Body.Close()
if got := resp2.Header.Get("Access-Control-Allow-Origin"); got != "https://B.COM" {
t.Fatalf("allow-origin = %q, want case-insensitive match", got)
}
}
func TestCorsOriginNotWhitelisted(t *testing.T) {
startCorsServer(t, 39202, true)
resp := corsGet(t, "http://127.0.0.1:39202/ping", "https://evil.com")
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d(未命中白名单请求本身仍应正常处理)", resp.StatusCode)
}
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
t.Fatalf("allow-origin = %q, want empty", got)
}
}
func TestCorsPreflight(t *testing.T) {
startCorsServer(t, 39203, true)
req, _ := http.NewRequest("OPTIONS", "http://127.0.0.1:39203/cell", nil)
req.Header.Set("Origin", "https://a.com")
req.Header.Set("Access-Control-Request-Method", "POST")
req.Header.Set("Access-Control-Request-Headers", "content-type, x-custom")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusNoContent {
t.Fatalf("status = %d, want 204", resp.StatusCode)
}
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://a.com" {
t.Fatalf("allow-origin = %q", got)
}
if got := resp.Header.Get("Access-Control-Allow-Methods"); !strings.Contains(got, "POST") {
t.Fatalf("allow-methods = %q, want POST", got)
}
// 请求头回显
if got := resp.Header.Get("Access-Control-Allow-Headers"); !strings.Contains(got, "x-custom") {
t.Fatalf("allow-headers = %q, want echo x-custom", got)
}
if got := resp.Header.Get("Access-Control-Max-Age"); got != "86400" {
t.Fatalf("max-age = %q", got)
}
}
func TestCorsHeadersOnCellRpc(t *testing.T) {
startCorsServer(t, 39204, true)
body := `{"ServiceName":"TestSvc","MethodName":"Hello","Data":{"Name":"tom","Age":1}}`
req, _ := http.NewRequest("POST", "http://127.0.0.1:39204/cell", bytes.NewBufferString(body))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Origin", "https://a.com")
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status = %d", resp.StatusCode)
}
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "https://a.com" {
t.Fatalf("/cell 响应缺少 CORS 头: %q", got)
}
}
func TestCorsNotConfigured(t *testing.T) {
startCorsServer(t, 39205, false)
resp := corsGet(t, "http://127.0.0.1:39205/ping", "https://a.com")
defer resp.Body.Close()
if got := resp.Header.Get("Access-Control-Allow-Origin"); got != "" {
t.Fatalf("未配置 CORS 却有 allow-origin = %q", got)
}
}