mirror of
https://github.com/yusing/godoxy.git
synced 2025-05-19 20:32:35 +02:00
restructured the project to comply community guideline, for others check release note
This commit is contained in:
parent
4120fd8d1c
commit
90487bfde6
134 changed files with 1110 additions and 625 deletions
15
.github/workflows/docker-image.yml
vendored
15
.github/workflows/docker-image.yml
vendored
|
@ -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"
|
||||
|
|
6
.gitignore
vendored
6
.gitignore
vendored
|
@ -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
|
||||
|
||||
|
|
10
Dockerfile
10
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/
|
||||
|
|
12
Makefile
12
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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
|
||||
<html>
|
||||
<head>
|
||||
<title>404 Not Found</title>
|
||||
<link type="text/css" rel="stylesheet" href="/$gperrorpage/style.css" />
|
||||
</head>
|
||||
<body>
|
||||
...
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
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 `<statusCode>.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
|
||||
```
|
||||
```
|
||||
|
||||
[🔼Back to top](#table-of-content)
|
|
@ -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
|
|
@ -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=
|
5
go.work
5
go.work
|
@ -1,5 +0,0 @@
|
|||
go 1.22.0
|
||||
|
||||
toolchain go1.23.1
|
||||
|
||||
use ./src
|
57
internal/api/handler.go
Normal file
57
internal/api/handler.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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) {
|
88
internal/api/v1/error_page/error_page.go
Normal file
88
internal/api/v1/error_page/error_page.go
Normal file
|
@ -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 <statusCode>.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]()
|
25
internal/api/v1/error_page/http_handler.go
Normal file
25
internal/api/v1/error_page/http_handler.go
Normal file
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
16
internal/api/v1/reload.go
Normal file
16
internal/api/v1/reload.go
Normal file
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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
|
33
internal/api/v1/utils/health_check.go
Normal file
33
internal/api/v1/utils/health_check.go
Normal file
|
@ -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
|
||||
}
|
23
internal/api/v1/utils/http_client.go
Normal file
23
internal/api/v1/utils/http_client.go
Normal file
|
@ -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},
|
||||
},
|
||||
}
|
31
internal/api/v1/utils/localhost.go
Normal file
31
internal/api/v1/utils/localhost.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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 {
|
|
@ -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
|
|
@ -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 {
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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) {
|
|
@ -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 {
|
|
@ -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 (
|
55
internal/common/env.go
Normal file
55
internal/common/env.go
Normal file
|
@ -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
|
||||
}
|
|
@ -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{}{}
|
||||
}
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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 {
|
|
@ -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"
|
|
@ -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() {
|
|
@ -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 (
|
|
@ -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) {
|
|
@ -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"
|
||||
)
|
||||
|
||||
/*
|
|
@ -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"
|
||||
)
|
||||
|
|
@ -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 {
|
|
@ -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) {
|
|
@ -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) {
|
|
@ -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)
|
|
@ -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) {
|
32
internal/http/content_type.go
Normal file
32
internal/http/content_type.go
Normal file
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
7
internal/http/status_code.go
Normal file
7
internal/http/status_code.go
Normal file
|
@ -0,0 +1,7 @@
|
|||
package http
|
||||
|
||||
import "net/http"
|
||||
|
||||
func IsSuccess(status int) bool {
|
||||
return status >= http.StatusOK && status < http.StatusMultipleChoices
|
||||
}
|
|
@ -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 (
|
|
@ -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 (
|
|
@ -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) {
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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{
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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 {
|
|
@ -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{
|
|
@ -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 {
|
|
@ -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) {
|
|
@ -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 {
|
|
@ -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"}
|
|
@ -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)
|
||||
}
|
|
@ -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 (
|
|
@ -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)
|
70
internal/route/middleware/custom_error_page.go
Normal file
70
internal/route/middleware/custom_error_page.go
Normal file
|
@ -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")
|
|
@ -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 {
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
|
@ -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 (
|
|
@ -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) {
|
|
@ -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 (
|
|
@ -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) {
|
|
@ -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{
|
|
@ -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) {
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue