13 Commits

Author SHA1 Message Date
895ee1e53f Merge pull request #544 from tobychui/v3.1.8
- Exposed timeout value from dpcore to UI
- Added active load balancing (if uptime monitor is enabled on that rule)
- Refactorized io stats and remove dependencies over wmic
- Removed SMTP input validation
- Fixed sticky session bug
- Fixed passive load balancer bug
- Fixed dockerfile bug
2025-02-16 17:13:37 +08:00
caf4ab331b Exposed dpcore timeout options
- Exposed idle timeout and response timeout option
- Updated upstream edit UI to use the new API
- Updated geodb
2025-02-16 16:58:25 +08:00
36c1f149e6 Fixed #497
- Removed SMTP input validations
- Updated version no.
- Added todo for removing SMTP all together in future revisions
2025-02-16 09:08:08 +08:00
b0dc4d6670 Fix #542 2025-02-15 16:26:10 -05:00
5d8bec7f24 Fixed sticky session bug
- Fixed sticky session bug in new active fallback lb implementation
2025-02-14 22:53:29 +08:00
32f60dfba6 Fixed #523
- Fixed passive fallback logic
- Added active fallback setting notify from uptime monitor
2025-02-14 22:04:51 +08:00
0abe4c12cf Fixed #526
- Fixed typos
2025-02-12 20:58:22 +08:00
7555611ba5 Fixed h2c enable crash bug
- Moved h2c roundtripper to a dedicated module
- Fixed h2c enable crash bug
2025-02-11 21:53:21 +08:00
e624227dae Merge pull request #520 from eyerrock/wmic-refactor
Remove WMIC dependency and unify network stats retrieval
2025-02-11 19:38:56 +08:00
27695584ab Update README.md
Added new start flags into README
2025-02-09 13:44:55 +08:00
e47a7a8357 Merge pull request #525 from Morethanevil/main
Update CHANGELOG.md
2025-02-09 10:53:13 +08:00
3246f8ea2c Update CHANGELOG.md
:)
2025-02-08 23:52:37 +01:00
ccbda6d7c2 refactored io stats 2025-02-08 16:11:47 +01:00
26 changed files with 14398 additions and 11840 deletions

View File

