This is an automated email from the ASF dual-hosted git repository.
hsluoyz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/casbin-gateway.git
The following commit(s) were added to refs/heads/master by this push:
new 064c4ba fix(version): send the update through the configured proxy
064c4ba is described below
commit 064c4bab62f57243271e4d778467932df626266f
Author: Yang Luo <[email protected]>
AuthorDate: Sun Aug 30 22:50:34 2026 +0800
fix(version): send the update through the configured proxy
---
conf/app.conf | 3 +-
proxy/proxy.go | 60 ++++++++++++++++++++---------
version/check.go | 26 ++++++++++++-
version/download.go | 4 +-
version/info.go | 7 +++-
version/update.go | 3 ++
web/src/components/shared/version-panel.tsx | 16 ++++++++
web/src/locales/en/data.json | 4 +-
web/src/locales/zh/data.json | 4 +-
web/src/types.ts | 4 ++
10 files changed, 105 insertions(+), 26 deletions(-)
diff --git a/conf/app.conf b/conf/app.conf
index 1ad22df..3ece953 100644
--- a/conf/app.conf
+++ b/conf/app.conf
@@ -50,7 +50,8 @@ gatewayEnabled = false
gatewayHttpPort = 80
gatewayHttpsPort = 443
-; Optional SOCKS5 proxy ("host:port") for outbound traffic such as ACME.
+; Optional SOCKS5 proxy ("host:port") for outbound traffic such as update
+; downloads and ACME.
httpProxy = ""
; Casdoor is optional. Leave "casdoorEndpoint" empty to sign in against
Gateway's
diff --git a/proxy/proxy.go b/proxy/proxy.go
index 10cfbfa..27bfab1 100644
--- a/proxy/proxy.go
+++ b/proxy/proxy.go
@@ -31,29 +31,34 @@ import (
var ProxyHttpClient *http.Client
var (
- proxyUrlOnce sync.Once
- proxyUrl *url.URL
+ proxyLock sync.Mutex
+ proxyRead bool
+ proxyRaw string
+ proxyUrl *url.URL
transportOnce sync.Once
transport *http.Transport
)
func InitHttpClient() {
- proxyUrlOnce.Do(initProxyUrl)
+ // Reading the setting here rather than on first use keeps the "proxy
+ // enabled" line in the startup log.
+ currentProxyUrl()
ProxyHttpClient = &http.Client{Transport: Transport()}
}
// Proxy picks the proxy to reach req through. It is meant to be assigned to
// http.Transport.Proxy, which calls it per request, so the httpProxy setting
is
-// read on first use instead of at package initialization: in -tags embed
builds
-// the embedded conf/app.conf is only loaded once main's init() runs, after
every
-// imported package has already initialized its own variables.
+// read on use instead of at package initialization: in -tags embed builds the
+// embedded conf/app.conf is only loaded once main's init() runs, after every
+// imported package has already initialized its own variables. Reading it per
+// request also means a proxy set on the Settings page applies straight away.
func Proxy(req *http.Request) (*url.URL, error) {
- proxyUrlOnce.Do(initProxyUrl)
- if proxyUrl == nil {
- return http.ProxyFromEnvironment(req)
+ if parsedUrl := currentProxyUrl(); parsedUrl != nil {
+ return parsedUrl, nil
}
- return proxyUrl, nil
+
+ return http.ProxyFromEnvironment(req)
}
// Transport returns the shared transport for outbound requests. Sharing one
@@ -66,14 +71,31 @@ func Transport() *http.Transport {
return transport
}
-// initProxyUrl parses the httpProxy setting. A bare "host:port" means SOCKS5,
-// which is what the setting has always been taken to mean; "socks5://",
-// "socks5h://", "http://" and "https://" addresses are honoured as written,
and
-// may carry credentials.
-func initProxyUrl() {
+// currentProxyUrl parses the httpProxy setting, re-parsing it only when the
+// setting itself has changed.
+func currentProxyUrl() *url.URL {
httpProxy :=
strings.TrimSpace(conf.GetConfigStringUnquoted("httpProxy"))
+
+ proxyLock.Lock()
+ defer proxyLock.Unlock()
+
+ if proxyRead && httpProxy == proxyRaw {
+ return proxyUrl
+ }
+
+ proxyRead = true
+ proxyRaw = httpProxy
+ proxyUrl = parseProxyUrl(httpProxy)
+ return proxyUrl
+}
+
+// parseProxyUrl reads a proxy address. A bare "host:port" means SOCKS5, which
+// is what the setting has always been taken to mean; "socks5://",
"socks5h://",
+// "http://" and "https://" addresses are honoured as written, and may carry
+// credentials.
+func parseProxyUrl(httpProxy string) *url.URL {
if httpProxy == "" {
- return
+ return nil
}
if !strings.Contains(httpProxy, "://") {
@@ -83,9 +105,9 @@ func initProxyUrl() {
parsedUrl, err := url.Parse(httpProxy)
if err != nil || parsedUrl.Host == "" {
fmt.Printf("httpProxy is not a valid proxy address, outbound
traffic is left unproxied: %s\n", httpProxy)
- return
+ return nil
}
- proxyUrl = parsedUrl
- fmt.Printf("Proxy enabled for outbound traffic: %s\n",
proxyUrl.Redacted())
+ fmt.Printf("Proxy enabled for outbound traffic: %s\n",
parsedUrl.Redacted())
+ return parsedUrl
}
diff --git a/version/check.go b/version/check.go
index e43ca61..680b4ea 100644
--- a/version/check.go
+++ b/version/check.go
@@ -15,15 +15,21 @@
package version
import (
+ "context"
"encoding/json"
+ "errors"
"fmt"
"io"
+ "net"
"net/http"
+ "net/url"
"regexp"
"runtime"
"strings"
"sync"
"time"
+
+ "github.com/apache/casbin-gateway/proxy"
)
const (
@@ -81,7 +87,7 @@ var (
cached *Release
cachedErr error
cachedAt time.Time
- httpClient = &http.Client{Timeout: checkTimeout}
+ httpClient = &http.Client{Timeout: checkTimeout, Transport:
proxy.Transport()}
reCommitSha = regexp.MustCompile(`\b[0-9a-f]{40}\b`)
)
@@ -153,6 +159,24 @@ func fetchLatestRelease() (*Release, error) {
return release, nil
}
+// IsNetworkError reports whether err is this machine failing to reach GitHub
+// rather than the update itself going wrong. Where GitHub is unreachable a
+// proxy is what fixes it, so the web UI says so instead of only quoting the
+// error.
+func IsNetworkError(err error) bool {
+ if err == nil {
+ return false
+ }
+
+ var netErr net.Error
+ var urlErr *url.Error
+ if errors.As(err, &netErr) || errors.As(err, &urlErr) {
+ return true
+ }
+
+ return errors.Is(err, context.DeadlineExceeded) || errors.Is(err,
io.ErrUnexpectedEOF)
+}
+
// releaseCommit finds the commit the nightly was built from. The release is
// created with --target <sha>, so target_commitish normally holds it; the
notes
// repeat it, which covers the case where GitHub hands back a branch name.
diff --git a/version/download.go b/version/download.go
index a22b949..0199320 100644
--- a/version/download.go
+++ b/version/download.go
@@ -30,6 +30,8 @@ import (
"sync"
"sync/atomic"
"time"
+
+ "github.com/apache/casbin-gateway/proxy"
)
// A single connection to the release CDN is throttled, and on some networks
@@ -109,7 +111,7 @@ func download(release *Release, path string) error {
func newDownloadClient() *http.Client {
return &http.Client{
Transport: &http.Transport{
- Proxy: http.ProxyFromEnvironment,
+ Proxy: proxy.Proxy,
DialContext: (&net.Dialer{Timeout:
connectTimeout, KeepAlive: 30 * time.Second}).DialContext,
TLSNextProto: map[string]func(string,
*tls.Conn) http.RoundTripper{},
TLSHandshakeTimeout: connectTimeout,
diff --git a/version/info.go b/version/info.go
index e1a5d00..16441bc 100644
--- a/version/info.go
+++ b/version/info.go
@@ -30,8 +30,10 @@ type Info struct {
// CheckError is a failed lookup rather than a failed update: the
version in
// hand is still worth showing.
CheckError string `json:"checkError"`
- Update Status `json:"update"`
- ReleaseUrl string `json:"releaseUrl"`
+ // CheckNetwork means the lookup failed because GitHub could not be
reached.
+ CheckNetwork bool `json:"checkNetwork"`
+ Update Status `json:"update"`
+ ReleaseUrl string `json:"releaseUrl"`
// InstallCommand is the manual way in, shown when CanUpdate is false.
InstallCommand string `json:"installCommand"`
}
@@ -52,6 +54,7 @@ func Describe(force bool) Info {
release, err := LatestRelease(force)
if err != nil {
info.CheckError = err.Error()
+ info.CheckNetwork = IsNetworkError(err)
return info
}
diff --git a/version/update.go b/version/update.go
index 3cc3b0b..dd06fee 100644
--- a/version/update.go
+++ b/version/update.go
@@ -55,6 +55,8 @@ type Status struct {
Total int64 `json:"total"`
Target string `json:"target"`
Error string `json:"error"`
+ // Network means the failure was reaching GitHub, which a proxy can fix.
+ Network bool `json:"network"`
}
// stagingDir holds the download and the unpacked executable. It sits next to
@@ -121,6 +123,7 @@ func fail(err error) {
status.Stage = StageFailed
status.Error = err.Error()
+ status.Network = IsNetworkError(err)
}
// BlockedReason says why this installation cannot replace its own executable,
diff --git a/web/src/components/shared/version-panel.tsx
b/web/src/components/shared/version-panel.tsx
index 75cd4b3..fb802c4 100644
--- a/web/src/components/shared/version-panel.tsx
+++ b/web/src/components/shared/version-panel.tsx
@@ -13,6 +13,7 @@
// limitations under the License.
import * as React from "react";
+import {Link} from "react-router-dom";
import {ArrowUpCircle, Check, CircleAlert, Copy, ExternalLink, Loader2,
RefreshCw} from "lucide-react";
import copy from "copy-to-clipboard";
import i18next from "i18next";
@@ -164,6 +165,8 @@ export function VersionPanel({
const [phase, setPhase] = React.useState<Phase>("idle");
const [status, setStatus] = React.useState<UpdateStatus | null>(null);
const [failure, setFailure] = React.useState("");
+ // A failure that is the network, not the update: a proxy is what fixes it.
+ const [failureNetwork, setFailureNetwork] = React.useState(false);
const load = React.useCallback((refresh: boolean) => {
setChecking(refresh);
@@ -202,6 +205,7 @@ export function VersionPanel({
setStatus(res.data);
if (res.data.stage === "failed") {
setFailure(res.data.error);
+ setFailureNetwork(res.data.network);
setPhase("idle");
} else if (res.data.stage === "restarting") {
setPhase("waiting");
@@ -236,6 +240,7 @@ export function VersionPanel({
stopped = true;
window.clearInterval(timer);
setFailure(i18next.t("general:The new version did not start, check the
Gateway log"));
+ setFailureNetwork(false);
setPhase("idle");
return;
}
@@ -249,6 +254,7 @@ export function VersionPanel({
stopped = true;
window.clearInterval(timer);
setFailure(res.data.error);
+ setFailureNetwork(res.data.network);
setPhase("idle");
return;
}
@@ -271,6 +277,7 @@ export function VersionPanel({
const startUpdate = () => {
setFailure("");
+ setFailureNetwork(false);
setStatus(null);
setPhase("starting");
MiscBackend.updateGateway()
@@ -396,6 +403,15 @@ export function VersionPanel({
</p>
) : null}
+ {failureNetwork || (failure === "" && info.checkNetwork) ? (
+ <p className="text-muted-foreground text-xs">
+ {i18next.t("general:GitHub could not be reached, if this machine
needs a proxy to reach the internet, set one and try again")}{" "}
+ <Link to="/settings" onClick={() => setOpen(false)}
className="text-primary underline underline-offset-2">
+ {i18next.t("general:Set a proxy")}
+ </Link>
+ </p>
+ ) : null}
+
{busy ? (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
diff --git a/web/src/locales/en/data.json b/web/src/locales/en/data.json
index 2c7ff9f..a1d843c 100644
--- a/web/src/locales/en/data.json
+++ b/web/src/locales/en/data.json
@@ -354,6 +354,7 @@
"Failed to save": "Failed to save",
"Failed to sign out": "Failed to sign out",
"Gateway Analytics": "Gateway Analytics",
+ "GitHub could not be reached, if this machine needs a proxy to reach the
internet, set one and try again": "GitHub could not be reached, if this machine
needs a proxy to reach the internet, set one and try again",
"Go to writable demo site?": "Go to writable demo site?",
"Half A Hour": "Half A Hour",
"Home": "Home",
@@ -398,6 +399,7 @@
"Search": "Search",
"Select": "Select",
"Self": "Self",
+ "Set a proxy": "Set a proxy",
"Signed out successfully": "Signed out successfully",
"Signing in...": "Signing in...",
"Sites": "Sites",
@@ -716,7 +718,7 @@
"Max records": "Max records",
"Network and certificates": "Network and certificates",
"Outbound SOCKS5 proxy": "Outbound SOCKS5 proxy",
- "Outbound SOCKS5 proxy hint": "\"host:port\", used for outbound traffic
such as ACME.",
+ "Outbound SOCKS5 proxy hint": "\"host:port\", used for outbound traffic
such as update downloads and ACME.",
"Page description": "Everything Casbin Gateway reads out of its
configuration. Changes are stored in the database and applied at once.",
"Pricing file": "Pricing file",
"Pricing file hint": "Optional JSON file of token prices in US dollars per
million, overriding the built-in list prices.",
diff --git a/web/src/locales/zh/data.json b/web/src/locales/zh/data.json
index 82d8c43..374cea4 100644
--- a/web/src/locales/zh/data.json
+++ b/web/src/locales/zh/data.json
@@ -354,6 +354,7 @@
"Failed to save": "保存失败",
"Failed to sign out": "退出登录失败",
"Gateway Analytics": "网关统计",
+ "GitHub could not be reached, if this machine needs a proxy to reach the
internet, set one and try again": "无法连接 GitHub。如果本机需要代理才能访问外网,请先配置代理后重试。",
"Go to writable demo site?": "跳转至可写演示站点?",
"Half A Hour": "半小时",
"Home": "首页",
@@ -398,6 +399,7 @@
"Search": "搜索",
"Select": "请选择",
"Self": "自己",
+ "Set a proxy": "去配置代理",
"Signed out successfully": "已退出登录",
"Signing in...": "正在登录...",
"Sites": "站点",
@@ -716,7 +718,7 @@
"Max records": "最大记录条数",
"Network and certificates": "网络与证书",
"Outbound SOCKS5 proxy": "出站 SOCKS5 代理",
- "Outbound SOCKS5 proxy hint": "形如 \"host:port\",用于 ACME 等出站流量。",
+ "Outbound SOCKS5 proxy hint": "形如 \"host:port\",用于升级下载、ACME 等出站流量。",
"Page description": "Casbin Gateway 的全部配置项。修改会保存到数据库并立即生效。",
"Pricing file": "价格文件",
"Pricing file hint": "可选的 JSON 文件,按每百万 Token 的美元价格覆盖内置价目表。",
diff --git a/web/src/types.ts b/web/src/types.ts
index 8059294..499acb6 100644
--- a/web/src/types.ts
+++ b/web/src/types.ts
@@ -668,6 +668,8 @@ export interface UpdateStatus {
total: number;
target: string;
error: string;
+ /** The failure was reaching GitHub, which a proxy can fix. */
+ network: boolean;
}
export interface VersionInfo {
@@ -679,6 +681,8 @@ export interface VersionInfo {
blocked: string;
/** A failed lookup, which leaves the current version worth showing anyway.
*/
checkError: string;
+ /** The lookup failed because GitHub could not be reached. */
+ checkNetwork: boolean;
update: UpdateStatus;
releaseUrl: string;
installCommand: string;
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]