Compare commits

..

22 Commits
0.7.4 ... 0.7.6

Author SHA1 Message Date
yusing
ba26e6a5d6 grafana dashboard template 2024-11-10 06:53:31 +08:00
yusing
6194bac4c4 metric unregistration on route removal, fixed multi-ips as visitor label detected from x headers 2024-11-10 06:47:59 +08:00
yusing
a1d1325ad6 updated health status impl 2024-11-10 06:35:56 +08:00
yusing
cceebff93a fixed CIDR whitelist shared its IP cache map when it should not 2024-11-10 03:25:33 +08:00
yusing
f97e3f65fe go version and deps update, fixed middlewares and metrics
- fixed "API JWT secret empty" warning output format
- fixed metrics initialized when it should not
- fixed middlewares.modifyRequest Host header not working properly
2024-11-08 06:14:08 +08:00
yusing
5214ae1760 uptime metrics 2024-11-07 11:44:01 +08:00
yusing
6be3aef367 changed req time elapsed to count on status code sent 2024-11-07 11:43:49 +08:00
yusing
6712e9b109 initial prometheus metrics support, simplfied some code 2024-11-06 12:24:12 +08:00
yusing
50a0686648 rebrand update Makefile 2024-11-06 05:06:17 +08:00
yusing
d47afa3081 removed extra ` from README 2024-11-06 05:05:46 +08:00
Yuzerion
1ddfe2fb92 Merge pull request #30 from codekoala/update-setup-instructions
Update setup instructions
2024-11-05 23:52:13 +08:00
Josh VanderLinden
3ae3d18566 Update setup instructions 2024-11-05 08:49:33 -05:00
yusing
5fdb171d65 rebrand changed startup message, built script and Dockerfile 2024-11-04 03:47:37 +08:00
yusing
99e43fe340 (non-breaking) rebrand changed environment variables 2024-11-04 03:29:26 +08:00
yusing
cf1ecbc826 added option to disable default app categories 2024-11-04 01:44:58 +08:00
yusing
5ff27b9e3d fixed extra output for ls-* commands 2024-11-04 01:32:26 +08:00
yusing
291304af75 readme update 2024-11-04 00:44:54 +08:00
yusing
b63ebfcb3b disabled auth by default (when no JWT secret is specified) 2024-11-04 00:32:19 +08:00
yusing
c6a9a816f6 copied default config into docker image, fixed ls-routes 2024-11-04 00:31:34 +08:00
yusing
f5cf716a91 rebrand as GoDoxy 2024-11-04 00:06:59 +08:00
yusing
6dbee61742 fixed wrong closing tag in example error page 2024-11-03 23:45:23 +08:00
yusing
d89d97b61f added predefined homepage categories 2024-11-03 11:44:30 +08:00
51 changed files with 1783 additions and 276 deletions

View File

@@ -2,21 +2,24 @@
TZ=ETC/UTC
# generate secret with `openssl rand -base64 32`
GOPROXY_API_JWT_SECRET=
GODOXY_API_JWT_SECRET=
# the JWT token time-to-live
GOPROXY_API_JWT_TOKEN_TTL=1h
GODOXY_API_JWT_TOKEN_TTL=1h
# API/WebUI login credentials
GOPROXY_API_USER=admin
GOPROXY_API_PASSWORD=password
GODOXY_API_USER=admin
GODOXY_API_PASSWORD=password
# Proxy listening address
GOPROXY_HTTP_ADDR=:80
GOPROXY_HTTPS_ADDR=:443
GODOXY_HTTP_ADDR=:80
GODOXY_HTTPS_ADDR=:443
# API listening address
GOPROXY_API_ADDR=127.0.0.1:8888
GODOXY_API_ADDR=127.0.0.1:8888
# Prometheus Metrics listening address (uncomment to enable)
#GODOXY_PROMETHEUS_ADDR=:8889
# Debug mode
GOPROXY_DEBUG=false
GODOXY_DEBUG=false

View File

@@ -17,6 +17,9 @@ runtimes:
- go@1.23.2
# This is the section where you manage your linters. (https://docs.trunk.io/check/configuration)
lint:
disabled:
- markdownlint
- yamllint
enabled:
- hadolint@2.12.0
- actionlint@1.7.3
@@ -24,14 +27,12 @@ lint:
- git-diff-check
- gofmt@1.20.4
- golangci-lint@1.61.0
- markdownlint@0.42.0
- osv-scanner@1.9.0
- oxipng@9.1.2
- prettier@3.3.3
- shellcheck@0.10.0
- shfmt@3.6.0
- trufflehog@3.82.7
- yamllint@1.35.1
actions:
disabled:
- trunk-announce

View File

@@ -1,5 +1,5 @@
# Stage 1: Builder
FROM golang:1.23.2-alpine AS builder
FROM golang:1.23.3-alpine AS builder
RUN apk add --no-cache tzdata make
WORKDIR /src
@@ -26,7 +26,7 @@ RUN --mount=type=cache,target="/go/pkg/mod" \
--mount=type=bind,src=pkg,dst=/src/pkg \
make build && \
mkdir -p /app/error_pages /app/certs && \
mv bin/go-proxy /app/go-proxy
mv bin/godoxy /app/godoxy
# Stage 2: Final image
FROM scratch
@@ -43,11 +43,14 @@ COPY --from=builder /app /app
# copy schema directory
COPY schema/ /app/schema/
# copy example config
COPY config.example.yml /app/config/config.yml
# copy certs
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
ENV DOCKER_HOST=unix:///var/run/docker.sock
ENV GOPROXY_DEBUG=0
ENV GODOXY_DEBUG=0
EXPOSE 80
EXPOSE 8888
@@ -55,4 +58,4 @@ EXPOSE 443
WORKDIR /app
CMD ["/app/go-proxy"]
CMD ["/app/godoxy"]

View File

@@ -13,7 +13,7 @@ build:
scripts/build.sh
test:
GOPROXY_TEST=1 go test ./internal/...
GODOXY_TEST=1 go test ./internal/...
up:
docker compose up -d
@@ -28,20 +28,20 @@ get:
go get -u ./cmd && go mod tidy
debug:
GOPROXY_DEBUG=1 make run
GODOXY_DEBUG=1 make run
debug-trace:
GOPROXY_DEBUG=1 GOPROXY_TRACE=1 run
GODOXY_DEBUG=1 GODOXY_TRACE=1 run
profile:
GODEBUG=gctrace=1 make debug
run: build
sudo setcap CAP_NET_BIND_SERVICE=+eip bin/go-proxy
bin/go-proxy
sudo setcap CAP_NET_BIND_SERVICE=+eip bin/godoxy
bin/godoxy
mtrace:
bin/go-proxy debug-ls-mtrace > mtrace.json
bin/godoxy debug-ls-mtrace > mtrace.json
archive:
git archive HEAD -o ../go-proxy-$$(date +"%Y%m%d%H%M").zip

View File

@@ -1,4 +1,4 @@
# go-proxy
# GoDoxy
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=yusing_go-proxy&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
[![Lines of Code](https://sonarcloud.io/api/project_badges/measure?project=yusing_go-proxy&metric=ncloc)](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
@@ -19,10 +19,11 @@ _Join our [Discord](https://discord.gg/umReR62nRd) for help and discussions_
<!-- TOC -->
- [go-proxy](#go-proxy)
- [GoDoxy](#godoxy)
- [Table of content](#table-of-content)
- [Key Features](#key-features)
- [Getting Started](#getting-started)
- [Prerequisites](#prerequisites)
- [Setup](#setup)
- [Manual Setup](#manual-setup)
- [Folder structrue](#folder-structrue)
@@ -53,9 +54,16 @@ _Join our [Discord](https://discord.gg/umReR62nRd) for help and discussions_
## Getting Started
### Prerequisites
Setup DNS Records point to machine which runs `GoDoxy`, e.g.
- A Record: `*.y.z` -> `10.0.10.1`
- AAAA Record: `*.y.z` -> `::ffff:a00:a01`
### Setup
1. Pull docker image
1. Pull the latest docker images
```shell
docker pull ghcr.io/yusing/go-proxy:latest
@@ -64,21 +72,25 @@ _Join our [Discord](https://discord.gg/umReR62nRd) for help and discussions_
2. Create new directory, `cd` into it, then run setup, or [set up manually](#manual-setup)
```shell
docker run --rm -v .:/setup ghcr.io/yusing/go-proxy /app/go-proxy setup
# Then set the JWT secret
sed -i "s|GOPROXY_API_JWT_SECRET=.*|GOPROXY_API_JWT_SECRET=$(openssl rand -base64 32)|g" .env
docker run --rm -v .:/setup ghcr.io/yusing/go-proxy /app/godoxy setup
```
3. Setup DNS Records point to machine which runs `go-proxy`, e.g.
3. _(Optional)_ setup WebUI login
- A Record: `*.y.z` -> `10.0.10.1`
- AAAA Record: `*.y.z` -> `::ffff:a00:a01`
- set random JWT secret
```shell
sed -i "s|API_JWT_SECRET=.*|API_JWT_SECRET=$(openssl rand -base64 32)|g" .env
```
4. Setup `docker-socket-proxy` other docker nodes _(if any)_ (see [Multi docker nodes setup](https://github.com/yusing/go-proxy/wiki/Configurations#multi-docker-nodes-setup)) and then them inside `config.yml`
- change username and password for WebUI authentication
```shell
sed -i "s|API_USERNAME=.*|API_USERNAME=admin|g" .env
sed -i "s|API_PASSWORD=.*|API_PASSWORD=some-strong-password|g" .env
```
5. Run go-proxy `docker compose up -d`
then list all routes to see if further configurations are needed:
`docker exec go-proxy /app/go-proxy ls-routes`
4. _(Optional)_ setup `docker-socket-proxy` other docker nodes (see [Multi docker nodes setup](https://github.com/yusing/go-proxy/wiki/Configurations#multi-docker-nodes-setup)) then add them inside `config.yml`
5. Start the container `docker compose up -d`
6. You may now do some extra configuration
- With text editor (e.g. Visual Studio Code)
@@ -101,12 +113,6 @@ _Join our [Discord](https://discord.gg/umReR62nRd) for help and discussions_
`wget https://raw.githubusercontent.com/yusing/go-proxy/v0.7/compose.example.yml -O compose.yml`
4. Set the JWT secret
`sed -i "s|GOPROXY_API_JWT_SECRET=.*|GOPROXY_API_JWT_SECRET=$(openssl rand -base64 32)|g" .env`
5. Start the container `docker compose up -d`
### Folder structrue
```shell

View File

@@ -16,6 +16,7 @@ import (
"github.com/yusing/go-proxy/internal/config"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/metrics"
"github.com/yusing/go-proxy/internal/net/http/middleware"
R "github.com/yusing/go-proxy/internal/route"
"github.com/yusing/go-proxy/internal/server"
@@ -48,11 +49,10 @@ func main() {
if err != nil {
log.Printf("failed to connect to api server: %s", err)
log.Printf("falling back to config file")
printJSON(config.RoutesByAlias())
} else {
printJSON(routes)
return
}
return
case common.CommandDebugListMTrace:
trace, err := query.ListMiddlewareTraces()
if err != nil {
@@ -63,7 +63,7 @@ func main() {
}
if args.Command == common.CommandStart {
logging.Info().Msgf("go-proxy version %s", pkg.GetVersion())
logging.Info().Msgf("GoDoxy version %s", pkg.GetVersion())
logging.Trace().Msg("trace enabled")
// logging.AddHook(notif.GetDispatcher())
} else {
@@ -95,6 +95,10 @@ func main() {
}
switch args.Command {
case common.CommandListRoutes:
cfg.StartProxyProviders()
printJSON(config.RoutesByAlias())
return
case common.CommandListConfigs:
printJSON(config.Value())
return
@@ -106,6 +110,10 @@ func main() {
return
}
if common.APIJWTSecret == nil {
logging.Warn().Msg("API JWT secret is empty, authentication is disabled")
}
cfg.StartProxyProviders()
config.WatchChanges()
@@ -123,7 +131,7 @@ func main() {
logging.Info().Msg("autocert not configured")
}
proxyServer := server.InitProxyServer(server.Options{
server.StartServer(server.Options{
Name: "proxy",
CertProvider: autocert,
HTTPAddr: common.ProxyHTTPAddr,
@@ -131,7 +139,7 @@ func main() {
Handler: http.HandlerFunc(R.ProxyHandler),
RedirectToHTTPS: config.Value().RedirectToHTTPS,
})
apiServer := server.InitAPIServer(server.Options{
server.StartServer(server.Options{
Name: "api",
CertProvider: autocert,
HTTPAddr: common.APIHTTPAddr,
@@ -139,8 +147,15 @@ func main() {
RedirectToHTTPS: config.Value().RedirectToHTTPS,
})
proxyServer.Start()
apiServer.Start()
if common.PrometheusEnabled {
server.StartServer(server.Options{
Name: "metrics",
CertProvider: autocert,
HTTPAddr: common.MetricsHTTPAddr,
Handler: metrics.NewHandler(),
RedirectToHTTPS: config.Value().RedirectToHTTPS,
})
}
// wait for signal
<-sig
@@ -165,5 +180,5 @@ func printJSON(obj any) {
logging.Fatal().Err(err).Send()
}
rawLogger := log.New(os.Stdout, "", 0)
rawLogger.Printf("%s", j) // raw output for convenience using "jq"
rawLogger.Print(string(j)) // raw output for convenience using "jq"
}

View File

@@ -2,7 +2,7 @@
services:
frontend:
image: ghcr.io/yusing/go-proxy-frontend:latest
container_name: go-proxy-frontend
container_name: godoxy-frontend
restart: unless-stopped
network_mode: host
env_file: .env
@@ -21,7 +21,7 @@ services:
- 172.16.0.0/12
app:
image: ghcr.io/yusing/go-proxy:latest
container_name: go-proxy
container_name: godoxy
restart: always
network_mode: host
env_file: .env

View File

@@ -57,13 +57,19 @@ providers:
# - my.site
# - node1.my.app
# homepage config
#
homepage:
# use default app categories detected from alias or docker image name
use_default_categories: true
# Below are fixed options (non hot-reloadable)
# timeout for shutdown (in seconds)
#
# timeout_shutdown: 5
timeout_shutdown: 5
# global setting redirect http requests to https (if https available, otherwise this will be ignored)
# proxy.<alias>.middlewares.redirect_http will override this
#
# redirect_to_https: false
redirect_to_https: false

View File

@@ -1,42 +1,51 @@
{{/* Credit: https://codepen.io/code2rithik/pen/XWpVvYL */}}
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Page Not Found</title>
<link rel="stylesheet" href="/$gperrorpage/404.css" type="text/css">
{{/* <script src="/$gperrorpage/404.js"> */}}
</head>
<body>
<script>0</script>
<div></div>
<svg id="svgWrap_2" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 700 250">
<g>
<path id="id3_2" d="M195.7 232.67h-37.1V149.7H27.76c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98H158.6V29.62h37.1v203.05z"/>
<path id="id2_2" d="M470.69 147.71c0 8.31-1.06 16.17-3.19 23.58-2.12 7.41-5.12 14.28-8.99 20.6-3.87 6.33-8.45 11.99-13.74 16.99-5.29 5-11.07 9.28-17.35 12.81a85.146 85.146 0 0 1-20.04 8.14 83.637 83.637 0 0 1-21.67 2.83H319.3c-7.46 0-14.73-.94-21.81-2.83-7.08-1.89-13.76-4.6-20.04-8.14a88.292 88.292 0 0 1-17.35-12.81c-5.29-5-9.84-10.67-13.66-16.99-3.82-6.32-6.8-13.19-8.92-20.6-2.12-7.41-3.19-15.27-3.19-23.58v-33.13c0-12.46 2.34-23.88 7.01-34.27 4.67-10.38 10.92-19.33 18.76-26.83 7.83-7.5 16.87-13.36 27.12-17.56 10.24-4.2 20.93-6.3 32.07-6.3h66.41c7.36 0 14.58.94 21.67 2.83 7.08 1.89 13.76 4.6 20.04 8.14a88.292 88.292 0 0 1 17.35 12.81c5.29 5 9.86 10.67 13.74 16.99 3.87 6.33 6.87 13.19 8.99 20.6 2.13 7.41 3.19 15.27 3.19 23.58v33.14zm-37.1-33.13c0-7.27-1.32-13.88-3.96-19.82-2.64-5.95-6.16-11.04-10.55-15.29-4.39-4.25-9.46-7.5-15.22-9.77-5.76-2.27-11.8-3.35-18.13-3.26h-66.41c-6.14-.09-12.11.97-17.91 3.19-5.81 2.22-10.95 5.43-15.44 9.63-4.48 4.2-8.07 9.3-10.76 15.29-2.69 6-4.04 12.67-4.04 20.04v33.13c0 7.36 1.32 14.02 3.96 19.97 2.64 5.95 6.18 11.02 10.62 15.22 4.44 4.2 9.56 7.43 15.36 9.7 5.8 2.27 11.87 3.35 18.2 3.26h66.41c7.27 0 13.85-1.2 19.75-3.61s10.93-5.73 15.08-9.98 7.36-9.32 9.63-15.22c2.27-5.9 3.4-12.34 3.4-19.33v-33.15zm-16-26.91a17.89 17.89 0 0 1 2.83 6.73c.47 2.41.47 4.77 0 7.08-.47 2.31-1.39 4.48-2.76 6.51-1.37 2.03-3.14 3.75-5.31 5.17l-99.4 66.41c-1.61 1.23-3.26 2.08-4.96 2.55-1.7.47-3.45.71-5.24.71-3.02 0-5.9-.71-8.64-2.12-2.74-1.42-4.96-3.44-6.66-6.09a17.89 17.89 0 0 1-2.83-6.73c-.47-2.41-.5-4.77-.07-7.08.43-2.31 1.3-4.48 2.62-6.51 1.32-2.03 3.07-3.75 5.24-5.17l99.69-66.41a17.89 17.89 0 0 1 6.73-2.83c2.41-.47 4.77-.47 7.08 0 2.31.47 4.48 1.37 6.51 2.69 2.03 1.32 3.75 3.02 5.17 5.09z"/>
<path id="id1_2" d="M688.33 232.67h-37.1V149.7H520.39c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98h112.57V29.62h37.1v203.05z"/>
</g>
</svg>
<svg id="svgWrap_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 700 250">
<g>
<path id="id3_1" d="M195.7 232.67h-37.1V149.7H27.76c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98H158.6V29.62h37.1v203.05z"/>
<path id="id2_1" d="M470.69 147.71c0 8.31-1.06 16.17-3.19 23.58-2.12 7.41-5.12 14.28-8.99 20.6-3.87 6.33-8.45 11.99-13.74 16.99-5.29 5-11.07 9.28-17.35 12.81a85.146 85.146 0 0 1-20.04 8.14 83.637 83.637 0 0 1-21.67 2.83H319.3c-7.46 0-14.73-.94-21.81-2.83-7.08-1.89-13.76-4.6-20.04-8.14a88.292 88.292 0 0 1-17.35-12.81c-5.29-5-9.84-10.67-13.66-16.99-3.82-6.32-6.8-13.19-8.92-20.6-2.12-7.41-3.19-15.27-3.19-23.58v-33.13c0-12.46 2.34-23.88 7.01-34.27 4.67-10.38 10.92-19.33 18.76-26.83 7.83-7.5 16.87-13.36 27.12-17.56 10.24-4.2 20.93-6.3 32.07-6.3h66.41c7.36 0 14.58.94 21.67 2.83 7.08 1.89 13.76 4.6 20.04 8.14a88.292 88.292 0 0 1 17.35 12.81c5.29 5 9.86 10.67 13.74 16.99 3.87 6.33 6.87 13.19 8.99 20.6 2.13 7.41 3.19 15.27 3.19 23.58v33.14zm-37.1-33.13c0-7.27-1.32-13.88-3.96-19.82-2.64-5.95-6.16-11.04-10.55-15.29-4.39-4.25-9.46-7.5-15.22-9.77-5.76-2.27-11.8-3.35-18.13-3.26h-66.41c-6.14-.09-12.11.97-17.91 3.19-5.81 2.22-10.95 5.43-15.44 9.63-4.48 4.2-8.07 9.3-10.76 15.29-2.69 6-4.04 12.67-4.04 20.04v33.13c0 7.36 1.32 14.02 3.96 19.97 2.64 5.95 6.18 11.02 10.62 15.22 4.44 4.2 9.56 7.43 15.36 9.7 5.8 2.27 11.87 3.35 18.2 3.26h66.41c7.27 0 13.85-1.2 19.75-3.61s10.93-5.73 15.08-9.98 7.36-9.32 9.63-15.22c2.27-5.9 3.4-12.34 3.4-19.33v-33.15zm-16-26.91a17.89 17.89 0 0 1 2.83 6.73c.47 2.41.47 4.77 0 7.08-.47 2.31-1.39 4.48-2.76 6.51-1.37 2.03-3.14 3.75-5.31 5.17l-99.4 66.41c-1.61 1.23-3.26 2.08-4.96 2.55-1.7.47-3.45.71-5.24.71-3.02 0-5.9-.71-8.64-2.12-2.74-1.42-4.96-3.44-6.66-6.09a17.89 17.89 0 0 1-2.83-6.73c-.47-2.41-.5-4.77-.07-7.08.43-2.31 1.3-4.48 2.62-6.51 1.32-2.03 3.07-3.75 5.24-5.17l99.69-66.41a17.89 17.89 0 0 1 6.73-2.83c2.41-.47 4.77-.47 7.08 0 2.31.47 4.48 1.37 6.51 2.69 2.03 1.32 3.75 3.02 5.17 5.09z"/>
<path id="id1_1" d="M688.33 232.67h-37.1V149.7H520.39c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98h112.57V29.62h37.1v203.05z"/>
</g>
</svg>
<html lang="en">
<svg>
<defs>
<filter id="glow">
<fegaussianblur class="blur" result="coloredBlur" stddeviation="4"></fegaussianblur>
<femerge>
<femergenode in="coloredBlur"></femergenode>
<femergenode in="SourceGraphic"></femergenode>
</femerge>
</filter>
</defs>
</svg>
<head>
<meta charset="UTF-8" />
<title>Page Not Found</title>
<link rel="stylesheet" href="/$gperrorpage/404.css" type="text/css">
<!-- <script src="/$gperrorpage/404.js"> </script> -->
</head>
<h2>Page Not Found</h2>
</body>
</html>
<body>
<script>0</script>
<div></div>
<svg id="svgWrap_2" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 700 250">
<g>
<path id="id3_2"
d="M195.7 232.67h-37.1V149.7H27.76c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98H158.6V29.62h37.1v203.05z" />
<path id="id2_2"
d="M470.69 147.71c0 8.31-1.06 16.17-3.19 23.58-2.12 7.41-5.12 14.28-8.99 20.6-3.87 6.33-8.45 11.99-13.74 16.99-5.29 5-11.07 9.28-17.35 12.81a85.146 85.146 0 0 1-20.04 8.14 83.637 83.637 0 0 1-21.67 2.83H319.3c-7.46 0-14.73-.94-21.81-2.83-7.08-1.89-13.76-4.6-20.04-8.14a88.292 88.292 0 0 1-17.35-12.81c-5.29-5-9.84-10.67-13.66-16.99-3.82-6.32-6.8-13.19-8.92-20.6-2.12-7.41-3.19-15.27-3.19-23.58v-33.13c0-12.46 2.34-23.88 7.01-34.27 4.67-10.38 10.92-19.33 18.76-26.83 7.83-7.5 16.87-13.36 27.12-17.56 10.24-4.2 20.93-6.3 32.07-6.3h66.41c7.36 0 14.58.94 21.67 2.83 7.08 1.89 13.76 4.6 20.04 8.14a88.292 88.292 0 0 1 17.35 12.81c5.29 5 9.86 10.67 13.74 16.99 3.87 6.33 6.87 13.19 8.99 20.6 2.13 7.41 3.19 15.27 3.19 23.58v33.14zm-37.1-33.13c0-7.27-1.32-13.88-3.96-19.82-2.64-5.95-6.16-11.04-10.55-15.29-4.39-4.25-9.46-7.5-15.22-9.77-5.76-2.27-11.8-3.35-18.13-3.26h-66.41c-6.14-.09-12.11.97-17.91 3.19-5.81 2.22-10.95 5.43-15.44 9.63-4.48 4.2-8.07 9.3-10.76 15.29-2.69 6-4.04 12.67-4.04 20.04v33.13c0 7.36 1.32 14.02 3.96 19.97 2.64 5.95 6.18 11.02 10.62 15.22 4.44 4.2 9.56 7.43 15.36 9.7 5.8 2.27 11.87 3.35 18.2 3.26h66.41c7.27 0 13.85-1.2 19.75-3.61s10.93-5.73 15.08-9.98 7.36-9.32 9.63-15.22c2.27-5.9 3.4-12.34 3.4-19.33v-33.15zm-16-26.91a17.89 17.89 0 0 1 2.83 6.73c.47 2.41.47 4.77 0 7.08-.47 2.31-1.39 4.48-2.76 6.51-1.37 2.03-3.14 3.75-5.31 5.17l-99.4 66.41c-1.61 1.23-3.26 2.08-4.96 2.55-1.7.47-3.45.71-5.24.71-3.02 0-5.9-.71-8.64-2.12-2.74-1.42-4.96-3.44-6.66-6.09a17.89 17.89 0 0 1-2.83-6.73c-.47-2.41-.5-4.77-.07-7.08.43-2.31 1.3-4.48 2.62-6.51 1.32-2.03 3.07-3.75 5.24-5.17l99.69-66.41a17.89 17.89 0 0 1 6.73-2.83c2.41-.47 4.77-.47 7.08 0 2.31.47 4.48 1.37 6.51 2.69 2.03 1.32 3.75 3.02 5.17 5.09z" />
<path id="id1_2"
d="M688.33 232.67h-37.1V149.7H520.39c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98h112.57V29.62h37.1v203.05z" />
</g>
</svg>
<svg id="svgWrap_1" xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" viewBox="0 0 700 250">
<g>
<path id="id3_1"
d="M195.7 232.67h-37.1V149.7H27.76c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98H158.6V29.62h37.1v203.05z" />
<path id="id2_1"
d="M470.69 147.71c0 8.31-1.06 16.17-3.19 23.58-2.12 7.41-5.12 14.28-8.99 20.6-3.87 6.33-8.45 11.99-13.74 16.99-5.29 5-11.07 9.28-17.35 12.81a85.146 85.146 0 0 1-20.04 8.14 83.637 83.637 0 0 1-21.67 2.83H319.3c-7.46 0-14.73-.94-21.81-2.83-7.08-1.89-13.76-4.6-20.04-8.14a88.292 88.292 0 0 1-17.35-12.81c-5.29-5-9.84-10.67-13.66-16.99-3.82-6.32-6.8-13.19-8.92-20.6-2.12-7.41-3.19-15.27-3.19-23.58v-33.13c0-12.46 2.34-23.88 7.01-34.27 4.67-10.38 10.92-19.33 18.76-26.83 7.83-7.5 16.87-13.36 27.12-17.56 10.24-4.2 20.93-6.3 32.07-6.3h66.41c7.36 0 14.58.94 21.67 2.83 7.08 1.89 13.76 4.6 20.04 8.14a88.292 88.292 0 0 1 17.35 12.81c5.29 5 9.86 10.67 13.74 16.99 3.87 6.33 6.87 13.19 8.99 20.6 2.13 7.41 3.19 15.27 3.19 23.58v33.14zm-37.1-33.13c0-7.27-1.32-13.88-3.96-19.82-2.64-5.95-6.16-11.04-10.55-15.29-4.39-4.25-9.46-7.5-15.22-9.77-5.76-2.27-11.8-3.35-18.13-3.26h-66.41c-6.14-.09-12.11.97-17.91 3.19-5.81 2.22-10.95 5.43-15.44 9.63-4.48 4.2-8.07 9.3-10.76 15.29-2.69 6-4.04 12.67-4.04 20.04v33.13c0 7.36 1.32 14.02 3.96 19.97 2.64 5.95 6.18 11.02 10.62 15.22 4.44 4.2 9.56 7.43 15.36 9.7 5.8 2.27 11.87 3.35 18.2 3.26h66.41c7.27 0 13.85-1.2 19.75-3.61s10.93-5.73 15.08-9.98 7.36-9.32 9.63-15.22c2.27-5.9 3.4-12.34 3.4-19.33v-33.15zm-16-26.91a17.89 17.89 0 0 1 2.83 6.73c.47 2.41.47 4.77 0 7.08-.47 2.31-1.39 4.48-2.76 6.51-1.37 2.03-3.14 3.75-5.31 5.17l-99.4 66.41c-1.61 1.23-3.26 2.08-4.96 2.55-1.7.47-3.45.71-5.24.71-3.02 0-5.9-.71-8.64-2.12-2.74-1.42-4.96-3.44-6.66-6.09a17.89 17.89 0 0 1-2.83-6.73c-.47-2.41-.5-4.77-.07-7.08.43-2.31 1.3-4.48 2.62-6.51 1.32-2.03 3.07-3.75 5.24-5.17l99.69-66.41a17.89 17.89 0 0 1 6.73-2.83c2.41-.47 4.77-.47 7.08 0 2.31.47 4.48 1.37 6.51 2.69 2.03 1.32 3.75 3.02 5.17 5.09z" />
<path id="id1_1"
d="M688.33 232.67h-37.1V149.7H520.39c-2.64 0-5.1-.5-7.36-1.49-2.27-.99-4.23-2.31-5.88-3.96-1.65-1.65-2.95-3.61-3.89-5.88s-1.42-4.67-1.42-7.22V29.62h36.82v82.98h112.57V29.62h37.1v203.05z" />
</g>
</svg>
<svg>
<defs>
<filter id="glow">
<fegaussianblur class="blur" result="coloredBlur" stddeviation="4"></fegaussianblur>
<femerge>
<femergenode in="coloredBlur"></femergenode>
<femergenode in="SourceGraphic"></femergenode>
</femerge>
</filter>
</defs>
</svg>
<h2>Page Not Found</h2>
</body>
</html>

File diff suppressed because it is too large Load Diff

16
go.mod
View File

@@ -1,15 +1,16 @@
module github.com/yusing/go-proxy
go 1.23.2
go 1.23.3
require (
github.com/coder/websocket v1.8.12
github.com/docker/cli v27.3.1+incompatible
github.com/docker/docker v27.3.1+incompatible
github.com/fsnotify/fsnotify v1.7.0
github.com/fsnotify/fsnotify v1.8.0
github.com/go-acme/lego/v4 v4.19.2
github.com/golang-jwt/jwt/v5 v5.2.1
github.com/gotify/server/v2 v2.5.0
github.com/prometheus/client_golang v1.20.5
github.com/puzpuzpuz/xsync/v3 v3.4.0
github.com/rs/zerolog v1.33.0
github.com/santhosh-tekuri/jsonschema v1.2.4
@@ -21,8 +22,10 @@ require (
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cloudflare/cloudflare-go v0.108.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cloudflare/cloudflare-go v0.109.0 // indirect
github.com/containerd/log v0.1.0 // indirect
github.com/distribution/reference v0.6.0 // indirect
github.com/docker/go-connections v0.5.0 // indirect
@@ -34,17 +37,21 @@ require (
github.com/goccy/go-json v0.10.3 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/kr/pretty v0.3.1 // indirect
github.com/klauspost/compress v1.17.11 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/miekg/dns v1.1.62 // indirect
github.com/moby/docker-image-spec v1.3.1 // indirect
github.com/moby/term v0.5.0 // indirect
github.com/morikuni/aec v1.0.0 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/opencontainers/image-spec v1.1.0 // indirect
github.com/ovh/go-ovh v1.6.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.60.1 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/rogpeppe/go-internal v1.13.1 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.56.0 // indirect
@@ -59,6 +66,7 @@ require (
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/tools v0.26.0 // indirect
google.golang.org/protobuf v1.35.1 // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gotest.tools/v3 v3.5.1 // indirect
)

33
go.sum
View File

@@ -2,16 +2,19 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cloudflare/cloudflare-go v0.108.0 h1:C4Skfjd8I8X3uEOGmQUT4/iGyZcWdkIU7HwvMoLkEE0=
github.com/cloudflare/cloudflare-go v0.108.0/go.mod h1:m492eNahT/9MsN7Ppnoge8AaI7QhVFtEgVm3I9HJFeU=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cloudflare/cloudflare-go v0.109.0 h1:Wjp+RfJD1lidIFUlrTBqUQnCBrUnmVsLxgzWYiURueg=
github.com/cloudflare/cloudflare-go v0.109.0/go.mod h1:m492eNahT/9MsN7Ppnoge8AaI7QhVFtEgVm3I9HJFeU=
github.com/coder/websocket v1.8.12 h1:5bUXkEPPIbewrnkU8LTCLVaxi4N4J8ahufH2vlo4NAo=
github.com/coder/websocket v1.8.12/go.mod h1:LNVeNrXQZfe5qhS9ALED3uA+l5pPqvwXg3CKoDBB2gs=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@@ -28,8 +31,8 @@ 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/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
github.com/go-acme/lego/v4 v4.19.2 h1:Y8hrmMvWETdqzzkRly7m98xtPJJivWFsgWi8fcvZo+Y=
github.com/go-acme/lego/v4 v4.19.2/go.mod h1:wtDe3dDkmV4/oI2nydpNXSJpvV10J9RCyZ6MbYxNtlQ=
github.com/go-jose/go-jose/v4 v4.0.4 h1:VsjPI33J0SB9vQM6PLmNjoHqMQNGPiZ0rHL7Ni7Q6/E=
@@ -61,10 +64,14 @@ github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nu
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
@@ -81,21 +88,29 @@ github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
github.com/ovh/go-ovh v1.6.0 h1:ixLOwxQdzYDx296sXcgS35TOPEahJkpjMGtzPadCjQI=
github.com/ovh/go-ovh v1.6.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c=
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/puzpuzpuz/xsync/v3 v3.4.0 h1:DuVBAdXuGFHv8adVXjWWZ63pJq+NRXOWVXlKDBZ+mJ4=
github.com/puzpuzpuz/xsync/v3 v3.4.0/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
@@ -181,8 +196,8 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

View File

@@ -1,7 +1,6 @@
package api
import (
"fmt"
"net"
"net/http"
@@ -20,7 +19,7 @@ func NewServeMux() ServeMux {
}
func (mux ServeMux) HandleFunc(method, endpoint string, handler http.HandlerFunc) {
mux.ServeMux.HandleFunc(fmt.Sprintf("%s %s", method, endpoint), checkHost(rateLimited(handler)))
mux.ServeMux.HandleFunc(method+" "+endpoint, checkHost(rateLimited(handler)))
}
func NewHandler() http.Handler {
@@ -55,6 +54,7 @@ func checkHost(f http.HandlerFunc) http.HandlerFunc {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
LogInfo(r).Interface("headers", r.Header).Msg("API request")
f(w, r)
}
}

View File

@@ -90,7 +90,7 @@ func LogoutHandler(w http.ResponseWriter, r *http.Request) {
}
func RequireAuth(next http.HandlerFunc) http.HandlerFunc {
if common.IsDebugSkipAuth {
if common.IsDebugSkipAuth || common.APIJWTSecret == nil {
return next
}

View File

@@ -10,7 +10,6 @@ import (
U "github.com/yusing/go-proxy/internal/api/v1/utils"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/config"
"github.com/yusing/go-proxy/internal/server"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
@@ -33,7 +32,6 @@ func StatsWS(w http.ResponseWriter, r *http.Request) {
}
originPats = append(originPats, localAddresses...)
}
U.LogInfo(r).Msgf("websocket API request from origins: %s", originPats)
if common.IsDebug {
originPats = []string{"*"}
}
@@ -62,9 +60,11 @@ func StatsWS(w http.ResponseWriter, r *http.Request) {
}
}
var startTime = time.Now()
func getStats() map[string]any {
return map[string]any{
"proxies": config.Statistics(),
"uptime": strutils.FormatDuration(server.GetProxyServer().Uptime()),
"uptime": strutils.FormatDuration(time.Since(startTime)),
}
}

View File

@@ -23,6 +23,9 @@ func generateJWTKey(size int) string {
}
func decodeJWTKey(key string) []byte {
if key == "" {
return nil
}
bytes, err := base64.StdEncoding.DecodeString(key)
if err != nil {
log.Panic().Err(err).Msg("failed to decode jwt key")

View File

@@ -12,55 +12,77 @@ import (
)
var (
NoSchemaValidation = GetEnvBool("GOPROXY_NO_SCHEMA_VALIDATION", true)
IsTest = GetEnvBool("GOPROXY_TEST", false) || strings.HasSuffix(os.Args[0], ".test")
IsDebug = GetEnvBool("GOPROXY_DEBUG", IsTest)
IsDebugSkipAuth = GetEnvBool("GOPROXY_DEBUG_SKIP_AUTH", false)
IsTrace = GetEnvBool("GOPROXY_TRACE", false) && IsDebug
prefixes = []string{"GODOXY_", "GOPROXY_", ""}
NoSchemaValidation = GetEnvBool("NO_SCHEMA_VALIDATION", true)
IsTest = GetEnvBool("TEST", false) || strings.HasSuffix(os.Args[0], ".test")
IsDebug = GetEnvBool("DEBUG", IsTest)
IsDebugSkipAuth = GetEnvBool("DEBUG_SKIP_AUTH", false)
IsTrace = GetEnvBool("TRACE", false) && IsDebug
IsProduction = !IsTest && !IsDebug
ProxyHTTPAddr,
ProxyHTTPHost,
ProxyHTTPPort,
ProxyHTTPURL = GetAddrEnv("GOPROXY_HTTP_ADDR", ":80", "http")
ProxyHTTPURL = GetAddrEnv("HTTP_ADDR", ":80", "http")
ProxyHTTPSAddr,
ProxyHTTPSHost,
ProxyHTTPSPort,
ProxyHTTPSURL = GetAddrEnv("GOPROXY_HTTPS_ADDR", ":443", "https")
ProxyHTTPSURL = GetAddrEnv("HTTPS_ADDR", ":443", "https")
APIHTTPAddr,
APIHTTPHost,
APIHTTPPort,
APIHTTPURL = GetAddrEnv("GOPROXY_API_ADDR", "127.0.0.1:8888", "http")
APIHTTPURL = GetAddrEnv("API_ADDR", "127.0.0.1:8888", "http")
APIJWTSecret = decodeJWTKey(GetEnv("GOPROXY_API_JWT_SECRET", generateJWTKey(32)))
APIJWTTokenTTL = GetDurationEnv("GOPROXY_API_JWT_TOKEN_TTL", time.Hour)
APIUser = GetEnv("GOPROXY_API_USER", "admin")
APIPasswordHash = HashPassword(GetEnv("GOPROXY_API_PASSWORD", "password"))
MetricsHTTPAddr,
MetricsHTTPHost,
MetricsHTTPPort,
MetricsHTTPURL = GetAddrEnv("PROMETHEUS_ADDR", "", "http")
PrometheusEnabled = MetricsHTTPURL != ""
APIJWTSecret = decodeJWTKey(GetEnvString("API_JWT_SECRET", ""))
APIJWTTokenTTL = GetDurationEnv("API_JWT_TOKEN_TTL", time.Hour)
APIUser = GetEnvString("API_USER", "admin")
APIPasswordHash = HashPassword(GetEnvString("API_PASSWORD", "password"))
)
func GetEnvBool(key string, defaultValue bool) bool {
value, ok := os.LookupEnv(key)
func GetEnv[T any](key string, defaultValue T, parser func(string) (T, error)) T {
var value string
var ok bool
for _, prefix := range prefixes {
value, ok = os.LookupEnv(prefix + key)
if ok && value != "" {
break
}
}
if !ok || value == "" {
return defaultValue
}
b, err := strconv.ParseBool(value)
if err != nil {
log.Fatal().Msgf("env %s: invalid boolean value: %s", key, value)
parsed, err := parser(value)
if err == nil {
return parsed
}
return b
log.Fatal().Err(err).Msgf("env %s: invalid %T value: %s", key, parsed, value)
return defaultValue
}
func GetEnv(key, defaultValue string) string {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
value = defaultValue
}
return value
func GetEnvString(key string, defaultValue string) string {
return GetEnv(key, defaultValue, func(s string) (string, error) {
return s, nil
})
}
func GetEnvBool(key string, defaultValue bool) bool {
return GetEnv(key, defaultValue, strconv.ParseBool)
}
func GetAddrEnv(key, defaultValue, scheme string) (addr, host, port, fullURL string) {
addr = GetEnv(key, defaultValue)
addr = GetEnvString(key, defaultValue)
if addr == "" {
return
}
host, port, err := net.SplitHostPort(addr)
if err != nil {
log.Fatal().Msgf("env %s: invalid address: %s", key, addr)
@@ -73,13 +95,5 @@ func GetAddrEnv(key, defaultValue, scheme string) (addr, host, port, fullURL str
}
func GetDurationEnv(key string, defaultValue time.Duration) time.Duration {
value, ok := os.LookupEnv(key)
if !ok || value == "" {
return defaultValue
}
d, err := time.ParseDuration(value)
if err != nil {
log.Fatal().Msgf("env %s: invalid duration value: %s", key, value)
}
return d
return GetEnv(key, defaultValue, time.ParseDuration)
}

View File

@@ -9,7 +9,6 @@ import (
"github.com/yusing/go-proxy/internal/proxy/entry"
"github.com/yusing/go-proxy/internal/route"
proxy "github.com/yusing/go-proxy/internal/route/provider"
F "github.com/yusing/go-proxy/internal/utils/functional"
"github.com/yusing/go-proxy/internal/utils/strutils"
)
@@ -69,6 +68,20 @@ func HomepageConfig() homepage.Config {
)
}
if instance.value.Homepage.UseDefaultCategories {
if en.Container != nil && item.Category == "" {
if category, ok := homepage.PredefinedCategories[en.Container.ImageName]; ok {
item.Category = category
}
}
if item.Category == "" {
if category, ok := homepage.PredefinedCategories[strings.ToLower(alias)]; ok {
item.Category = category
}
}
}
switch {
case entry.IsDocker(r):
if item.Category == "" {
@@ -125,13 +138,12 @@ func Statistics() map[string]any {
providerStats := make(map[string]proxy.ProviderStats)
instance.providers.RangeAll(func(name string, p *proxy.Provider) {
providerStats[name] = p.Statistics()
})
stats := p.Statistics()
providerStats[name] = stats
for _, stats := range providerStats {
nTotalRPs += stats.NumRPs
nTotalStreams += stats.NumStreams
}
})
return map[string]any{
"num_total_streams": nTotalStreams,
@@ -139,14 +151,3 @@ func Statistics() map[string]any {
"providers": providerStats,
}
}
func FindRoute(alias string) *route.Route {
return F.MapFind(instance.providers,
func(p *proxy.Provider) (*route.Route, bool) {
if route, ok := p.GetRoute(alias); ok {
return route, true
}
return nil, false
},
)
}

View File

@@ -6,6 +6,7 @@ type (
AutoCert AutoCertConfig `json:"autocert" yaml:",flow"`
ExplicitOnly bool `json:"explicit_only" yaml:"explicit_only"`
MatchDomains []string `json:"match_domains" yaml:"match_domains"`
Homepage HomepageConfig `json:"homepage" yaml:"homepage"`
TimeoutShutdown int `json:"timeout_shutdown" yaml:"timeout_shutdown"`
RedirectToHTTPS bool `json:"redirect_to_https" yaml:"redirect_to_https"`
}
@@ -18,8 +19,10 @@ type (
func DefaultConfig() *Config {
return &Config{
Providers: Providers{},
TimeoutShutdown: 3,
Homepage: HomepageConfig{
UseDefaultCategories: true,
},
RedirectToHTTPS: false,
}
}

View File

@@ -0,0 +1,5 @@
package types
type HomepageConfig struct {
UseDefaultCategories bool `json:"use_default_categories" yaml:"use_default_categories"`
}

View File

@@ -20,7 +20,7 @@ type (
ContainerID string `json:"container_id" yaml:"-"`
ImageName string `json:"image_name" yaml:"-"`
Labels map[string]string `json:"labels" yaml:"-"`
Labels map[string]string `json:"-" yaml:"-"`
PublicPortMapping PortMapping `json:"public_ports" yaml:"-"` // non-zero publicPort:types.Port
PrivatePortMapping PortMapping `json:"private_ports" yaml:"-"` // privatePort:types.Port

View File

@@ -11,4 +11,5 @@ type Waker interface {
health.HealthMonitor
http.Handler
net.Stream
Wake() error
}

View File

@@ -4,8 +4,11 @@ import (
"sync/atomic"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/yusing/go-proxy/internal/common"
. "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/metrics"
gphttp "github.com/yusing/go-proxy/internal/net/http"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/proxy/entry"
@@ -20,6 +23,7 @@ type waker struct {
rp *gphttp.ReverseProxy
stream net.Stream
hc health.HealthChecker
metric *metrics.Gauge
ready atomic.Bool
}
@@ -53,6 +57,13 @@ func newWaker(providerSubTask task.Task, entry entry.Entry, rp *gphttp.ReversePr
default:
panic("both nil")
}
if common.PrometheusEnabled {
m := metrics.GetServiceMetrics()
fqn := providerSubTask.Parent().Name() + "/" + entry.TargetName()
waker.metric = m.HealthStatus.With(metrics.HealthMetricLabels(fqn))
waker.metric.Set(float64(watcher.Status()))
}
return watcher, nil
}
@@ -68,8 +79,11 @@ func NewStreamWaker(providerSubTask task.Task, entry entry.Entry, stream net.Str
// Start implements health.HealthMonitor.
func (w *Watcher) Start(routeSubTask task.Task) E.Error {
routeSubTask.Finish("ignored")
w.task.OnCancel("stop route", func() {
w.task.OnCancel("stop route and cleanup", func() {
routeSubTask.Parent().Finish(w.task.FinishCause())
if w.metric != nil {
prometheus.Unregister(w.metric)
}
})
return nil
}
@@ -96,8 +110,16 @@ func (w *Watcher) Uptime() time.Duration {
return 0
}
// Status implements health.HealthMonitor.
func (w *Watcher) Status() health.Status {
status := w.getStatusUpdateReady()
if w.metric != nil {
w.metric.Set(float64(status))
}
return status
}
// Status implements health.HealthMonitor.
func (w *Watcher) getStatusUpdateReady() health.Status {
if !w.ContainerRunning {
return health.StatusNapping
}

View File

@@ -98,6 +98,10 @@ func registerWatcher(providerSubtask task.Task, entry entry.Entry, waker *waker)
return w, nil
}
func (w *Watcher) Wake() error {
return w.wakeIfStopped()
}
// WakeDebug logs a debug message related to waking the container.
func (w *Watcher) WakeDebug() *zerolog.Event {
return w.Debug().Str("action", "wake")

View File

@@ -0,0 +1,64 @@
package homepage
// PredefinedCategories by alias or docker image name
var PredefinedCategories = map[string]string{
"sonarr": "Torrenting",
"radarr": "Torrenting",
"bazarr": "Torrenting",
"lidarr": "Torrenting",
"readarr": "Torrenting",
"prowlarr": "Torrenting",
"watcharr": "Torrenting",
"qbittorrent": "Torrenting",
"qbit": "Torrenting",
"qbt": "Torrenting",
"transmission": "Torrenting",
"jellyfin": "Media",
"jellyseerr": "Media",
"emby": "Media",
"plex": "Media",
"navidrome": "Media",
"immich": "Media",
"tautulli": "Media",
"nextcloud": "Media",
"invidious": "Media",
"uptime": "Monitoring",
"uptime-kuma": "Monitoring",
"prometheus": "Monitoring",
"grafana": "Monitoring",
"netdata": "Monitoring",
"changedetection.io": "Monitoring",
"changedetection": "Monitoring",
"influxdb": "Monitoring",
"influx": "Monitoring",
"dozzle": "Monitoring",
"adguardhome": "Networking",
"adgh": "Networking",
"adg": "Networking",
"pihole": "Networking",
"flaresolverr": "Networking",
"homebridge": "Home Automation",
"home-assistant": "Home Automation",
"dockge": "Container Management",
"portainer-ce": "Container Management",
"portainer-be": "Container Management",
"rss": "RSS",
"rsshub": "RSS",
"rss-bridge": "RSS",
"miniflux": "RSS",
"freshrss": "RSS",
"paperless": "Documents",
"paperless-ngx": "Documents",
"s-pdf": "Documents",
"minio": "Storage",
"filebrowser": "Storage",
"rclone": "Storage",
}

View File

@@ -51,7 +51,7 @@ func init() {
).Level(level).With().Timestamp().Logger()
}
func DiscardLogger() { logger = zerolog.Nop() }
func DiscardLogger() { zerolog.SetGlobalLevel(zerolog.Disabled) }
func AddHook(h zerolog.Hook) { logger = logger.Hook(h) }

View File

@@ -0,0 +1,13 @@
package metrics
import (
"net/http"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
func NewHandler() http.Handler {
mux := http.NewServeMux()
mux.Handle("/metrics", promhttp.Handler())
return mux
}

View File

@@ -0,0 +1,36 @@
package metrics
import "github.com/prometheus/client_golang/prometheus"
type (
HTTPRouteMetricLabels struct {
Service, Method, Host, Visitor, Path string
}
StreamRouteMetricLabels struct {
Service, Visitor string
}
HealthMetricLabels string
)
func (lbl *HTTPRouteMetricLabels) toPromLabels() prometheus.Labels {
return prometheus.Labels{
"service": lbl.Service,
"method": lbl.Method,
"host": lbl.Host,
"visitor": lbl.Visitor,
"path": lbl.Path,
}
}
func (lbl *StreamRouteMetricLabels) toPromLabels() prometheus.Labels {
return prometheus.Labels{
"service": lbl.Service,
"visitor": lbl.Visitor,
}
}
func (lbl HealthMetricLabels) toPromLabels() prometheus.Labels {
return prometheus.Labels{
"service": string(lbl),
}
}

View File

@@ -0,0 +1,73 @@
package metrics
import "github.com/prometheus/client_golang/prometheus"
type (
Counter struct {
mv *prometheus.CounterVec
collector prometheus.Counter
}
Gauge struct {
mv *prometheus.GaugeVec
collector prometheus.Gauge
}
Labels interface {
toPromLabels() prometheus.Labels
}
)
func NewCounter(opts prometheus.CounterOpts, labels ...string) *Counter {
m := &Counter{
mv: prometheus.NewCounterVec(opts, labels),
}
if len(labels) == 0 {
m.collector = m.mv.WithLabelValues()
m.collector.Add(0)
}
prometheus.MustRegister(m)
return m
}
func NewGauge(opts prometheus.GaugeOpts, labels ...string) *Gauge {
m := &Gauge{
mv: prometheus.NewGaugeVec(opts, labels),
}
if len(labels) == 0 {
m.collector = m.mv.WithLabelValues()
m.collector.Set(0)
}
prometheus.MustRegister(m)
return m
}
func (c *Counter) Collect(ch chan<- prometheus.Metric) {
c.mv.Collect(ch)
}
func (c *Counter) Describe(ch chan<- *prometheus.Desc) {
c.mv.Describe(ch)
}
func (c *Counter) Inc() {
c.collector.Inc()
}
func (c *Counter) With(l Labels) *Counter {
return &Counter{mv: c.mv, collector: c.mv.With(l.toPromLabels())}
}
func (g *Gauge) Collect(ch chan<- prometheus.Metric) {
g.mv.Collect(ch)
}
func (g *Gauge) Describe(ch chan<- *prometheus.Desc) {
g.mv.Describe(ch)
}
func (g *Gauge) Set(v float64) {
g.collector.Set(v)
}
func (g *Gauge) With(l Labels) *Gauge {
return &Gauge{mv: g.mv, collector: g.mv.With(l.toPromLabels())}
}

105
internal/metrics/metrics.go Normal file
View File

@@ -0,0 +1,105 @@
package metrics
import (
"strings"
"github.com/prometheus/client_golang/prometheus"
"github.com/yusing/go-proxy/internal/common"
)
type (
RouteMetrics struct {
HTTPReqTotal,
HTTP2xx3xx,
HTTP4xx,
HTTP5xx *Counter
HTTPReqElapsed *Gauge
}
ServiceMetrics struct {
HealthStatus *Gauge
}
)
var (
rm RouteMetrics
sm ServiceMetrics
)
const (
routerNamespace = "router"
routerHTTPSubsystem = "http"
serviceNamespace = "service"
)
func GetRouteMetrics() *RouteMetrics {
return &rm
}
func GetServiceMetrics() *ServiceMetrics {
return &sm
}
func (rm *RouteMetrics) UnregisterService(service string) {
lbls := &HTTPRouteMetricLabels{Service: service}
prometheus.Unregister(rm.HTTP2xx3xx.With(lbls))
prometheus.Unregister(rm.HTTP4xx.With(lbls))
prometheus.Unregister(rm.HTTP5xx.With(lbls))
prometheus.Unregister(rm.HTTPReqElapsed.With(lbls))
}
func init() {
if !common.PrometheusEnabled {
return
}
initRouteMetrics()
initServiceMetrics()
}
func initRouteMetrics() {
lbls := []string{"service", "method", "host", "visitor", "path"}
partitionsHelp := ", partitioned by " + strings.Join(lbls, ", ")
rm = RouteMetrics{
HTTPReqTotal: NewCounter(prometheus.CounterOpts{
Namespace: routerNamespace,
Subsystem: routerHTTPSubsystem,
Name: "req_total",
Help: "How many requests processed" + partitionsHelp,
}),
HTTP2xx3xx: NewCounter(prometheus.CounterOpts{
Namespace: routerNamespace,
Subsystem: routerHTTPSubsystem,
Name: "req_ok_count",
Help: "How many 2xx-3xx requests processed" + partitionsHelp,
}, lbls...),
HTTP4xx: NewCounter(prometheus.CounterOpts{
Namespace: routerNamespace,
Subsystem: routerHTTPSubsystem,
Name: "req_4xx_count",
Help: "How many 4xx requests processed" + partitionsHelp,
}, lbls...),
HTTP5xx: NewCounter(prometheus.CounterOpts{
Namespace: routerNamespace,
Subsystem: routerHTTPSubsystem,
Name: "req_5xx_count",
Help: "How many 5xx requests processed" + partitionsHelp,
}, lbls...),
HTTPReqElapsed: NewGauge(prometheus.GaugeOpts{
Namespace: routerNamespace,
Subsystem: routerHTTPSubsystem,
Name: "req_elapsed_ms",
Help: "How long it took to process the request and respond a status code" + partitionsHelp,
}, lbls...),
}
}
func initServiceMetrics() {
sm = ServiceMetrics{
HealthStatus: NewGauge(prometheus.GaugeOpts{
Namespace: serviceNamespace,
Name: "health_status",
Help: "The health status of the router by service",
}, "service"),
}
}

View File

@@ -0,0 +1,26 @@
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/yusing/go-proxy/internal/common"
)
func InitRouterMetrics(getRPsCount func() int, getStreamsCount func() int) {
if !common.PrometheusEnabled {
return
}
prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Namespace: "entrypoint",
Name: "num_reverse_proxies",
Help: "The number of reverse proxies",
}, func() float64 {
return float64(getRPsCount())
}))
prometheus.MustRegister(prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Namespace: "entrypoint",
Name: "num_streams",
Help: "The number of streams",
}, func() float64 {
return float64(getStreamsCount())
}))
}

View File

@@ -1,15 +0,0 @@
package http
import "net/http"
type DummyResponseWriter struct{}
func (w DummyResponseWriter) Header() http.Header {
return make(http.Header)
}
func (w DummyResponseWriter) Write([]byte) (_ int, _ error) {
return
}
func (w DummyResponseWriter) WriteHeader(int) {}

View File

@@ -1,7 +1,6 @@
package loadbalancer
import (
"context"
"net/http"
"sync"
"time"
@@ -10,7 +9,6 @@ import (
"github.com/yusing/go-proxy/internal/common"
idlewatcher "github.com/yusing/go-proxy/internal/docker/idlewatcher/types"
E "github.com/yusing/go-proxy/internal/error"
gphttp "github.com/yusing/go-proxy/internal/net/http"
"github.com/yusing/go-proxy/internal/net/http/middleware"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/watcher/health"
@@ -225,18 +223,15 @@ func (lb *LoadBalancer) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
return
}
if r.Header.Get(common.HeaderCheckRedirect) != "" {
ctx, cancel := context.WithTimeout(r.Context(), 1*time.Second)
defer cancel()
// send dummy request to wake all servers
var dummyRW gphttp.DummyResponseWriter
// wake all servers
for _, srv := range srvs {
// wake only if server implements Waker
_, ok := srv.handler.(idlewatcher.Waker)
if !ok {
continue
waker, ok := srv.handler.(idlewatcher.Waker)
if ok {
if err := waker.Wake(); err != nil {
lb.Err(err).Msgf("failed to wake server %s", srv.Name)
}
}
wakeReq := r.Clone(ctx)
srv.ServeHTTP(dummyRW, wakeReq)
}
}
lb.impl.ServeHTTP(srvs, rw, r)

View File

@@ -11,15 +11,14 @@ import (
type cidrWhitelist struct {
cidrWhitelistOpts
m *Middleware
m *Middleware
cachedAddr F.Map[string, bool] // cache for trusted IPs
}
type cidrWhitelistOpts struct {
Allow []*types.CIDR `json:"allow"`
StatusCode int `json:"statusCode"`
Message string `json:"message"`
cachedAddr F.Map[string, bool] // cache for trusted IPs
}
var (
@@ -28,7 +27,6 @@ var (
Allow: []*types.CIDR{},
StatusCode: http.StatusForbidden,
Message: "IP not allowed",
cachedAddr: F.NewMapOf[string, bool](),
}
)
@@ -39,6 +37,7 @@ func NewCIDRWhitelist(opts OptionsRaw) (*Middleware, E.Error) {
before: wl.checkIP,
}
wl.cidrWhitelistOpts = cidrWhitelistDefaults
wl.cachedAddr = F.NewMapOf[string, bool]()
err := Deserialize(opts, &wl.cidrWhitelistOpts)
if err != nil {
return nil, err

View File

@@ -2,7 +2,6 @@ package middleware
import (
"encoding/json"
"errors"
"net/http"
"github.com/rs/zerolog"
@@ -157,8 +156,8 @@ func patchReverseProxy(rpName string, rp *ReverseProxy, middlewares []*Middlewar
mid := BuildMiddlewareFromChain(rpName, middlewares)
if mid.before != nil {
ori := rp.ServeHTTP
rp.ServeHTTP = func(w http.ResponseWriter, r *http.Request) {
ori := rp.HandlerFunc
rp.HandlerFunc = func(w http.ResponseWriter, r *http.Request) {
mid.before(ori, w, r)
}
}
@@ -167,7 +166,10 @@ func patchReverseProxy(rpName string, rp *ReverseProxy, middlewares []*Middlewar
if rp.ModifyResponse != nil {
ori := rp.ModifyResponse
rp.ModifyResponse = func(res *http.Response) error {
return errors.Join(mid.modifyResponse(res), ori(res))
if err := mid.modifyResponse(res); err != nil {
return err
}
return ori(res)
}
} else {
rp.ModifyResponse = mid.modifyResponse

View File

@@ -1,6 +1,8 @@
package middleware
import (
"net/http"
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
)
@@ -22,11 +24,9 @@ var ModifyRequest = &Middleware{withOptions: NewModifyRequest}
func NewModifyRequest(optsRaw OptionsRaw) (*Middleware, E.Error) {
mr := new(modifyRequest)
var mrFunc RewriteFunc
mrFunc := mr.modifyRequest
if common.IsDebug {
mrFunc = mr.modifyRequestWithTrace
} else {
mrFunc = mr.modifyRequest
}
mr.m = &Middleware{
impl: mr,
@@ -41,6 +41,9 @@ func NewModifyRequest(optsRaw OptionsRaw) (*Middleware, E.Error) {
func (mr *modifyRequest) modifyRequest(req *Request) {
for k, v := range mr.SetHeaders {
if http.CanonicalHeaderKey(k) == "Host" {
req.Host = v
}
req.Header.Set(k, v)
}
for k, v := range mr.AddHeaders {

View File

@@ -9,7 +9,10 @@ import (
func TestSetModifyRequest(t *testing.T) {
opts := OptionsRaw{
"set_headers": map[string]string{"User-Agent": "go-proxy/v0.5.0"},
"set_headers": map[string]string{
"User-Agent": "go-proxy/v0.5.0",
"Host": "test.example.com",
},
"add_headers": map[string]string{"Accept-Encoding": "test-value"},
"hide_headers": []string{"Accept"},
}
@@ -28,6 +31,7 @@ func TestSetModifyRequest(t *testing.T) {
})
ExpectNoError(t, err)
ExpectEqual(t, result.RequestHeaders.Get("User-Agent"), "go-proxy/v0.5.0")
ExpectEqual(t, result.RequestHeaders.Get("Host"), "test.example.com")
ExpectTrue(t, slices.Contains(result.RequestHeaders.Values("Accept-Encoding"), "test-value"))
ExpectEqual(t, result.RequestHeaders.Get("Accept"), "")
})

View File

@@ -2,6 +2,7 @@ package middleware
import (
"net"
"strings"
)
const (
@@ -15,10 +16,7 @@ const (
var SetXForwarded = &Middleware{
before: Rewrite(func(req *Request) {
req.Header.Del("Forwarded")
req.Header.Del(xForwardedFor)
req.Header.Del(xForwardedHost)
req.Header.Del(xForwardedProto)
delXForwarded(req)
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
if err == nil {
req.Header.Set(xForwardedFor, clientIP)
@@ -35,10 +33,18 @@ var SetXForwarded = &Middleware{
}
var HideXForwarded = &Middleware{
before: Rewrite(func(req *Request) {
req.Header.Del("Forwarded")
req.Header.Del(xForwardedFor)
req.Header.Del(xForwardedHost)
req.Header.Del(xForwardedProto)
}),
before: Rewrite(delXForwarded),
}
func delXForwarded(req *Request) {
req.Header.Del("Forwarded")
toRemove := make([]string, 0)
for k := range req.Header {
if strings.HasPrefix(k, "X-Forwarded-") {
toRemove = append(toRemove, k)
}
}
for _, k := range toRemove {
req.Header.Del(k)
}
}

View File

@@ -22,8 +22,11 @@ import (
"net/url"
"strings"
"sync"
"time"
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/metrics"
"github.com/yusing/go-proxy/internal/net/types"
U "github.com/yusing/go-proxy/internal/utils"
"golang.org/x/net/http/httpguts"
@@ -86,12 +89,43 @@ type ReverseProxy struct {
// implementation is used.
ModifyResponse func(*http.Response) error
ServeHTTP http.HandlerFunc
HandlerFunc http.HandlerFunc
TargetName string
TargetURL types.URL
}
type httpMetricLogger struct {
http.ResponseWriter
timestamp time.Time
labels *metrics.HTTPRouteMetricLabels
}
// WriteHeader implements http.ResponseWriter.
func (l *httpMetricLogger) WriteHeader(status int) {
l.ResponseWriter.WriteHeader(status)
duration := time.Since(l.timestamp)
go func() {
m := metrics.GetRouteMetrics()
m.HTTPReqTotal.Inc()
m.HTTPReqElapsed.With(l.labels).Set(float64(duration.Milliseconds()))
// ignore 1xx
switch {
case status >= 500:
m.HTTP5xx.With(l.labels).Inc()
case status >= 400:
m.HTTP4xx.With(l.labels).Inc()
case status >= 200:
m.HTTP2xx3xx.With(l.labels).Inc()
}
}()
}
func (l *httpMetricLogger) Unwrap() http.ResponseWriter {
return l.ResponseWriter
}
func singleJoiningSlash(a, b string) string {
aslash := strings.HasSuffix(a, "/")
bslash := strings.HasPrefix(b, "/")
@@ -157,10 +191,14 @@ func NewReverseProxy(name string, target types.URL, transport http.RoundTripper)
TargetName: name,
TargetURL: target,
}
rp.ServeHTTP = rp.serveHTTP
rp.HandlerFunc = rp.handler
return rp
}
func (p *ReverseProxy) UnregisterMetrics() {
metrics.GetRouteMetrics().UnregisterService(p.TargetName)
}
func rewriteRequestURL(req *http.Request, target *url.URL) {
targetQuery := target.RawQuery
req.URL.Scheme = target.Scheme
@@ -225,9 +263,41 @@ func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response
return true
}
func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
if _, ok := rw.(DummyResponseWriter); ok {
return
func (p *ReverseProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
p.HandlerFunc(rw, req)
}
func (p *ReverseProxy) handler(rw http.ResponseWriter, req *http.Request) {
if common.PrometheusEnabled {
t := time.Now()
var visitor string
if realIPs := req.Header.Values("X-Real-IP"); len(realIPs) > 0 {
visitor = realIPs[len(realIPs)-1]
}
if visitor == "" {
if fwdIPs := req.Header.Values("X-Forwarded-For"); len(fwdIPs) > 0 {
visitor = fwdIPs[len(fwdIPs)-1]
}
}
if visitor == "" {
var err error
visitor, _, err = net.SplitHostPort(req.RemoteAddr)
if err != nil {
visitor = req.RemoteAddr
}
}
lbls := &metrics.HTTPRouteMetricLabels{
Service: p.TargetName,
Method: req.Method,
Host: req.Host,
Visitor: visitor,
Path: req.URL.Path,
}
rw = &httpMetricLogger{
ResponseWriter: rw,
timestamp: t,
labels: lbls,
}
}
transport := p.Transport
@@ -367,7 +437,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
Proto: outreq.Proto,
ProtoMajor: outreq.ProtoMajor,
ProtoMinor: outreq.ProtoMinor,
Header: make(http.Header),
Header: http.Header{},
Body: io.NopCloser(bytes.NewReader([]byte("Origin server is not reachable."))),
Request: outreq,
TLS: outreq.TLS,

View File

@@ -8,6 +8,7 @@ import (
"sync"
"github.com/rs/zerolog"
"github.com/yusing/go-proxy/internal/common"
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
E "github.com/yusing/go-proxy/internal/error"
gphttp "github.com/yusing/go-proxy/internal/net/http"
@@ -38,10 +39,6 @@ type (
}
SubdomainKey = PT.Alias
ReverseProxyHandler struct {
*gphttp.ReverseProxy
}
)
var (
@@ -52,10 +49,6 @@ var (
// globalMux = http.NewServeMux() // TODO: support regex subdomain matching.
)
func (rp ReverseProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
rp.ReverseProxy.ServeHTTP(w, r)
}
func GetReverseProxies() F.Map[string, *HTTPRoute] {
return httpRoutes
}
@@ -77,10 +70,11 @@ func NewHTTPRoute(entry *entry.ReverseProxyEntry) (impl, E.Error) {
trans = gphttp.DefaultTransport.Clone()
}
rp := gphttp.NewReverseProxy(string(entry.Alias), entry.URL, trans)
service := string(entry.Alias)
rp := gphttp.NewReverseProxy(service, entry.URL, trans)
if len(entry.Middlewares) > 0 {
err := middleware.PatchReverseProxy(string(entry.Alias), rp, entry.Middlewares)
err := middleware.PatchReverseProxy(service, rp, entry.Middlewares)
if err != nil {
return nil, err
}
@@ -136,11 +130,11 @@ func (r *HTTPRoute) Start(providerSubtask task.Task) E.Error {
if r.handler == nil {
switch {
case len(r.PathPatterns) == 1 && r.PathPatterns[0] == "/":
r.handler = ReverseProxyHandler{r.rp}
r.handler = r.rp
default:
mux := http.NewServeMux()
for _, p := range r.PathPatterns {
mux.HandleFunc(string(p), r.rp.ServeHTTP)
mux.HandleFunc(string(p), r.rp.HandlerFunc)
}
r.handler = mux
}
@@ -163,6 +157,9 @@ func (r *HTTPRoute) Start(providerSubtask task.Task) E.Error {
})
}
if common.PrometheusEnabled {
r.task.OnFinished("unreg metrics", r.rp.UnregisterMetrics)
}
return nil
}

View File

@@ -0,0 +1,7 @@
package route
import "github.com/yusing/go-proxy/internal/metrics"
func init() {
metrics.InitRouterMetrics(httpRoutes.Size, streamRoutes.Size)
}

View File

@@ -31,7 +31,7 @@ var (
func DockerProviderImpl(name, dockerHost string, explicitOnly bool) (ProviderImpl, error) {
if dockerHost == common.DockerHostFromEnv {
dockerHost = common.GetEnv("DOCKER_HOST", client.DefaultDockerHost)
dockerHost = common.GetEnvString("DOCKER_HOST", client.DefaultDockerHost)
}
return &DockerProvider{
name,

View File

@@ -1,25 +0,0 @@
package server
var proxyServer, apiServer *Server
func InitProxyServer(opt Options) *Server {
if proxyServer == nil {
proxyServer = NewServer(opt)
}
return proxyServer
}
func InitAPIServer(opt Options) *Server {
if apiServer == nil {
apiServer = NewServer(opt)
}
return apiServer
}
func GetProxyServer() *Server {
return proxyServer
}
func GetAPIServer() *Server {
return apiServer
}

View File

@@ -38,6 +38,12 @@ type Options struct {
Handler http.Handler
}
func StartServer(opt Options) (s *Server) {
s = NewServer(opt)
s.Start()
return s
}
func NewServer(opt Options) (s *Server) {
var httpSer, httpsSer *http.Server
var httpHandler http.Handler

View File

@@ -12,7 +12,7 @@ import (
)
var (
branch = common.GetEnv("GOPROXY_BRANCH", "v0.7")
branch = common.GetEnvString("BRANCH", "v0.7")
baseURL = "https://github.com/yusing/go-proxy/raw/" + branch
requiredConfigs = []Config{
{common.ConfigBasePath, true, false, ""},

View File

@@ -50,8 +50,25 @@ func NewEventQueue(parent task.Task, flushInterval time.Duration, onFlush OnFlus
}
func (e *EventQueue) Start(eventCh <-chan Event, errCh <-chan E.Error) {
if common.IsProduction {
origOnFlush := e.onFlush
// recover panic in onFlush when in production mode
e.onFlush = func(flushTask task.Task, events []Event) {
defer func() {
if err := recover(); err != nil {
e.onError(E.New("recovered panic in onFlush").
Withf("%v", err).
Subject(e.task.Parent().String()))
}
}()
origOnFlush(flushTask, events)
}
}
go func() {
defer e.ticker.Stop()
defer e.task.Finish(nil)
for {
select {
case <-e.task.Context().Done():
@@ -61,18 +78,7 @@ func (e *EventQueue) Start(eventCh <-chan Event, errCh <-chan E.Error) {
flushTask := e.task.Subtask("flush events")
queue := e.queue
e.queue = make([]Event, 0, eventQueueCapacity)
if !common.IsDebug {
go func() {
defer func() {
if err := recover(); err != nil {
e.onError(E.Errorf("recovered panic in onFlush: %v", err).Subject(e.task.Parent().String()))
}
}()
e.onFlush(flushTask, queue)
}()
} else {
go e.onFlush(flushTask, queue)
}
go e.onFlush(flushTask, queue)
flushTask.Wait()
}
e.ticker.Reset(e.flushInterval)

View File

@@ -7,8 +7,11 @@ import (
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/yusing/go-proxy/internal/common"
E "github.com/yusing/go-proxy/internal/error"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/metrics"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/notif"
"github.com/yusing/go-proxy/internal/task"
@@ -26,6 +29,8 @@ type (
checkHealth HealthCheckFunc
startTime time.Time
metric *metrics.Gauge
task task.Task
}
)
@@ -59,6 +64,10 @@ func (mon *monitor) Start(routeSubtask task.Task) E.Error {
return E.From(ErrNegativeInterval)
}
if common.PrometheusEnabled {
mon.metric = metrics.GetServiceMetrics().HealthStatus.With(metrics.HealthMetricLabels(mon.service))
}
go func() {
logger := logging.With().Str("name", mon.service).Logger()
@@ -67,6 +76,9 @@ func (mon *monitor) Start(routeSubtask task.Task) E.Error {
mon.status.Store(StatusUnknown)
}
mon.task.Finish(nil)
if mon.metric != nil {
prometheus.Unregister(mon.metric)
}
}()
if err := mon.checkUpdateHealth(); err != nil {
@@ -173,6 +185,9 @@ func (mon *monitor) checkUpdateHealth() error {
notif.Notify(mon.service, "server is down")
}
}
if mon.metric != nil {
mon.metric.Set(float64(status))
}
return nil
}

View File

@@ -1,11 +1,10 @@
package health
type Status int
type Status uint8
const (
StatusUnknown Status = (iota << 1)
StatusHealthy
StatusUnknown Status = 0
StatusHealthy = (1 << iota)
StatusNapping
StatusStarting
StatusUnhealthy

View File

@@ -1,7 +1,7 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"title": "go-proxy config file",
"title": "GoDoxy config file",
"properties": {
"autocert": {
"title": "Autocert configuration",

View File

@@ -1,6 +1,6 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "go-proxy providers file",
"title": "GoDoxy standalone include file",
"oneOf": [
{
"type": "object"

View File

@@ -1,5 +1,5 @@
#!/bin/sh
mkdir -p bin
echo building go-proxy version ${VERSION}, build flags \"${BUILD_FLAGS}\"
go build -ldflags "${BUILD_FLAGS}" -pgo=auto -o bin/go-proxy ./cmd
echo building GoDoxy version ${VERSION}, build flags \"${BUILD_FLAGS}\"
go build -ldflags "${BUILD_FLAGS}" -pgo=auto -o bin/godoxy ./cmd