mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 08:57:55 +00:00
feat: M2 HTTP/HTTPS 完善
- store: Local 增加 customDomains/subdomain/locations/hostHeaderRewrite/httpHeaders/basicAuth;Remote 增加 vhostHttpPort;migrate() 补列;CRUD 读写(JSOm 编解码) - render: http/https proxy 输出 customDomains/subdomain/locations/hostHeaderRewrite/httpHeaders/basicAuth - main: renderRemote 透传 M2 字段(customDomains 逗号拆分 splitCSV) - web: LocalNode 折叠 HTTP 路由高级区;RemoteNode vhostHTTPPort;StatusView 显示 HTTP 访问端口 - 测试: TestRenderHTTPAdvanced / TestRenderHTTPSUsesCustomDomains;端到端 PUT canvas→config 验证通过
This commit is contained in:
@ -32,6 +32,13 @@ type Proxy struct {
|
||||
PoolCount int
|
||||
Metadatas map[string]string
|
||||
Annotations map[string]string
|
||||
|
||||
// M2 HTTP/HTTPS routing (http/https only).
|
||||
Locations []string // path routing
|
||||
HostHeaderRewrite string // rewrite Host header
|
||||
HTTPHeaders map[string]string // additional request headers
|
||||
BasicAuthUser string
|
||||
BasicAuthPassword string
|
||||
}
|
||||
|
||||
// frpcAuth mirrors frpc's auth section.
|
||||
@ -53,6 +60,12 @@ type frpcTLS struct {
|
||||
ServerName string `json:"serverName,omitempty"`
|
||||
}
|
||||
|
||||
// frpcBasicAuth mirrors frpc's HTTP basicAuth section.
|
||||
type frpcBasicAuth struct {
|
||||
User string `json:"user,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
}
|
||||
|
||||
// frpcProxy is the serialized proxy object. Different types use different
|
||||
// fields; empty ones are omitted so the JSON is accepted by frpc.
|
||||
type frpcProxy struct {
|
||||
@ -71,6 +84,12 @@ type frpcProxy struct {
|
||||
PoolCount int `json:"poolCount,omitempty"`
|
||||
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
|
||||
// M2 HTTP/HTTPS routing.
|
||||
Locations []string `json:"locations,omitempty"`
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
HTTPHeaders map[string]string `json:"httpHeaders,omitempty"`
|
||||
HTTPBasicAuth *frpcBasicAuth `json:"basicAuth,omitempty"`
|
||||
}
|
||||
|
||||
// Config is a complete frpc configuration document.
|
||||
@ -109,17 +128,23 @@ func Render(remote store.Remote, proxies []Proxy) ([]byte, error) {
|
||||
nameCounts := make(map[string]int)
|
||||
for _, p := range proxies {
|
||||
frpcP := frpcProxy{
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
LocalIP: p.LocalIP,
|
||||
LocalPort: p.LocalPort,
|
||||
SubDomain: p.SubDomain,
|
||||
UseEncryption: p.UseEncryption,
|
||||
UseCompression: p.UseCompression,
|
||||
BandwidthLimit: p.BandwidthLimit,
|
||||
PoolCount: p.PoolCount,
|
||||
Metadatas: p.Metadatas,
|
||||
Annotations: p.Annotations,
|
||||
Name: p.Name,
|
||||
Type: p.Type,
|
||||
LocalIP: p.LocalIP,
|
||||
LocalPort: p.LocalPort,
|
||||
SubDomain: p.SubDomain,
|
||||
UseEncryption: p.UseEncryption,
|
||||
UseCompression: p.UseCompression,
|
||||
BandwidthLimit: p.BandwidthLimit,
|
||||
PoolCount: p.PoolCount,
|
||||
Metadatas: p.Metadatas,
|
||||
Annotations: p.Annotations,
|
||||
Locations: p.Locations,
|
||||
HostHeaderRewrite: p.HostHeaderRewrite,
|
||||
HTTPHeaders: p.HTTPHeaders,
|
||||
}
|
||||
if p.BasicAuthUser != "" || p.BasicAuthPassword != "" {
|
||||
frpcP.HTTPBasicAuth = &frpcBasicAuth{User: p.BasicAuthUser, Password: p.BasicAuthPassword}
|
||||
}
|
||||
// http/https proxies use customDomains instead of remotePort. The
|
||||
// default domain is <name>.local; frps matches it by Host header.
|
||||
|
||||
@ -101,3 +101,58 @@ func TestRenderTransportOmittedWhenEmpty(t *testing.T) {
|
||||
t.Fatalf("proxy advanced should be empty, got %+v", cfg.Proxies[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderHTTPAdvanced(t *testing.T) {
|
||||
remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000, Token: "secret"}
|
||||
data, err := Render(remote, []Proxy{
|
||||
{Name: "web", Type: "http", LocalIP: "127.0.0.1", LocalPort: 8080,
|
||||
CustomDomains: []string{"app.example.com"},
|
||||
Locations: []string{"/api", "/admin"},
|
||||
HostHeaderRewrite: "backend.internal",
|
||||
HTTPHeaders: map[string]string{"X-Custom": "val"},
|
||||
BasicAuthUser: "user", BasicAuthPassword: "pass"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var cfg Config
|
||||
if err := json.Unmarshal(data, &cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
p := cfg.Proxies[0]
|
||||
if p.CustomDomains[0] != "app.example.com" {
|
||||
t.Fatalf("customDomains = %+v", p.CustomDomains)
|
||||
}
|
||||
if len(p.Locations) != 2 || p.Locations[0] != "/api" {
|
||||
t.Fatalf("locations = %+v", p.Locations)
|
||||
}
|
||||
if p.HostHeaderRewrite != "backend.internal" {
|
||||
t.Fatalf("hostHeaderRewrite = %q", p.HostHeaderRewrite)
|
||||
}
|
||||
if p.HTTPHeaders["X-Custom"] != "val" {
|
||||
t.Fatalf("httpHeaders = %+v", p.HTTPHeaders)
|
||||
}
|
||||
if p.HTTPBasicAuth == nil || p.HTTPBasicAuth.User != "user" || p.HTTPBasicAuth.Password != "pass" {
|
||||
t.Fatalf("basicAuth = %+v", p.HTTPBasicAuth)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenderHTTPSUsesCustomDomains(t *testing.T) {
|
||||
remote := store.Remote{Name: "srv", IP: "1.2.3.4", Port: 7000}
|
||||
data, err := Render(remote, []Proxy{
|
||||
{Name: "secure", Type: "https", LocalIP: "127.0.0.1", LocalPort: 8443,
|
||||
CustomDomains: []string{"secure.example.com"}},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var cfg Config
|
||||
_ = json.Unmarshal(data, &cfg)
|
||||
p := cfg.Proxies[0]
|
||||
if p.RemotePort != 0 {
|
||||
t.Fatalf("https proxy should not set remotePort, got %d", p.RemotePort)
|
||||
}
|
||||
if len(p.CustomDomains) != 1 || p.CustomDomains[0] != "secure.example.com" {
|
||||
t.Fatalf("customDomains = %+v", p.CustomDomains)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user