@ -1,3 +1,23 @@
# v3.1.7 08 Feb 2025
+ Merged and added new tagging system for HTTP Proxy rules [by @adoolaard](https://github.com/adoolaard)
+ Added inline editing for redirection rules [#510](https://github.com/tobychui/zoraxy/issues/510)
+ Added uptime monitor status dot detail info (now clickable) [#467](https://github.com/tobychui/zoraxy/issues/467)
+ Added close connection support to port 80 listener [#405](https://github.com/tobychui/zoraxy/issues/450)
+ Optimized port collision check on startup
+ Optimized dark theme color scheme (Free consultation by 3S Design studio)
+ Fixed capital letter rule unable to delete bug [#507](https://github.com/tobychui/zoraxy/issues/507)
+ Fixed docker statistic not save bug [by @PassiveLemon](https://github.com/PassiveLemon) [#505](https://github.com/tobychui/zoraxy/issues/505)
# v3.1.6 31 Dec 2024
+ Exposed log file, sys.uuid and static web server path to start flag (customizable conf and sys.db path is still wip)
+ Optimized connection close implementation
+ Added toggle for uptime monitor
+ Added optional copy HTTP custom headers to websocket connection [#444](https://github.com/tobychui/zoraxy/issues/444)
# v3.1.5 28 Dec 2024 # v3.1.5 28 Dec 2024
+ Fixed hostname case sensitive bug [#435](https://github.com/tobychui/zoraxy/issues/435) + Fixed hostname case sensitive bug [#435](https://github.com/tobychui/zoraxy/issues/435)

View File

@ -101,12 +101,20 @@ Usage of zoraxy:
ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400) ACME auto TLS/SSL certificate renew check interval (seconds) (default 86400)
-cfgupgrade -cfgupgrade
Enable auto config upgrade if breaking change is detected (default true) Enable auto config upgrade if breaking change is detected (default true)
-db string
Database backend to use (leveldb, boltdb, auto) Note that fsdb will be used on unsupported platforms like RISCV (default "auto")
-default_inbound_enabled
If web server is enabled by default (default true)
-default_inbound_port int
Default web server listening port (default 443)
-docker -docker
Run Zoraxy in docker compatibility mode Run Zoraxy in docker compatibility mode
-earlyrenew int -earlyrenew int
Number of days to early renew a soon expiring certificate (days) (default 30) Number of days to early renew a soon expiring certificate (days) (default 30)
-fastgeoip -fastgeoip
Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices) Enable high speed geoip lookup, require 1GB extra memory (Not recommend for low end devices)
-log string
Log folder path (default "./log")
-mdns -mdns
Enable mDNS scanner and transponder (default true) Enable mDNS scanner and transponder (default true)
-mdnsname string -mdnsname string
@ -117,12 +125,16 @@ Usage of zoraxy:
Management web interface listening port (default ":8000") Management web interface listening port (default ":8000")
-sshlb -sshlb
Allow loopback web ssh connection (DANGER) Allow loopback web ssh connection (DANGER)
-update_geoip
Download the latest GeoIP data and exit
-uuid string
sys.uuid file path (default "./sys.uuid")
-version -version
Show version of this server Show version of this server
-webfm -webfm
Enable web file manager for static web server root folder (default true) Enable web file manager for static web server root folder (default true)
-webroot string -webroot string
Static web server root folder. Only allow chnage in start paramters (default "./www") Static web server root folder. Only allow change in start paramters (default "./www")
-ztauth string -ztauth string
ZeroTier authtoken for the local node ZeroTier authtoken for the local node
-ztport int -ztport int

View File

@ -32,7 +32,7 @@ RUN curl -Lo ZeroTierOne.tar.gz https://codeload.github.com/zerotier/ZeroTierOne
FROM docker.io/ubuntu:latest FROM docker.io/ubuntu:latest
RUN apt-get update -y &&\ RUN apt-get update -y &&\
apt-get install -y bash sudo netcat-openbsd libssl-dev ca-certificates apt-get install -y bash sudo netcat-openbsd libssl-dev ca-certificates openssh-server
COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/ COPY --chmod=700 ./entrypoint.sh /opt/zoraxy/
COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy COPY --from=build-zoraxy /usr/local/bin/zoraxy /usr/local/bin/zoraxy

View File

@ -42,7 +42,7 @@ import (
const ( const (
/* Build Constants */ /* Build Constants */
SYSTEM_NAME = "Zoraxy" SYSTEM_NAME = "Zoraxy"
SYSTEM_VERSION = "3.1.7" SYSTEM_VERSION = "3.1.8"
DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */ DEVELOPMENT_BUILD = false /* Development: Set to false to use embedded web fs */
/* System Constants */ /* System Constants */

View File

@ -16,8 +16,10 @@ require (
github.com/grandcat/zeroconf v1.0.0 github.com/grandcat/zeroconf v1.0.0
github.com/likexian/whois v1.15.1 github.com/likexian/whois v1.15.1
github.com/microcosm-cc/bluemonday v1.0.26 github.com/microcosm-cc/bluemonday v1.0.26
github.com/shirou/gopsutil/v4 v4.25.1
github.com/syndtr/goleveldb v1.0.0
golang.org/x/net v0.29.0 golang.org/x/net v0.29.0
golang.org/x/sys v0.25.0 golang.org/x/sys v0.28.0
golang.org/x/text v0.18.0 golang.org/x/text v0.18.0
) )
@ -26,13 +28,15 @@ require (
cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.4 // indirect
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0 // indirect
github.com/benbjohnson/clock v1.3.0 // indirect github.com/benbjohnson/clock v1.3.0 // indirect
github.com/ebitengine/purego v0.8.2 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
github.com/golang/snappy v0.0.1 // indirect github.com/golang/snappy v0.0.1 // indirect
github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.114 // indirect
github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
github.com/shopspring/decimal v1.3.1 // indirect github.com/shopspring/decimal v1.3.1 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect github.com/tidwall/btree v0.0.0-20191029221954-400434d76274 // indirect
github.com/tidwall/buntdb v1.1.2 // indirect github.com/tidwall/buntdb v1.1.2 // indirect
github.com/tidwall/gjson v1.12.1 // indirect github.com/tidwall/gjson v1.12.1 // indirect
@ -43,6 +47,7 @@ require (
github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect github.com/tidwall/tinyqueue v0.0.0-20180302190814-1e39f5511563 // indirect
github.com/tjfoc/gmsm v1.4.1 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect
github.com/vultr/govultr/v3 v3.9.1 // indirect github.com/vultr/govultr/v3 v3.9.1 // indirect
github.com/yusufpapurcu/wmi v1.2.4 // indirect
go.mongodb.org/mongo-driver v1.12.0 // indirect go.mongodb.org/mongo-driver v1.12.0 // indirect
) )
@ -175,7 +180,7 @@ require (
github.com/softlayer/softlayer-go v1.1.5 // indirect github.com/softlayer/softlayer-go v1.1.5 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cast v1.6.0 // indirect
github.com/stretchr/testify v1.9.0 // indirect github.com/stretchr/testify v1.10.0 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.1002 // indirect
github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/dnspod v1.0.1002 // indirect
github.com/transip/gotransip/v6 v6.26.0 // indirect github.com/transip/gotransip/v6 v6.26.0 // indirect

View File

@ -176,6 +176,8 @@ github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk=
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -221,6 +223,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ= github.com/go-oauth2/oauth2/v4 v4.5.2 h1:CuZhD3lhGuI6aNLyUbRHXsgG2RwGRBOuCBfd4WQKqBQ=
github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ= github.com/go-oauth2/oauth2/v4 v4.5.2/go.mod h1:wk/2uLImWIa9VVQDgxz99H2GDbhmfi/9/Xr+GvkSUSQ=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw= github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk= github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g= github.com/go-resty/resty/v2 v2.13.1 h1:x+LHXBI2nMB1vqndymf26quycC4aggYJ7DECYbiz03g=
@ -570,6 +574,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg= github.com/pquerna/otp v1.4.0 h1:wZvl1TIVxKRThZIBiwOOHOGP/1+nZyWBil9Y2XNEDzg=
github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= github.com/pquerna/otp v1.4.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
@ -609,6 +615,8 @@ github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZ
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/shirou/gopsutil/v4 v4.25.1 h1:QSWkTc+fu9LTAWfkZwZ6j8MSUk4A2LV7rbH0ZqmLjXs=
github.com/shirou/gopsutil/v4 v4.25.1/go.mod h1:RoUCUpndaJFtT+2zsZzzmhvbfGoDCJ7nFXKJf8GqJbI=
github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8=
github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
@ -661,8 +669,8 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE= github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@ -737,6 +745,8 @@ github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9de
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE= go.mongodb.org/mongo-driver v1.12.0 h1:aPx33jmn/rQuJXPQLZQ8NtfPQG8CaqgLThFtqRb0PiE=
go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0= go.mongodb.org/mongo-driver v1.12.0/go.mod h1:AZkxhPnFJUoH7kZlFkVKucV20K387miPfm7oimrSmK0=
@ -893,6 +903,7 @@ golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -907,6 +918,7 @@ golang.org/x/sys v0.0.0-20200918174421-af09f7315aff/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201110211018-35f3e6cf4a65/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -927,8 +939,8 @@ golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=

View File

@ -2,7 +2,6 @@ package dpcore
import ( import (
"context" "context"
"crypto/tls"
"errors" "errors"
"io" "io"
"log" "log"
@ -12,8 +11,8 @@ import (
"strings" "strings"
"time" "time"
"golang.org/x/net/http2"
"imuslab.com/zoraxy/mod/dynamicproxy/domainsniff" "imuslab.com/zoraxy/mod/dynamicproxy/domainsniff"
"imuslab.com/zoraxy/mod/dynamicproxy/modh2c"
"imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy" "imuslab.com/zoraxy/mod/dynamicproxy/permissionpolicy"
) )
@ -84,9 +83,12 @@ type requestCanceler interface {
} }
type DpcoreOptions struct { type DpcoreOptions struct {
IgnoreTLSVerification bool //Disable all TLS verification when request pass through this proxy router IgnoreTLSVerification bool //Disable all TLS verification when request pass through this proxy router
FlushInterval time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately) FlushInterval time.Duration //Duration to flush in normal requests. Stream request or keep-alive request will always flush with interval of -1 (immediately)
UseH2CRoundTripper bool //Use H2C RoundTripper for HTTP/2.0 connection MaxConcurrentConnection int //Maxmium concurrent requests to this server
ResponseHeaderTimeout int64 //Timeout for response header, set to 0 for default
IdleConnectionTimeout int64 //Idle connection timeout, set to 0 for default
UseH2CRoundTripper bool //Use H2C RoundTripper for HTTP/2.0 connection
} }
func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy { func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOptions) *ReverseProxy {
@ -104,30 +106,38 @@ func NewDynamicProxyCore(target *url.URL, prepender string, dpcOptions *DpcoreOp
} }
thisTransporter := http.DefaultTransport thisTransporter := http.DefaultTransport
if dpcOptions.UseH2CRoundTripper {
thisTransporter = &http2.Transport{
DialTLS: func(network, addr string, cfg *tls.Config) (net.Conn, error) {
return net.Dial(network, addr)
},
AllowHTTP: true,
}
}
//Hack the default transporter to handle more connections //Hack the default transporter to handle more connections
optimalConcurrentConnection := 32 optimalConcurrentConnection := 32
if dpcOptions.MaxConcurrentConnection > 0 {
optimalConcurrentConnection = dpcOptions.MaxConcurrentConnection
}
thisTransporter.(*http.Transport).IdleConnTimeout = 30 * time.Second
thisTransporter.(*http.Transport).MaxIdleConns = optimalConcurrentConnection * 2 thisTransporter.(*http.Transport).MaxIdleConns = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).MaxIdleConnsPerHost = optimalConcurrentConnection thisTransporter.(*http.Transport).MaxIdleConnsPerHost = optimalConcurrentConnection
thisTransporter.(*http.Transport).IdleConnTimeout = 30 * time.Second
thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2 thisTransporter.(*http.Transport).MaxConnsPerHost = optimalConcurrentConnection * 2
thisTransporter.(*http.Transport).DisableCompression = true thisTransporter.(*http.Transport).DisableCompression = true
//TODO: Add user adjustable timeout option here if dpcOptions.ResponseHeaderTimeout > 0 {
//Set response header timeout
thisTransporter.(*http.Transport).ResponseHeaderTimeout = time.Duration(dpcOptions.ResponseHeaderTimeout) * time.Millisecond
}
if dpcOptions.IdleConnectionTimeout > 0 {
//Set idle connection timeout
thisTransporter.(*http.Transport).IdleConnTimeout = time.Duration(dpcOptions.IdleConnectionTimeout) * time.Millisecond
}
if dpcOptions.IgnoreTLSVerification { if dpcOptions.IgnoreTLSVerification {
//Ignore TLS certificate validation error //Ignore TLS certificate validation error
thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true thisTransporter.(*http.Transport).TLSClientConfig.InsecureSkipVerify = true
} }
if dpcOptions.UseH2CRoundTripper {
//Use H2C RoundTripper for HTTP/2.0 connection
thisTransporter = modh2c.NewH2CRoundTripper()
}
return &ReverseProxy{ return &ReverseProxy{
Director: director, Director: director,
Prepender: prepender, Prepender: prepender,

View File

@ -3,6 +3,7 @@ package loadbalance
import ( import (
"strings" "strings"
"sync" "sync"
"time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/gorilla/sessions" "github.com/gorilla/sessions"
@ -25,11 +26,12 @@ type Options struct {
} }
type RouteManager struct { type RouteManager struct {
SessionStore *sessions.CookieStore SessionStore *sessions.CookieStore
LoadBalanceMap sync.Map //Sync map to store the last load balance state of a given node OnlineStatus sync.Map //Store the online status notify by uptime monitor
OnlineStatusMap sync.Map //Sync map to store the online status of a given ip address or domain name Options Options //Options for the load balancer
onlineStatusTickerStop chan bool //Stopping channel for the online status pinger
Options Options //Options for the load balancer cacheTicker *time.Ticker //Ticker for cache cleanup
cacheTickerStop chan bool //Stop the cache cleanup
} }
/* Upstream or Origin Server */ /* Upstream or Origin Server */
@ -41,8 +43,12 @@ type Upstream struct {
SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections SkipWebSocketOriginCheck bool //Skip origin check on websocket upgrade connections
//Load balancing configs //Load balancing configs
Weight int //Random weight for round robin, 0 for fallback only Weight int //Random weight for round robin, 0 for fallback only
MaxConn int //TODO: Maxmium connection to this server, 0 for unlimited
//HTTP Transport Config
MaxConn int //Maxmium concurrent requests to this upstream dpcore instance
RespTimeout int64 //Response header timeout in milliseconds
IdleTimeout int64 //Idle connection timeout in milliseconds
//currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected //currentConnectionCounts atomic.Uint64 //Counter for number of client currently connected
proxy *dpcore.ReverseProxy proxy *dpcore.ReverseProxy
@ -55,14 +61,31 @@ func NewLoadBalancer(options *Options) *RouteManager {
options.SystemUUID = uuid.New().String() options.SystemUUID = uuid.New().String()
} }
//Create a ticker for cache cleanup every 12 hours
cacheTicker := time.NewTicker(12 * time.Hour)
cacheTickerStop := make(chan bool)
go func() {
options.Logger.PrintAndLog("LoadBalancer", "Upstream state cache ticker started", nil)
for {
select {
case <-cacheTickerStop:
return
case <-cacheTicker.C:
//Clean up the cache
options.Logger.PrintAndLog("LoadBalancer", "Cleaning up upstream state cache", nil)
}
}
}()
//Generate a session store for stickySession //Generate a session store for stickySession
store := sessions.NewCookieStore([]byte(options.SystemUUID)) store := sessions.NewCookieStore([]byte(options.SystemUUID))
return &RouteManager{ return &RouteManager{
SessionStore: store, SessionStore: store,
LoadBalanceMap: sync.Map{}, OnlineStatus: sync.Map{},
OnlineStatusMap: sync.Map{}, Options: *options,
onlineStatusTickerStop: nil,
Options: *options, cacheTicker: cacheTicker,
cacheTickerStop: cacheTickerStop,
} }
} }
@ -90,11 +113,20 @@ func GetUpstreamsAsString(upstreams []*Upstream) string {
return strings.Join(targets, ", ") return strings.Join(targets, ", ")
} }
func (m *RouteManager) Close() { // Reset the current session store and clear all previous sessions
if m.onlineStatusTickerStop != nil { func (m *RouteManager) ResetSessions() {
m.onlineStatusTickerStop <- true m.SessionStore = sessions.NewCookieStore([]byte(m.Options.SystemUUID))
} }
func (m *RouteManager) Close() {
//Close the session store
m.SessionStore.MaxAge(0)
//Stop the cache cleanup
if m.cacheTicker != nil {
m.cacheTicker.Stop()
}
close(m.cacheTickerStop)
} }
// Log Println, replace all log.Println or fmt.Println with this // Log Println, replace all log.Println or fmt.Println with this

View File

@ -1,39 +1,72 @@
package loadbalance package loadbalance
import ( import (
"net/http" "strconv"
"strings"
"time" "time"
) )
// Return the last ping status to see if the target is online // Return if the target host is online
func (m *RouteManager) IsTargetOnline(matchingDomainOrIp string) bool { func (m *RouteManager) IsTargetOnline(upstreamIP string) bool {
value, ok := m.LoadBalanceMap.Load(matchingDomainOrIp) value, ok := m.OnlineStatus.Load(upstreamIP)
if !ok { if !ok {
return false // Assume online if not found, also update the map
m.OnlineStatus.Store(upstreamIP, true)
return true
} }
isOnline, ok := value.(bool) isOnline, ok := value.(bool)
return ok && isOnline return ok && isOnline
} }
// Ping a target to see if it is online // Notify the host online state, should be called from uptime monitor
func PingTarget(targetMatchingDomainOrIp string, requireTLS bool) bool { func (m *RouteManager) NotifyHostOnlineState(upstreamIP string, isOnline bool) {
client := &http.Client{ //if the upstream IP contains http or https, strip it
Timeout: 10 * time.Second, upstreamIP = strings.TrimPrefix(upstreamIP, "http://")
upstreamIP = strings.TrimPrefix(upstreamIP, "https://")
//Check previous state and update
if m.IsTargetOnline(upstreamIP) == isOnline {
return
} }
url := targetMatchingDomainOrIp m.OnlineStatus.Store(upstreamIP, isOnline)
if requireTLS { m.println("Updating upstream "+upstreamIP+" online state to "+strconv.FormatBool(isOnline), nil)
url = "https://" + url }
} else {
url = "http://" + url // Set this host unreachable for a given amount of time defined in timeout
} // this shall be used in passive fallback. The uptime monitor should call to NotifyHostOnlineState() instead
func (m *RouteManager) NotifyHostUnreachableWithTimeout(upstreamIp string, timeout int64) {
resp, err := client.Get(url) //if the upstream IP contains http or https, strip it
if err != nil { upstreamIp = strings.TrimPrefix(upstreamIp, "http://")
return false upstreamIp = strings.TrimPrefix(upstreamIp, "https://")
} if timeout <= 0 {
defer resp.Body.Close() //Set to the default timeout
timeout = 60
return resp.StatusCode >= 200 && resp.StatusCode <= 600 }
if !m.IsTargetOnline(upstreamIp) {
//Already offline
return
}
m.OnlineStatus.Store(upstreamIp, false)
m.println("Setting upstream "+upstreamIp+" unreachable for "+strconv.FormatInt(timeout, 10)+"s", nil)
go func() {
//Set the upstream back to online after the timeout
<-time.After(time.Duration(timeout) * time.Second)
m.NotifyHostOnlineState(upstreamIp, true)
}()
}
// FilterOfflineOrigins return only online origins from a list of origins
func (m *RouteManager) FilterOfflineOrigins(origins []*Upstream) []*Upstream {
var onlineOrigins []*Upstream
for _, origin := range origins {
if m.IsTargetOnline(origin.OriginIpOrDomain) {
onlineOrigins = append(onlineOrigins, origin)
}
}
return onlineOrigins
} }

View File

@ -19,39 +19,62 @@ func (m *RouteManager) GetRequestUpstreamTarget(w http.ResponseWriter, r *http.R
if len(origins) == 0 { if len(origins) == 0 {
return nil, errors.New("no upstream is defined for this host") return nil, errors.New("no upstream is defined for this host")
} }
var targetOrigin = origins[0]
//Pick the origin
if useStickySession { if useStickySession {
//Use stick session, check which origins this request previously used //Use stick session, check which origins this request previously used
targetOriginId, err := m.getSessionHandler(r, origins) targetOriginId, err := m.getSessionHandler(r, origins)
if err != nil { if err != nil {
//No valid session found. Assign a new upstream // No valid session found or origin is offline
// Filter the offline origins
origins = m.FilterOfflineOrigins(origins)
if len(origins) == 0 {
return nil, errors.New("no online upstream is available for origin: " + r.Host)
}
//Get a random origin
targetOrigin, index, err := getRandomUpstreamByWeight(origins) targetOrigin, index, err := getRandomUpstreamByWeight(origins)
if err != nil { if err != nil {
m.println("Unable to get random upstream", err) m.println("Unable to get random upstream", err)
targetOrigin = origins[0] targetOrigin = origins[0]
index = 0 index = 0
} }
//fmt.Println("DEBUG: (Sticky Session) Registering session origin " + origins[index].OriginIpOrDomain)
m.setSessionHandler(w, r, targetOrigin.OriginIpOrDomain, index) m.setSessionHandler(w, r, targetOrigin.OriginIpOrDomain, index)
return targetOrigin, nil return targetOrigin, nil
} }
//Valid session found. Resume the previous session //Valid session found and origin is online
//fmt.Println("DEBUG: (Sticky Session) Picking origin " + origins[targetOriginId].OriginIpOrDomain)
return origins[targetOriginId], nil return origins[targetOriginId], nil
} else { }
//Do not use stick session. Get a random one //No sticky session, get a random origin
var err error m.clearSessionHandler(w, r) //Clear the session
targetOrigin, _, err = getRandomUpstreamByWeight(origins)
if err != nil {
m.println("Failed to get next origin", err)
targetOrigin = origins[0]
}
//Filter the offline origins
origins = m.FilterOfflineOrigins(origins)
if len(origins) == 0 {
return nil, errors.New("no online upstream is available for origin: " + r.Host)
}
//Get a random origin
targetOrigin, _, err := getRandomUpstreamByWeight(origins)
if err != nil {
m.println("Failed to get next origin", err)
targetOrigin = origins[0]
} }
//fmt.Println("DEBUG: Picking origin " + targetOrigin.OriginIpOrDomain) //fmt.Println("DEBUG: Picking origin " + targetOrigin.OriginIpOrDomain)
return targetOrigin, nil return targetOrigin, nil
} }
// GetUsableUpstreamCounts return the number of usable upstreams
func (m *RouteManager) GetUsableUpstreamCounts(origins []*Upstream) int {
origins = m.FilterOfflineOrigins(origins)
return len(origins)
}
/* Features related to session access */ /* Features related to session access */
//Set a new origin for this connection by session //Set a new origin for this connection by session
func (m *RouteManager) setSessionHandler(w http.ResponseWriter, r *http.Request, originIpOrDomain string, index int) error { func (m *RouteManager) setSessionHandler(w http.ResponseWriter, r *http.Request, originIpOrDomain string, index int) error {
@ -70,6 +93,20 @@ func (m *RouteManager) setSessionHandler(w http.ResponseWriter, r *http.Request,
return nil return nil
} }
func (m *RouteManager) clearSessionHandler(w http.ResponseWriter, r *http.Request) error {
session, err := m.SessionStore.Get(r, "STICKYSESSION")
if err != nil {
return err
}
session.Options.MaxAge = -1
session.Options.Path = "/"
err = session.Save(r, w)
if err != nil {
return err
}
return nil
}
// Get the previous connected origin from session // Get the previous connected origin from session
func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) (int, error) { func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream) (int, error) {
// Get existing session // Get existing session
@ -86,15 +123,22 @@ func (m *RouteManager) getSessionHandler(r *http.Request, upstreams []*Upstream)
return -1, errors.New("no session has been set") return -1, errors.New("no session has been set")
} }
originDomain := originDomainRaw.(string) originDomain := originDomainRaw.(string)
originID := originIDRaw.(int) //originID := originIDRaw.(int)
//Check if it has been modified //Check if the upstream still exists
if len(upstreams) < originID || upstreams[originID].OriginIpOrDomain != originDomain { for i, upstream := range upstreams {
//Mismatch or upstreams has been updated if upstream.OriginIpOrDomain == originDomain {
return -1, errors.New("upstreams has been changed") if !m.IsTargetOnline(originDomain) {
//Origin is offline
return -1, errors.New("origin is offline")
}
//Ok, the origin is still online
return i, nil
}
} }
return originID, nil return -1, errors.New("origin is no longer exists")
} }
/* Functions related to random upstream picking */ /* Functions related to random upstream picking */

View File

@ -39,8 +39,11 @@ func (u *Upstream) StartProxy() error {
} }
proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{ proxy := dpcore.NewDynamicProxyCore(path, "", &dpcore.DpcoreOptions{
IgnoreTLSVerification: u.SkipCertValidations, IgnoreTLSVerification: u.SkipCertValidations,
FlushInterval: 100 * time.Millisecond, FlushInterval: 100 * time.Millisecond,
ResponseHeaderTimeout: u.RespTimeout,
IdleConnectionTimeout: u.IdleTimeout,
MaxConcurrentConnection: u.MaxConn,
}) })
u.proxy = proxy u.proxy = proxy

