restructured the project to comply community guideline, for others check release note

This commit is contained in:
yusing 2024-09-28 09:51:34 +08:00
parent 4120fd8d1c
commit 90487bfde6
134 changed files with 1110 additions and 625 deletions

View file

@ -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
View file

@ -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

View file

@ -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/

View file

@ -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

View file

@ -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

View file

@ -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
}

View file

@ -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)

View file

@ -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

View file

@ -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=

View file

@ -1,5 +0,0 @@
go 1.22.0
toolchain go1.23.1
use ./src

57
internal/api/handler.go Normal file
View 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)
}
}

View file

@ -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) {

View 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]()

View 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)
}

View file

@ -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
}

View file

@ -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
View 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)
}
}

View file

@ -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)
}
}

View file

@ -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

View 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
}

View 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},
},
}

View 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
}

View file

@ -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 {

View file

@ -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

View file

@ -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 {

View file

@ -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"
)

View file

@ -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) {

View file

@ -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 {

View file

@ -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
View 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
}

View file

@ -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{}{}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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"

View file

@ -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() {

View file

@ -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 (

View file

@ -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) {

View file

@ -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"
)
/*

View file

@ -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"
)

View file

@ -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 {

View file

@ -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) {

View file

@ -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) {

View file

@ -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)

View file

@ -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) {

View 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"
}

View file

@ -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
}

View file

@ -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)

View file

@ -0,0 +1,7 @@
package http
import "net/http"
func IsSuccess(status int) bool {
return status >= http.StatusOK && status < http.StatusMultipleChoices
}

View file

@ -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 (

View file

@ -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 (

View file

@ -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) {

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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{

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 {

View file

@ -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{

View file

@ -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 {

View file

@ -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) {

View file

@ -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 {

View file

@ -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"}

View file

@ -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)
}

View file

@ -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 (

View file

@ -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)

View 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")

View file

@ -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 {

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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 (

View file

@ -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) {

View file

@ -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 (

View file

@ -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) {

View file

@ -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{

View file

@ -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