mirror of
https://github.com/yusing/godoxy.git
synced 2026-02-06 18:49:33 +01:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ba26e6a5d6 | ||
|
|
6194bac4c4 | ||
|
|
a1d1325ad6 | ||
|
|
cceebff93a | ||
|
|
f97e3f65fe | ||
|
|
5214ae1760 | ||
|
|
6be3aef367 | ||
|
|
6712e9b109 | ||
|
|
50a0686648 | ||
|
|
d47afa3081 | ||
|
|
1ddfe2fb92 | ||
|
|
3ae3d18566 | ||
|
|
5fdb171d65 | ||
|
|
99e43fe340 | ||
|
|
cf1ecbc826 | ||
|
|
5ff27b9e3d | ||
|
|
291304af75 | ||
|
|
b63ebfcb3b | ||
|
|
c6a9a816f6 | ||
|
|
f5cf716a91 | ||
|
|
6dbee61742 | ||
|
|
d89d97b61f |
19
.env.example
19
.env.example
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
11
Dockerfile
11
Dockerfile
@@ -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"]
|
||||
|
||||
12
Makefile
12
Makefile
@@ -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
|
||||
|
||||
44
README.md
44
README.md
@@ -1,4 +1,4 @@
|
||||
# go-proxy
|
||||
# GoDoxy
|
||||
|
||||
[](https://sonarcloud.io/summary/new_code?id=yusing_go-proxy)
|
||||
[](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
|
||||
|
||||
31
cmd/main.go
31
cmd/main.go
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
1002
examples/grafana_template.json
Normal file
1002
examples/grafana_template.json
Normal file
File diff suppressed because it is too large
Load Diff
16
go.mod
16
go.mod
@@ -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
33
go.sum
@@ -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=
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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)),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
5
internal/config/types/homepage_config.go
Normal file
5
internal/config/types/homepage_config.go
Normal file
@@ -0,0 +1,5 @@
|
||||
package types
|
||||
|
||||
type HomepageConfig struct {
|
||||
UseDefaultCategories bool `json:"use_default_categories" yaml:"use_default_categories"`
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -11,4 +11,5 @@ type Waker interface {
|
||||
health.HealthMonitor
|
||||
http.Handler
|
||||
net.Stream
|
||||
Wake() error
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
|
||||
64
internal/homepage/categories.go
Normal file
64
internal/homepage/categories.go
Normal 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",
|
||||
}
|
||||
@@ -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) }
|
||||
|
||||
|
||||
13
internal/metrics/http_handler.go
Normal file
13
internal/metrics/http_handler.go
Normal 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
|
||||
}
|
||||
36
internal/metrics/labels.go
Normal file
36
internal/metrics/labels.go
Normal 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),
|
||||
}
|
||||
}
|
||||
73
internal/metrics/metric.go
Normal file
73
internal/metrics/metric.go
Normal 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
105
internal/metrics/metrics.go
Normal 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"),
|
||||
}
|
||||
}
|
||||
26
internal/metrics/router_metrics.go
Normal file
26
internal/metrics/router_metrics.go
Normal 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())
|
||||
}))
|
||||
}
|
||||
@@ -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) {}
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"), "")
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
7
internal/route/metrics.go
Normal file
7
internal/route/metrics.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package route
|
||||
|
||||
import "github.com/yusing/go-proxy/internal/metrics"
|
||||
|
||||
func init() {
|
||||
metrics.InitRouterMetrics(httpRoutes.Size, streamRoutes.Size)
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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, ""},
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user