View File

@ -0,0 +1,45 @@
package modh2c
/*
modh2c.go
This module is a simple h2c roundtripper for dpcore
*/
import (
"context"
"crypto/tls"
"net"
"net/http"
"time"
"golang.org/x/net/http2"
)
type H2CRoundTripper struct {
}
func NewH2CRoundTripper() *H2CRoundTripper {
return &H2CRoundTripper{}
}
// Example from https://github.com/thrawn01/h2c-golang-example/blob/master/cmd/client/main.go
func (h2c *H2CRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
defer cancel()
req, err := http.NewRequestWithContext(ctx, req.Method, req.RequestURI, nil)
if err != nil {
return nil, err
}
tr := &http2.Transport{
AllowHTTP: true,
DialTLSContext: func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
var d net.Dialer
return d.DialContext(ctx, network, addr)
},
}
return tr.RoundTrip(req)
}

View File

@ -1,7 +1,9 @@
package dynamicproxy package dynamicproxy
import ( import (
"context"
"errors" "errors"
"fmt"
"log" "log"
"net" "net"
"net/http" "net/http"
@ -198,14 +200,21 @@ func (h *ProxyHandler) hostRequest(w http.ResponseWriter, r *http.Request, targe
Version: target.parent.Option.HostVersion, Version: target.parent.Option.HostVersion,
}) })
//validate the error
var dnsError *net.DNSError var dnsError *net.DNSError
if err != nil { if err != nil {
if errors.As(err, &dnsError) { if errors.As(err, &dnsError) {
http.ServeFile(w, r, "./web/hosterror.html") http.ServeFile(w, r, "./web/hosterror.html")
h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 404, "host-http", r.URL.Hostname())
} else if errors.Is(err, context.Canceled) {
//Request canceled by client, usually due to manual refresh before page load
http.Error(w, "Request canceled", http.StatusRequestTimeout)
h.Parent.logRequest(r, false, http.StatusRequestTimeout, "host-http", r.URL.Hostname())
} else { } else {
//Notify the load balancer that the host is unreachable
fmt.Println(err.Error())
h.Parent.loadBalancer.NotifyHostUnreachableWithTimeout(selectedUpstream.OriginIpOrDomain, PassiveLoadBalanceNotifyTimeout)
http.ServeFile(w, r, "./web/rperror.html") http.ServeFile(w, r, "./web/rperror.html")
//TODO: Take this upstream offline automatically
h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname()) h.Parent.logRequest(r, false, 521, "host-http", r.URL.Hostname())
} }
} }

