diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml
index a6370b22..84e3c8d9 100644
--- a/.github/workflows/docker-image.yml
+++ b/.github/workflows/docker-image.yml
@@ -130,18 +130,3 @@ jobs:
run: |
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- scan:
- runs-on: ubuntu-latest
- needs:
- - merge
- steps:
- - name: Scan Image with Trivy
- uses: aquasecurity/trivy-action@0.20.0
- with:
- image-ref: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- format: "sarif"
- output: "trivy-results.sarif"
- - name: Upload Trivy SARIF Report
- uses: github/codeql-action/upload-sarif@v3
- with:
- sarif_file: "trivy-results.sarif"
diff --git a/.gitignore b/.gitignore
index 1b8c3100..c2e8386f 100755
--- a/.gitignore
+++ b/.gitignore
@@ -3,8 +3,7 @@ compose.yml
config*/
certs*/
bin/
-
-templates/codemirror/
+error_pages/
logs/
log/
@@ -13,7 +12,8 @@ log/
go.work.sum
-!src/**/
+!cmd/**/
+!internal/**/
todo.md
diff --git a/Dockerfile b/Dockerfile
index 83d966e2..a82fb68d 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -5,7 +5,7 @@ RUN apk add --no-cache tzdata
WORKDIR /src
# Only copy go.mod and go.sum initially for better caching
-COPY src/go.mod src/go.sum ./
+COPY go.mod go.sum /src
# Utilize build cache
RUN --mount=type=cache,target="/go/pkg/mod" \
@@ -16,8 +16,10 @@ ENV GOCACHE=/root/.cache/go-build
# Build the application with better caching
RUN --mount=type=cache,target="/go/pkg/mod" \
--mount=type=cache,target="/root/.cache/go-build" \
- --mount=type=bind,src=src,dst=/src \
- CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -pgo=auto -o /go-proxy .
+ --mount=type=bind,src=cmd,dst=/src/cmd \
+ --mount=type=bind,src=internal,dst=/src/internal \
+ CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -pgo=auto -o /app/go-proxy ./cmd && \
+ mkdir /app/error_pages /app/certs
# Stage 2: Final image
FROM scratch
@@ -28,7 +30,7 @@ LABEL maintainer="yusing@6uo.me"
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
# copy binary
-COPY --from=builder /go-proxy /app/
+COPY --from=builder /app /
# copy schema directory
COPY schema/ /app/schema/
diff --git a/Makefile b/Makefile
index 11e77848..717d6bad 100755
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,5 @@
+BUILD_FLAG ?= -s -w
+
.PHONY: all setup build test up restart logs get debug run archive repush rapid-crash debug-list-containers
all: debug
@@ -10,10 +12,10 @@ setup:
build:
mkdir -p bin
CGO_ENABLED=0 GOOS=linux \
- go build -ldflags '${BUILD_FLAG}' -pgo=auto -o bin/go-proxy github.com/yusing/go-proxy
+ go build -ldflags '${BUILD_FLAG}' -pgo=auto -o bin/go-proxy ./cmd
test:
- go test ./src/...
+ go test ./internal/...
up:
docker compose up -d
@@ -25,13 +27,13 @@ logs:
docker compose logs -f
get:
- cd src && go get -u && go mod tidy && cd ..
+ cd cmd && go get -u && go mod tidy && cd ..
debug:
- make build && sudo GOPROXY_DEBUG=1 bin/go-proxy
+ make BUILD_FLAG="" build && sudo GOPROXY_DEBUG=1 bin/go-proxy
run:
- BUILD_FLAG="-s -w" make build && sudo bin/go-proxy
+ make build && sudo bin/go-proxy
archive:
git archive HEAD -o ../go-proxy-$$(date +"%Y%m%d%H%M").zip
diff --git a/README.md b/README.md
index ce2ca6fd..b42b1ba0 100755
--- a/README.md
+++ b/README.md
@@ -40,6 +40,8 @@ A lightweight, easy-to-use, and [performant](docs/benchmark_result.md) reverse p
- Auto hot-reload on container state / config file changes
- **idlesleeper**: stop containers on idle, wake it up on traffic _(optional, see [showcase](#idlesleeper))_
- HTTP(s) reserve proxy
+- [HTTP middleware support](docs/middlewares.md) _(experimental)_
+- [Custom error pages support](docs/middlewares.md#custom-error-pages)
- TCP and UDP port forwarding
- Web UI for configuration and monitoring (See [screenshots](https://github.com/yusing/go-proxy-frontend?tab=readme-ov-file#screenshots))
- Supports linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6 multi-platform
diff --git a/src/main.go b/cmd/main.go
similarity index 87%
rename from src/main.go
rename to cmd/main.go
index 79de91fc..5e2a94bc 100755
--- a/src/main.go
+++ b/cmd/main.go
@@ -16,23 +16,24 @@ import (
"time"
"github.com/sirupsen/logrus"
- "github.com/yusing/go-proxy/api"
- apiUtils "github.com/yusing/go-proxy/api/v1/utils"
- "github.com/yusing/go-proxy/common"
- "github.com/yusing/go-proxy/config"
- "github.com/yusing/go-proxy/docker"
- "github.com/yusing/go-proxy/docker/idlewatcher"
- E "github.com/yusing/go-proxy/error"
- R "github.com/yusing/go-proxy/route"
- "github.com/yusing/go-proxy/server"
- F "github.com/yusing/go-proxy/utils/functional"
+ "github.com/yusing/go-proxy/internal"
+ "github.com/yusing/go-proxy/internal/api"
+ apiUtils "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/docker"
+ "github.com/yusing/go-proxy/internal/docker/idlewatcher"
+ E "github.com/yusing/go-proxy/internal/error"
+ R "github.com/yusing/go-proxy/internal/route"
+ "github.com/yusing/go-proxy/internal/server"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
func main() {
args := common.GetArgs()
if args.Command == common.CommandSetup {
- Setup()
+ internal.Setup()
return
}
diff --git a/docs/middlewares.md b/docs/middlewares.md
index 6a1fbb46..959c85c6 100644
--- a/docs/middlewares.md
+++ b/docs/middlewares.md
@@ -8,6 +8,7 @@
- [Table of content](#table-of-content)
- [Available middlewares](#available-middlewares)
- [Redirect http](#redirect-http)
+ - [Custom error pages](#custom-error-pages)
- [Modify request or response](#modify-request-or-response)
- [Set headers](#set-headers)
- [Add headers](#add-headers)
@@ -48,6 +49,56 @@ server {
[🔼Back to top](#table-of-content)
+### Custom error pages
+
+To enable custom error pages, mount a folder containing your `html`, `js`, `css` files to `/app/error_pages` of _go-proxy_ container **(subfolders are ignored, please place all files in root directory)**
+
+Any path under `error_pages` directory (e.g. `href` tag), should starts with `/$gperrorpage/`
+
+Example:
+```html
+
+
+ 404 Not Found
+
+
+
+ ...
+
+
+```
+
+Hot-reloading is **supported**, you can **edit**, **rename** or **delete** files **without restarting**. Changes will be reflected after page reload
+
+Error page will be served if:
+- status code is not in range of 200 to 300
+- content type is `text/html`, `application/xhtml+xml` or `text/plain`
+
+Error page will be served:
+
+- from file `.html` if exists
+- otherwise from `404.html`
+- if they don't exist, original response will be served
+
+```yaml
+# docker labels
+proxy.app1.middlewares.custom_error_page:
+
+# include file
+app1:
+ middlewares:
+ custom_error_page:
+```
+
+nginx equivalent:
+```nginx
+location / {
+ try_files $uri $uri/ /error_pages/404.html =404;
+}
+```
+
+[🔼Back to top](#table-of-content)
+
### Modify request or response
```yaml
@@ -89,6 +140,8 @@ location / {
}
```
+[🔼Back to top](#table-of-content)
+
#### Add headers
```yaml
@@ -114,6 +167,8 @@ location / {
}
```
+[🔼Back to top](#table-of-content)
+
#### Hide headers
```yaml
@@ -171,6 +226,8 @@ app1:
set_x_forwarded:
```
+[🔼Back to top](#table-of-content)
+
### Forward Authorization header (experimental)
Fields:
@@ -226,6 +283,8 @@ http:
- session_id
```
+[🔼Back to top](#table-of-content)
+
## Examples
### Authentik
@@ -242,4 +301,6 @@ services:
proxy.authentik.middlewares.set_x_forwarded:
proxy.authentik.middlewares.modify_request.add_headers: |
Strict-Transport-Security: "max-age=63072000" always
-```
\ No newline at end of file
+```
+
+[🔼Back to top](#table-of-content)
\ No newline at end of file
diff --git a/src/go.mod b/go.mod
similarity index 94%
rename from src/go.mod
rename to go.mod
index c273d718..d8432571 100644
--- a/src/go.mod
+++ b/go.mod
@@ -17,7 +17,7 @@ require (
require (
github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
- github.com/cloudflare/cloudflare-go v0.105.0 // indirect
+ github.com/cloudflare/cloudflare-go v0.106.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
@@ -39,9 +39,9 @@ require (
github.com/pkg/errors v0.9.1 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // indirect
go.opentelemetry.io/otel v1.30.0 // indirect
- go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 // indirect
+ go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 // indirect
go.opentelemetry.io/otel/metric v1.30.0 // indirect
- go.opentelemetry.io/otel/sdk v1.24.0 // indirect
+ go.opentelemetry.io/otel/sdk v1.30.0 // indirect
go.opentelemetry.io/otel/trace v1.30.0 // indirect
golang.org/x/crypto v0.27.0 // indirect
golang.org/x/mod v0.21.0 // indirect
diff --git a/src/go.sum b/go.sum
similarity index 86%
rename from src/go.sum
rename to go.sum
index 2e7394cc..cb8f5356 100644
--- a/src/go.sum
+++ b/go.sum
@@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERo
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
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.105.0 h1:yu2IatITLZ4dw7/byzRrlE5DfUvtub0k9CHZ5zBlj90=
-github.com/cloudflare/cloudflare-go v0.105.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
+github.com/cloudflare/cloudflare-go v0.106.0 h1:q41gC5Wc1nfi0D1ZhSHokWcd9mGMbqC7RE7qiP+qE00=
+github.com/cloudflare/cloudflare-go v0.106.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -43,8 +43,10 @@ github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0 h1:Wqo399gCIufwto+VfwCSvsnfGpF/w5E9CNxSwbpD6No=
-github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 h1:asbCHRVmodnJTuQ3qamDwqVOIjwqUPTYmYuemVOx+Ys=
+github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0/go.mod h1:ggCgvZ2r7uOoQjOyu2Y1NhHmEPPzzuhWgcza5M1Ji1I=
github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@@ -91,18 +93,18 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 h1:ZIg3ZT/
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0/go.mod h1:DQAwmETtZV00skUwgD6+0U89g80NKsJE3DCKeLLPQMI=
go.opentelemetry.io/otel v1.30.0 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0 h1:t6wl9SPayj+c7lEIFgm4ooDBZVb01IhLB4InpomhRw8=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
-go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.30.0/go.mod h1:KQsVNh4OjgjTG0G6EiNi1jVpnaeeKsKMRwbLN+f1+8M=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8=
+go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0/go.mod h1:4lVs6obhSVRb1EW5FhOuBTyiQhtRtAnnva9vD3yRfq8=
go.opentelemetry.io/otel/metric v1.30.0 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
-go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
-go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
+go.opentelemetry.io/otel/sdk v1.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
+go.opentelemetry.io/otel/sdk v1.30.0/go.mod h1:p14X4Ok8S+sygzblytT1nqG98QG2KYKv++HE0LY/mhg=
go.opentelemetry.io/otel/trace v1.30.0 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
-go.opentelemetry.io/proto/otlp v1.1.0 h1:2Di21piLrCqJ3U3eXGCTPHE9R8Nh+0uglSnOyxikMeI=
-go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
+go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
+go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -148,14 +150,14 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto v0.0.0-20240227224415-6ceb2ff114de h1:F6qOa9AZTYJXOUEr4jDysRDLrm4PHePlge4v4TGAlxY=
-google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2 h1:rIo7ocm2roD9DcFIX67Ym8icoGCKSARAiPljFhh5suQ=
-google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
-google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
-google.golang.org/grpc v1.63.1 h1:pNClQmvdlyNUiwFETOux/PYqfhmA7BrswEdGRnib1fA=
-google.golang.org/grpc v1.63.1/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
-google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
-google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
+google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
+google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
+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=
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=
diff --git a/go.work b/go.work
deleted file mode 100644
index aad4eaa8..00000000
--- a/go.work
+++ /dev/null
@@ -1,5 +0,0 @@
-go 1.22.0
-
-toolchain go1.23.1
-
-use ./src
diff --git a/internal/api/handler.go b/internal/api/handler.go
new file mode 100644
index 00000000..4139842c
--- /dev/null
+++ b/internal/api/handler.go
@@ -0,0 +1,57 @@
+package api
+
+import (
+ "fmt"
+ "net/http"
+
+ v1 "github.com/yusing/go-proxy/internal/api/v1"
+ "github.com/yusing/go-proxy/internal/api/v1/error_page"
+ . "github.com/yusing/go-proxy/internal/api/v1/utils"
+ "github.com/yusing/go-proxy/internal/common"
+ "github.com/yusing/go-proxy/internal/config"
+)
+
+type ServeMux struct{ *http.ServeMux }
+
+func NewServeMux() ServeMux {
+ return ServeMux{http.NewServeMux()}
+}
+
+func (mux ServeMux) HandleFunc(method, endpoint string, handler http.HandlerFunc) {
+ mux.ServeMux.HandleFunc(fmt.Sprintf("%s %s", method, endpoint), checkHost(handler))
+}
+
+func NewHandler(cfg *config.Config) http.Handler {
+ mux := NewServeMux()
+ mux.HandleFunc("GET", "/v1", v1.Index)
+ mux.HandleFunc("GET", "/v1/checkhealth", wrap(cfg, v1.CheckHealth))
+ mux.HandleFunc("HEAD", "/v1/checkhealth", wrap(cfg, v1.CheckHealth))
+ mux.HandleFunc("POST", "/v1/reload", wrap(cfg, v1.Reload))
+ mux.HandleFunc("GET", "/v1/list", wrap(cfg, v1.List))
+ mux.HandleFunc("GET", "/v1/list/{what}", wrap(cfg, v1.List))
+ mux.HandleFunc("GET", "/v1/file", v1.GetFileContent)
+ mux.HandleFunc("GET", "/v1/file/{filename}", v1.GetFileContent)
+ mux.HandleFunc("POST", "/v1/file/{filename}", v1.SetFileContent)
+ mux.HandleFunc("PUT", "/v1/file/{filename}", v1.SetFileContent)
+ mux.HandleFunc("GET", "/v1/stats", wrap(cfg, v1.Stats))
+ mux.HandleFunc("GET", "/v1/error_page", error_page.GetHandleFunc())
+ return mux
+}
+
+// allow only requests to API server with host matching common.APIHTTPAddr
+func checkHost(f http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ if r.Host != common.APIHTTPAddr {
+ Logger.Warnf("invalid request to API server with host: %s, expected: %s", r.Host, common.APIHTTPAddr)
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+ f(w, r)
+ }
+}
+
+func wrap(cfg *config.Config, f func(cfg *config.Config, w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
+ f(cfg, w, r)
+ }
+}
diff --git a/src/api/v1/checkhealth.go b/internal/api/v1/checkhealth.go
similarity index 85%
rename from src/api/v1/checkhealth.go
rename to internal/api/v1/checkhealth.go
index 19476d45..6a2b483f 100644
--- a/src/api/v1/checkhealth.go
+++ b/internal/api/v1/checkhealth.go
@@ -5,9 +5,9 @@ import (
"net/http"
"strings"
- U "github.com/yusing/go-proxy/api/v1/utils"
- "github.com/yusing/go-proxy/config"
- R "github.com/yusing/go-proxy/route"
+ U "github.com/yusing/go-proxy/internal/api/v1/utils"
+ "github.com/yusing/go-proxy/internal/config"
+ R "github.com/yusing/go-proxy/internal/route"
)
func CheckHealth(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
diff --git a/internal/api/v1/error_page/error_page.go b/internal/api/v1/error_page/error_page.go
new file mode 100644
index 00000000..a2a46e31
--- /dev/null
+++ b/internal/api/v1/error_page/error_page.go
@@ -0,0 +1,88 @@
+package error_page
+
+import (
+ "context"
+ "fmt"
+ "os"
+ "path"
+ "sync"
+
+ api "github.com/yusing/go-proxy/internal/api/v1/utils"
+ "github.com/yusing/go-proxy/internal/common"
+ U "github.com/yusing/go-proxy/internal/utils"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
+ W "github.com/yusing/go-proxy/internal/watcher"
+ "github.com/yusing/go-proxy/internal/watcher/events"
+)
+
+const errPagesBasePath = common.ErrorPagesBasePath
+
+var setup = sync.OnceFunc(func() {
+ dirWatcher = W.NewDirectoryWatcher(context.Background(), errPagesBasePath)
+ loadContent()
+ go watchDir()
+})
+
+func GetStaticFile(filename string) ([]byte, bool) {
+ return fileContentMap.Load(filename)
+}
+
+// try .html -> 404.html -> not ok
+func GetErrorPageByStatus(statusCode int) (content []byte, ok bool) {
+ content, ok = fileContentMap.Load(fmt.Sprintf("%d.html", statusCode))
+ if !ok && statusCode != 404 {
+ return fileContentMap.Load("404.html")
+ }
+ return
+}
+
+func loadContent() {
+ files, err := U.ListFiles(errPagesBasePath, 0)
+ if err != nil {
+ api.Logger.Error(err)
+ return
+ }
+ for _, file := range files {
+ if fileContentMap.Has(file) {
+ continue
+ }
+ content, err := os.ReadFile(file)
+ if err != nil {
+ api.Logger.Errorf("failed to read error page resource %s: %s", file, err)
+ continue
+ }
+ file = path.Base(file)
+ api.Logger.Infof("error page resource %s loaded", file)
+ fileContentMap.Store(file, content)
+ }
+}
+
+func watchDir() {
+ eventCh, errCh := dirWatcher.Events(context.Background())
+ for {
+ select {
+ case event, ok := <-eventCh:
+ if !ok {
+ return
+ }
+ filename := event.ActorName
+ switch event.Action {
+ case events.ActionFileWritten:
+ fileContentMap.Delete(filename)
+ loadContent()
+ case events.ActionFileDeleted:
+ fileContentMap.Delete(filename)
+ api.Logger.Infof("error page resource %s deleted", filename)
+ case events.ActionFileRenamed:
+ api.Logger.Infof("error page resource %s deleted", filename)
+ fileContentMap.Delete(filename)
+ loadContent()
+ }
+ case err := <-errCh:
+ api.Logger.Errorf("error watching error page directory: %s", err)
+ }
+ }
+}
+
+var dirWatcher W.Watcher
+var fileContentMap = F.NewMapOf[string, []byte]()
diff --git a/internal/api/v1/error_page/http_handler.go b/internal/api/v1/error_page/http_handler.go
new file mode 100644
index 00000000..826fd9db
--- /dev/null
+++ b/internal/api/v1/error_page/http_handler.go
@@ -0,0 +1,25 @@
+package error_page
+
+import "net/http"
+
+func GetHandleFunc() http.HandlerFunc {
+ setup()
+ return serveHTTP
+}
+
+func serveHTTP(w http.ResponseWriter, r *http.Request) {
+ if r.Method != http.MethodGet {
+ http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
+ return
+ }
+ if r.URL.Path == "/" {
+ http.Error(w, "invalid path", http.StatusNotFound)
+ return
+ }
+ content, ok := fileContentMap.Load(r.URL.Path)
+ if !ok {
+ http.Error(w, "404 not found", http.StatusNotFound)
+ return
+ }
+ w.Write(content)
+}
diff --git a/src/api/v1/file.go b/internal/api/v1/file.go
similarity index 66%
rename from src/api/v1/file.go
rename to internal/api/v1/file.go
index a0adc094..ed17b0d4 100644
--- a/src/api/v1/file.go
+++ b/internal/api/v1/file.go
@@ -6,10 +6,11 @@ import (
"os"
"path"
- U "github.com/yusing/go-proxy/api/v1/utils"
- "github.com/yusing/go-proxy/common"
- "github.com/yusing/go-proxy/config"
- "github.com/yusing/go-proxy/proxy/provider"
+ U "github.com/yusing/go-proxy/internal/api/v1/utils"
+ "github.com/yusing/go-proxy/internal/common"
+ "github.com/yusing/go-proxy/internal/config"
+ E "github.com/yusing/go-proxy/internal/error"
+ "github.com/yusing/go-proxy/internal/proxy/provider"
)
func GetFileContent(w http.ResponseWriter, r *http.Request) {
@@ -37,14 +38,15 @@ func SetFileContent(w http.ResponseWriter, r *http.Request) {
return
}
+ var validateErr E.NestedError
if filename == common.ConfigFileName {
- err = config.Validate(content).Error()
+ validateErr = config.Validate(content)
} else {
- err = provider.Validate(content).Error()
+ validateErr = provider.Validate(content)
}
- if err != nil {
- U.HandleErr(w, r, err, http.StatusBadRequest)
+ if validateErr != nil {
+ U.RespondJson(w, validateErr.JSONObject(), http.StatusBadRequest)
return
}
diff --git a/src/api/v1/index.go b/internal/api/v1/index.go
similarity index 100%
rename from src/api/v1/index.go
rename to internal/api/v1/index.go
diff --git a/src/api/v1/list.go b/internal/api/v1/list.go
similarity index 84%
rename from src/api/v1/list.go
rename to internal/api/v1/list.go
index 66bf9c06..48a8744b 100644
--- a/src/api/v1/list.go
+++ b/internal/api/v1/list.go
@@ -5,10 +5,9 @@ import (
"net/http"
"os"
- "github.com/yusing/go-proxy/common"
- "github.com/yusing/go-proxy/config"
-
- U "github.com/yusing/go-proxy/api/v1/utils"
+ U "github.com/yusing/go-proxy/internal/api/v1/utils"
+ "github.com/yusing/go-proxy/internal/common"
+ "github.com/yusing/go-proxy/internal/config"
)
func List(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
@@ -38,7 +37,7 @@ func listRoutes(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
}
}
- if err := U.RespondJson(routes, w); err != nil {
+ if err := U.RespondJson(w, routes); err != nil {
U.HandleErr(w, r, err)
}
}
diff --git a/internal/api/v1/reload.go b/internal/api/v1/reload.go
new file mode 100644
index 00000000..c62c61ea
--- /dev/null
+++ b/internal/api/v1/reload.go
@@ -0,0 +1,16 @@
+package v1
+
+import (
+ "net/http"
+
+ U "github.com/yusing/go-proxy/internal/api/v1/utils"
+ "github.com/yusing/go-proxy/internal/config"
+)
+
+func Reload(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
+ if err := cfg.Reload(); err != nil {
+ U.RespondJson(w, err.JSONObject(), http.StatusInternalServerError)
+ } else {
+ w.WriteHeader(http.StatusOK)
+ }
+}
diff --git a/src/api/v1/stats.go b/internal/api/v1/stats.go
similarity index 52%
rename from src/api/v1/stats.go
rename to internal/api/v1/stats.go
index 154fe01a..1d6bf35c 100644
--- a/src/api/v1/stats.go
+++ b/internal/api/v1/stats.go
@@ -3,10 +3,10 @@ package v1
import (
"net/http"
- U "github.com/yusing/go-proxy/api/v1/utils"
- "github.com/yusing/go-proxy/config"
- "github.com/yusing/go-proxy/server"
- "github.com/yusing/go-proxy/utils"
+ U "github.com/yusing/go-proxy/internal/api/v1/utils"
+ "github.com/yusing/go-proxy/internal/config"
+ "github.com/yusing/go-proxy/internal/server"
+ "github.com/yusing/go-proxy/internal/utils"
)
func Stats(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
@@ -14,7 +14,7 @@ func Stats(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
"proxies": cfg.Statistics(),
"uptime": utils.FormatDuration(server.GetProxyServer().Uptime()),
}
- if err := U.RespondJson(stats, w); err != nil {
+ if err := U.RespondJson(w, stats); err != nil {
U.HandleErr(w, r, err)
}
}
diff --git a/src/api/v1/utils/error.go b/internal/api/v1/utils/error.go
similarity index 85%
rename from src/api/v1/utils/error.go
rename to internal/api/v1/utils/error.go
index 3f4dc925..1a9f58c4 100644
--- a/src/api/v1/utils/error.go
+++ b/internal/api/v1/utils/error.go
@@ -6,12 +6,14 @@ import (
"net/http"
"github.com/sirupsen/logrus"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
+var Logger = logrus.WithField("module", "api")
+
func HandleErr(w http.ResponseWriter, r *http.Request, origErr error, code ...int) {
err := E.From(origErr).Subjectf("%s %s", r.Method, r.URL)
- logrus.WithField("module", "api").Error(err)
+ Logger.Error(err)
if len(code) > 0 {
http.Error(w, err.String(), code[0])
return
diff --git a/internal/api/v1/utils/health_check.go b/internal/api/v1/utils/health_check.go
new file mode 100644
index 00000000..a03f9c89
--- /dev/null
+++ b/internal/api/v1/utils/health_check.go
@@ -0,0 +1,33 @@
+package utils
+
+import (
+ "net"
+ "net/http"
+
+ "github.com/yusing/go-proxy/internal/common"
+)
+
+func IsSiteHealthy(url string) bool {
+ // try HEAD first
+ // if HEAD is not allowed, try GET
+ resp, err := httpClient.Head(url)
+ if resp != nil {
+ resp.Body.Close()
+ }
+ if err != nil && resp != nil && resp.StatusCode == http.StatusMethodNotAllowed {
+ _, err = httpClient.Get(url)
+ }
+ if resp != nil {
+ resp.Body.Close()
+ }
+ return err == nil
+}
+
+func IsStreamHealthy(scheme, address string) bool {
+ conn, err := net.DialTimeout(scheme, address, common.DialTimeout)
+ if err != nil {
+ return false
+ }
+ conn.Close()
+ return true
+}
diff --git a/internal/api/v1/utils/http_client.go b/internal/api/v1/utils/http_client.go
new file mode 100644
index 00000000..bcacbf37
--- /dev/null
+++ b/internal/api/v1/utils/http_client.go
@@ -0,0 +1,23 @@
+package utils
+
+import (
+ "crypto/tls"
+ "net"
+ "net/http"
+
+ "github.com/yusing/go-proxy/internal/common"
+)
+
+var httpClient = &http.Client{
+ Timeout: common.ConnectionTimeout,
+ Transport: &http.Transport{
+ Proxy: http.ProxyFromEnvironment,
+ DisableKeepAlives: true,
+ ForceAttemptHTTP2: true,
+ DialContext: (&net.Dialer{
+ Timeout: common.DialTimeout,
+ KeepAlive: common.KeepAlive, // this is different from DisableKeepAlives
+ }).DialContext,
+ TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
+ },
+}
diff --git a/internal/api/v1/utils/localhost.go b/internal/api/v1/utils/localhost.go
new file mode 100644
index 00000000..4c5dde1a
--- /dev/null
+++ b/internal/api/v1/utils/localhost.go
@@ -0,0 +1,31 @@
+package utils
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+
+ "github.com/yusing/go-proxy/internal/common"
+ E "github.com/yusing/go-proxy/internal/error"
+)
+
+func ReloadServer() E.NestedError {
+ resp, err := httpClient.Post(fmt.Sprintf("%s/v1/reload", common.APIHTTPURL), "", nil)
+ if err != nil {
+ return E.From(err)
+ }
+ defer resp.Body.Close()
+ if resp.StatusCode != http.StatusOK {
+ failure := E.Failure("server reload").Extraf("status code: %v", resp.StatusCode)
+ b, err := io.ReadAll(resp.Body)
+ if err != nil {
+ return failure.Extraf("unable to read response body: %s", err)
+ }
+ reloadErr, ok := E.FromJSON(b)
+ if ok {
+ return E.Join("reload success, but server returned error", reloadErr)
+ }
+ return failure.Extraf("unable to read response body")
+ }
+ return nil
+}
diff --git a/src/api/v1/utils/utils.go b/internal/api/v1/utils/utils.go
similarity index 51%
rename from src/api/v1/utils/utils.go
rename to internal/api/v1/utils/utils.go
index a7501b18..da4199ad 100644
--- a/src/api/v1/utils/utils.go
+++ b/internal/api/v1/utils/utils.go
@@ -5,9 +5,12 @@ import (
"net/http"
)
-func RespondJson(data any, w http.ResponseWriter) error {
+func RespondJson(w http.ResponseWriter, data any, code ...int) error {
+ if len(code) > 0 {
+ w.WriteHeader(code[0])
+ }
w.Header().Set("Content-Type", "application/json")
- j, err := json.Marshal(data)
+ j, err := json.MarshalIndent(data, "", " ")
if err != nil {
return err
} else {
diff --git a/src/autocert/config.go b/internal/autocert/config.go
similarity index 93%
rename from src/autocert/config.go
rename to internal/autocert/config.go
index 4143e8b7..93f873fd 100644
--- a/src/autocert/config.go
+++ b/internal/autocert/config.go
@@ -7,8 +7,8 @@ import (
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/lego"
- E "github.com/yusing/go-proxy/error"
- M "github.com/yusing/go-proxy/models"
+ E "github.com/yusing/go-proxy/internal/error"
+ M "github.com/yusing/go-proxy/internal/models"
)
type Config M.AutoCertConfig
diff --git a/src/autocert/constants.go b/internal/autocert/constants.go
similarity index 100%
rename from src/autocert/constants.go
rename to internal/autocert/constants.go
diff --git a/src/autocert/dummy.go b/internal/autocert/dummy.go
similarity index 100%
rename from src/autocert/dummy.go
rename to internal/autocert/dummy.go
diff --git a/src/autocert/provider.go b/internal/autocert/provider.go
similarity index 97%
rename from src/autocert/provider.go
rename to internal/autocert/provider.go
index 97fb8025..b7217f10 100644
--- a/src/autocert/provider.go
+++ b/internal/autocert/provider.go
@@ -14,9 +14,9 @@ import (
"github.com/go-acme/lego/v4/challenge"
"github.com/go-acme/lego/v4/lego"
"github.com/go-acme/lego/v4/registration"
- E "github.com/yusing/go-proxy/error"
- M "github.com/yusing/go-proxy/models"
- U "github.com/yusing/go-proxy/utils"
+ E "github.com/yusing/go-proxy/internal/error"
+ M "github.com/yusing/go-proxy/internal/models"
+ U "github.com/yusing/go-proxy/internal/utils"
)
type Provider struct {
diff --git a/src/autocert/provider_test/ovh_test.go b/internal/autocert/provider_test/ovh_test.go
similarity index 92%
rename from src/autocert/provider_test/ovh_test.go
rename to internal/autocert/provider_test/ovh_test.go
index 032c0a42..9463f1d8 100644
--- a/src/autocert/provider_test/ovh_test.go
+++ b/internal/autocert/provider_test/ovh_test.go
@@ -4,8 +4,8 @@ import (
"testing"
"github.com/go-acme/lego/v4/providers/dns/ovh"
- U "github.com/yusing/go-proxy/utils"
- . "github.com/yusing/go-proxy/utils/testing"
+ U "github.com/yusing/go-proxy/internal/utils"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
"gopkg.in/yaml.v3"
)
diff --git a/src/autocert/setup.go b/internal/autocert/setup.go
similarity index 91%
rename from src/autocert/setup.go
rename to internal/autocert/setup.go
index e8d1ca2c..2b44f943 100644
--- a/src/autocert/setup.go
+++ b/internal/autocert/setup.go
@@ -4,7 +4,7 @@ import (
"context"
"os"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
func (p *Provider) Setup(ctx context.Context) (err E.NestedError) {
diff --git a/src/autocert/state.go b/internal/autocert/state.go
similarity index 100%
rename from src/autocert/state.go
rename to internal/autocert/state.go
diff --git a/src/autocert/user.go b/internal/autocert/user.go
similarity index 100%
rename from src/autocert/user.go
rename to internal/autocert/user.go
diff --git a/src/common/args.go b/internal/common/args.go
similarity index 95%
rename from src/common/args.go
rename to internal/common/args.go
index e84d6f93..cdc48f4e 100644
--- a/src/common/args.go
+++ b/internal/common/args.go
@@ -4,7 +4,7 @@ import (
"flag"
"github.com/sirupsen/logrus"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type Args struct {
diff --git a/src/common/constants.go b/internal/common/constants.go
similarity index 90%
rename from src/common/constants.go
rename to internal/common/constants.go
index 156f5454..69bed7a2 100644
--- a/src/common/constants.go
+++ b/internal/common/constants.go
@@ -7,7 +7,7 @@ import (
const (
ConnectionTimeout = 5 * time.Second
DialTimeout = 3 * time.Second
- KeepAlive = 5 * time.Second
+ KeepAlive = 60 * time.Second
)
// file, folder structure
@@ -30,6 +30,10 @@ const (
ComposeExampleFileName = "compose.example.yml"
)
+const (
+ ErrorPagesBasePath = "error_pages"
+)
+
const DockerHostFromEnv = "$DOCKER_HOST"
const (
diff --git a/internal/common/env.go b/internal/common/env.go
new file mode 100644
index 00000000..de2f714e
--- /dev/null
+++ b/internal/common/env.go
@@ -0,0 +1,55 @@
+package common
+
+import (
+ "fmt"
+ "net"
+ "os"
+
+ "github.com/sirupsen/logrus"
+ U "github.com/yusing/go-proxy/internal/utils"
+)
+
+var (
+ NoSchemaValidation = GetEnvBool("GOPROXY_NO_SCHEMA_VALIDATION")
+ IsDebug = GetEnvBool("GOPROXY_DEBUG")
+
+ ProxyHTTPAddr,
+ ProxyHTTPHost,
+ ProxyHTTPPort,
+ ProxyHTTPURL = GetAddrEnv("GOPROXY_HTTP_ADDR", ":80", "http")
+
+ ProxyHTTPSAddr,
+ ProxyHTTPSHost,
+ ProxyHTTPSPort,
+ ProxyHTTPSURL = GetAddrEnv("GOPROXY_HTTPS_ADDR", ":443", "https")
+
+ APIHTTPAddr,
+ APIHTTPHost,
+ APIHTTPPort,
+ APIHTTPURL = GetAddrEnv("GOPROXY_API_ADDR", "127.0.0.1:8888", "http")
+)
+
+func GetEnvBool(key string) bool {
+ return U.ParseBool(os.Getenv(key))
+}
+
+func GetEnv(key, defaultValue string) string {
+ value, ok := os.LookupEnv(key)
+ if !ok {
+ value = defaultValue
+ }
+ return value
+}
+
+func GetAddrEnv(key, defaultValue, scheme string) (addr, host, port, fullURL string) {
+ addr = GetEnv(key, defaultValue)
+ host, port, err := net.SplitHostPort(addr)
+ if err != nil {
+ logrus.Fatalf("Invalid address: %s", addr)
+ }
+ if host == "" {
+ host = "localhost"
+ }
+ fullURL = fmt.Sprintf("%s://%s:%s", scheme, host, port)
+ return
+}
diff --git a/src/common/http.go b/internal/common/http.go
similarity index 100%
rename from src/common/http.go
rename to internal/common/http.go
diff --git a/src/common/ports.go b/internal/common/ports.go
similarity index 100%
rename from src/common/ports.go
rename to internal/common/ports.go
diff --git a/src/config/config.go b/internal/config/config.go
similarity index 87%
rename from src/config/config.go
rename to internal/config/config.go
index ef7b1ca4..26052f84 100644
--- a/src/config/config.go
+++ b/internal/config/config.go
@@ -5,16 +5,16 @@ import (
"os"
"github.com/sirupsen/logrus"
- "github.com/yusing/go-proxy/autocert"
- "github.com/yusing/go-proxy/common"
- E "github.com/yusing/go-proxy/error"
- M "github.com/yusing/go-proxy/models"
- PR "github.com/yusing/go-proxy/proxy/provider"
- R "github.com/yusing/go-proxy/route"
- U "github.com/yusing/go-proxy/utils"
- F "github.com/yusing/go-proxy/utils/functional"
- W "github.com/yusing/go-proxy/watcher"
- "github.com/yusing/go-proxy/watcher/events"
+ "github.com/yusing/go-proxy/internal/autocert"
+ "github.com/yusing/go-proxy/internal/common"
+ E "github.com/yusing/go-proxy/internal/error"
+ M "github.com/yusing/go-proxy/internal/models"
+ PR "github.com/yusing/go-proxy/internal/proxy/provider"
+ R "github.com/yusing/go-proxy/internal/route"
+ U "github.com/yusing/go-proxy/internal/utils"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
+ W "github.com/yusing/go-proxy/internal/watcher"
+ "github.com/yusing/go-proxy/internal/watcher/events"
"gopkg.in/yaml.v3"
)
@@ -45,7 +45,7 @@ func Load() E.NestedError {
value: M.DefaultConfig(),
proxyProviders: F.NewMapOf[string, *PR.Provider](),
l: logrus.WithField("module", "config"),
- watcher: W.NewFileWatcher(common.ConfigFileName),
+ watcher: W.NewConfigFileWatcher(common.ConfigFileName),
reloadReq: make(chan struct{}, 1),
}
return instance.load()
@@ -116,8 +116,9 @@ func (cfg *Config) WatchChanges() {
case <-cfg.watcherCtx.Done():
return
case event := <-eventCh:
- if event.Action == events.ActionFileDeleted {
- cfg.stopProviders()
+ if event.Action == events.ActionFileDeleted || event.Action == events.ActionFileRenamed {
+ cfg.l.Error("config file deleted or renamed, ignoring...")
+ continue
} else {
cfg.reloadReq <- struct{}{}
}
diff --git a/src/config/query.go b/internal/config/query.go
similarity index 87%
rename from src/config/query.go
rename to internal/config/query.go
index 47e15e51..d3195f5b 100644
--- a/src/config/query.go
+++ b/internal/config/query.go
@@ -1,11 +1,11 @@
package config
import (
- M "github.com/yusing/go-proxy/models"
- PR "github.com/yusing/go-proxy/proxy/provider"
- R "github.com/yusing/go-proxy/route"
- U "github.com/yusing/go-proxy/utils"
- F "github.com/yusing/go-proxy/utils/functional"
+ M "github.com/yusing/go-proxy/internal/models"
+ PR "github.com/yusing/go-proxy/internal/proxy/provider"
+ R "github.com/yusing/go-proxy/internal/route"
+ U "github.com/yusing/go-proxy/internal/utils"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
func (cfg *Config) DumpEntries() map[string]*M.RawEntry {
diff --git a/src/docker/client.go b/internal/docker/client.go
similarity index 95%
rename from src/docker/client.go
rename to internal/docker/client.go
index 89c16f5c..41328572 100644
--- a/src/docker/client.go
+++ b/internal/docker/client.go
@@ -8,9 +8,9 @@ import (
"github.com/docker/cli/cli/connhelper"
"github.com/docker/docker/client"
"github.com/sirupsen/logrus"
- "github.com/yusing/go-proxy/common"
- E "github.com/yusing/go-proxy/error"
- F "github.com/yusing/go-proxy/utils/functional"
+ "github.com/yusing/go-proxy/internal/common"
+ E "github.com/yusing/go-proxy/internal/error"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
type Client struct {
diff --git a/src/docker/client_info.go b/internal/docker/client_info.go
similarity index 96%
rename from src/docker/client_info.go
rename to internal/docker/client_info.go
index c09190a9..8446393e 100644
--- a/src/docker/client_info.go
+++ b/internal/docker/client_info.go
@@ -8,7 +8,7 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type ClientInfo struct {
diff --git a/src/docker/container.go b/internal/docker/container.go
similarity index 98%
rename from src/docker/container.go
rename to internal/docker/container.go
index 956223e9..0e492e42 100644
--- a/src/docker/container.go
+++ b/internal/docker/container.go
@@ -6,7 +6,7 @@ import (
"strings"
"github.com/docker/docker/api/types"
- U "github.com/yusing/go-proxy/utils"
+ U "github.com/yusing/go-proxy/internal/utils"
)
type Container struct {
diff --git a/src/docker/homepage_label.go b/internal/docker/homepage_label.go
similarity index 100%
rename from src/docker/homepage_label.go
rename to internal/docker/homepage_label.go
diff --git a/src/docker/idlewatcher/html/loading_page.html b/internal/docker/idlewatcher/html/loading_page.html
similarity index 100%
rename from src/docker/idlewatcher/html/loading_page.html
rename to internal/docker/idlewatcher/html/loading_page.html
diff --git a/src/docker/idlewatcher/http.go b/internal/docker/idlewatcher/http.go
similarity index 92%
rename from src/docker/idlewatcher/http.go
rename to internal/docker/idlewatcher/http.go
index c2203660..f2b46056 100644
--- a/src/docker/idlewatcher/http.go
+++ b/internal/docker/idlewatcher/http.go
@@ -19,13 +19,7 @@ type templateData struct {
//go:embed html/loading_page.html
var loadingPage []byte
-var loadingPageTmpl = func() *template.Template {
- tmpl, err := template.New("loading").Parse(string(loadingPage))
- if err != nil {
- panic(err)
- }
- return tmpl
-}()
+var loadingPageTmpl = template.Must(template.New("loading_page").Parse(string(loadingPage)))
const (
htmlContentType = "text/html; charset=utf-8"
diff --git a/src/docker/idlewatcher/round_trip.go b/internal/docker/idlewatcher/round_trip.go
similarity index 96%
rename from src/docker/idlewatcher/round_trip.go
rename to internal/docker/idlewatcher/round_trip.go
index b1d8c2a3..738bfc62 100644
--- a/src/docker/idlewatcher/round_trip.go
+++ b/internal/docker/idlewatcher/round_trip.go
@@ -18,7 +18,10 @@ func (rt roundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*http.Response, error) {
// wake the container
- w.wakeCh <- struct{}{}
+ select {
+ case w.wakeCh <- struct{}{}:
+ default:
+ }
// target site is ready, passthrough
if w.ready.Load() {
diff --git a/src/docker/idlewatcher/watcher.go b/internal/docker/idlewatcher/watcher.go
similarity index 96%
rename from src/docker/idlewatcher/watcher.go
rename to internal/docker/idlewatcher/watcher.go
index af17f008..4fa5cd98 100644
--- a/src/docker/idlewatcher/watcher.go
+++ b/internal/docker/idlewatcher/watcher.go
@@ -9,11 +9,11 @@ import (
"github.com/docker/docker/api/types/container"
"github.com/sirupsen/logrus"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- P "github.com/yusing/go-proxy/proxy"
- PT "github.com/yusing/go-proxy/proxy/fields"
- W "github.com/yusing/go-proxy/watcher"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ P "github.com/yusing/go-proxy/internal/proxy"
+ PT "github.com/yusing/go-proxy/internal/proxy/fields"
+ W "github.com/yusing/go-proxy/internal/watcher"
)
type (
diff --git a/src/docker/inspect.go b/internal/docker/inspect.go
similarity index 88%
rename from src/docker/inspect.go
rename to internal/docker/inspect.go
index ae7f8f5c..7ee8e5f5 100644
--- a/src/docker/inspect.go
+++ b/internal/docker/inspect.go
@@ -4,7 +4,7 @@ import (
"context"
"time"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
func (c Client) Inspect(containerID string) (Container, E.NestedError) {
diff --git a/src/docker/label.go b/internal/docker/label.go
similarity index 95%
rename from src/docker/label.go
rename to internal/docker/label.go
index 39b288ff..8326622a 100644
--- a/src/docker/label.go
+++ b/internal/docker/label.go
@@ -4,9 +4,9 @@ import (
"reflect"
"strings"
- E "github.com/yusing/go-proxy/error"
- U "github.com/yusing/go-proxy/utils"
- F "github.com/yusing/go-proxy/utils/functional"
+ E "github.com/yusing/go-proxy/internal/error"
+ U "github.com/yusing/go-proxy/internal/utils"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
/*
diff --git a/src/docker/label_parser.go b/internal/docker/label_parser.go
similarity index 97%
rename from src/docker/label_parser.go
rename to internal/docker/label_parser.go
index 91b1c348..e9769c94 100644
--- a/src/docker/label_parser.go
+++ b/internal/docker/label_parser.go
@@ -3,7 +3,7 @@ package docker
import (
"strings"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
"gopkg.in/yaml.v3"
)
diff --git a/src/docker/label_parser_test.go b/internal/docker/label_parser_test.go
similarity index 96%
rename from src/docker/label_parser_test.go
rename to internal/docker/label_parser_test.go
index 4596f902..1fd9ce80 100644
--- a/src/docker/label_parser_test.go
+++ b/internal/docker/label_parser_test.go
@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
- E "github.com/yusing/go-proxy/error"
- . "github.com/yusing/go-proxy/utils/testing"
+ E "github.com/yusing/go-proxy/internal/error"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func makeLabel(namespace string, alias string, field string) string {
diff --git a/src/docker/label_test.go b/internal/docker/label_test.go
similarity index 95%
rename from src/docker/label_test.go
rename to internal/docker/label_test.go
index 045001ab..f8fe7aac 100644
--- a/src/docker/label_test.go
+++ b/internal/docker/label_test.go
@@ -4,8 +4,8 @@ import (
"fmt"
"testing"
- U "github.com/yusing/go-proxy/utils"
- . "github.com/yusing/go-proxy/utils/testing"
+ U "github.com/yusing/go-proxy/internal/utils"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestNestedLabel(t *testing.T) {
diff --git a/src/docker/labels.go b/internal/docker/labels.go
similarity index 100%
rename from src/docker/labels.go
rename to internal/docker/labels.go
diff --git a/src/docker/proxy_properties.go b/internal/docker/proxy_properties.go
similarity index 100%
rename from src/docker/proxy_properties.go
rename to internal/docker/proxy_properties.go
diff --git a/src/error/builder.go b/internal/error/builder.go
similarity index 100%
rename from src/error/builder.go
rename to internal/error/builder.go
diff --git a/src/error/builder_test.go b/internal/error/builder_test.go
similarity index 95%
rename from src/error/builder_test.go
rename to internal/error/builder_test.go
index afd33ae7..de7ce06e 100644
--- a/src/error/builder_test.go
+++ b/internal/error/builder_test.go
@@ -3,7 +3,7 @@ package error
import (
"testing"
- . "github.com/yusing/go-proxy/utils/testing"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestBuilderEmpty(t *testing.T) {
diff --git a/src/error/error.go b/internal/error/error.go
similarity index 80%
rename from src/error/error.go
rename to internal/error/error.go
index fc2b246d..6286c6e5 100644
--- a/src/error/error.go
+++ b/internal/error/error.go
@@ -1,6 +1,7 @@
package error
import (
+ "encoding/json"
"errors"
"fmt"
"strings"
@@ -13,6 +14,11 @@ type (
err error
extras []nestedError
}
+ jsonNestedError struct {
+ Subject string
+ Err string
+ Extras []jsonNestedError
+ }
)
func From(err error) NestedError {
@@ -22,6 +28,29 @@ func From(err error) NestedError {
return &nestedError{err: err}
}
+func FromJSON(data []byte) (NestedError, bool) {
+ var j jsonNestedError
+ if err := json.Unmarshal(data, &j); err != nil {
+ return nil, false
+ }
+ if j.Err == "" {
+ return nil, false
+ }
+ extras := make([]nestedError, len(j.Extras))
+ for i, e := range j.Extras {
+ extra, ok := fromJSONObject(e)
+ if !ok {
+ return nil, false
+ }
+ extras[i] = *extra
+ }
+ return &nestedError{
+ subject: j.Subject,
+ err: errors.New(j.Err),
+ extras: extras,
+ }, true
+}
+
// Check is a helper function that
// convert (T, error) to (T, NestedError).
func Check[T any](obj T, err error) (T, NestedError) {
@@ -157,6 +186,23 @@ func (ne NestedError) Subjectf(format string, args ...any) NestedError {
return ne
}
+func (ne NestedError) JSONObject() jsonNestedError {
+ extras := make([]jsonNestedError, len(ne.extras))
+ for i, e := range ne.extras {
+ extras[i] = e.JSONObject()
+ }
+ return jsonNestedError{
+ Subject: ne.subject,
+ Err: ne.err.Error(),
+ Extras: extras,
+ }
+}
+
+func (ne NestedError) JSON() []byte {
+ b, _ := json.MarshalIndent(ne.JSONObject(), "", " ")
+ return b
+}
+
func (ne NestedError) NoError() bool {
return ne == nil
}
@@ -169,6 +215,14 @@ func errorf(format string, args ...any) NestedError {
return From(fmt.Errorf(format, args...))
}
+func fromJSONObject(obj jsonNestedError) (NestedError, bool) {
+ data, err := json.Marshal(obj)
+ if err != nil {
+ return nil, false
+ }
+ return FromJSON(data)
+}
+
func (ne NestedError) withError(err NestedError) NestedError {
if ne != nil && err != nil {
ne.extras = append(ne.extras, *err)
diff --git a/src/error/error_test.go b/internal/error/error_test.go
similarity index 96%
rename from src/error/error_test.go
rename to internal/error/error_test.go
index b46cb18c..4c08a3f8 100644
--- a/src/error/error_test.go
+++ b/internal/error/error_test.go
@@ -4,8 +4,8 @@ import (
"errors"
"testing"
- . "github.com/yusing/go-proxy/error"
- . "github.com/yusing/go-proxy/utils/testing"
+ . "github.com/yusing/go-proxy/internal/error"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestErrorIs(t *testing.T) {
diff --git a/src/error/errors.go b/internal/error/errors.go
similarity index 100%
rename from src/error/errors.go
rename to internal/error/errors.go
diff --git a/internal/http/content_type.go b/internal/http/content_type.go
new file mode 100644
index 00000000..826f020e
--- /dev/null
+++ b/internal/http/content_type.go
@@ -0,0 +1,32 @@
+package http
+
+import (
+ "mime"
+ "net/http"
+)
+
+type ContentType string
+
+func GetContentType(h http.Header) ContentType {
+ ct := h.Get("Content-Type")
+ if ct == "" {
+ return ""
+ }
+ ct, _, err := mime.ParseMediaType(ct)
+ if err != nil {
+ return ""
+ }
+ return ContentType(ct)
+}
+
+func (ct ContentType) IsHTML() bool {
+ return ct == "text/html" || ct == "application/xhtml+xml"
+}
+
+func (ct ContentType) IsJSON() bool {
+ return ct == "application/json"
+}
+
+func (ct ContentType) IsPlainText() bool {
+ return ct == "text/plain"
+}
diff --git a/src/route/middleware/headers.go b/internal/http/header_utils.go
similarity index 64%
rename from src/route/middleware/headers.go
rename to internal/http/header_utils.go
index b12b134a..ab736589 100644
--- a/src/route/middleware/headers.go
+++ b/internal/http/header_utils.go
@@ -1,15 +1,13 @@
-package middleware
+package http
import (
"net/http"
"slices"
-
- gpHTTP "github.com/yusing/go-proxy/http"
)
-func removeHop(h Header) {
- reqUpType := gpHTTP.UpgradeType(h)
- gpHTTP.RemoveHopByHopHeaders(h)
+func RemoveHop(h http.Header) {
+ reqUpType := UpgradeType(h)
+ RemoveHopByHopHeaders(h)
if reqUpType != "" {
h.Set("Connection", "Upgrade")
@@ -19,7 +17,7 @@ func removeHop(h Header) {
}
}
-func copyHeader(dst, src Header) {
+func CopyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
@@ -27,7 +25,7 @@ func copyHeader(dst, src Header) {
}
}
-func filterHeaders(h Header, allowed []string) {
+func FilterHeaders(h http.Header, allowed []string) {
if allowed == nil {
return
}
diff --git a/src/http/modify_response_writer.go b/internal/http/modify_response_writer.go
similarity index 100%
rename from src/http/modify_response_writer.go
rename to internal/http/modify_response_writer.go
diff --git a/src/http/reverse_proxy_mod.go b/internal/http/reverse_proxy_mod.go
similarity index 92%
rename from src/http/reverse_proxy_mod.go
rename to internal/http/reverse_proxy_mod.go
index 43810a3a..57d018f5 100644
--- a/src/http/reverse_proxy_mod.go
+++ b/internal/http/reverse_proxy_mod.go
@@ -10,6 +10,7 @@ package http
// Copyright (c) 2024 yusing
import (
+ "bytes"
"context"
"errors"
"fmt"
@@ -129,20 +130,13 @@ type ReverseProxy struct {
// Response from the backend. It is called if the backend
// returns a response at all, with any HTTP status code.
// If the backend is unreachable, the optional ErrorHandler is
- // called without any call to ModifyResponse.
+ // called before ModifyResponse.
//
// If ModifyResponse returns an error, ErrorHandler is called
// with its error value. If ErrorHandler is nil, its default
// implementation is used.
ModifyResponse func(*http.Response) error
- // ErrorHandler is an optional function that handles errors
- // reaching the backend or errors from ModifyResponse.
- //
- // If nil, the default is to log the provided error and return
- // a 502 Status Bad Gateway response.
- ErrorHandler func(http.ResponseWriter, *http.Request, error)
-
ServeHTTP http.HandlerFunc
}
@@ -255,9 +249,11 @@ func copyHeader(dst, src http.Header) {
}
}
-func (p *ReverseProxy) errorHandler(rw http.ResponseWriter, _ *http.Request, err error) {
- logger.Errorf("http: proxy error: %s", err)
- rw.WriteHeader(http.StatusBadGateway)
+func (p *ReverseProxy) errorHandler(rw http.ResponseWriter, r *http.Request, err error, writeHeader bool) {
+ logger.Errorf("http proxy to %s failed: %s", r.URL.String(), err)
+ if writeHeader {
+ rw.WriteHeader(http.StatusBadGateway)
+ }
}
// modifyResponse conditionally runs the optional ModifyResponse hook
@@ -268,7 +264,7 @@ func (p *ReverseProxy) modifyResponse(rw http.ResponseWriter, res *http.Response
}
if err := p.ModifyResponse(res); err != nil {
res.Body.Close()
- p.errorHandler(rw, req, err)
+ p.errorHandler(rw, req, err, true)
return false
}
return true
@@ -324,7 +320,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
reqUpType := UpgradeType(outreq.Header)
if !IsPrint(reqUpType) {
- p.errorHandler(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType))
+ p.errorHandler(rw, req, fmt.Errorf("client tried to switch to invalid protocol %q", reqUpType), true)
return
}
@@ -384,8 +380,20 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
res, err := transport.RoundTrip(outreq)
if err != nil {
- p.errorHandler(rw, outreq, err)
- return
+ p.errorHandler(rw, outreq, err, false)
+ errMsg := err.Error()
+ res = &http.Response{
+ Status: http.StatusText(http.StatusBadGateway),
+ StatusCode: http.StatusBadGateway,
+ Proto: outreq.Proto,
+ ProtoMajor: outreq.ProtoMajor,
+ ProtoMinor: outreq.ProtoMinor,
+ Header: make(http.Header),
+ Body: io.NopCloser(bytes.NewReader([]byte(errMsg))),
+ Request: outreq,
+ ContentLength: int64(len(errMsg)),
+ TLS: outreq.TLS,
+ }
}
// Deal with 101 Switching Protocols responses: (WebSocket, h2c, etc)
@@ -418,16 +426,9 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
_, err = io.Copy(rw, res.Body)
if err != nil {
- defer res.Body.Close()
- // note: removed
- // Since we're streaming the response, if we run into an error all we can do
- // is abort the request. Issue 23643: ReverseProxy should use ErrAbortHandler
- // on read error while copying body.
- // if !shouldPanicOnCopyError(req) {
- // p.logf("suppressing panic for copyResponse error in test; copy error: %s", err)
- // return
- // }
- panic(http.ErrAbortHandler)
+ p.errorHandler(rw, req, err, true)
+ res.Body.Close()
+ return
}
res.Body.Close() // close now, instead of defer, to populate res.Trailer
@@ -480,23 +481,23 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
reqUpType := UpgradeType(req.Header)
resUpType := UpgradeType(res.Header)
if !IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller.
- p.errorHandler(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType))
+ p.errorHandler(rw, req, fmt.Errorf("backend tried to switch to invalid protocol %q", resUpType), true)
}
if !strings.EqualFold(reqUpType, resUpType) {
- p.errorHandler(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType))
+ p.errorHandler(rw, req, fmt.Errorf("backend tried to switch protocol %q when %q was requested", resUpType, reqUpType), true)
return
}
backConn, ok := res.Body.(io.ReadWriteCloser)
if !ok {
- p.errorHandler(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"))
+ p.errorHandler(rw, req, fmt.Errorf("internal error: 101 switching protocols response with non-writable body"), true)
return
}
rc := http.NewResponseController(rw)
conn, brw, hijackErr := rc.Hijack()
if errors.Is(hijackErr, http.ErrNotSupported) {
- p.errorHandler(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw))
+ p.errorHandler(rw, req, fmt.Errorf("can't switch protocols using non-Hijacker ResponseWriter type %T", rw), true)
return
}
@@ -513,7 +514,7 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
defer close(backConnCloseCh)
if hijackErr != nil {
- p.errorHandler(rw, req, fmt.Errorf("hijack failed on protocol switch: %w", hijackErr))
+ p.errorHandler(rw, req, fmt.Errorf("hijack failed on protocol switch: %w", hijackErr), true)
return
}
defer conn.Close()
@@ -523,11 +524,11 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
res.Header = rw.Header()
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
if err := res.Write(brw); err != nil {
- p.errorHandler(rw, req, fmt.Errorf("response write: %s", err))
+ p.errorHandler(rw, req, fmt.Errorf("response write: %s", err), true)
return
}
if err := brw.Flush(); err != nil {
- p.errorHandler(rw, req, fmt.Errorf("response flush: %s", err))
+ p.errorHandler(rw, req, fmt.Errorf("response flush: %s", err), true)
return
}
errc := make(chan error, 1)
diff --git a/internal/http/status_code.go b/internal/http/status_code.go
new file mode 100644
index 00000000..db8002ca
--- /dev/null
+++ b/internal/http/status_code.go
@@ -0,0 +1,7 @@
+package http
+
+import "net/http"
+
+func IsSuccess(status int) bool {
+ return status >= http.StatusOK && status < http.StatusMultipleChoices
+}
diff --git a/src/models/autocert_config.go b/internal/models/autocert_config.go
similarity index 100%
rename from src/models/autocert_config.go
rename to internal/models/autocert_config.go
diff --git a/src/models/config.go b/internal/models/config.go
similarity index 100%
rename from src/models/config.go
rename to internal/models/config.go
diff --git a/src/models/proxy_providers.go b/internal/models/proxy_providers.go
similarity index 100%
rename from src/models/proxy_providers.go
rename to internal/models/proxy_providers.go
diff --git a/src/models/raw_entry.go b/internal/models/raw_entry.go
similarity index 95%
rename from src/models/raw_entry.go
rename to internal/models/raw_entry.go
index e5971c2a..8647a980 100644
--- a/src/models/raw_entry.go
+++ b/internal/models/raw_entry.go
@@ -5,9 +5,9 @@ import (
"strconv"
"strings"
- . "github.com/yusing/go-proxy/common"
- D "github.com/yusing/go-proxy/docker"
- F "github.com/yusing/go-proxy/utils/functional"
+ . "github.com/yusing/go-proxy/internal/common"
+ D "github.com/yusing/go-proxy/internal/docker"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
type (
diff --git a/src/proxy/entry.go b/internal/proxy/entry.go
similarity index 93%
rename from src/proxy/entry.go
rename to internal/proxy/entry.go
index c752e796..3d0faacd 100644
--- a/src/proxy/entry.go
+++ b/internal/proxy/entry.go
@@ -5,10 +5,10 @@ import (
"net/url"
"time"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- M "github.com/yusing/go-proxy/models"
- T "github.com/yusing/go-proxy/proxy/fields"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ M "github.com/yusing/go-proxy/internal/models"
+ T "github.com/yusing/go-proxy/internal/proxy/fields"
)
type (
diff --git a/src/proxy/fields/alias.go b/internal/proxy/fields/alias.go
similarity index 100%
rename from src/proxy/fields/alias.go
rename to internal/proxy/fields/alias.go
diff --git a/src/proxy/fields/headers.go b/internal/proxy/fields/headers.go
similarity index 87%
rename from src/proxy/fields/headers.go
rename to internal/proxy/fields/headers.go
index fd1483ed..86a9837f 100644
--- a/src/proxy/fields/headers.go
+++ b/internal/proxy/fields/headers.go
@@ -4,7 +4,7 @@ import (
"net/http"
"strings"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
func ValidateHTTPHeaders(headers map[string]string) (http.Header, E.NestedError) {
diff --git a/src/proxy/fields/host.go b/internal/proxy/fields/host.go
similarity index 77%
rename from src/proxy/fields/host.go
rename to internal/proxy/fields/host.go
index 08399719..ca4a0f1b 100644
--- a/src/proxy/fields/host.go
+++ b/internal/proxy/fields/host.go
@@ -1,7 +1,7 @@
package fields
import (
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type Host string
diff --git a/src/proxy/fields/path_mode.go b/internal/proxy/fields/path_mode.go
similarity index 87%
rename from src/proxy/fields/path_mode.go
rename to internal/proxy/fields/path_mode.go
index f4d4889d..4f8f4dab 100644
--- a/src/proxy/fields/path_mode.go
+++ b/internal/proxy/fields/path_mode.go
@@ -1,7 +1,7 @@
package fields
import (
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type PathMode string
diff --git a/src/proxy/fields/path_pattern.go b/internal/proxy/fields/path_pattern.go
similarity index 94%
rename from src/proxy/fields/path_pattern.go
rename to internal/proxy/fields/path_pattern.go
index a95b0ce9..071b6773 100644
--- a/src/proxy/fields/path_pattern.go
+++ b/internal/proxy/fields/path_pattern.go
@@ -3,7 +3,7 @@ package fields
import (
"regexp"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type PathPattern string
diff --git a/src/proxy/fields/path_pattern_test.go b/internal/proxy/fields/path_pattern_test.go
similarity index 88%
rename from src/proxy/fields/path_pattern_test.go
rename to internal/proxy/fields/path_pattern_test.go
index 76d70bcb..0d2444ee 100644
--- a/src/proxy/fields/path_pattern_test.go
+++ b/internal/proxy/fields/path_pattern_test.go
@@ -3,8 +3,8 @@ package fields
import (
"testing"
- E "github.com/yusing/go-proxy/error"
- U "github.com/yusing/go-proxy/utils/testing"
+ E "github.com/yusing/go-proxy/internal/error"
+ U "github.com/yusing/go-proxy/internal/utils/testing"
)
var validPatterns = []string{
diff --git a/src/proxy/fields/port.go b/internal/proxy/fields/port.go
similarity index 93%
rename from src/proxy/fields/port.go
rename to internal/proxy/fields/port.go
index 10872376..a0e34ced 100644
--- a/src/proxy/fields/port.go
+++ b/internal/proxy/fields/port.go
@@ -3,7 +3,7 @@ package fields
import (
"strconv"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type Port int
diff --git a/src/proxy/fields/scheme.go b/internal/proxy/fields/scheme.go
similarity index 91%
rename from src/proxy/fields/scheme.go
rename to internal/proxy/fields/scheme.go
index 44e0a8e1..457b9cd7 100644
--- a/src/proxy/fields/scheme.go
+++ b/internal/proxy/fields/scheme.go
@@ -1,7 +1,7 @@
package fields
import (
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type Scheme string
diff --git a/src/proxy/fields/signal.go b/internal/proxy/fields/signal.go
similarity index 84%
rename from src/proxy/fields/signal.go
rename to internal/proxy/fields/signal.go
index c2bae6c4..0083b006 100644
--- a/src/proxy/fields/signal.go
+++ b/internal/proxy/fields/signal.go
@@ -1,7 +1,7 @@
package fields
import (
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type Signal string
diff --git a/src/proxy/fields/stop_method.go b/internal/proxy/fields/stop_method.go
similarity index 89%
rename from src/proxy/fields/stop_method.go
rename to internal/proxy/fields/stop_method.go
index 6d4e7471..bac9ad48 100644
--- a/src/proxy/fields/stop_method.go
+++ b/internal/proxy/fields/stop_method.go
@@ -1,7 +1,7 @@
package fields
import (
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type StopMethod string
diff --git a/src/proxy/fields/stream_port.go b/internal/proxy/fields/stream_port.go
similarity index 92%
rename from src/proxy/fields/stream_port.go
rename to internal/proxy/fields/stream_port.go
index a1418fdd..881d18e7 100644
--- a/src/proxy/fields/stream_port.go
+++ b/internal/proxy/fields/stream_port.go
@@ -3,8 +3,8 @@ package fields
import (
"strings"
- "github.com/yusing/go-proxy/common"
- E "github.com/yusing/go-proxy/error"
+ "github.com/yusing/go-proxy/internal/common"
+ E "github.com/yusing/go-proxy/internal/error"
)
type StreamPort struct {
diff --git a/src/proxy/fields/stream_port_test.go b/internal/proxy/fields/stream_port_test.go
similarity index 87%
rename from src/proxy/fields/stream_port_test.go
rename to internal/proxy/fields/stream_port_test.go
index 39def288..fce707cf 100644
--- a/src/proxy/fields/stream_port_test.go
+++ b/internal/proxy/fields/stream_port_test.go
@@ -3,8 +3,8 @@ package fields
import (
"testing"
- E "github.com/yusing/go-proxy/error"
- . "github.com/yusing/go-proxy/utils/testing"
+ E "github.com/yusing/go-proxy/internal/error"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
var validPorts = []string{
diff --git a/src/proxy/fields/stream_scheme.go b/internal/proxy/fields/stream_scheme.go
similarity index 95%
rename from src/proxy/fields/stream_scheme.go
rename to internal/proxy/fields/stream_scheme.go
index 318ad2e8..67684600 100644
--- a/src/proxy/fields/stream_scheme.go
+++ b/internal/proxy/fields/stream_scheme.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type StreamScheme struct {
diff --git a/src/proxy/fields/timeout.go b/internal/proxy/fields/timeout.go
similarity index 86%
rename from src/proxy/fields/timeout.go
rename to internal/proxy/fields/timeout.go
index 4f70f4bb..b299beae 100644
--- a/src/proxy/fields/timeout.go
+++ b/internal/proxy/fields/timeout.go
@@ -3,7 +3,7 @@ package fields
import (
"time"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
func ValidateDurationPostitive(value string) (time.Duration, E.NestedError) {
diff --git a/src/proxy/provider/docker.go b/internal/proxy/provider/docker.go
similarity index 95%
rename from src/proxy/provider/docker.go
rename to internal/proxy/provider/docker.go
index d16f764d..e12ca93d 100755
--- a/src/proxy/provider/docker.go
+++ b/internal/proxy/provider/docker.go
@@ -7,11 +7,11 @@ import (
"strings"
"github.com/sirupsen/logrus"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- M "github.com/yusing/go-proxy/models"
- R "github.com/yusing/go-proxy/route"
- W "github.com/yusing/go-proxy/watcher"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ M "github.com/yusing/go-proxy/internal/models"
+ R "github.com/yusing/go-proxy/internal/route"
+ W "github.com/yusing/go-proxy/internal/watcher"
)
type DockerProvider struct {
diff --git a/src/proxy/provider/docker_test.go b/internal/proxy/provider/docker_test.go
similarity index 96%
rename from src/proxy/provider/docker_test.go
rename to internal/proxy/provider/docker_test.go
index 9e0185ae..45bfae29 100644
--- a/src/proxy/provider/docker_test.go
+++ b/internal/proxy/provider/docker_test.go
@@ -5,13 +5,13 @@ import (
"testing"
"github.com/docker/docker/api/types"
- "github.com/yusing/go-proxy/common"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- P "github.com/yusing/go-proxy/proxy"
- T "github.com/yusing/go-proxy/proxy/fields"
+ "github.com/yusing/go-proxy/internal/common"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ P "github.com/yusing/go-proxy/internal/proxy"
+ T "github.com/yusing/go-proxy/internal/proxy/fields"
- . "github.com/yusing/go-proxy/utils/testing"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
var dummyNames = []string{"/a"}
diff --git a/src/proxy/provider/file.go b/internal/proxy/provider/file.go
similarity index 84%
rename from src/proxy/provider/file.go
rename to internal/proxy/provider/file.go
index 9216c3aa..a227fa71 100644
--- a/src/proxy/provider/file.go
+++ b/internal/proxy/provider/file.go
@@ -5,12 +5,12 @@ import (
"os"
"path"
- "github.com/yusing/go-proxy/common"
- E "github.com/yusing/go-proxy/error"
- M "github.com/yusing/go-proxy/models"
- R "github.com/yusing/go-proxy/route"
- U "github.com/yusing/go-proxy/utils"
- W "github.com/yusing/go-proxy/watcher"
+ "github.com/yusing/go-proxy/internal/common"
+ E "github.com/yusing/go-proxy/internal/error"
+ M "github.com/yusing/go-proxy/internal/models"
+ R "github.com/yusing/go-proxy/internal/route"
+ U "github.com/yusing/go-proxy/internal/utils"
+ W "github.com/yusing/go-proxy/internal/watcher"
)
type FileProvider struct {
@@ -94,5 +94,5 @@ func (p *FileProvider) LoadRoutesImpl() (routes R.Routes, res E.NestedError) {
}
func (p *FileProvider) NewWatcher() W.Watcher {
- return W.NewFileWatcher(p.fileName)
+ return W.NewConfigFileWatcher(p.fileName)
}
diff --git a/src/proxy/provider/provider.go b/internal/proxy/provider/provider.go
similarity index 96%
rename from src/proxy/provider/provider.go
rename to internal/proxy/provider/provider.go
index 3f4cafc7..e3882613 100644
--- a/src/proxy/provider/provider.go
+++ b/internal/proxy/provider/provider.go
@@ -5,9 +5,9 @@ import (
"path"
"github.com/sirupsen/logrus"
- E "github.com/yusing/go-proxy/error"
- R "github.com/yusing/go-proxy/route"
- W "github.com/yusing/go-proxy/watcher"
+ E "github.com/yusing/go-proxy/internal/error"
+ R "github.com/yusing/go-proxy/internal/route"
+ W "github.com/yusing/go-proxy/internal/watcher"
)
type (
diff --git a/src/route/constants.go b/internal/route/constants.go
similarity index 100%
rename from src/route/constants.go
rename to internal/route/constants.go
diff --git a/src/route/http.go b/internal/route/http.go
similarity index 85%
rename from src/route/http.go
rename to internal/route/http.go
index 8fa20ca8..8c3710e7 100755
--- a/src/route/http.go
+++ b/internal/route/http.go
@@ -1,7 +1,9 @@
package route
import (
+ "encoding/json"
"fmt"
+ "slices"
"sync"
"net/http"
@@ -9,14 +11,14 @@ import (
"strings"
"github.com/sirupsen/logrus"
- "github.com/yusing/go-proxy/common"
- "github.com/yusing/go-proxy/docker/idlewatcher"
- E "github.com/yusing/go-proxy/error"
- . "github.com/yusing/go-proxy/http"
- P "github.com/yusing/go-proxy/proxy"
- PT "github.com/yusing/go-proxy/proxy/fields"
- "github.com/yusing/go-proxy/route/middleware"
- F "github.com/yusing/go-proxy/utils/functional"
+ "github.com/yusing/go-proxy/internal/common"
+ "github.com/yusing/go-proxy/internal/docker/idlewatcher"
+ E "github.com/yusing/go-proxy/internal/error"
+ . "github.com/yusing/go-proxy/internal/http"
+ P "github.com/yusing/go-proxy/internal/proxy"
+ PT "github.com/yusing/go-proxy/internal/proxy/fields"
+ "github.com/yusing/go-proxy/internal/route/middleware"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
type (
@@ -170,10 +172,21 @@ func (u *URL) MarshalText() (text []byte, err error) {
func ProxyHandler(w http.ResponseWriter, r *http.Request) {
mux, err := findMuxFunc(r.Host)
if err != nil {
- http.Error(w, err.Error(), http.StatusNotFound)
logrus.Error(E.Failure("request").
Subjectf("%s %s", r.Method, r.URL.String()).
With(err))
+ acceptTypes := r.Header.Values("Accept")
+ switch {
+ case slices.Contains(acceptTypes, "text/html"):
+
+ case slices.Contains(acceptTypes, "application/json"):
+ w.WriteHeader(http.StatusNotFound)
+ json.NewEncoder(w).Encode(map[string]string{
+ "error": err.Error(),
+ })
+ default:
+ http.Error(w, err.Error(), http.StatusNotFound)
+ }
return
}
mux.ServeHTTP(w, r)
diff --git a/internal/route/middleware/custom_error_page.go b/internal/route/middleware/custom_error_page.go
new file mode 100644
index 00000000..e42d505b
--- /dev/null
+++ b/internal/route/middleware/custom_error_page.go
@@ -0,0 +1,70 @@
+package middleware
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/http"
+ "path/filepath"
+ "strings"
+
+ "github.com/sirupsen/logrus"
+ "github.com/yusing/go-proxy/internal/api/v1/error_page"
+ gpHTTP "github.com/yusing/go-proxy/internal/http"
+)
+
+const staticFilePathPrefix = "/$gperrorpage/"
+
+var CustomErrorPage = &Middleware{
+ before: func(next http.Handler, w ResponseWriter, r *Request) {
+ path := r.URL.Path
+ if path != "" && path[0] != '/' {
+ path = "/" + path
+ }
+ if strings.HasPrefix(path, staticFilePathPrefix) {
+ filename := path[len(staticFilePathPrefix):]
+ file, ok := error_page.GetStaticFile(filename)
+ if !ok {
+ http.NotFound(w, r)
+ errPageLogger.Errorf("unable to load resource %s", filename)
+ } else {
+ ext := filepath.Ext(filename)
+ switch ext {
+ case ".html":
+ w.Header().Set("Content-Type", "text/html; charset=utf-8")
+ case ".js":
+ w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
+ case ".css":
+ w.Header().Set("Content-Type", "text/css; charset=utf-8")
+ default:
+ errPageLogger.Errorf("unexpected file type %q for %s", ext, filename)
+ }
+ w.Write(file)
+ }
+ return
+ }
+ next.ServeHTTP(w, r)
+ },
+ modifyResponse: func(resp *Response) error {
+ // only handles non-success status code and html/plain content type
+ contentType := gpHTTP.GetContentType(resp.Header)
+ if !gpHTTP.IsSuccess(resp.StatusCode) && (contentType.IsHTML() || contentType.IsPlainText()) {
+ errorPage, ok := error_page.GetErrorPageByStatus(resp.StatusCode)
+ if ok {
+ errPageLogger.Debugf("error page for status %d loaded", resp.StatusCode)
+ io.Copy(io.Discard, resp.Body) // drain the original body
+ resp.Body.Close()
+ resp.Body = io.NopCloser(bytes.NewReader(errorPage))
+ resp.ContentLength = int64(len(errorPage))
+ resp.Header.Set("Content-Length", fmt.Sprint(len(errorPage)))
+ resp.Header.Set("Content-Type", "text/html; charset=utf-8")
+ } else {
+ errPageLogger.Errorf("unable to load error page for status %d", resp.StatusCode)
+ }
+ return nil
+ }
+ return nil
+ },
+}
+
+var errPageLogger = logrus.WithField("middleware", "error_page")
diff --git a/src/route/middleware/forward_auth.go b/internal/route/middleware/forward_auth.go
similarity index 92%
rename from src/route/middleware/forward_auth.go
rename to internal/route/middleware/forward_auth.go
index f7e80e7c..6232bd11 100644
--- a/src/route/middleware/forward_auth.go
+++ b/internal/route/middleware/forward_auth.go
@@ -14,11 +14,11 @@ import (
"time"
"github.com/sirupsen/logrus"
- "github.com/yusing/go-proxy/common"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- gpHTTP "github.com/yusing/go-proxy/http"
- U "github.com/yusing/go-proxy/utils"
+ "github.com/yusing/go-proxy/internal/common"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ gpHTTP "github.com/yusing/go-proxy/internal/http"
+ U "github.com/yusing/go-proxy/internal/utils"
)
type (
@@ -91,7 +91,7 @@ func newForwardAuth() (fa *forwardAuth) {
}
func (fa *forwardAuth) forward(next http.Handler, w ResponseWriter, req *Request) {
- removeHop(req.Header)
+ gpHTTP.RemoveHop(req.Header)
faReq, err := http.NewRequestWithContext(
req.Context(),
@@ -105,10 +105,10 @@ func (fa *forwardAuth) forward(next http.Handler, w ResponseWriter, req *Request
return
}
- copyHeader(faReq.Header, req.Header)
- removeHop(faReq.Header)
+ gpHTTP.CopyHeader(faReq.Header, req.Header)
+ gpHTTP.RemoveHop(faReq.Header)
- filterHeaders(faReq.Header, fa.AuthResponseHeaders)
+ gpHTTP.FilterHeaders(faReq.Header, fa.AuthResponseHeaders)
fa.setAuthHeaders(req, faReq)
faResp, err := fa.client.Do(faReq)
@@ -127,8 +127,8 @@ func (fa *forwardAuth) forward(next http.Handler, w ResponseWriter, req *Request
}
if faResp.StatusCode < http.StatusOK || faResp.StatusCode >= http.StatusMultipleChoices {
- copyHeader(w.Header(), faResp.Header)
- removeHop(w.Header())
+ gpHTTP.CopyHeader(w.Header(), faResp.Header)
+ gpHTTP.RemoveHop(w.Header())
redirectURL, err := faResp.Location()
if err != nil {
diff --git a/src/route/middleware/middleware.go b/internal/route/middleware/middleware.go
similarity index 81%
rename from src/route/middleware/middleware.go
rename to internal/route/middleware/middleware.go
index fae2d0c6..e02cb801 100644
--- a/src/route/middleware/middleware.go
+++ b/internal/route/middleware/middleware.go
@@ -3,9 +3,9 @@ package middleware
import (
"net/http"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- gpHTTP "github.com/yusing/go-proxy/http"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ gpHTTP "github.com/yusing/go-proxy/internal/http"
)
type (
@@ -21,7 +21,7 @@ type (
BeforeFunc func(next http.Handler, w ResponseWriter, r *Request)
RewriteFunc func(req *ProxyRequest)
- ModifyResponseFunc func(res *Response) error
+ ModifyResponseFunc func(resp *Response) error
CloneWithOptFunc func(opts OptionsRaw, rp *ReverseProxy) (*Middleware, E.NestedError)
OptionsRaw = map[string]any
@@ -68,7 +68,7 @@ func (m *Middleware) WithOptionsClone(optsRaw OptionsRaw, rp *ReverseProxy) (*Mi
func PatchReverseProxy(rp *ReverseProxy, middlewares map[string]OptionsRaw) (res E.NestedError) {
befores := make([]BeforeFunc, 0, len(middlewares))
rewrites := make([]RewriteFunc, 0, len(middlewares))
- modifyResponses := make([]ModifyResponseFunc, 0, len(middlewares))
+ modResps := make([]ModifyResponseFunc, 0, len(middlewares))
invalidM := E.NewBuilder("invalid middlewares")
invalidOpts := E.NewBuilder("invalid options")
@@ -96,7 +96,7 @@ func PatchReverseProxy(rp *ReverseProxy, middlewares map[string]OptionsRaw) (res
rewrites = append(rewrites, m.rewrite)
}
if m.modifyResponse != nil {
- modifyResponses = append(modifyResponses, m.modifyResponse)
+ modResps = append(modResps, m.modifyResponse)
}
}
@@ -118,29 +118,26 @@ func PatchReverseProxy(rp *ReverseProxy, middlewares map[string]OptionsRaw) (res
}
if len(rewrites) > 0 {
- origRewrite := rp.Rewrite
+ if rp.Rewrite != nil {
+ rewrites = append([]RewriteFunc{rp.Rewrite}, rewrites...)
+ }
rp.Rewrite = func(req *ProxyRequest) {
- if origRewrite != nil {
- origRewrite(req)
- }
for _, rewrite := range rewrites {
rewrite(req)
}
}
}
- if len(modifyResponses) > 0 {
- origModifyResponse := rp.ModifyResponse
+ if len(modResps) > 0 {
+ if rp.ModifyResponse != nil {
+ modResps = append([]ModifyResponseFunc{rp.ModifyResponse}, modResps...)
+ }
rp.ModifyResponse = func(res *Response) error {
- if origModifyResponse != nil {
- return origModifyResponse(res)
+ b := E.NewBuilder("errors in middleware ModifyResponse")
+ for _, mr := range modResps {
+ b.AddE(mr(res))
}
- for _, modifyResponse := range modifyResponses {
- if err := modifyResponse(res); err != nil {
- return err
- }
- }
- return nil
+ return b.Build().Error()
}
}
diff --git a/src/route/middleware/middlewares.go b/internal/route/middleware/middlewares.go
similarity index 64%
rename from src/route/middleware/middlewares.go
rename to internal/route/middleware/middlewares.go
index ab21daa5..31b8c68e 100644
--- a/src/route/middleware/middlewares.go
+++ b/internal/route/middleware/middlewares.go
@@ -4,7 +4,7 @@ import (
"fmt"
"strings"
- D "github.com/yusing/go-proxy/docker"
+ D "github.com/yusing/go-proxy/internal/docker"
)
var middlewares map[string]*Middleware
@@ -14,15 +14,17 @@ func Get(name string) (middleware *Middleware, ok bool) {
return
}
-// initialize middleware names
+// initialize middleware names and label parsers
func init() {
middlewares = map[string]*Middleware{
- "set_x_forwarded": SetXForwarded,
- "add_x_forwarded": AddXForwarded,
- "redirect_http": RedirectHTTP,
- "forward_auth": ForwardAuth.m,
- "modify_response": ModifyResponse.m,
- "modify_request": ModifyRequest.m,
+ "set_x_forwarded": SetXForwarded,
+ "add_x_forwarded": AddXForwarded,
+ "redirect_http": RedirectHTTP,
+ "forward_auth": ForwardAuth.m,
+ "modify_response": ModifyResponse.m,
+ "modify_request": ModifyRequest.m,
+ "error_page": CustomErrorPage,
+ "custom_error_page": CustomErrorPage,
}
names := make(map[*Middleware][]string)
for name, m := range middlewares {
diff --git a/src/route/middleware/modify_request.go b/internal/route/middleware/modify_request.go
similarity index 90%
rename from src/route/middleware/modify_request.go
rename to internal/route/middleware/modify_request.go
index 28994959..a722c7f8 100644
--- a/src/route/middleware/modify_request.go
+++ b/internal/route/middleware/modify_request.go
@@ -1,9 +1,9 @@
package middleware
import (
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- U "github.com/yusing/go-proxy/utils"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ U "github.com/yusing/go-proxy/internal/utils"
)
type (
diff --git a/src/route/middleware/modify_request_test.go b/internal/route/middleware/modify_request_test.go
similarity index 95%
rename from src/route/middleware/modify_request_test.go
rename to internal/route/middleware/modify_request_test.go
index c925464f..1ed4c5c0 100644
--- a/src/route/middleware/modify_request_test.go
+++ b/internal/route/middleware/modify_request_test.go
@@ -4,7 +4,7 @@ import (
"slices"
"testing"
- . "github.com/yusing/go-proxy/utils/testing"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestSetModifyRequest(t *testing.T) {
diff --git a/src/route/middleware/modify_response.go b/internal/route/middleware/modify_response.go
similarity index 90%
rename from src/route/middleware/modify_response.go
rename to internal/route/middleware/modify_response.go
index 1509b82a..ea1aafb2 100644
--- a/src/route/middleware/modify_response.go
+++ b/internal/route/middleware/modify_response.go
@@ -3,9 +3,9 @@ package middleware
import (
"net/http"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- U "github.com/yusing/go-proxy/utils"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ U "github.com/yusing/go-proxy/internal/utils"
)
type (
diff --git a/src/route/middleware/modify_response_test.go b/internal/route/middleware/modify_response_test.go
similarity index 95%
rename from src/route/middleware/modify_response_test.go
rename to internal/route/middleware/modify_response_test.go
index c63a1eb8..9c7a7a87 100644
--- a/src/route/middleware/modify_response_test.go
+++ b/internal/route/middleware/modify_response_test.go
@@ -4,7 +4,7 @@ import (
"slices"
"testing"
- . "github.com/yusing/go-proxy/utils/testing"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestSetModifyResponse(t *testing.T) {
diff --git a/src/route/middleware/redirect_http.go b/internal/route/middleware/redirect_http.go
similarity index 88%
rename from src/route/middleware/redirect_http.go
rename to internal/route/middleware/redirect_http.go
index e50685ed..a0e72e15 100644
--- a/src/route/middleware/redirect_http.go
+++ b/internal/route/middleware/redirect_http.go
@@ -3,7 +3,7 @@ package middleware
import (
"net/http"
- "github.com/yusing/go-proxy/common"
+ "github.com/yusing/go-proxy/internal/common"
)
var RedirectHTTP = &Middleware{
diff --git a/src/route/middleware/redirect_http_test.go b/internal/route/middleware/redirect_http_test.go
similarity index 85%
rename from src/route/middleware/redirect_http_test.go
rename to internal/route/middleware/redirect_http_test.go
index 18b77b1b..6d2b2f65 100644
--- a/src/route/middleware/redirect_http_test.go
+++ b/internal/route/middleware/redirect_http_test.go
@@ -4,8 +4,8 @@ import (
"net/http"
"testing"
- "github.com/yusing/go-proxy/common"
- . "github.com/yusing/go-proxy/utils/testing"
+ "github.com/yusing/go-proxy/internal/common"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestRedirectToHTTPs(t *testing.T) {
diff --git a/src/route/middleware/test_data/sample_headers.json b/internal/route/middleware/test_data/sample_headers.json
similarity index 100%
rename from src/route/middleware/test_data/sample_headers.json
rename to internal/route/middleware/test_data/sample_headers.json
diff --git a/src/route/middleware/test_utils.go b/internal/route/middleware/test_utils.go
similarity index 96%
rename from src/route/middleware/test_utils.go
rename to internal/route/middleware/test_utils.go
index f19dbd25..bb49e70a 100644
--- a/src/route/middleware/test_utils.go
+++ b/internal/route/middleware/test_utils.go
@@ -9,8 +9,8 @@ import (
"net/http/httptest"
"net/url"
- E "github.com/yusing/go-proxy/error"
- gpHTTP "github.com/yusing/go-proxy/http"
+ E "github.com/yusing/go-proxy/internal/error"
+ gpHTTP "github.com/yusing/go-proxy/internal/http"
)
//go:embed test_data/sample_headers.json
diff --git a/src/route/middleware/x_forwarded.go b/internal/route/middleware/x_forwarded.go
similarity index 100%
rename from src/route/middleware/x_forwarded.go
rename to internal/route/middleware/x_forwarded.go
diff --git a/src/route/route.go b/internal/route/route.go
similarity index 88%
rename from src/route/route.go
rename to internal/route/route.go
index 01402892..5aaf6a5d 100755
--- a/src/route/route.go
+++ b/internal/route/route.go
@@ -4,10 +4,10 @@ import (
"fmt"
"net/url"
- E "github.com/yusing/go-proxy/error"
- M "github.com/yusing/go-proxy/models"
- P "github.com/yusing/go-proxy/proxy"
- F "github.com/yusing/go-proxy/utils/functional"
+ E "github.com/yusing/go-proxy/internal/error"
+ M "github.com/yusing/go-proxy/internal/models"
+ P "github.com/yusing/go-proxy/internal/proxy"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
type (
diff --git a/src/route/stream.go b/internal/route/stream.go
similarity index 96%
rename from src/route/stream.go
rename to internal/route/stream.go
index 8fe6668c..af1446cb 100755
--- a/src/route/stream.go
+++ b/internal/route/stream.go
@@ -9,8 +9,8 @@ import (
"time"
"github.com/sirupsen/logrus"
- E "github.com/yusing/go-proxy/error"
- P "github.com/yusing/go-proxy/proxy"
+ E "github.com/yusing/go-proxy/internal/error"
+ P "github.com/yusing/go-proxy/internal/proxy"
)
type StreamRoute struct {
diff --git a/src/route/tcp.go b/internal/route/tcp.go
similarity index 93%
rename from src/route/tcp.go
rename to internal/route/tcp.go
index 48da2539..4ce5901c 100755
--- a/src/route/tcp.go
+++ b/internal/route/tcp.go
@@ -7,8 +7,8 @@ import (
"sync"
"time"
- T "github.com/yusing/go-proxy/proxy/fields"
- U "github.com/yusing/go-proxy/utils"
+ T "github.com/yusing/go-proxy/internal/proxy/fields"
+ U "github.com/yusing/go-proxy/internal/utils"
)
const tcpDialTimeout = 5 * time.Second
diff --git a/src/route/udp.go b/internal/route/udp.go
similarity index 94%
rename from src/route/udp.go
rename to internal/route/udp.go
index 41b11e2a..071d660e 100755
--- a/src/route/udp.go
+++ b/internal/route/udp.go
@@ -5,9 +5,9 @@ import (
"io"
"net"
- T "github.com/yusing/go-proxy/proxy/fields"
- U "github.com/yusing/go-proxy/utils"
- F "github.com/yusing/go-proxy/utils/functional"
+ T "github.com/yusing/go-proxy/internal/proxy/fields"
+ U "github.com/yusing/go-proxy/internal/utils"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
)
type (
diff --git a/src/server/instance.go b/internal/server/instance.go
similarity index 100%
rename from src/server/instance.go
rename to internal/server/instance.go
diff --git a/src/server/server.go b/internal/server/server.go
similarity index 98%
rename from src/server/server.go
rename to internal/server/server.go
index 57276dd1..d29830b9 100644
--- a/src/server/server.go
+++ b/internal/server/server.go
@@ -7,7 +7,7 @@ import (
"time"
"github.com/sirupsen/logrus"
- "github.com/yusing/go-proxy/autocert"
+ "github.com/yusing/go-proxy/internal/autocert"
"golang.org/x/net/context"
)
diff --git a/src/setup.go b/internal/setup.go
similarity index 97%
rename from src/setup.go
rename to internal/setup.go
index 4aaa95d8..aa17dd47 100644
--- a/src/setup.go
+++ b/internal/setup.go
@@ -1,4 +1,4 @@
-package main
+package internal
import (
"fmt"
@@ -9,7 +9,7 @@ import (
"os"
"path"
- . "github.com/yusing/go-proxy/common"
+ . "github.com/yusing/go-proxy/internal/common"
)
var branch = GetEnv("GOPROXY_BRANCH", "v0.5")
diff --git a/src/utils/format.go b/internal/utils/format.go
similarity index 100%
rename from src/utils/format.go
rename to internal/utils/format.go
diff --git a/internal/utils/fs.go b/internal/utils/fs.go
new file mode 100644
index 00000000..09855dfd
--- /dev/null
+++ b/internal/utils/fs.go
@@ -0,0 +1,34 @@
+package utils
+
+import (
+ "fmt"
+ "os"
+ "path"
+)
+
+// Recursively lists all files in a directory until `maxDepth` is reached
+// Returns a slice of file paths relative to `dir`
+func ListFiles(dir string, maxDepth int) ([]string, error) {
+ entries, err := os.ReadDir(dir)
+ if err != nil {
+ return nil, fmt.Errorf("error listing directory %s: %w", dir, err)
+ }
+ files := make([]string, 0)
+ for _, entry := range entries {
+ if entry.IsDir() {
+ if maxDepth <= 0 {
+ continue
+ }
+ subEntries, err := ListFiles(path.Join(dir, entry.Name()), maxDepth-1)
+ if err != nil {
+ return nil, err
+ }
+ for _, subEntry := range subEntries {
+ files = append(files, path.Join(dir, entry.Name(), subEntry))
+ }
+ } else {
+ files = append(files, path.Join(dir, entry.Name()))
+ }
+ }
+ return files, nil
+}
diff --git a/src/utils/functional/functional.go b/internal/utils/functional/functional.go
similarity index 100%
rename from src/utils/functional/functional.go
rename to internal/utils/functional/functional.go
diff --git a/src/utils/functional/map.go b/internal/utils/functional/map.go
similarity index 97%
rename from src/utils/functional/map.go
rename to internal/utils/functional/map.go
index 812cbe6b..e0d3284f 100644
--- a/src/utils/functional/map.go
+++ b/internal/utils/functional/map.go
@@ -4,7 +4,7 @@ import (
"github.com/puzpuzpuz/xsync/v3"
"gopkg.in/yaml.v3"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
type Map[KT comparable, VT any] struct {
diff --git a/src/utils/functional/map_test.go b/internal/utils/functional/map_test.go
similarity index 91%
rename from src/utils/functional/map_test.go
rename to internal/utils/functional/map_test.go
index 031993e6..c9382a38 100644
--- a/src/utils/functional/map_test.go
+++ b/internal/utils/functional/map_test.go
@@ -3,8 +3,8 @@ package functional_test
import (
"testing"
- . "github.com/yusing/go-proxy/utils/functional"
- . "github.com/yusing/go-proxy/utils/testing"
+ . "github.com/yusing/go-proxy/internal/utils/functional"
+ . "github.com/yusing/go-proxy/internal/utils/testing"
)
func TestNewMapFrom(t *testing.T) {
diff --git a/src/utils/functional/map_utils.go b/internal/utils/functional/map_utils.go
similarity index 100%
rename from src/utils/functional/map_utils.go
rename to internal/utils/functional/map_utils.go
diff --git a/src/utils/functional/slice.go b/internal/utils/functional/slice.go
similarity index 100%
rename from src/utils/functional/slice.go
rename to internal/utils/functional/slice.go
diff --git a/src/utils/io.go b/internal/utils/io.go
similarity index 98%
rename from src/utils/io.go
rename to internal/utils/io.go
index cd0ff84a..4274de06 100644
--- a/src/utils/io.go
+++ b/internal/utils/io.go
@@ -8,7 +8,7 @@ import (
"os"
"syscall"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
)
// TODO: move to "utils/io"
diff --git a/internal/utils/must.go b/internal/utils/must.go
new file mode 100644
index 00000000..897c561b
--- /dev/null
+++ b/internal/utils/must.go
@@ -0,0 +1,8 @@
+package utils
+
+func Must[T any](v T, err error) T {
+ if err != nil {
+ panic(err)
+ }
+ return v
+}
diff --git a/src/utils/schema.go b/internal/utils/schema.go
similarity index 100%
rename from src/utils/schema.go
rename to internal/utils/schema.go
diff --git a/src/utils/serialization.go b/internal/utils/serialization.go
similarity index 99%
rename from src/utils/serialization.go
rename to internal/utils/serialization.go
index bfff55e1..b5267526 100644
--- a/src/utils/serialization.go
+++ b/internal/utils/serialization.go
@@ -8,7 +8,7 @@ import (
"strings"
"github.com/santhosh-tekuri/jsonschema"
- E "github.com/yusing/go-proxy/error"
+ E "github.com/yusing/go-proxy/internal/error"
"gopkg.in/yaml.v3"
)
diff --git a/src/utils/string.go b/internal/utils/string.go
similarity index 100%
rename from src/utils/string.go
rename to internal/utils/string.go
diff --git a/src/utils/testing/testing.go b/internal/utils/testing/testing.go
similarity index 100%
rename from src/utils/testing/testing.go
rename to internal/utils/testing/testing.go
diff --git a/internal/watcher/config_file_watcher.go b/internal/watcher/config_file_watcher.go
new file mode 100644
index 00000000..ddb3ea82
--- /dev/null
+++ b/internal/watcher/config_file_watcher.go
@@ -0,0 +1,21 @@
+package watcher
+
+import (
+ "context"
+ "sync"
+
+ "github.com/yusing/go-proxy/internal/common"
+)
+
+var configDirWatcher *dirWatcher
+var configDirWatcherMu sync.Mutex
+
+// create a new file watcher for file under ConfigBasePath
+func NewConfigFileWatcher(filename string) Watcher {
+ configDirWatcherMu.Lock()
+ defer configDirWatcherMu.Unlock()
+ if configDirWatcher == nil {
+ configDirWatcher = NewDirectoryWatcher(context.Background(), common.ConfigBasePath)
+ }
+ return configDirWatcher.Add(filename)
+}
diff --git a/internal/watcher/directory_watcher.go b/internal/watcher/directory_watcher.go
new file mode 100644
index 00000000..acbd7b3f
--- /dev/null
+++ b/internal/watcher/directory_watcher.go
@@ -0,0 +1,146 @@
+package watcher
+
+import (
+ "context"
+ "errors"
+ "strings"
+ "sync"
+
+ "github.com/fsnotify/fsnotify"
+ "github.com/sirupsen/logrus"
+ E "github.com/yusing/go-proxy/internal/error"
+ F "github.com/yusing/go-proxy/internal/utils/functional"
+ "github.com/yusing/go-proxy/internal/watcher/events"
+)
+
+type dirWatcher struct {
+ dir string
+ w *fsnotify.Watcher
+
+ fwMap F.Map[string, *fileWatcher]
+ mu sync.Mutex
+
+ eventCh chan Event
+ errCh chan E.NestedError
+
+ ctx context.Context
+}
+
+func NewDirectoryWatcher(ctx context.Context, dirPath string) *dirWatcher {
+ //! subdirectories are not watched
+ w, err := fsnotify.NewWatcher()
+ if err != nil {
+ logrus.Panicf("unable to create fs watcher: %s", err)
+ }
+ if err = w.Add(dirPath); err != nil {
+ logrus.Panicf("unable to create fs watcher: %s", err)
+ }
+ helper := &dirWatcher{
+ dir: dirPath,
+ w: w,
+ fwMap: F.NewMapOf[string, *fileWatcher](),
+ eventCh: make(chan Event),
+ errCh: make(chan E.NestedError),
+ ctx: ctx,
+ }
+ go helper.start()
+ return helper
+}
+
+func (h *dirWatcher) Events(_ context.Context) (<-chan Event, <-chan E.NestedError) {
+ return h.eventCh, h.errCh
+}
+
+func (h *dirWatcher) Add(relPath string) *fileWatcher {
+ h.mu.Lock()
+ defer h.mu.Unlock()
+
+ // check if the watcher already exists
+ s, ok := h.fwMap.Load(relPath)
+ if ok {
+ return s
+ }
+ s = &fileWatcher{
+ relPath: relPath,
+ eventCh: make(chan Event),
+ errCh: make(chan E.NestedError),
+ }
+ go func() {
+ defer func() {
+ close(s.eventCh)
+ close(s.errCh)
+ }()
+ for {
+ select {
+ case <-h.ctx.Done():
+ return
+ case _, ok := <-h.eventCh:
+ if !ok { // directory watcher closed
+ return
+ }
+ }
+ }
+ }()
+ h.fwMap.Store(relPath, s)
+ return s
+}
+
+func (h *dirWatcher) start() {
+ defer close(h.eventCh)
+ defer h.w.Close()
+
+ for {
+ select {
+ case <-h.ctx.Done():
+ return
+ case fsEvent, ok := <-h.w.Events:
+ if !ok {
+ return
+ }
+ // retrieve the watcher
+ relPath := strings.TrimPrefix(fsEvent.Name, h.dir)
+ relPath = strings.TrimPrefix(relPath, "/")
+
+ msg := Event{
+ Type: events.EventTypeFile,
+ ActorName: relPath,
+ }
+ switch {
+ case fsEvent.Has(fsnotify.Write):
+ msg.Action = events.ActionFileWritten
+ case fsEvent.Has(fsnotify.Create):
+ msg.Action = events.ActionFileCreated
+ case fsEvent.Has(fsnotify.Remove):
+ msg.Action = events.ActionFileDeleted
+ case fsEvent.Has(fsnotify.Rename):
+ msg.Action = events.ActionFileRenamed
+ default: // ignore other events
+ continue
+ }
+
+ // send event to directory watcher
+ select {
+ case h.eventCh <- msg:
+ default:
+ }
+
+ // send event to file watcher too
+ w, ok := h.fwMap.Load(relPath)
+ if ok {
+ select {
+ case w.eventCh <- msg:
+ default:
+ }
+ }
+ case err := <-h.w.Errors:
+ if errors.Is(err, fsnotify.ErrClosed) {
+ // closed manually?
+ return
+ }
+ select {
+ case h.errCh <- E.From(err):
+ default:
+ }
+ }
+ }
+}
diff --git a/src/watcher/docker_watcher.go b/internal/watcher/docker_watcher.go
similarity index 96%
rename from src/watcher/docker_watcher.go
rename to internal/watcher/docker_watcher.go
index c3c686ee..d9c6a999 100644
--- a/src/watcher/docker_watcher.go
+++ b/internal/watcher/docker_watcher.go
@@ -8,9 +8,9 @@ import (
docker_events "github.com/docker/docker/api/types/events"
"github.com/docker/docker/api/types/filters"
"github.com/sirupsen/logrus"
- D "github.com/yusing/go-proxy/docker"
- E "github.com/yusing/go-proxy/error"
- "github.com/yusing/go-proxy/watcher/events"
+ D "github.com/yusing/go-proxy/internal/docker"
+ E "github.com/yusing/go-proxy/internal/error"
+ "github.com/yusing/go-proxy/internal/watcher/events"
)
type (
diff --git a/src/watcher/events/events.go b/internal/watcher/events/events.go
similarity index 84%
rename from src/watcher/events/events.go
rename to internal/watcher/events/events.go
index a892f890..87de55d1 100644
--- a/src/watcher/events/events.go
+++ b/internal/watcher/events/events.go
@@ -9,9 +9,9 @@ import (
type (
Event struct {
Type EventType
- ActorName string
- ActorID string
- ActorAttributes map[string]string
+ ActorName string // docker: container name, file: relative file path
+ ActorID string // docker: container id, file: empty
+ ActorAttributes map[string]string // docker: container labels, file: empty
Action Action
}
Action uint16
@@ -19,9 +19,10 @@ type (
)
const (
- ActionFileModified Action = (1 << iota)
+ ActionFileWritten Action = (1 << iota)
ActionFileCreated
ActionFileDeleted
+ ActionFileRenamed
ActionContainerCreate
ActionContainerStart
diff --git a/internal/watcher/file_watcher.go b/internal/watcher/file_watcher.go
new file mode 100644
index 00000000..4bd310b8
--- /dev/null
+++ b/internal/watcher/file_watcher.go
@@ -0,0 +1,17 @@
+package watcher
+
+import (
+ "context"
+
+ E "github.com/yusing/go-proxy/internal/error"
+)
+
+type fileWatcher struct {
+ relPath string
+ eventCh chan Event
+ errCh chan E.NestedError
+}
+
+func (fw *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) {
+ return fw.eventCh, fw.errCh
+}
diff --git a/src/watcher/watcher.go b/internal/watcher/watcher.go
similarity index 61%
rename from src/watcher/watcher.go
rename to internal/watcher/watcher.go
index 17b70dd8..fa39951d 100644
--- a/src/watcher/watcher.go
+++ b/internal/watcher/watcher.go
@@ -3,8 +3,8 @@ package watcher
import (
"context"
- E "github.com/yusing/go-proxy/error"
- "github.com/yusing/go-proxy/watcher/events"
+ E "github.com/yusing/go-proxy/internal/error"
+ "github.com/yusing/go-proxy/internal/watcher/events"
)
type Event = events.Event
diff --git a/src/api/handler.go b/src/api/handler.go
deleted file mode 100644
index d25d47da..00000000
--- a/src/api/handler.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package api
-
-import (
- "net/http"
-
- v1 "github.com/yusing/go-proxy/api/v1"
- "github.com/yusing/go-proxy/config"
-)
-
-func NewHandler(cfg *config.Config) http.Handler {
- mux := http.NewServeMux()
- mux.HandleFunc("GET /v1", v1.Index)
- mux.HandleFunc("GET /v1/checkhealth", wrap(cfg, v1.CheckHealth))
- mux.HandleFunc("HEAD /v1/checkhealth", wrap(cfg, v1.CheckHealth))
- mux.HandleFunc("POST /v1/reload", wrap(cfg, v1.Reload))
- mux.HandleFunc("GET /v1/list", wrap(cfg, v1.List))
- mux.HandleFunc("GET /v1/list/{what}", wrap(cfg, v1.List))
- mux.HandleFunc("GET /v1/file", v1.GetFileContent)
- mux.HandleFunc("GET /v1/file/{filename}", v1.GetFileContent)
- mux.HandleFunc("POST /v1/file/{filename}", v1.SetFileContent)
- mux.HandleFunc("PUT /v1/file/{filename}", v1.SetFileContent)
- mux.HandleFunc("GET /v1/stats", wrap(cfg, v1.Stats))
- return mux
-}
-
-func wrap(cfg *config.Config, f func(cfg *config.Config, w http.ResponseWriter, r *http.Request)) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- f(cfg, w, r)
- }
-}
diff --git a/src/api/v1/reload.go b/src/api/v1/reload.go
deleted file mode 100644
index 44efaf59..00000000
--- a/src/api/v1/reload.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package v1
-
-import (
- "net/http"
-
- U "github.com/yusing/go-proxy/api/v1/utils"
- "github.com/yusing/go-proxy/config"
-)
-
-func Reload(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
- if err := cfg.Reload().Error(); err != nil {
- U.HandleErr(w, r, err)
- return
- }
- w.WriteHeader(http.StatusOK)
-}
diff --git a/src/api/v1/utils/net.go b/src/api/v1/utils/net.go
deleted file mode 100644
index a9c7743d..00000000
--- a/src/api/v1/utils/net.go
+++ /dev/null
@@ -1,62 +0,0 @@
-package utils
-
-import (
- "crypto/tls"
- "fmt"
- "net"
- "net/http"
-
- "github.com/yusing/go-proxy/common"
- E "github.com/yusing/go-proxy/error"
-)
-
-func IsSiteHealthy(url string) bool {
- // try HEAD first
- // if HEAD is not allowed, try GET
- resp, err := HttpClient.Head(url)
- if resp != nil {
- resp.Body.Close()
- }
- if err != nil && resp != nil && resp.StatusCode == http.StatusMethodNotAllowed {
- _, err = HttpClient.Get(url)
- }
- if resp != nil {
- resp.Body.Close()
- }
- return err == nil
-}
-
-func IsStreamHealthy(scheme, address string) bool {
- conn, err := net.DialTimeout(scheme, address, common.DialTimeout)
- if err != nil {
- return false
- }
- conn.Close()
- return true
-}
-
-func ReloadServer() E.NestedError {
- resp, err := HttpClient.Post(fmt.Sprintf("http://localhost%v/reload", common.APIHTTPAddr), "", nil)
- if err != nil {
- return E.From(err)
- }
- defer resp.Body.Close()
- if resp.StatusCode != http.StatusOK {
- return E.Failure("server reload").Subjectf("status code: %v", resp.StatusCode)
- }
- return nil
-}
-
-var HttpClient = &http.Client{
- Timeout: common.ConnectionTimeout,
- Transport: &http.Transport{
- Proxy: http.ProxyFromEnvironment,
- DisableKeepAlives: true,
- ForceAttemptHTTP2: true,
- DialContext: (&net.Dialer{
- Timeout: common.DialTimeout,
- KeepAlive: common.KeepAlive, // this is different from DisableKeepAlives
- }).DialContext,
- TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
- },
-}
diff --git a/src/common/env.go b/src/common/env.go
deleted file mode 100644
index ed9ac593..00000000
--- a/src/common/env.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package common
-
-import (
- "net"
- "os"
-
- "github.com/sirupsen/logrus"
- U "github.com/yusing/go-proxy/utils"
-)
-
-var (
- NoSchemaValidation = GetEnvBool("GOPROXY_NO_SCHEMA_VALIDATION")
- IsDebug = GetEnvBool("GOPROXY_DEBUG")
- ProxyHTTPAddr = GetEnv("GOPROXY_HTTP_ADDR", ":80")
- ProxyHTTPSAddr = GetEnv("GOPROXY_HTTPS_ADDR", ":443")
- APIHTTPAddr = GetEnv("GOPROXY_API_ADDR", "127.0.0.1:8888")
-
- ProxyHTTPPort = getPort(ProxyHTTPAddr)
- ProxyHTTPSPort = getPort(ProxyHTTPSAddr)
- ProxyAPIPort = getPort(APIHTTPAddr)
-)
-
-func GetEnvBool(key string) bool {
- return U.ParseBool(os.Getenv(key))
-}
-
-func GetEnv(key string, defaultValue string) string {
- value, ok := os.LookupEnv(key)
- if !ok {
- value = defaultValue
- }
- return value
-}
-
-func getPort(addr string) string {
- _, port, err := net.SplitHostPort(addr)
- if err != nil {
- logrus.Fatalf("Invalid address: %s", addr)
- }
- return port
-}
diff --git a/src/watcher/file_watcher.go b/src/watcher/file_watcher.go
deleted file mode 100644
index 2ce79398..00000000
--- a/src/watcher/file_watcher.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package watcher
-
-import (
- "context"
- "path"
-
- "github.com/yusing/go-proxy/common"
- E "github.com/yusing/go-proxy/error"
-)
-
-type fileWatcher struct {
- filename string
-}
-
-func NewFileWatcher(filename string) Watcher {
- if path.Base(filename) != filename {
- panic("filename must be a relative path")
- }
- return &fileWatcher{filename: filename}
-}
-
-func (f *fileWatcher) Events(ctx context.Context) (<-chan Event, <-chan E.NestedError) {
- if fwHelper == nil {
- fwHelper = newFileWatcherHelper(common.ConfigBasePath)
- }
- return fwHelper.Add(ctx, f)
-}
-
-var fwHelper *fileWatcherHelper
diff --git a/src/watcher/file_watcher_helper.go b/src/watcher/file_watcher_helper.go
deleted file mode 100644
index 351b7035..00000000
--- a/src/watcher/file_watcher_helper.go
+++ /dev/null
@@ -1,124 +0,0 @@
-package watcher
-
-import (
- "context"
- "errors"
- "path"
- "sync"
-
- "github.com/fsnotify/fsnotify"
- "github.com/sirupsen/logrus"
- E "github.com/yusing/go-proxy/error"
- "github.com/yusing/go-proxy/watcher/events"
-)
-
-type fileWatcherHelper struct {
- w *fsnotify.Watcher
- m map[string]*fileWatcherStream
- wg sync.WaitGroup
- mu sync.Mutex
-}
-
-type fileWatcherStream struct {
- *fileWatcher
- stopped chan struct{}
- eventCh chan Event
- errCh chan E.NestedError
-}
-
-func newFileWatcherHelper(dirPath string) *fileWatcherHelper {
- w, err := fsnotify.NewWatcher()
- if err != nil {
- logrus.Panicf("unable to create fs watcher: %s", err)
- }
- if err = w.Add(dirPath); err != nil {
- logrus.Panicf("unable to create fs watcher: %s", err)
- }
- helper := &fileWatcherHelper{
- w: w,
- m: make(map[string]*fileWatcherStream),
- }
- go helper.start()
- return helper
-}
-
-func (h *fileWatcherHelper) Add(ctx context.Context, w *fileWatcher) (<-chan Event, <-chan E.NestedError) {
- h.mu.Lock()
- defer h.mu.Unlock()
-
- // check if the watcher already exists
- s, ok := h.m[w.filename]
- if ok {
- return s.eventCh, s.errCh
- }
- s = &fileWatcherStream{
- fileWatcher: w,
- stopped: make(chan struct{}),
- eventCh: make(chan Event),
- errCh: make(chan E.NestedError),
- }
- go func() {
- for {
- select {
- case <-ctx.Done():
- s.stopped <- struct{}{}
- case <-s.stopped:
- h.mu.Lock()
- defer h.mu.Unlock()
- close(s.eventCh)
- close(s.errCh)
- delete(h.m, w.filename)
- return
- }
- }
- }()
- h.m[w.filename] = s
- return s.eventCh, s.errCh
-}
-
-func (h *fileWatcherHelper) start() {
- defer h.wg.Done()
-
- for {
- select {
- case fsEvent, ok := <-h.w.Events:
- if !ok {
- // closed manually?
- fsLogger.Error("channel closed")
- return
- }
- // retrieve the watcher
- w, ok := h.m[path.Base(fsEvent.Name)]
- if !ok {
- // watcher for this file does not exist
- continue
- }
-
- msg := Event{
- Type: events.EventTypeFile,
- ActorName: w.filename,
- }
- switch {
- case fsEvent.Has(fsnotify.Create):
- msg.Action = events.ActionFileCreated
- case fsEvent.Has(fsnotify.Write):
- msg.Action = events.ActionFileModified
- case fsEvent.Has(fsnotify.Remove), fsEvent.Has(fsnotify.Rename):
- msg.Action = events.ActionFileDeleted
- default: // ignore other events
- continue
- }
-
- // send event
- w.eventCh <- msg
- case err := <-h.w.Errors:
- if errors.Is(err, fsnotify.ErrClosed) {
- // closed manually?
- return
- }
- fsLogger.Error(err)
- }
- }
-}
-
-var fsLogger = logrus.WithField("module", "fsnotify")