mirror of
https://gitcode.com/JianFeeeee/webui4frpc.git
synced 2026-09-20 00:47:57 +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:
@ -27,6 +27,15 @@ type Local struct {
|
||||
PoolCount int `json:"poolCount,omitempty"`
|
||||
Metadatas map[string]string `json:"metadatas,omitempty"`
|
||||
Annotations map[string]string `json:"annotations,omitempty"`
|
||||
|
||||
// M2 HTTP/HTTPS routing (http/https only).
|
||||
CustomDomains string `json:"customDomains,omitempty"` // comma-separated
|
||||
SubDomain string `json:"subdomain,omitempty"`
|
||||
Locations []string `json:"locations,omitempty"` // path routing
|
||||
HostHeaderRewrite string `json:"hostHeaderRewrite,omitempty"`
|
||||
HTTPHeaders map[string]string `json:"httpHeaders,omitempty"`
|
||||
BasicAuthUser string `json:"basicAuthUser,omitempty"`
|
||||
BasicAuthPassword string `json:"basicAuthPassword,omitempty"`
|
||||
}
|
||||
|
||||
// Remote is a remote server node on the canvas.
|
||||
@ -43,6 +52,10 @@ type Remote struct {
|
||||
TransportTLS bool `json:"transportTls,omitempty"`
|
||||
TransportPool int `json:"transportPool,omitempty"`
|
||||
TransportTLSServerName string `json:"transportTlsServerName,omitempty"`
|
||||
|
||||
// M2: frps vhostHTTPPort (optional, displayed on the status page as the
|
||||
// HTTP access port for http/https services).
|
||||
VhostHTTPPort int `json:"vhostHttpPort,omitempty"`
|
||||
}
|
||||
|
||||
// Link connects one local to one remote.
|
||||
@ -148,12 +161,20 @@ func (s *Store) migrate() error {
|
||||
"pool_count INTEGER NOT NULL DEFAULT 0",
|
||||
"metadatas TEXT NOT NULL DEFAULT ''",
|
||||
"annotations TEXT NOT NULL DEFAULT ''",
|
||||
"custom_domains TEXT NOT NULL DEFAULT ''",
|
||||
"subdomain TEXT NOT NULL DEFAULT ''",
|
||||
"locations TEXT NOT NULL DEFAULT ''",
|
||||
"host_header_rewrite TEXT NOT NULL DEFAULT ''",
|
||||
"http_headers TEXT NOT NULL DEFAULT ''",
|
||||
"basic_auth_user TEXT NOT NULL DEFAULT ''",
|
||||
"basic_auth_password TEXT NOT NULL DEFAULT ''",
|
||||
},
|
||||
"remotes": {
|
||||
"transport_protocol TEXT NOT NULL DEFAULT ''",
|
||||
"transport_tls INTEGER NOT NULL DEFAULT 0",
|
||||
"transport_pool INTEGER NOT NULL DEFAULT 0",
|
||||
"transport_tls_server_name TEXT NOT NULL DEFAULT ''",
|
||||
"vhost_http_port INTEGER NOT NULL DEFAULT 0",
|
||||
},
|
||||
}
|
||||
for table, cols := range tables {
|
||||
@ -191,7 +212,7 @@ func (s *Store) Close() error { return s.db.Close() }
|
||||
// ---- Locals ----
|
||||
|
||||
func (s *Store) ListLocals() ([]Local, error) {
|
||||
rows, err := s.db.Query("SELECT name, ip, port, protocol, use_encryption, use_compression, bandwidth_limit, pool_count, metadatas, annotations FROM locals ORDER BY name")
|
||||
rows, err := s.db.Query("SELECT name, ip, port, protocol, use_encryption, use_compression, bandwidth_limit, pool_count, metadatas, annotations, custom_domains, subdomain, locations, host_header_rewrite, http_headers, basic_auth_user, basic_auth_password FROM locals ORDER BY name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -200,14 +221,16 @@ func (s *Store) ListLocals() ([]Local, error) {
|
||||
for rows.Next() {
|
||||
var l Local
|
||||
var enc, comp int
|
||||
var metaRaw, annoRaw string
|
||||
if err := rows.Scan(&l.Name, &l.IP, &l.Port, &l.Protocol, &enc, &comp, &l.BandwidthLimit, &l.PoolCount, &metaRaw, &annoRaw); err != nil {
|
||||
var metaRaw, annoRaw, hdrRaw, locRaw string
|
||||
if err := rows.Scan(&l.Name, &l.IP, &l.Port, &l.Protocol, &enc, &comp, &l.BandwidthLimit, &l.PoolCount, &metaRaw, &annoRaw, &l.CustomDomains, &l.SubDomain, &locRaw, &l.HostHeaderRewrite, &hdrRaw, &l.BasicAuthUser, &l.BasicAuthPassword); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l.UseEncryption = enc != 0
|
||||
l.UseCompression = comp != 0
|
||||
l.Metadatas = decodeMap(metaRaw)
|
||||
l.Annotations = decodeMap(annoRaw)
|
||||
l.Locations = decodeSlice(locRaw)
|
||||
l.HTTPHeaders = decodeMap(hdrRaw)
|
||||
out = append(out, l)
|
||||
}
|
||||
return out, rows.Err()
|
||||
@ -216,27 +239,53 @@ func (s *Store) ListLocals() ([]Local, error) {
|
||||
func (s *Store) GetLocal(name string) (Local, bool) {
|
||||
var l Local
|
||||
var enc, comp int
|
||||
var metaRaw, annoRaw string
|
||||
row := s.db.QueryRow("SELECT name, ip, port, protocol, use_encryption, use_compression, bandwidth_limit, pool_count, metadatas, annotations FROM locals WHERE name = ?", name)
|
||||
if err := row.Scan(&l.Name, &l.IP, &l.Port, &l.Protocol, &enc, &comp, &l.BandwidthLimit, &l.PoolCount, &metaRaw, &annoRaw); err != nil {
|
||||
var metaRaw, annoRaw, hdrRaw, locRaw string
|
||||
row := s.db.QueryRow("SELECT name, ip, port, protocol, use_encryption, use_compression, bandwidth_limit, pool_count, metadatas, annotations, custom_domains, subdomain, locations, host_header_rewrite, http_headers, basic_auth_user, basic_auth_password FROM locals WHERE name = ?", name)
|
||||
if err := row.Scan(&l.Name, &l.IP, &l.Port, &l.Protocol, &enc, &comp, &l.BandwidthLimit, &l.PoolCount, &metaRaw, &annoRaw, &l.CustomDomains, &l.SubDomain, &locRaw, &l.HostHeaderRewrite, &hdrRaw, &l.BasicAuthUser, &l.BasicAuthPassword); err != nil {
|
||||
return Local{}, false
|
||||
}
|
||||
l.UseEncryption = enc != 0
|
||||
l.UseCompression = comp != 0
|
||||
l.Metadatas = decodeMap(metaRaw)
|
||||
l.Annotations = decodeMap(annoRaw)
|
||||
l.Locations = decodeSlice(locRaw)
|
||||
l.HTTPHeaders = decodeMap(hdrRaw)
|
||||
return l, true
|
||||
}
|
||||
|
||||
func (s *Store) UpsertLocal(l Local) error {
|
||||
_, err := s.db.Exec(
|
||||
"INSERT INTO locals(name, ip, port, protocol, use_encryption, use_compression, bandwidth_limit, pool_count, metadatas, annotations) VALUES(?,?,?,?,?,?,?,?,?,?) "+
|
||||
"ON CONFLICT(name) DO UPDATE SET ip=excluded.ip, port=excluded.port, protocol=excluded.protocol, use_encryption=excluded.use_encryption, use_compression=excluded.use_compression, bandwidth_limit=excluded.bandwidth_limit, pool_count=excluded.pool_count, metadatas=excluded.metadatas, annotations=excluded.annotations",
|
||||
l.Name, l.IP, l.Port, l.Protocol, boolToInt(l.UseEncryption), boolToInt(l.UseCompression), l.BandwidthLimit, l.PoolCount, encodeMap(l.Metadatas), encodeMap(l.Annotations),
|
||||
"INSERT INTO locals(name, ip, port, protocol, use_encryption, use_compression, bandwidth_limit, pool_count, metadatas, annotations, custom_domains, subdomain, locations, host_header_rewrite, http_headers, basic_auth_user, basic_auth_password) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) "+
|
||||
"ON CONFLICT(name) DO UPDATE SET ip=excluded.ip, port=excluded.port, protocol=excluded.protocol, use_encryption=excluded.use_encryption, use_compression=excluded.use_compression, bandwidth_limit=excluded.bandwidth_limit, pool_count=excluded.pool_count, metadatas=excluded.metadatas, annotations=excluded.annotations, custom_domains=excluded.custom_domains, subdomain=excluded.subdomain, locations=excluded.locations, host_header_rewrite=excluded.host_header_rewrite, http_headers=excluded.http_headers, basic_auth_user=excluded.basic_auth_user, basic_auth_password=excluded.basic_auth_password",
|
||||
l.Name, l.IP, l.Port, l.Protocol, boolToInt(l.UseEncryption), boolToInt(l.UseCompression), l.BandwidthLimit, l.PoolCount, encodeMap(l.Metadatas), encodeMap(l.Annotations), l.CustomDomains, l.SubDomain, encodeSlice(l.Locations), l.HostHeaderRewrite, encodeMap(l.HTTPHeaders), l.BasicAuthUser, l.BasicAuthPassword,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// encodeSlice serializes an optional []string as JSON (empty -> "").
|
||||
func encodeSlice(s []string) string {
|
||||
if len(s) == 0 {
|
||||
return ""
|
||||
}
|
||||
b, err := json.Marshal(s)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// decodeSlice reads a JSON []string column; empty/invalid -> nil.
|
||||
func decodeSlice(raw string) []string {
|
||||
if raw == "" {
|
||||
return nil
|
||||
}
|
||||
var s []string
|
||||
if err := json.Unmarshal([]byte(raw), &s); err != nil {
|
||||
return nil
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// encodeMap serializes an optional JSON map so nil stays an empty string.
|
||||
func encodeMap(m map[string]string) string {
|
||||
if len(m) == 0 {
|
||||
@ -269,7 +318,7 @@ func (s *Store) DeleteLocal(name string) error {
|
||||
// ---- Remotes ----
|
||||
|
||||
func (s *Store) ListRemotes() ([]Remote, error) {
|
||||
rows, err := s.db.Query("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name FROM remotes ORDER BY name")
|
||||
rows, err := s.db.Query("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, vhost_http_port FROM remotes ORDER BY name")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -278,7 +327,7 @@ func (s *Store) ListRemotes() ([]Remote, error) {
|
||||
for rows.Next() {
|
||||
var r Remote
|
||||
var en, tls int
|
||||
if err := rows.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName); err != nil {
|
||||
if err := rows.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName, &r.VhostHTTPPort); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.Enabled = en != 0
|
||||
@ -291,8 +340,8 @@ func (s *Store) ListRemotes() ([]Remote, error) {
|
||||
func (s *Store) GetRemote(name string) (Remote, bool) {
|
||||
var r Remote
|
||||
var en, tls int
|
||||
row := s.db.QueryRow("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name FROM remotes WHERE name = ?", name)
|
||||
if err := row.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName); err != nil {
|
||||
row := s.db.QueryRow("SELECT name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, vhost_http_port FROM remotes WHERE name = ?", name)
|
||||
if err := row.Scan(&r.Name, &r.IP, &r.Port, &r.Token, &r.URL, &en, &r.TransportProtocol, &tls, &r.TransportPool, &r.TransportTLSServerName, &r.VhostHTTPPort); err != nil {
|
||||
return Remote{}, false
|
||||
}
|
||||
r.Enabled = en != 0
|
||||
@ -302,9 +351,9 @@ func (s *Store) GetRemote(name string) (Remote, bool) {
|
||||
|
||||
func (s *Store) UpsertRemote(r Remote) error {
|
||||
_, err := s.db.Exec(
|
||||
"INSERT INTO remotes(name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name) VALUES(?,?,?,?,?,?,?,?,?,?) "+
|
||||
"ON CONFLICT(name) DO UPDATE SET ip=excluded.ip, port=excluded.port, token=excluded.token, url=excluded.url, enabled=excluded.enabled, transport_protocol=excluded.transport_protocol, transport_tls=excluded.transport_tls, transport_pool=excluded.transport_pool, transport_tls_server_name=excluded.transport_tls_server_name",
|
||||
r.Name, r.IP, r.Port, r.Token, r.URL, boolToInt(r.Enabled), r.TransportProtocol, boolToInt(r.TransportTLS), r.TransportPool, r.TransportTLSServerName,
|
||||
"INSERT INTO remotes(name, ip, port, token, url, enabled, transport_protocol, transport_tls, transport_pool, transport_tls_server_name, vhost_http_port) VALUES(?,?,?,?,?,?,?,?,?,?,?) "+
|
||||
"ON CONFLICT(name) DO UPDATE SET ip=excluded.ip, port=excluded.port, token=excluded.token, url=excluded.url, enabled=excluded.enabled, transport_protocol=excluded.transport_protocol, transport_tls=excluded.transport_tls, transport_pool=excluded.transport_pool, transport_tls_server_name=excluded.transport_tls_server_name, vhost_http_port=excluded.vhost_http_port",
|
||||
r.Name, r.IP, r.Port, r.Token, r.URL, boolToInt(r.Enabled), r.TransportProtocol, boolToInt(r.TransportTLS), r.TransportPool, r.TransportTLSServerName, r.VhostHTTPPort,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user