View File

@ -123,7 +123,7 @@
<div class="ui container"> <div class="ui container">
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="eight wide column"> <div class="eight wide column">
<h1>What happend?</h1> <h1>What happened?</h1>
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p> <p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">

View File

@ -28,6 +28,7 @@ import (
type ProxyType int type ProxyType int
const PassiveLoadBalanceNotifyTimeout = 60 //Time to assume a passive load balance is unreachable, in seconds
const ( const (
ProxyTypeRoot ProxyType = iota //Root Proxy, everything not matching will be routed here ProxyTypeRoot ProxyType = iota //Root Proxy, everything not matching will be routed here
ProxyTypeHost //Host Proxy, match by host (domain) name ProxyTypeHost //Host Proxy, match by host (domain) name
@ -193,7 +194,7 @@ type ProxyEndpoint struct {
DefaultSiteValue string //Fallback routing target, optional DefaultSiteValue string //Fallback routing target, optional
//Internal Logic Elements //Internal Logic Elements
parent *Router `json:"-"` parent *Router `json:"-"`
Tags []string // Tags for the proxy endpoint Tags []string // Tags for the proxy endpoint
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -4,14 +4,9 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"net/http" "net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strconv"
"strings"
"time" "time"
"github.com/shirou/gopsutil/v4/net"
"imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/info/logger"
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
@ -202,144 +197,21 @@ func (n *NetStatBuffers) HandleGetNetworkInterfaceStats(w http.ResponseWriter, r
// Get network interface stats, return accumulated rx bits, tx bits and error if any // Get network interface stats, return accumulated rx bits, tx bits and error if any
func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) { func (n *NetStatBuffers) GetNetworkInterfaceStats() (int64, int64, error) {
if runtime.GOOS == "windows" { // Get aggregated network I/O stats for all interfaces
//Windows wmic sometime freeze and not respond. counters, err := net.IOCounters(false)
//The safer way is to make a bypass mechanism if err != nil {
//when timeout with channel return 0, 0, err
}
type wmicResult struct { if len(counters) == 0 {
RX int64 return 0, 0, errors.New("no network interfaces found")
TX int64
Err error
}
callbackChan := make(chan wmicResult)
cmd := exec.Command("wmic", "path", "Win32_PerfRawData_Tcpip_NetworkInterface", "Get", "BytesReceivedPersec,BytesSentPersec,BytesTotalPersec")
//Execute the cmd in goroutine
go func() {
out, err := cmd.Output()
if err != nil {
callbackChan <- wmicResult{0, 0, err}
return
}
//Filter out the first line
lines := strings.Split(strings.ReplaceAll(string(out), "\r\n", "\n"), "\n")
if len(lines) >= 2 && len(lines[1]) >= 0 {
dataLine := lines[1]
for strings.Contains(dataLine, " ") {
dataLine = strings.ReplaceAll(dataLine, " ", " ")
}
dataLine = strings.TrimSpace(dataLine)
info := strings.Split(dataLine, " ")
if len(info) != 3 {
callbackChan <- wmicResult{0, 0, errors.New("invalid wmic results length")}
}
rxString := info[0]
txString := info[1]
rx := int64(0)
tx := int64(0)
if s, err := strconv.ParseInt(rxString, 10, 64); err == nil {
rx = s
}
if s, err := strconv.ParseInt(txString, 10, 64); err == nil {
tx = s
}
time.Sleep(100 * time.Millisecond)
callbackChan <- wmicResult{rx * 4, tx * 4, nil}
} else {
//Invalid data
callbackChan <- wmicResult{0, 0, errors.New("invalid wmic results")}
}
}()
go func() {
//Spawn a timer to terminate the cmd process if timeout
time.Sleep(3 * time.Second)
if cmd != nil && cmd.Process != nil {
cmd.Process.Kill()
callbackChan <- wmicResult{0, 0, errors.New("wmic execution timeout")}
}
}()
result := wmicResult{}
result = <-callbackChan
cmd = nil
if result.Err != nil {
n.logger.PrintAndLog("netstat", "Unable to extract NIC info from wmic", result.Err)
}
return result.RX, result.TX, result.Err
} else if runtime.GOOS == "linux" {
allIfaceRxByteFiles, err := filepath.Glob("/sys/class/net/*/statistics/rx_bytes")
if err != nil {
//Permission denied
return 0, 0, errors.New("access denied")
}
if len(allIfaceRxByteFiles) == 0 {
return 0, 0, errors.New("no valid iface found")
}
rxSum := int64(0)
txSum := int64(0)
for _, rxByteFile := range allIfaceRxByteFiles {
rxBytes, err := os.ReadFile(rxByteFile)
if err == nil {
rxBytesInt, err := strconv.Atoi(strings.TrimSpace(string(rxBytes)))
if err == nil {
rxSum += int64(rxBytesInt)
}
}
//Usually the tx_bytes file is nearby it. Read it as well
txByteFile := filepath.Join(filepath.Dir(rxByteFile), "tx_bytes")
txBytes, err := os.ReadFile(txByteFile)
if err == nil {
txBytesInt, err := strconv.Atoi(strings.TrimSpace(string(txBytes)))
if err == nil {
txSum += int64(txBytesInt)
}
}
}
//Return value as bits
return rxSum * 8, txSum * 8, nil
} else if runtime.GOOS == "darwin" {
cmd := exec.Command("netstat", "-ib") //get data from netstat -ib
out, err := cmd.Output()
if err != nil {
return 0, 0, err
}
outStrs := string(out) //byte array to multi-line string
for _, outStr := range strings.Split(strings.TrimSuffix(outStrs, "\n"), "\n") { //foreach multi-line string
if strings.HasPrefix(outStr, "en") { //search for ethernet interface
if strings.Contains(outStr, "<Link#") { //search for the link with <Link#?>
outStrSplit := strings.Fields(outStr) //split by white-space
rxSum, errRX := strconv.Atoi(outStrSplit[6]) //received bytes sum
if errRX != nil {
return 0, 0, errRX
}
txSum, errTX := strconv.Atoi(outStrSplit[9]) //transmitted bytes sum
if errTX != nil {
return 0, 0, errTX
}
return int64(rxSum) * 8, int64(txSum) * 8, nil
}
}
}
return 0, 0, nil //no ethernet adapters with en*/<Link#*>
} }
return 0, 0, errors.New("platform not supported") var totalRx, totalTx uint64
for _, counter := range counters {
totalRx += counter.BytesRecv
totalTx += counter.BytesSent
}
// Convert bytes to bits
return int64(totalRx * 8), int64(totalTx * 8), nil
} }

58
src/mod/uptime/typedef.go Normal file
View File

@ -0,0 +1,58 @@
package uptime
import "imuslab.com/zoraxy/mod/info/logger"
const (
logModuleName = "uptime-monitor"
)
type Record struct {
Timestamp int64
ID string
Name string
URL string
Protocol string
Online bool
StatusCode int
Latency int64
}
type ProxyType string
const (
ProxyType_Host ProxyType = "Origin Server"
ProxyType_Vdir ProxyType = "Virtual Directory"
)
type Target struct {
ID string
Name string
URL string
Protocol string
ProxyType ProxyType
}
type Config struct {
Targets []*Target
Interval int
MaxRecordsStore int
OnlineStateNotify func(upstreamIP string, isOnline bool)
Logger *logger.Logger
}
type Monitor struct {
Config *Config
OnlineStatusLog map[string][]*Record
}
// Default configs
var exampleTarget = Target{
ID: "example",
Name: "Example",
URL: "example.com",
Protocol: "https",
}
func defaultNotify(upstreamIP string, isOnline bool) {
// Do nothing
}

View File

@ -14,56 +14,6 @@ import (
"imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/utils"
) )
const (
logModuleName = "uptime-monitor"
)
type Record struct {
Timestamp int64
ID string
Name string
URL string
Protocol string
Online bool
StatusCode int
Latency int64
}
type ProxyType string
const (
ProxyType_Host ProxyType = "Origin Server"
ProxyType_Vdir ProxyType = "Virtual Directory"
)
type Target struct {
ID string
Name string
URL string
Protocol string
ProxyType ProxyType
}
type Config struct {
Targets []*Target
Interval int
MaxRecordsStore int
Logger *logger.Logger
}
type Monitor struct {
Config *Config
OnlineStatusLog map[string][]*Record
}
// Default configs
var exampleTarget = Target{
ID: "example",
Name: "Example",
URL: "example.com",
Protocol: "https",
}
// Create a new uptime monitor // Create a new uptime monitor
func NewUptimeMonitor(config *Config) (*Monitor, error) { func NewUptimeMonitor(config *Config) (*Monitor, error) {
//Create new monitor object //Create new monitor object
@ -77,6 +27,11 @@ func NewUptimeMonitor(config *Config) (*Monitor, error) {
config.Logger, _ = logger.NewFmtLogger() config.Logger, _ = logger.NewFmtLogger()
} }
if config.OnlineStateNotify == nil {
//Use default notify function if not provided
config.OnlineStateNotify = defaultNotify
}
//Start the endpoint listener //Start the endpoint listener
ticker := time.NewTicker(time.Duration(config.Interval) * time.Second) ticker := time.NewTicker(time.Duration(config.Interval) * time.Second)
done := make(chan bool) done := make(chan bool)
@ -218,6 +173,7 @@ func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
end := time.Now().UnixNano() / int64(time.Millisecond) end := time.Now().UnixNano() / int64(time.Millisecond)
if err != nil { if err != nil {
m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err) m.Config.Logger.PrintAndLog(logModuleName, "Ping upstream timeout. Assume offline", err)
m.Config.OnlineStateNotify(url, false)
return false, 0, 0 return false, 0, 0
} else { } else {
diff := end - start diff := end - start
@ -231,7 +187,7 @@ func (m *Monitor) getWebsiteStatusWithLatency(url string) (bool, int64, int) {
} else { } else {
succ = false succ = false
} }
m.Config.OnlineStateNotify(url, true)
return succ, diff, statusCode return succ, diff, statusCode
} }

View File

@ -163,10 +163,11 @@ func ReverseProxtInit() {
go func() { go func() {
//This must be done in go routine to prevent blocking on system startup //This must be done in go routine to prevent blocking on system startup
uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{ uptimeMonitor, _ = uptime.NewUptimeMonitor(&uptime.Config{
Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter), Targets: GetUptimeTargetsFromReverseProxyRules(dynamicProxyRouter),
Interval: 300, //5 minutes Interval: 300, //5 minutes
MaxRecordsStore: 288, //1 day MaxRecordsStore: 288, //1 day
Logger: SystemWideLogger, //Logger OnlineStateNotify: loadBalancer.NotifyHostOnlineState, //Notify the load balancer for online state
Logger: SystemWideLogger, //Logger
}) })
SystemWideLogger.Println("Uptime Monitor background service started") SystemWideLogger.Println("Uptime Monitor background service started")
@ -594,6 +595,7 @@ func ReverseProxyHandleEditEndpoint(w http.ResponseWriter, r *http.Request) {
return return
} }
targetProxyEntry.Remove() targetProxyEntry.Remove()
loadBalancer.ResetSessions()
dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule) dynamicProxyRouter.AddProxyRouteToRuntime(readyRoutingRule)
//Save it to file //Save it to file

