mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-06-01 13:17:21 +02:00
Merge pull request #449 from tobychui/v3.1.6
- Exposed log file, sys.uuid and static web server path to start flag - Optimized connection close implementation - Added toggle for uptime monitor - Added optional copy HTTP custom headers to websocket connection
This commit is contained in:
commit
d1e5581eea
@ -59,6 +59,7 @@ func RegisterHTTPProxyAPIs(authRouter *auth.RouterDef) {
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHopByHop", HandleHopByHop)
|
||||
authRouter.HandleFunc("/api/proxy/header/handleHostOverwrite", HandleHostOverwrite)
|
||||
authRouter.HandleFunc("/api/proxy/header/handlePermissionPolicy", HandlePermissionPolicy)
|
||||
authRouter.HandleFunc("/api/proxy/header/handleWsHeaderBehavior", HandleWsHeaderBehavior)
|
||||
/* Reverse proxy auth related */
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/list", ListProxyBasicAuthExceptionPaths)
|
||||
authRouter.HandleFunc("/api/proxy/auth/exceptions/add", AddProxyBasicAuthExceptionPaths)
|
||||
|
@ -48,7 +48,7 @@ func LoadReverseProxyConfig(configFilepath string) error {
|
||||
}
|
||||
|
||||
//Parse it into dynamic proxy endpoint
|
||||
thisConfigEndpoint := dynamicproxy.ProxyEndpoint{}
|
||||
thisConfigEndpoint := dynamicproxy.GetDefaultProxyEndpoint()
|
||||
err = json.Unmarshal(endpointConfig, &thisConfigEndpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -129,31 +129,23 @@ func RemoveReverseProxyConfig(endpoint string) error {
|
||||
// Get the default root config that point to the internal static web server
|
||||
// this will be used if root config is not found (new deployment / missing root.config file)
|
||||
func GetDefaultRootConfig() (*dynamicproxy.ProxyEndpoint, error) {
|
||||
//Default Authentication Provider
|
||||
defaultAuth := &dynamicproxy.AuthenticationProvider{
|
||||
AuthMethod: dynamicproxy.AuthMethodNone,
|
||||
BasicAuthCredentials: []*dynamicproxy.BasicAuthCredentials{},
|
||||
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
||||
}
|
||||
//Default settings
|
||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&dynamicproxy.ProxyEndpoint{
|
||||
ProxyType: dynamicproxy.ProxyTypeRoot,
|
||||
RootOrMatchingDomain: "/",
|
||||
ActiveOrigins: []*loadbalance.Upstream{
|
||||
{
|
||||
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||
RequireTLS: false,
|
||||
SkipCertValidations: false,
|
||||
Weight: 0,
|
||||
},
|
||||
//Get the default proxy endpoint
|
||||
rootProxyEndpointConfig := dynamicproxy.GetDefaultProxyEndpoint()
|
||||
rootProxyEndpointConfig.ProxyType = dynamicproxy.ProxyTypeRoot
|
||||
rootProxyEndpointConfig.RootOrMatchingDomain = "/"
|
||||
rootProxyEndpointConfig.ActiveOrigins = []*loadbalance.Upstream{
|
||||
{
|
||||
OriginIpOrDomain: "127.0.0.1:" + staticWebServer.GetListeningPort(),
|
||||
RequireTLS: false,
|
||||
SkipCertValidations: false,
|
||||
Weight: 0,
|
||||
},
|
||||
InactiveOrigins: []*loadbalance.Upstream{},
|
||||
BypassGlobalTLS: false,
|
||||
VirtualDirectories: []*dynamicproxy.VirtualDirectoryEndpoint{},
|
||||
AuthenticationProvider: defaultAuth,
|
||||
DefaultSiteOption: dynamicproxy.DefaultSite_InternalStaticWebServer,
|
||||
DefaultSiteValue: "",
|
||||
})
|
||||
}
|
||||
rootProxyEndpointConfig.DefaultSiteOption = dynamicproxy.DefaultSite_InternalStaticWebServer
|
||||
rootProxyEndpointConfig.DefaultSiteValue = ""
|
||||
|
||||
//Default settings
|
||||
rootProxyEndpoint, err := dynamicProxyRouter.PrepareProxyRoute(&rootProxyEndpointConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -175,7 +167,11 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Specify the folder path to be zipped
|
||||
folderPath := "./conf/"
|
||||
if !utils.FileExists("./conf") {
|
||||
SystemWideLogger.PrintAndLog("Backup", "Configuration folder not found", nil)
|
||||
return
|
||||
}
|
||||
folderPath := "./conf"
|
||||
|
||||
// Set the Content-Type header to indicate it's a zip file
|
||||
w.Header().Set("Content-Type", "application/zip")
|
||||
@ -230,7 +226,7 @@ func ExportConfigAsZip(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Open the file on disk
|
||||
file, err := os.Open("sys.db")
|
||||
file, err := os.Open("./sys.db")
|
||||
if err != nil {
|
||||
SystemWideLogger.PrintAndLog("Backup", "Unable to open sysdb", err)
|
||||
return
|
||||
@ -279,6 +275,8 @@ func ImportConfigFromZip(w http.ResponseWriter, r *http.Request) {
|
||||
targetDir := "./conf"
|
||||
if utils.FileExists(targetDir) {
|
||||
//Backup the old config to old
|
||||
//backupPath := filepath.Dir(*path_conf) + filepath.Base(*path_conf) + ".old_" + strconv.Itoa(int(time.Now().Unix()))
|
||||
//os.Rename(*path_conf, backupPath)
|
||||
os.Rename("./conf", "./conf.old_"+strconv.Itoa(int(time.Now().Unix())))
|
||||
}
|
||||
|
||||
|
12
src/def.go
12
src/def.go
@ -42,11 +42,10 @@ import (
|
||||
const (
|
||||
/* Build Constants */
|
||||
SYSTEM_NAME = "Zoraxy"
|
||||
SYSTEM_VERSION = "3.1.5"
|
||||
SYSTEM_VERSION = "3.1.6"
|
||||
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
|
||||
|
||||
/* System Constants */
|
||||
DATABASE_PATH = "sys.db"
|
||||
TMP_FOLDER = "./tmp"
|
||||
WEBSERV_DEFAULT_PORT = 5487
|
||||
MDNS_HOSTNAME_PREFIX = "zoraxy_" /* Follow by node UUID */
|
||||
@ -59,7 +58,6 @@ const (
|
||||
ACME_AUTORENEW_CONFIG_PATH = "./conf/acme_conf.json"
|
||||
CSRF_COOKIENAME = "zoraxy_csrf"
|
||||
LOG_PREFIX = "zr"
|
||||
LOG_FOLDER = "./log"
|
||||
LOG_EXTENSION = ".log"
|
||||
|
||||
/* Configuration Folder Storage Path Constants */
|
||||
@ -86,10 +84,16 @@ var (
|
||||
acmeAutoRenewInterval = flag.Int("autorenew", 86400, "ACME auto TLS/SSL certificate renew check interval (seconds)")
|
||||
acmeCertAutoRenewDays = flag.Int("earlyrenew", 30, "Number of days to early renew a soon expiring certificate (days)")
|
||||
enableHighSpeedGeoIPLookup = flag.Bool("fastgeoip", false, "Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)")
|
||||
staticWebServerRoot = flag.String("webroot", "./www", "Static web server root folder. Only allow chnage in start paramters")
|
||||
allowWebFileManager = flag.Bool("webfm", true, "Enable web file manager for static web server root folder")
|
||||
enableAutoUpdate = flag.Bool("cfgupgrade", true, "Enable auto config upgrade if breaking change is detected")
|
||||
|
||||
/* Path Configuration Flags */
|
||||
//path_database = flag.String("dbpath", "./sys.db", "Database path")
|
||||
//path_conf = flag.String("conf", "./conf", "Configuration folder path")
|
||||
path_uuid = flag.String("uuid", "./sys.uuid", "sys.uuid file path")
|
||||
path_logFile = flag.String("log", "./log", "Log folder path")
|
||||
path_webserver = flag.String("webroot", "./www", "Static web server root folder. Only allow change in start paramters")
|
||||
|
||||
/* Maintaince Function Flags */
|
||||
geoDbUpdate = flag.Bool("update_geoip", false, "Download the latest GeoIP data and exit")
|
||||
)
|
||||
|
@ -86,7 +86,7 @@ func main() {
|
||||
SetupCloseHandler()
|
||||
|
||||
//Read or create the system uuid
|
||||
uuidRecord := "./sys.uuid"
|
||||
uuidRecord := *path_uuid
|
||||
if !utils.FileExists(uuidRecord) {
|
||||
newSystemUUID := uuid.New().String()
|
||||
os.WriteFile(uuidRecord, []byte(newSystemUUID), 0775)
|
||||
|
@ -222,10 +222,12 @@ func (h *ProxyHandler) handleRootRouting(w http.ResponseWriter, r *http.Request)
|
||||
h.Parent.logRequest(r, false, 444, "root-noresponse", domainOnly)
|
||||
hijacker, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
w.Header().Set("Connection", "close")
|
||||
return
|
||||
}
|
||||
conn, _, err := hijacker.Hijack()
|
||||
if err != nil {
|
||||
w.Header().Set("Connection", "close")
|
||||
return
|
||||
}
|
||||
conn.Close()
|
||||
|
65
src/mod/dynamicproxy/default.go
Normal file
65
src/mod/dynamicproxy/default.go
Normal file
@ -0,0 +1,65 @@
|
||||
package dynamicproxy
|
||||
|
||||
import (
|
||||
"github.com/google/uuid"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/loadbalance"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||
)
|
||||
|
||||
/*
|
||||
Default Provider
|
||||
|
||||
This script provide the default options for all datatype
|
||||
provided by dynamicproxy module
|
||||
|
||||
*/
|
||||
|
||||
// GetDefaultAuthenticationProvider return a default authentication provider
|
||||
func GetDefaultAuthenticationProvider() *AuthenticationProvider {
|
||||
return &AuthenticationProvider{
|
||||
AuthMethod: AuthMethodNone,
|
||||
BasicAuthCredentials: []*BasicAuthCredentials{},
|
||||
BasicAuthExceptionRules: []*BasicAuthExceptionRule{},
|
||||
BasicAuthGroupIDs: []string{},
|
||||
AutheliaURL: "",
|
||||
UseHTTPS: false,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultHeaderRewriteRules return a default header rewrite rules
|
||||
func GetDefaultHeaderRewriteRules() *HeaderRewriteRules {
|
||||
return &HeaderRewriteRules{
|
||||
UserDefinedHeaders: []*rewrite.UserDefinedHeader{},
|
||||
RequestHostOverwrite: "",
|
||||
HSTSMaxAge: 0,
|
||||
EnablePermissionPolicyHeader: false,
|
||||
PermissionPolicy: nil,
|
||||
DisableHopByHopHeaderRemoval: false,
|
||||
}
|
||||
}
|
||||
|
||||
// GetDefaultProxyEndpoint return a default proxy endpoint
|
||||
func GetDefaultProxyEndpoint() ProxyEndpoint {
|
||||
randomPrefix := uuid.New().String()
|
||||
return ProxyEndpoint{
|
||||
ProxyType: ProxyTypeHost,
|
||||
RootOrMatchingDomain: randomPrefix + ".internal",
|
||||
MatchingDomainAlias: []string{},
|
||||
ActiveOrigins: []*loadbalance.Upstream{},
|
||||
InactiveOrigins: []*loadbalance.Upstream{},
|
||||
UseStickySession: false,
|
||||
UseActiveLoadBalance: false,
|
||||
Disabled: false,
|
||||
BypassGlobalTLS: false,
|
||||
VirtualDirectories: []*VirtualDirectoryEndpoint{},
|
||||
HeaderRewriteRules: GetDefaultHeaderRewriteRules(),
|
||||
EnableWebsocketCustomHeaders: false,
|
||||
AuthenticationProvider: GetDefaultAuthenticationProvider(),
|
||||
RequireRateLimit: false,
|
||||
RateLimit: 0,
|
||||
DisableUptimeMonitor: false,
|
||||
AccessFilterUUID: "default",
|
||||
DefaultSiteOption: DefaultSite_InternalStaticWebServer,
|
||||
DefaultSiteValue: "",
|
||||
}
|
||||
}
|
@ -99,6 +99,23 @@ func DomainUsesTLS(targetURL string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
/*
|
||||
WebSocket Header Sniff
|
||||
*/
|
||||
|
||||
// Check if the requst is a special case where
|
||||
// user defined header shall not be passed over
|
||||
|
||||
func RequireWebsocketHeaderCopy(r *http.Request) bool {
|
||||
//Return false for proxmox
|
||||
if IsProxmox(r) {
|
||||
return false
|
||||
}
|
||||
|
||||
//Add more edge cases here
|
||||
return true
|
||||
}
|
||||
|
||||
/*
|
||||
Request Handlers
|
||||
*/
|
||||
|
@ -157,12 +157,18 @@ func (router *Router) StartProxyService() error {
|
||||
router.Option.Logger.PrintAndLog("dprouter", "failed to get upstream for hostname", err)
|
||||
router.logRequest(r, false, 404, "vdir-http", r.Host)
|
||||
}
|
||||
|
||||
endpointProxyRewriteRules := GetDefaultHeaderRewriteRules()
|
||||
if sep.HeaderRewriteRules != nil {
|
||||
endpointProxyRewriteRules = sep.HeaderRewriteRules
|
||||
}
|
||||
|
||||
selectedUpstream.ServeHTTP(w, r, &dpcore.ResponseRewriteRuleSet{
|
||||
ProxyDomain: selectedUpstream.OriginIpOrDomain,
|
||||
OriginalHost: originalHostHeader,
|
||||
UseTLS: selectedUpstream.RequireTLS,
|
||||
HostHeaderOverwrite: sep.HeaderRewriteRules.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: sep.HeaderRewriteRules.DisableHopByHopHeaderRemoval,
|
||||
HostHeaderOverwrite: endpointProxyRewriteRules.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: endpointProxyRewriteRules.DisableHopByHopHeaderRemoval,
|
||||
PathPrefix: "",
|
||||
Version: sep.parent.Option.HostVersion,
|
||||
})
|
||||
|
@ -27,7 +27,12 @@ import (
|
||||
|
||||
// Check if a user define header exists in this endpoint, ignore case
|
||||
func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
||||
for _, header := range ep.HeaderRewriteRules.UserDefinedHeaders {
|
||||
endpointProxyRewriteRules := GetDefaultHeaderRewriteRules()
|
||||
if ep.HeaderRewriteRules != nil {
|
||||
endpointProxyRewriteRules = ep.HeaderRewriteRules
|
||||
}
|
||||
|
||||
for _, header := range endpointProxyRewriteRules.UserDefinedHeaders {
|
||||
if strings.EqualFold(header.Key, key) {
|
||||
return true
|
||||
}
|
||||
@ -38,6 +43,9 @@ func (ep *ProxyEndpoint) UserDefinedHeaderExists(key string) bool {
|
||||
// Remvoe a user defined header from the list
|
||||
func (ep *ProxyEndpoint) RemoveUserDefinedHeader(key string) error {
|
||||
newHeaderList := []*rewrite.UserDefinedHeader{}
|
||||
if ep.HeaderRewriteRules == nil {
|
||||
ep.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||
}
|
||||
for _, header := range ep.HeaderRewriteRules.UserDefinedHeaders {
|
||||
if !strings.EqualFold(header.Key, key) {
|
||||
newHeaderList = append(newHeaderList, header)
|
||||
@ -55,6 +63,9 @@ func (ep *ProxyEndpoint) AddUserDefinedHeader(newHeaderRule *rewrite.UserDefined
|
||||
ep.RemoveUserDefinedHeader(newHeaderRule.Key)
|
||||
}
|
||||
|
||||
if ep.HeaderRewriteRules == nil {
|
||||
ep.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||
}
|
||||
newHeaderRule.Key = cases.Title(language.Und, cases.NoLower).String(newHeaderRule.Key)
|
||||
ep.HeaderRewriteRules.UserDefinedHeaders = append(ep.HeaderRewriteRules.UserDefinedHeaders, newHeaderRule)
|
||||
return nil
|
||||
@ -106,7 +117,7 @@ func (ep *ProxyEndpoint) RemoveVirtualDirectoryRuleByMatchingPath(matchingPath s
|
||||
return errors.New("target virtual directory routing rule not found")
|
||||
}
|
||||
|
||||
// Delete a vdir rule by its matching path
|
||||
// Add a vdir rule by its matching path
|
||||
func (ep *ProxyEndpoint) AddVirtualDirectoryRule(vdir *VirtualDirectoryEndpoint) (*ProxyEndpoint, error) {
|
||||
//Check for matching path duplicate
|
||||
if ep.GetVirtualDirectoryRuleByMatchingPath(vdir.MatchingPath) != nil {
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/dpcore"
|
||||
"imuslab.com/zoraxy/mod/dynamicproxy/rewrite"
|
||||
"imuslab.com/zoraxy/mod/netutils"
|
||||
@ -143,10 +144,15 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + requestURL)
|
||||
}
|
||||
h.Parent.logRequest(r, true, 101, "host-websocket", selectedUpstream.OriginIpOrDomain)
|
||||
|
||||
if target.HeaderRewriteRules == nil {
|
||||
target.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||
}
|
||||
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: selectedUpstream.SkipCertValidations,
|
||||
SkipOriginCheck: selectedUpstream.SkipWebSocketOriginCheck,
|
||||
CopyAllHeaders: true,
|
||||
CopyAllHeaders: target.EnableWebsocketCustomHeaders,
|
||||
UserDefinedHeaders: target.HeaderRewriteRules.UserDefinedHeaders,
|
||||
Logger: h.Parent.Option.Logger,
|
||||
})
|
||||
@ -163,15 +169,19 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
}
|
||||
|
||||
//Populate the user-defined headers with the values from the request
|
||||
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.HeaderRewriteRules.UserDefinedHeaders)
|
||||
headerRewriteOptions := GetDefaultHeaderRewriteRules()
|
||||
if target.HeaderRewriteRules != nil {
|
||||
headerRewriteOptions = target.HeaderRewriteRules
|
||||
}
|
||||
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, headerRewriteOptions.UserDefinedHeaders)
|
||||
|
||||
//Build downstream and upstream header rules
|
||||
upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||
HSTSMaxAge: target.HeaderRewriteRules.HSTSMaxAge,
|
||||
HSTSMaxAge: headerRewriteOptions.HSTSMaxAge,
|
||||
HSTSIncludeSubdomains: target.ContainsWildcardName(true),
|
||||
EnablePermissionPolicyHeader: target.HeaderRewriteRules.EnablePermissionPolicyHeader,
|
||||
PermissionPolicy: target.HeaderRewriteRules.PermissionPolicy,
|
||||
EnablePermissionPolicyHeader: headerRewriteOptions.EnablePermissionPolicyHeader,
|
||||
PermissionPolicy: headerRewriteOptions.PermissionPolicy,
|
||||
})
|
||||
|
||||
//Handle the request reverse proxy
|
||||
@ -183,8 +193,8 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
PathPrefix: "",
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
HostHeaderOverwrite: target.HeaderRewriteRules.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: target.HeaderRewriteRules.DisableHopByHopHeaderRemoval,
|
||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||
NoRemoveHopByHop: headerRewriteOptions.DisableHopByHopHeaderRemoval,
|
||||
Version: target.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
@ -222,11 +232,16 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
if target.RequireTLS {
|
||||
u, _ = url.Parse("wss://" + wsRedirectionEndpoint + r.URL.String())
|
||||
}
|
||||
|
||||
if target.parent.HeaderRewriteRules != nil {
|
||||
target.parent.HeaderRewriteRules = GetDefaultHeaderRewriteRules()
|
||||
}
|
||||
|
||||
h.Parent.logRequest(r, true, 101, "vdir-websocket", target.Domain)
|
||||
wspHandler := websocketproxy.NewProxy(u, websocketproxy.Options{
|
||||
SkipTLSValidation: target.SkipCertValidations,
|
||||
SkipOriginCheck: true, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||
CopyAllHeaders: true,
|
||||
SkipOriginCheck: target.parent.EnableWebsocketCustomHeaders, //You should not use websocket via virtual directory. But keep this to true for compatibility
|
||||
CopyAllHeaders: domainsniff.RequireWebsocketHeaderCopy(r), //Left this as default to prevent nginx user setting / as vdir
|
||||
UserDefinedHeaders: target.parent.HeaderRewriteRules.UserDefinedHeaders,
|
||||
Logger: h.Parent.Option.Logger,
|
||||
})
|
||||
@ -243,15 +258,20 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
}
|
||||
|
||||
//Populate the user-defined headers with the values from the request
|
||||
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, target.parent.HeaderRewriteRules.UserDefinedHeaders)
|
||||
headerRewriteOptions := GetDefaultHeaderRewriteRules()
|
||||
if target.parent.HeaderRewriteRules != nil {
|
||||
headerRewriteOptions = target.parent.HeaderRewriteRules
|
||||
}
|
||||
|
||||
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(r, headerRewriteOptions.UserDefinedHeaders)
|
||||
|
||||
//Build downstream and upstream header rules, use the parent (subdomain) endpoint's headers
|
||||
upstreamHeaders, downstreamHeaders := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||
HSTSMaxAge: target.parent.HeaderRewriteRules.HSTSMaxAge,
|
||||
HSTSMaxAge: headerRewriteOptions.HSTSMaxAge,
|
||||
HSTSIncludeSubdomains: target.parent.ContainsWildcardName(true),
|
||||
EnablePermissionPolicyHeader: target.parent.HeaderRewriteRules.EnablePermissionPolicyHeader,
|
||||
PermissionPolicy: target.parent.HeaderRewriteRules.PermissionPolicy,
|
||||
EnablePermissionPolicyHeader: headerRewriteOptions.EnablePermissionPolicyHeader,
|
||||
PermissionPolicy: headerRewriteOptions.PermissionPolicy,
|
||||
})
|
||||
|
||||
//Handle the virtual directory reverse proxy request
|
||||
@ -262,7 +282,7 @@ func (h *ProxyHandler) vdirRequest(w http.ResponseWriter, r *http.Request, targe
|
||||
PathPrefix: target.MatchingPath,
|
||||
UpstreamHeaders: upstreamHeaders,
|
||||
DownstreamHeaders: downstreamHeaders,
|
||||
HostHeaderOverwrite: target.parent.HeaderRewriteRules.RequestHostOverwrite,
|
||||
HostHeaderOverwrite: headerRewriteOptions.RequestHostOverwrite,
|
||||
Version: target.parent.parent.Option.HostVersion,
|
||||
})
|
||||
|
||||
|
@ -1,5 +1,12 @@
|
||||
package dynamicproxy
|
||||
|
||||
/*
|
||||
typdef.go
|
||||
|
||||
This script handle the type definition for dynamic proxy and endpoints
|
||||
|
||||
If you are looking for the default object initailization, please refer to default.go
|
||||
*/
|
||||
import (
|
||||
_ "embed"
|
||||
"net"
|
||||
@ -165,7 +172,8 @@ type ProxyEndpoint struct {
|
||||
VirtualDirectories []*VirtualDirectoryEndpoint
|
||||
|
||||
//Custom Headers
|
||||
HeaderRewriteRules *HeaderRewriteRules
|
||||
HeaderRewriteRules *HeaderRewriteRules
|
||||
EnableWebsocketCustomHeaders bool //Enable custom headers for websocket connections as well (default only http reqiests)
|
||||
|
||||
//Authentication
|
||||
AuthenticationProvider *AuthenticationProvider
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -16,6 +16,15 @@ import (
|
||||
func GetRequesterIP(r *http.Request) string {
|
||||
ip := r.Header.Get("X-Real-Ip")
|
||||
if ip == "" {
|
||||
CF_Connecting_IP := r.Header.Get("CF-Connecting-IP")
|
||||
Fastly_Client_IP := r.Header.Get("Fastly-Client-IP")
|
||||
if CF_Connecting_IP != "" {
|
||||
//Use CF Connecting IP
|
||||
return CF_Connecting_IP
|
||||
} else if Fastly_Client_IP != "" {
|
||||
//Use Fastly Client IP
|
||||
return Fastly_Client_IP
|
||||
}
|
||||
ip = r.Header.Get("X-Forwarded-For")
|
||||
}
|
||||
if ip == "" {
|
||||
|
@ -172,6 +172,11 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
if req.Host != "" {
|
||||
requestHeader.Set("Host", req.Host)
|
||||
}
|
||||
if userAgent := req.Header.Get("User-Agent"); userAgent != "" {
|
||||
requestHeader.Set("User-Agent", userAgent)
|
||||
} else {
|
||||
requestHeader.Set("User-Agent", "zoraxy-wsproxy/1.1")
|
||||
}
|
||||
|
||||
// Pass X-Forwarded-For headers too, code below is a part of
|
||||
// httputil.ReverseProxy. See http://en.wikipedia.org/wiki/X-Forwarded-For
|
||||
@ -195,19 +200,29 @@ func (w *WebsocketProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||
requestHeader.Set("X-Forwarded-Proto", "https")
|
||||
}
|
||||
|
||||
// Enable the director to copy any additional headers it desires for
|
||||
// forwarding to the remote server.
|
||||
if w.Director != nil {
|
||||
w.Director(req, requestHeader)
|
||||
}
|
||||
|
||||
// Replace header variables and copy user-defined headers
|
||||
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(req, w.Options.UserDefinedHeaders)
|
||||
upstreamHeaders, _ := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||
})
|
||||
for _, headerValuePair := range upstreamHeaders {
|
||||
requestHeader.Set(headerValuePair[0], headerValuePair[1])
|
||||
if w.Options.CopyAllHeaders {
|
||||
// Rewrite the user defined headers
|
||||
// This is reported to be not compatible with Proxmox and Home Assistant
|
||||
// but required by some other projects like MeshCentral
|
||||
// we will make this optional
|
||||
rewrittenUserDefinedHeaders := rewrite.PopulateRequestHeaderVariables(req, w.Options.UserDefinedHeaders)
|
||||
upstreamHeaders, _ := rewrite.SplitUpDownStreamHeaders(&rewrite.HeaderRewriteOptions{
|
||||
UserDefinedHeaders: rewrittenUserDefinedHeaders,
|
||||
})
|
||||
for _, headerValuePair := range upstreamHeaders {
|
||||
//Do not copy Upgrade and Connection headers, it will be handled by the upgrader
|
||||
if strings.EqualFold(headerValuePair[0], "Upgrade") || strings.EqualFold(headerValuePair[0], "Connection") {
|
||||
continue
|
||||
}
|
||||
requestHeader.Set(headerValuePair[0], headerValuePair[1])
|
||||
}
|
||||
|
||||
// Enable the director to copy any additional headers it desires for
|
||||
// forwarding to the remote server.
|
||||
if w.Director != nil {
|
||||
w.Director(req, requestHeader)
|
||||
}
|
||||
}
|
||||
|
||||
// Connect to the backend URL, also pass the headers we get from the requst
|
||||
|
@ -96,7 +96,7 @@ func ReverseProxtInit() {
|
||||
RedirectRuleTable: redirectTable,
|
||||
GeodbStore: geodbStore,
|
||||
StatisticCollector: statisticCollector,
|
||||
WebDirectory: *staticWebServerRoot,
|
||||
WebDirectory: *path_webserver,
|
||||
AccessController: accessController,
|
||||
AutheliaRouter: autheliaRouter,
|
||||
LoadBalancer: loadBalancer,
|
||||
@ -320,10 +320,6 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
BasicAuthExceptionRules: []*dynamicproxy.BasicAuthExceptionRule{},
|
||||
}
|
||||
|
||||
thisCustomHeaderRules := dynamicproxy.HeaderRewriteRules{
|
||||
UserDefinedHeaders: []*rewrite.UserDefinedHeader{},
|
||||
}
|
||||
|
||||
//Generate a proxy endpoint object
|
||||
thisProxyEndpoint := dynamicproxy.ProxyEndpoint{
|
||||
//I/O
|
||||
@ -353,7 +349,7 @@ func ReverseProxyHandleAddEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
AuthenticationProvider: &thisAuthenticationProvider,
|
||||
|
||||
//Header Rewrite
|
||||
HeaderRewriteRules: &thisCustomHeaderRules,
|
||||
HeaderRewriteRules: dynamicproxy.GetDefaultHeaderRewriteRules(),
|
||||
|
||||
//Default Site
|
||||
DefaultSiteOption: 0,
|
||||
@ -471,6 +467,12 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
bypassGlobalTLS := (bpgtls == "true")
|
||||
|
||||
//Disable uptime monitor
|
||||
disbleUtm, err := utils.PostBool(r, "dutm")
|
||||
if err != nil {
|
||||
disbleUtm = false
|
||||
}
|
||||
|
||||
// Auth Provider
|
||||
authProviderTypeStr, _ := utils.PostPara(r, "authprovider")
|
||||
if authProviderTypeStr == "" {
|
||||
@ -536,6 +538,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
|
||||
newProxyEndpoint.RequireRateLimit = requireRateLimit
|
||||
newProxyEndpoint.RateLimit = proxyRateLimit
|
||||
newProxyEndpoint.UseStickySession = useStickySession
|
||||
newProxyEndpoint.DisableUptimeMonitor = disbleUtm
|
||||
|
||||
//Prepare to replace the current routing rule
|
||||
readyRoutingRule, err := dynamicProxyRouter.PrepareProxyRoute(newProxyEndpoint)
|
||||
@ -1557,3 +1560,39 @@ func HandlePermissionPolicy(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
|
||||
func HandleWsHeaderBehavior(w http.ResponseWriter, r *http.Request) {
|
||||
domain, err := utils.PostPara(r, "domain")
|
||||
if err != nil {
|
||||
domain, err = utils.GetPara(r, "domain")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "domain or matching rule not defined")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
targetProxyEndpoint, err := dynamicProxyRouter.LoadProxy(domain)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "target endpoint not exists")
|
||||
return
|
||||
}
|
||||
|
||||
if r.Method == http.MethodGet {
|
||||
js, _ := json.Marshal(targetProxyEndpoint.EnableWebsocketCustomHeaders)
|
||||
utils.SendJSONResponse(w, string(js))
|
||||
} else if r.Method == http.MethodPost {
|
||||
enableWsHeader, err := utils.PostBool(r, "enable")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid enable state given")
|
||||
return
|
||||
}
|
||||
|
||||
targetProxyEndpoint.EnableWebsocketCustomHeaders = enableWsHeader
|
||||
SaveReverseProxyConfig(targetProxyEndpoint)
|
||||
targetProxyEndpoint.UpdateToRuntime()
|
||||
utils.SendOK(w)
|
||||
|
||||
} else {
|
||||
http.Error(w, "405 - Method not allowed", http.StatusMethodNotAllowed)
|
||||
}
|
||||
}
|
||||
|
@ -54,14 +54,14 @@ var (
|
||||
|
||||
func startupSequence() {
|
||||
//Start a system wide logger and log viewer
|
||||
l, err := logger.NewLogger(LOG_PREFIX, LOG_FOLDER)
|
||||
l, err := logger.NewLogger(LOG_PREFIX, *path_logFile)
|
||||
if err == nil {
|
||||
SystemWideLogger = l
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
LogViewer = logviewer.NewLogViewer(&logviewer.ViewerOption{
|
||||
RootFolder: LOG_FOLDER,
|
||||
RootFolder: *path_logFile,
|
||||
Extension: LOG_EXTENSION,
|
||||
})
|
||||
|
||||
@ -73,7 +73,7 @@ func startupSequence() {
|
||||
backendType = dbinc.BackendBoltDB
|
||||
}
|
||||
l.PrintAndLog("database", "Using "+backendType.String()+" as the database backend", nil)
|
||||
db, err := database.NewDatabase(DATABASE_PATH, backendType)
|
||||
db, err := database.NewDatabase("./sys.db", backendType)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
@ -158,7 +158,7 @@ func startupSequence() {
|
||||
staticWebServer = webserv.NewWebServer(&webserv.WebServerOptions{
|
||||
Sysdb: sysdb,
|
||||
Port: strconv.Itoa(WEBSERV_DEFAULT_PORT), //Default Port
|
||||
WebRoot: *staticWebServerRoot,
|
||||
WebRoot: *path_webserver,
|
||||
EnableDirectoryListing: true,
|
||||
EnableWebDirManager: *allowWebFileManager,
|
||||
Logger: SystemWideLogger,
|
||||
|
@ -258,6 +258,13 @@
|
||||
if (payload.UseStickySession){
|
||||
useStickySessionChecked = "checked";
|
||||
}
|
||||
|
||||
let enableUptimeMonitor = "";
|
||||
//Note the config file store the uptime monitor as disable, so we need to reverse the logic
|
||||
if (!payload.DisableUptimeMonitor){
|
||||
enableUptimeMonitor = "checked";
|
||||
}
|
||||
|
||||
input = `<button class="ui basic compact tiny button" style="margin-left: 0.4em; margin-top: 1em;" onclick="editUpstreams('${uuid}');"><i class="grey server icon"></i> Edit Upstreams</button>
|
||||
<div class="ui divider"></div>
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
@ -265,7 +272,11 @@
|
||||
<label>Use Sticky Session<br>
|
||||
<small>Enable stick session on load balancing</small></label>
|
||||
</div>
|
||||
|
||||
<div class="ui checkbox" style="margin-top: 0.4em;">
|
||||
<input type="checkbox" class="EnableUptimeMonitor" ${enableUptimeMonitor}>
|
||||
<label>Monitor Uptime<br>
|
||||
<small>Enable active uptime monitor</small></label>
|
||||
</div>
|
||||
`;
|
||||
column.append(input);
|
||||
$(column).find(".upstreamList").addClass("editing");
|
||||
@ -441,6 +452,7 @@
|
||||
|
||||
var epttype = "host";
|
||||
let useStickySession = $(row).find(".UseStickySession")[0].checked;
|
||||
let DisableUptimeMonitor = !$(row).find(".EnableUptimeMonitor")[0].checked;
|
||||
let authProviderType = $(row).find(".authProviderPicker input[type='radio']:checked").val();
|
||||
let requireRateLimit = $(row).find(".RequireRateLimit")[0].checked;
|
||||
let rateLimit = $(row).find(".RateLimit").val();
|
||||
@ -453,6 +465,7 @@
|
||||
"type": epttype,
|
||||
"rootname": uuid,
|
||||
"ss":useStickySession,
|
||||
"dutm": DisableUptimeMonitor,
|
||||
"bpgtls": bypassGlobalTLS,
|
||||
"authprovider" :authProviderType,
|
||||
"rate" :requireRateLimit,
|
||||
|
@ -41,7 +41,7 @@
|
||||
<div class="ui radio defaultsite checkbox">
|
||||
<input type="radio" name="defaultsiteOption" value="closeresp">
|
||||
<label>Close Connection<br>
|
||||
<small>Close the connection without any response or in TLS mode, send an empty response</small>
|
||||
<small>Close the connection without any response</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -117,6 +117,16 @@
|
||||
<label>Remove Hop-by-hop Header<br>
|
||||
<small>This should be ON by default</small></label>
|
||||
</div>
|
||||
|
||||
<div class="ui divider"></div>
|
||||
<h4> WebSocket Custom Headers</h4>
|
||||
<p>Copy custom headers from HTTP requests to WebSocket connections.
|
||||
Might be required by some projects like MeshCentral.</p>
|
||||
<div class="ui toggle checkbox">
|
||||
<input type="checkbox" id="copyCustomHeadersWS" name="">
|
||||
<label>Enable WebSocket Custom Header<br>
|
||||
<small>This should be OFF by default</small></label>
|
||||
</div>
|
||||
<div class="ui yellow message">
|
||||
<p><i class="exclamation triangle icon"></i>Settings in this section are for advanced users. Invalid settings might cause werid, unexpected behavior.</p>
|
||||
</div>
|
||||
@ -597,6 +607,7 @@
|
||||
})
|
||||
}
|
||||
|
||||
/* Manual Hostname overwrite */
|
||||
function initManualHostOverwriteValue(){
|
||||
$.get("/api/proxy/header/handleHostOverwrite?domain=" + editingEndpoint.ep, function(data){
|
||||
if (data.error != undefined){
|
||||
@ -643,6 +654,42 @@
|
||||
})
|
||||
}
|
||||
initHopByHopRemoverState();
|
||||
|
||||
/* WebSocket Custom Headers */
|
||||
function initWebSocketCustomHeaderState(){
|
||||
$.get("/api/proxy/header/handleWsHeaderBehavior?domain=" + editingEndpoint.ep, function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error);
|
||||
}else{
|
||||
if (data == true){
|
||||
$("#copyCustomHeadersWS").parent().checkbox("set checked");
|
||||
}else{
|
||||
$("#copyCustomHeadersWS").parent().checkbox("set unchecked");
|
||||
}
|
||||
|
||||
//Bind event to the checkbox
|
||||
$("#copyCustomHeadersWS").on("change", function(evt){
|
||||
let isChecked = $(this)[0].checked;
|
||||
$.cjax({
|
||||
url: "/api/proxy/header/handleWsHeaderBehavior",
|
||||
method: "POST",
|
||||
data: {
|
||||
"domain": editingEndpoint.ep,
|
||||
"enable": isChecked,
|
||||
},
|
||||
success: function(data){
|
||||
if (data.error != undefined){
|
||||
parent.msgbox(data.error, false);
|
||||
}else{
|
||||
parent.msgbox("WebSocket Custom Header rule updated");
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
initWebSocketCustomHeaderState();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
3
tools/benchmark/go.mod
Normal file
3
tools/benchmark/go.mod
Normal file
@ -0,0 +1,3 @@
|
||||
module imuslab.com/zoraxy/benchmark
|
||||
|
||||
go 1.23.2
|
51
tools/benchmark/main.go
Normal file
51
tools/benchmark/main.go
Normal file
@ -0,0 +1,51 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
//Global variables
|
||||
stopchan chan bool
|
||||
|
||||
//Runtime flags
|
||||
benchmarkWebserverListeningPort int
|
||||
)
|
||||
|
||||
func init() {
|
||||
flag.IntVar(&benchmarkWebserverListeningPort, "port", 8123, "Port to listen on")
|
||||
flag.Parse()
|
||||
}
|
||||
|
||||
/* SIGTERM handler, do shutdown sequences before closing */
|
||||
func SetupCloseHandler() {
|
||||
c := make(chan os.Signal, 2)
|
||||
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
||||
go func() {
|
||||
<-c
|
||||
//Stop all request loops
|
||||
fmt.Println("Stopping request generators")
|
||||
if stopchan != nil {
|
||||
stopchan <- true
|
||||
}
|
||||
|
||||
// Wait for all goroutines to finish
|
||||
time.Sleep(1 * time.Second)
|
||||
os.Exit(0)
|
||||
}()
|
||||
}
|
||||
|
||||
func main() {
|
||||
//Setup the SIGTERM handler
|
||||
SetupCloseHandler()
|
||||
//Start the web server
|
||||
fmt.Println("Starting web server on port", benchmarkWebserverListeningPort)
|
||||
fmt.Println("In Zoraxy, point your test proxy rule to this server at the given port")
|
||||
startWebServer()
|
||||
select {}
|
||||
}
|
42
tools/benchmark/server.go
Normal file
42
tools/benchmark/server.go
Normal file
@ -0,0 +1,42 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Start the web server for reciving test request
|
||||
// in Zoraxy, point test.localhost to this server at the given port in the start variables
|
||||
func startWebServer() {
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
// Print the request details to console
|
||||
fmt.Printf("Timestamp: %s\n", time.Now().Format(time.RFC1123))
|
||||
fmt.Printf("Request type: %s\n", r.Method)
|
||||
fmt.Printf("Payload size: %d bytes\n", r.ContentLength)
|
||||
fmt.Printf("Request URI: %s\n", r.RequestURI)
|
||||
fmt.Printf("User Agent: %s\n", r.UserAgent())
|
||||
fmt.Printf("Remote Address: %s\n", r.RemoteAddr)
|
||||
fmt.Println("----------------------------------------")
|
||||
|
||||
//Set header to text
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
// Send response, print the request details to web page
|
||||
w.Write([]byte("----------------------------------------\n"))
|
||||
w.Write([]byte("Request type: " + r.Method + "\n"))
|
||||
w.Write([]byte(fmt.Sprintf("Payload size: %d bytes\n", r.ContentLength)))
|
||||
w.Write([]byte("Request URI: " + r.RequestURI + "\n"))
|
||||
w.Write([]byte("User Agent: " + r.UserAgent() + "\n"))
|
||||
w.Write([]byte("Remote Address: " + r.RemoteAddr + "\n"))
|
||||
w.Write([]byte("----------------------------------------\n"))
|
||||
})
|
||||
|
||||
go func() {
|
||||
err := http.ListenAndServe(fmt.Sprintf(":%d", benchmarkWebserverListeningPort), nil)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to start server: %v\n", err)
|
||||
stopchan <- true
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
5
tools/websocket_echo/go.mod
Normal file
5
tools/websocket_echo/go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module aroz.org/zoraxy/websocket-echo
|
||||
|
||||
go 1.23.2
|
||||
|
||||
require github.com/gorilla/websocket v1.5.3
|
2
tools/websocket_echo/go.sum
Normal file
2
tools/websocket_echo/go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
44
tools/websocket_echo/main.go
Normal file
44
tools/websocket_echo/main.go
Normal file
@ -0,0 +1,44 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
)
|
||||
|
||||
var upgrader = websocket.Upgrader{
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true
|
||||
},
|
||||
}
|
||||
|
||||
func echo(w http.ResponseWriter, r *http.Request) {
|
||||
conn, err := upgrader.Upgrade(w, r, nil)
|
||||
if err != nil {
|
||||
log.Println("Upgrade error:", err)
|
||||
return
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
for key, values := range r.Header {
|
||||
for _, value := range values {
|
||||
message := fmt.Sprintf("%s: %s", key, value)
|
||||
if err := conn.WriteMessage(websocket.TextMessage, []byte(message)); err != nil {
|
||||
log.Println("WriteMessage error:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := conn.WriteMessage(websocket.CloseMessage, websocket.FormatCloseMessage(websocket.CloseNormalClosure, "")); err != nil {
|
||||
log.Println("CloseMessage error:", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
http.HandleFunc("/echo", echo)
|
||||
log.Fatal(http.ListenAndServe(":8888", nil))
|
||||
}
|
67
tools/websocket_echo/test.html
Normal file
67
tools/websocket_echo/test.html
Normal file
@ -0,0 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>WebSocket Echo Test</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebSocket Echo Test</h1>
|
||||
<p>1. Go run main.go</p>
|
||||
<p>2. Create a Zoraxy proxy rule (and add to hosts file) from ws.localhost (port 80) to 127.0.0.1:8888</p>
|
||||
<p>3. Click the Connect button below to test if headers are correctly sent over</p>
|
||||
<button id="connectBtn">Connect</button>
|
||||
<button id="disconnectBtn" disabled>Disconnect</button>
|
||||
<input type="text" id="messageInput" placeholder="Enter message">
|
||||
<button id="sendBtn" disabled>Send</button>
|
||||
<div id="output"></div>
|
||||
|
||||
<script>
|
||||
let socket;
|
||||
const connectBtn = document.getElementById('connectBtn');
|
||||
const disconnectBtn = document.getElementById('disconnectBtn');
|
||||
const sendBtn = document.getElementById('sendBtn');
|
||||
const messageInput = document.getElementById('messageInput');
|
||||
const output = document.getElementById('output');
|
||||
|
||||
connectBtn.addEventListener('click', () => {
|
||||
output.innerHTML = '';
|
||||
//socket = new WebSocket('ws://localhost:8888/echo');
|
||||
socket = new WebSocket('ws://ws.localhost/echo');
|
||||
|
||||
socket.onopen = () => {
|
||||
output.innerHTML += '<p>Connected to WebSocket server</p>';
|
||||
connectBtn.disabled = true;
|
||||
disconnectBtn.disabled = false;
|
||||
sendBtn.disabled = false;
|
||||
};
|
||||
|
||||
socket.onmessage = (event) => {
|
||||
output.innerHTML += `<p>Received: ${event.data}</p>`;
|
||||
};
|
||||
|
||||
socket.onclose = () => {
|
||||
output.innerHTML += '<p>Disconnected from WebSocket server</p>';
|
||||
connectBtn.disabled = false;
|
||||
disconnectBtn.disabled = true;
|
||||
sendBtn.disabled = true;
|
||||
};
|
||||
|
||||
socket.onerror = (error) => {
|
||||
output.innerHTML += `<p>Error: ${error.message}</p>`;
|
||||
};
|
||||
});
|
||||
|
||||
disconnectBtn.addEventListener('click', () => {
|
||||
socket.close();
|
||||
});
|
||||
|
||||
sendBtn.addEventListener('click', () => {
|
||||
const message = messageInput.value;
|
||||
socket.send(message);
|
||||
output.innerHTML += `<p>Sent: ${message}</p>`;
|
||||
messageInput.value = '';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Loading…
x
Reference in New Issue
Block a user