View File

@ -79,6 +79,25 @@ func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
utils.SendErrorResponse(w, "upstream origin not set") utils.SendErrorResponse(w, "upstream origin not set")
return return
} }
//Response timeout in seconds, set to 0 for default
respTimeout, err := utils.PostInt(r, "respt")
if err != nil {
respTimeout = 0
}
//Idle timeout in seconds, set to 0 for default
idleTimeout, err := utils.PostInt(r, "idlet")
if err != nil {
idleTimeout = 0
}
//Max concurrent connection to dpcore instance, set to 0 for default
maxConn, err := utils.PostInt(r, "maxconn")
if err != nil {
maxConn = 0
}
requireTLS, _ := utils.PostBool(r, "tls") requireTLS, _ := utils.PostBool(r, "tls")
skipTlsValidation, _ := utils.PostBool(r, "tlsval") skipTlsValidation, _ := utils.PostBool(r, "tlsval")
bpwsorg, _ := utils.PostBool(r, "bpwsorg") bpwsorg, _ := utils.PostBool(r, "bpwsorg")
@ -91,7 +110,9 @@ func ReverseProxyUpstreamAdd(w http.ResponseWriter, r *http.Request) {
SkipCertValidations: skipTlsValidation, SkipCertValidations: skipTlsValidation,
SkipWebSocketOriginCheck: bpwsorg, SkipWebSocketOriginCheck: bpwsorg,
Weight: 1, Weight: 1,
MaxConn: 0, MaxConn: maxConn,
RespTimeout: int64(respTimeout),
IdleTimeout: int64(idleTimeout),
} }
//Add the new upstream to endpoint //Add the new upstream to endpoint

View File

@ -259,6 +259,8 @@
/* /*
SMTP Settings SMTP Settings
TODO: Remove SMTP support in future versions
*/ */
//Bind events to the form //Bind events to the form
@ -273,11 +275,13 @@
adminAddr: $('input[name=recvAddr]').val() adminAddr: $('input[name=recvAddr]').val()
}; };
/*
var inputValid = validateSMTPInputs(); var inputValid = validateSMTPInputs();
if (!inputValid){ if (!inputValid){
msgbox("SMTP input not valid", false, 5000); msgbox("SMTP input not valid", false, 5000);
return; return;
} }
*/
$.cjax({ $.cjax({
type: "POST", type: "POST",

View File

@ -123,7 +123,7 @@
<div class="ui container"> <div class="ui container">
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="eight wide column"> <div class="eight wide column">
<h1>What happend?</h1> <h1>What happened?</h1>
<p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p> <p>The reverse proxy target domain is not found.<br>For more information, see the error message on the reverse proxy terminal.</p>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">

View File

@ -124,7 +124,7 @@
<div class="ui container"> <div class="ui container">
<div class="ui stackable grid"> <div class="ui stackable grid">
<div class="eight wide column"> <div class="eight wide column">
<h1>What happend?</h1> <h1>What happened?</h1>
<p>The web server reported a bad gateway error.<br>For more information, see the error message on the reverse proxy terminal.</p> <p>The web server reported a bad gateway error.<br>For more information, see the error message on the reverse proxy terminal.</p>
</div> </div>
<div class="eight wide column"> <div class="eight wide column">

View File

@ -57,6 +57,17 @@
margin-bottom: 0.4em; margin-bottom: 0.4em;
} }
} }
.advanceUpstreamOptions{
padding: 0.6em;
background-color: var(--theme_advance);
width: 100%;
border-radius: 0.4em;
}
.advanceUpstreamOptions.ui.accordion .content{
padding: 1em !important;
}
</style> </style>
</head> </head>
<body> <body>
@ -121,6 +132,38 @@
<label>Skip WebSocket Origin Check<br> <label>Skip WebSocket Origin Check<br>
<small>Check this to allow cross-origin websocket requests</small></label> <small>Check this to allow cross-origin websocket requests</small></label>
</div> </div>
<div class="ui advanceUpstreamOptions accordion" style="margin-top:0.6em;">
<div class="title">
<i class="dropdown icon"></i>
Advanced Options
</div>
<div class="content">
<p>Max Concurrent Connections</p>
<div class="ui mini fluid input" style="margin-top: -0.6em;">
<input type="number" min="0" id="maxConn" value="0">
</div>
<small>Set to 0 for default value (32 connections)</small>
<br><br>
<p>Response Timeout</p>
<div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
<input type="number" min="0" id="respTimeout" value="0">
<div class="ui basic label">
Seconds
</div>
</div>
<small>Maximum waiting time for server header response, set to 0 for default</small>
<br><br>
<p>Idle Timeout</p>
<div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
<input type="number" min="0" id="idleTimeout" value="0">
<div class="ui basic label">
Seconds
</div>
</div>
<small>Maximum allowed keep-alive time forcefully closes the connection, set to 0 for default</small>
</div>
</div>
<br><br> <br><br>
<button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button> <button class="ui basic button" onclick="addNewUpstream();"><i class="ui green circle add icon"></i> Create</button>
</div> </div>
@ -172,6 +215,8 @@
renderUpstreamEntryToTable(upstream, false); renderUpstreamEntryToTable(upstream, false);
}); });
$(".advanceUpstreamOptions.accordion").accordion();
let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length; let totalUpstreams = data.ActiveOrigins.length + data.InactiveOrigins.length;
if (totalUpstreams == 1){ if (totalUpstreams == 1){
$(".lowPriorityButton").addClass('disabled'); $(".lowPriorityButton").addClass('disabled');
@ -227,6 +272,8 @@
let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}` let url = `${upstream.RequireTLS?"https://":"http://"}${upstream.OriginIpOrDomain}`
let payload = encodeURIComponent(JSON.stringify(upstream)); let payload = encodeURIComponent(JSON.stringify(upstream));
let domUID = newUID(); let domUID = newUID();
//Timeout values are stored as ms in the backend
$("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"inactive"} basic segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}"> $("#upstreamTable").append(`<div class="ui upstreamEntry ${isActive?"":"inactive"} basic segment" data-domid="${domUID}" data-payload="${payload}" data-priority="${upstream.Priority}">
<h4 class="ui header"> <h4 class="ui header">
<div class="ui toggle checkbox" style="display:inline-block;"> <div class="ui toggle checkbox" style="display:inline-block;">
@ -262,6 +309,39 @@
<label>Skip WebSocket Origin Check<br> <label>Skip WebSocket Origin Check<br>
<small>Check this to allow cross-origin websocket requests</small></label> <small>Check this to allow cross-origin websocket requests</small></label>
</div><br> </div><br>
<!-- Advance Settings -->
<div class="ui advanceUpstreamOptions accordion" style="margin-top:0.6em;">
<div class="title">
<i class="dropdown icon"></i>
Advanced Options
</div>
<div class="content">
<p>Max Concurrent Connections</p>
<div class="ui mini fluid input" style="margin-top: -0.6em;">
<input type="number" min="0" class="maxConn" value="${upstream.MaxConn}">
</div>
<small>Set to 0 for default value (32 connections)</small>
<br>
<p style="margin-top: 0.6em;">Response Timeout</p>
<div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
<input type="number" min="0" class="respTimeout" value="${upstream.RespTimeout/1000}">
<div class="ui basic label">
Seconds
</div>
</div>
<small>Maximum waiting time before Zoraxy receive server header response, set to 0 for default</small>
<br>
<p style="margin-top: 0.6em;">Idle Timeout</p>
<div class="ui mini right labeled fluid input" style="margin-top: -0.6em;">
<input type="number" min="0" class="idleTimeout" value="${upstream.IdleTimeout/1000}">
<div class="ui basic label">
Seconds
</div>
</div>
<small>Maximum allowed keep-alive time before Zoraxy forcefully close the connection, set to 0 for default</small>
</div>
</div>
</div> </div>
<div class="upstreamActions"> <div class="upstreamActions">
<!-- Change Priority --> <!-- Change Priority -->
@ -316,12 +396,32 @@
let skipVerification = $("#skipTlsVerification")[0].checked; let skipVerification = $("#skipTlsVerification")[0].checked;
let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked; let skipWebSocketOriginCheck = $("#SkipWebSocketOriginCheck")[0].checked;
let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked; let activateLoadbalancer = $("#activateNewUpstreamCheckbox")[0].checked;
let maxConn = $("#maxConn").val();
let respTimeout = $("#respTimeout").val();
let idleTimeout = $("#idleTimeout").val();
if (maxConn == "" || isNaN(maxConn)){
maxConn = 0;
}
if (respTimeout == "" || isNaN(respTimeout)){
respTimeout = 0;
}
if (idleTimeout == "" || isNaN(idleTimeout)){
idleTimeout = 0;
}
if (origin == ""){ if (origin == ""){
parent.msgbox("Upstream origin cannot be empty", false); parent.msgbox("Upstream origin cannot be empty", false);
return; return;
} }
//Convert seconds to ms
respTimeout = parseInt(respTimeout) * 1000;
idleTimeout = parseInt(idleTimeout) * 1000;
$.cjax({ $.cjax({
url: "/api/proxy/upstream/add", url: "/api/proxy/upstream/add",
method: "POST", method: "POST",
@ -332,6 +432,9 @@
"tlsval": skipVerification, "tlsval": skipVerification,
"bpwsorg":skipWebSocketOriginCheck, "bpwsorg":skipWebSocketOriginCheck,
"active": activateLoadbalancer, "active": activateLoadbalancer,
"maxconn": maxConn,
"respt": respTimeout,
"idlet": idleTimeout,
}, },
success: function(data){ success: function(data){
if (data.error != undefined){ if (data.error != undefined){
@ -340,6 +443,9 @@
parent.msgbox("New upstream origin added"); parent.msgbox("New upstream origin added");
initOriginList(); initOriginList();
$("#originURL").val(""); $("#originURL").val("");
$("#maxConn").val("0");
$("#respTimeout").val("0");
$("#idleTimeout").val("0");
} }
} }
}) })
@ -356,11 +462,34 @@
let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked; let skipTLSVerification = $(upstream).find(".skipVerificationCheckbox")[0].checked;
let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked; let skipWebSocketOriginCheck = $(upstream).find(".SkipWebSocketOriginCheck")[0].checked;
//Advance options
let maxConn = $(upstream).find(".maxConn").val();
let respTimeout = $(upstream).find(".respTimeout").val();
let idleTimeout = $(upstream).find(".idleTimeout").val();
if (maxConn == "" || isNaN(maxConn)){
maxConn = 0;
}
if (respTimeout == "" || isNaN(respTimeout)){
respTimeout = 0;
}
if (idleTimeout == "" || isNaN(idleTimeout)){
idleTimeout = 0;
}
respTimeout = parseInt(respTimeout) * 1000;
idleTimeout = parseInt(idleTimeout) * 1000;
//Update the original setting with new one just applied //Update the original setting with new one just applied
originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val(); originalSettings.OriginIpOrDomain = $(upstream).find(".newOrigin").val();
originalSettings.RequireTLS = requireTLS; originalSettings.RequireTLS = requireTLS;
originalSettings.SkipCertValidations = skipTLSVerification; originalSettings.SkipCertValidations = skipTLSVerification;
originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck; originalSettings.SkipWebSocketOriginCheck = skipWebSocketOriginCheck;
originalSettings.MaxConn = parseInt(maxConn);
originalSettings.RespTimeout = respTimeout;
originalSettings.IdleTimeout = idleTimeout;
//console.log(originalSettings); //console.log(originalSettings);
return originalSettings; return originalSettings;