mirror of
https://github.com/yusing/godoxy.git
synced 2025-07-08 07:24:04 +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: |
|
run: |
|
||||||
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ steps.meta.outputs.version }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
|
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
|
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*/
|
config*/
|
||||||
certs*/
|
certs*/
|
||||||
bin/
|
bin/
|
||||||
|
error_pages/
|
||||||
templates/codemirror/
|
|
||||||
|
|
||||||
logs/
|
logs/
|
||||||
log/
|
log/
|
||||||
|
@ -13,7 +12,8 @@ log/
|
||||||
|
|
||||||
go.work.sum
|
go.work.sum
|
||||||
|
|
||||||
!src/**/
|
!cmd/**/
|
||||||
|
!internal/**/
|
||||||
|
|
||||||
todo.md
|
todo.md
|
||||||
|
|
||||||
|
|
10
Dockerfile
10
Dockerfile
|
@ -5,7 +5,7 @@ RUN apk add --no-cache tzdata
|
||||||
WORKDIR /src
|
WORKDIR /src
|
||||||
|
|
||||||
# Only copy go.mod and go.sum initially for better caching
|
# 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
|
# Utilize build cache
|
||||||
RUN --mount=type=cache,target="/go/pkg/mod" \
|
RUN --mount=type=cache,target="/go/pkg/mod" \
|
||||||
|
@ -16,8 +16,10 @@ ENV GOCACHE=/root/.cache/go-build
|
||||||
# Build the application with better caching
|
# Build the application with better caching
|
||||||
RUN --mount=type=cache,target="/go/pkg/mod" \
|
RUN --mount=type=cache,target="/go/pkg/mod" \
|
||||||
--mount=type=cache,target="/root/.cache/go-build" \
|
--mount=type=cache,target="/root/.cache/go-build" \
|
||||||
--mount=type=bind,src=src,dst=/src \
|
--mount=type=bind,src=cmd,dst=/src/cmd \
|
||||||
CGO_ENABLED=0 GOOS=linux go build -ldflags '-w -s' -pgo=auto -o /go-proxy .
|
--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
|
# Stage 2: Final image
|
||||||
FROM scratch
|
FROM scratch
|
||||||
|
@ -28,7 +30,7 @@ LABEL maintainer="yusing@6uo.me"
|
||||||
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
COPY --from=builder /usr/share/zoneinfo /usr/share/zoneinfo
|
||||||
|
|
||||||
# copy binary
|
# copy binary
|
||||||
COPY --from=builder /go-proxy /app/
|
COPY --from=builder /app /
|
||||||
|
|
||||||
# copy schema directory
|
# copy schema directory
|
||||||
COPY schema/ /app/schema/
|
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
|
.PHONY: all setup build test up restart logs get debug run archive repush rapid-crash debug-list-containers
|
||||||
|
|
||||||
all: debug
|
all: debug
|
||||||
|
@ -10,10 +12,10 @@ setup:
|
||||||
build:
|
build:
|
||||||
mkdir -p bin
|
mkdir -p bin
|
||||||
CGO_ENABLED=0 GOOS=linux \
|
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:
|
test:
|
||||||
go test ./src/...
|
go test ./internal/...
|
||||||
|
|
||||||
up:
|
up:
|
||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
@ -25,13 +27,13 @@ logs:
|
||||||
docker compose logs -f
|
docker compose logs -f
|
||||||
|
|
||||||
get:
|
get:
|
||||||
cd src && go get -u && go mod tidy && cd ..
|
cd cmd && go get -u && go mod tidy && cd ..
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
make build && sudo GOPROXY_DEBUG=1 bin/go-proxy
|
make BUILD_FLAG="" build && sudo GOPROXY_DEBUG=1 bin/go-proxy
|
||||||
|
|
||||||
run:
|
run:
|
||||||
BUILD_FLAG="-s -w" make build && sudo bin/go-proxy
|
make build && sudo bin/go-proxy
|
||||||
|
|
||||||
archive:
|
archive:
|
||||||
git archive HEAD -o ../go-proxy-$$(date +"%Y%m%d%H%M").zip
|
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
|
- Auto hot-reload on container state / config file changes
|
||||||
- **idlesleeper**: stop containers on idle, wake it up on traffic _(optional, see [showcase](#idlesleeper))_
|
- **idlesleeper**: stop containers on idle, wake it up on traffic _(optional, see [showcase](#idlesleeper))_
|
||||||
- HTTP(s) reserve proxy
|
- 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
|
- 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))
|
- 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
|
- Supports linux/amd64, linux/arm64, linux/arm/v7, linux/arm/v6 multi-platform
|
||||||
|
|
|
@ -16,23 +16,24 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/api"
|
"github.com/yusing/go-proxy/internal"
|
||||||
apiUtils "github.com/yusing/go-proxy/api/v1/utils"
|
"github.com/yusing/go-proxy/internal/api"
|
||||||
"github.com/yusing/go-proxy/common"
|
apiUtils "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||||
"github.com/yusing/go-proxy/config"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/docker"
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
"github.com/yusing/go-proxy/docker/idlewatcher"
|
"github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
||||||
R "github.com/yusing/go-proxy/route"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"github.com/yusing/go-proxy/server"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
"github.com/yusing/go-proxy/internal/server"
|
||||||
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
args := common.GetArgs()
|
args := common.GetArgs()
|
||||||
|
|
||||||
if args.Command == common.CommandSetup {
|
if args.Command == common.CommandSetup {
|
||||||
Setup()
|
internal.Setup()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
- [Table of content](#table-of-content)
|
- [Table of content](#table-of-content)
|
||||||
- [Available middlewares](#available-middlewares)
|
- [Available middlewares](#available-middlewares)
|
||||||
- [Redirect http](#redirect-http)
|
- [Redirect http](#redirect-http)
|
||||||
|
- [Custom error pages](#custom-error-pages)
|
||||||
- [Modify request or response](#modify-request-or-response)
|
- [Modify request or response](#modify-request-or-response)
|
||||||
- [Set headers](#set-headers)
|
- [Set headers](#set-headers)
|
||||||
- [Add headers](#add-headers)
|
- [Add headers](#add-headers)
|
||||||
|
@ -48,6 +49,56 @@ server {
|
||||||
|
|
||||||
[🔼Back to top](#table-of-content)
|
[🔼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
|
### Modify request or response
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -89,6 +140,8 @@ location / {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
||||||
#### Add headers
|
#### Add headers
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -114,6 +167,8 @@ location / {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
||||||
#### Hide headers
|
#### Hide headers
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
|
@ -171,6 +226,8 @@ app1:
|
||||||
set_x_forwarded:
|
set_x_forwarded:
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
||||||
### Forward Authorization header (experimental)
|
### Forward Authorization header (experimental)
|
||||||
|
|
||||||
Fields:
|
Fields:
|
||||||
|
@ -226,6 +283,8 @@ http:
|
||||||
- session_id
|
- session_id
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
### Authentik
|
### Authentik
|
||||||
|
@ -242,4 +301,6 @@ services:
|
||||||
proxy.authentik.middlewares.set_x_forwarded:
|
proxy.authentik.middlewares.set_x_forwarded:
|
||||||
proxy.authentik.middlewares.modify_request.add_headers: |
|
proxy.authentik.middlewares.modify_request.add_headers: |
|
||||||
Strict-Transport-Security: "max-age=63072000" always
|
Strict-Transport-Security: "max-age=63072000" always
|
||||||
```
|
```
|
||||||
|
|
||||||
|
[🔼Back to top](#table-of-content)
|
|
@ -17,7 +17,7 @@ require (
|
||||||
require (
|
require (
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // 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/containerd/log v0.1.0 // indirect
|
||||||
github.com/distribution/reference v0.6.0 // indirect
|
github.com/distribution/reference v0.6.0 // indirect
|
||||||
github.com/docker/go-connections v0.5.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
|
@ -39,9 +39,9 @@ require (
|
||||||
github.com/pkg/errors v0.9.1 // indirect
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.55.0 // 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 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/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
|
go.opentelemetry.io/otel/trace v1.30.0 // indirect
|
||||||
golang.org/x/crypto v0.27.0 // indirect
|
golang.org/x/crypto v0.27.0 // indirect
|
||||||
golang.org/x/mod v0.21.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/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 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
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.106.0 h1:q41gC5Wc1nfi0D1ZhSHokWcd9mGMbqC7RE7qiP+qE00=
|
||||||
github.com/cloudflare/cloudflare-go v0.105.0/go.mod h1:pfUQ4PIG4ISI0/Mmc21Bp86UnFU0ktmPf3iTgbSL+cM=
|
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 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
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=
|
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-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 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
|
||||||
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
|
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/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.0/go.mod h1:qmOFXW2epJhM0qSnUUYpldc7gVz2KMQwJ/QYCDIa7XU=
|
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 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc=
|
||||||
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/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/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 h1:F2t8sK4qf1fAmY9ua4ohFS/K+FUuOPemHUIXHtktrts=
|
||||||
go.opentelemetry.io/otel v1.30.0/go.mod h1:tFw4Br9b7fOS+uEao81PJjVMjW/5fvNCbpsDIXqP0pc=
|
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.30.0 h1:lsInsfvhVIfOI6qHVyysXMNDnjO9Npvl7tlDPJFBVd4=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.24.0/go.mod h1:iSDOcsnSA5INXzZtwaBPrKp/lWu/V14Dd+llD0oI2EA=
|
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.24.0 h1:Xw8U6u2f8DK2XAkGRFV7BBLENgnTGX9i4rQRxJf+/vs=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.30.0 h1:umZgi92IyxfXd/l4kaDhnKgY8rnN/cZcF1LKc6I8OQ8=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.24.0/go.mod h1:6KW1Fm6R/s6Z3PGXwSJN2K4eT6wQB3vXX6CVnYX9NmM=
|
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 h1:4xNulvn9gjzo4hjg+wzIKG7iNFEaBMX00Qd4QIZs7+w=
|
||||||
go.opentelemetry.io/otel/metric v1.30.0/go.mod h1:aXTfST94tswhWEb+5QjlSqG+cZlmyXy/u8jFpor3WqQ=
|
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.30.0 h1:cHdik6irO49R5IysVhdn8oaiR9m8XluDaJAs4DfOrYE=
|
||||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
|
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 h1:7UBkkYzeg3C7kQX8VAidWh2biiQbtAKjyIML8dQ9wmc=
|
||||||
go.opentelemetry.io/otel/trace v1.30.0/go.mod h1:5EyKqTzzmyqB9bwtCCq6pDLktPK6fmGf/Dph+8VI02o=
|
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.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
go.opentelemetry.io/proto/otlp v1.1.0/go.mod h1:GpBHCBWiqvVLDqmHZsoMM3C5ySeKTC7ej/RNTae6MdY=
|
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-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-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
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-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/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 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-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20240311132316-a219d84964c2/go.mod h1:O1cOfN1Cy6QEYr7VxtjOyP5AdAuR0aJ/MYZaaof623Y=
|
google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU=
|
||||||
google.golang.org/grpc v1.63.1 h1:pNClQmvdlyNUiwFETOux/PYqfhmA7BrswEdGRnib1fA=
|
google.golang.org/grpc v1.66.1 h1:hO5qAXR19+/Z44hmvIM4dQFMSYX9XcWsByfoxutBpAM=
|
||||||
google.golang.org/grpc v1.63.1/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA=
|
google.golang.org/grpc v1.66.1/go.mod h1:s3/l6xSSCURdVfAnL+TqCNMyTDAGN6+lZeVxnZR128Y=
|
||||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
|
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
|
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 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 h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
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"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/api/v1/utils"
|
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||||
"github.com/yusing/go-proxy/config"
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
)
|
)
|
||||||
|
|
||||||
func CheckHealth(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
|
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"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/api/v1/utils"
|
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/config"
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
"github.com/yusing/go-proxy/proxy/provider"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
|
"github.com/yusing/go-proxy/internal/proxy/provider"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetFileContent(w http.ResponseWriter, r *http.Request) {
|
func GetFileContent(w http.ResponseWriter, r *http.Request) {
|
||||||
|
@ -37,14 +38,15 @@ func SetFileContent(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var validateErr E.NestedError
|
||||||
if filename == common.ConfigFileName {
|
if filename == common.ConfigFileName {
|
||||||
err = config.Validate(content).Error()
|
validateErr = config.Validate(content)
|
||||||
} else {
|
} else {
|
||||||
err = provider.Validate(content).Error()
|
validateErr = provider.Validate(content)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if validateErr != nil {
|
||||||
U.HandleErr(w, r, err, http.StatusBadRequest)
|
U.RespondJson(w, validateErr.JSONObject(), http.StatusBadRequest)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,10 +5,9 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/common"
|
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||||
"github.com/yusing/go-proxy/config"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
U "github.com/yusing/go-proxy/api/v1/utils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func List(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
|
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)
|
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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/api/v1/utils"
|
U "github.com/yusing/go-proxy/internal/api/v1/utils"
|
||||||
"github.com/yusing/go-proxy/config"
|
"github.com/yusing/go-proxy/internal/config"
|
||||||
"github.com/yusing/go-proxy/server"
|
"github.com/yusing/go-proxy/internal/server"
|
||||||
"github.com/yusing/go-proxy/utils"
|
"github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Stats(cfg *config.Config, w http.ResponseWriter, r *http.Request) {
|
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(),
|
"proxies": cfg.Statistics(),
|
||||||
"uptime": utils.FormatDuration(server.GetProxyServer().Uptime()),
|
"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)
|
U.HandleErr(w, r, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -6,12 +6,14 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"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) {
|
func HandleErr(w http.ResponseWriter, r *http.Request, origErr error, code ...int) {
|
||||||
err := E.From(origErr).Subjectf("%s %s", r.Method, r.URL)
|
err := E.From(origErr).Subjectf("%s %s", r.Method, r.URL)
|
||||||
logrus.WithField("module", "api").Error(err)
|
Logger.Error(err)
|
||||||
if len(code) > 0 {
|
if len(code) > 0 {
|
||||||
http.Error(w, err.String(), code[0])
|
http.Error(w, err.String(), code[0])
|
||||||
return
|
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"
|
"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")
|
w.Header().Set("Content-Type", "application/json")
|
||||||
j, err := json.Marshal(data)
|
j, err := json.MarshalIndent(data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
} else {
|
} else {
|
|
@ -7,8 +7,8 @@ import (
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/certcrypto"
|
"github.com/go-acme/lego/v4/certcrypto"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/internal/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Config M.AutoCertConfig
|
type Config M.AutoCertConfig
|
|
@ -14,9 +14,9 @@ import (
|
||||||
"github.com/go-acme/lego/v4/challenge"
|
"github.com/go-acme/lego/v4/challenge"
|
||||||
"github.com/go-acme/lego/v4/lego"
|
"github.com/go-acme/lego/v4/lego"
|
||||||
"github.com/go-acme/lego/v4/registration"
|
"github.com/go-acme/lego/v4/registration"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/internal/models"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Provider struct {
|
type Provider struct {
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
"github.com/go-acme/lego/v4/providers/dns/ovh"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"os"
|
"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) {
|
func (p *Provider) Setup(ctx context.Context) (err E.NestedError) {
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"flag"
|
"flag"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Args struct {
|
type Args struct {
|
|
@ -7,7 +7,7 @@ import (
|
||||||
const (
|
const (
|
||||||
ConnectionTimeout = 5 * time.Second
|
ConnectionTimeout = 5 * time.Second
|
||||||
DialTimeout = 3 * time.Second
|
DialTimeout = 3 * time.Second
|
||||||
KeepAlive = 5 * time.Second
|
KeepAlive = 60 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
// file, folder structure
|
// file, folder structure
|
||||||
|
@ -30,6 +30,10 @@ const (
|
||||||
ComposeExampleFileName = "compose.example.yml"
|
ComposeExampleFileName = "compose.example.yml"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
ErrorPagesBasePath = "error_pages"
|
||||||
|
)
|
||||||
|
|
||||||
const DockerHostFromEnv = "$DOCKER_HOST"
|
const DockerHostFromEnv = "$DOCKER_HOST"
|
||||||
|
|
||||||
const (
|
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"
|
"os"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/autocert"
|
"github.com/yusing/go-proxy/internal/autocert"
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/internal/models"
|
||||||
PR "github.com/yusing/go-proxy/proxy/provider"
|
PR "github.com/yusing/go-proxy/internal/proxy/provider"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
"github.com/yusing/go-proxy/watcher/events"
|
"github.com/yusing/go-proxy/internal/watcher/events"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,7 +45,7 @@ func Load() E.NestedError {
|
||||||
value: M.DefaultConfig(),
|
value: M.DefaultConfig(),
|
||||||
proxyProviders: F.NewMapOf[string, *PR.Provider](),
|
proxyProviders: F.NewMapOf[string, *PR.Provider](),
|
||||||
l: logrus.WithField("module", "config"),
|
l: logrus.WithField("module", "config"),
|
||||||
watcher: W.NewFileWatcher(common.ConfigFileName),
|
watcher: W.NewConfigFileWatcher(common.ConfigFileName),
|
||||||
reloadReq: make(chan struct{}, 1),
|
reloadReq: make(chan struct{}, 1),
|
||||||
}
|
}
|
||||||
return instance.load()
|
return instance.load()
|
||||||
|
@ -116,8 +116,9 @@ func (cfg *Config) WatchChanges() {
|
||||||
case <-cfg.watcherCtx.Done():
|
case <-cfg.watcherCtx.Done():
|
||||||
return
|
return
|
||||||
case event := <-eventCh:
|
case event := <-eventCh:
|
||||||
if event.Action == events.ActionFileDeleted {
|
if event.Action == events.ActionFileDeleted || event.Action == events.ActionFileRenamed {
|
||||||
cfg.stopProviders()
|
cfg.l.Error("config file deleted or renamed, ignoring...")
|
||||||
|
continue
|
||||||
} else {
|
} else {
|
||||||
cfg.reloadReq <- struct{}{}
|
cfg.reloadReq <- struct{}{}
|
||||||
}
|
}
|
|
@ -1,11 +1,11 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/internal/models"
|
||||||
PR "github.com/yusing/go-proxy/proxy/provider"
|
PR "github.com/yusing/go-proxy/internal/proxy/provider"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (cfg *Config) DumpEntries() map[string]*M.RawEntry {
|
func (cfg *Config) DumpEntries() map[string]*M.RawEntry {
|
|
@ -8,9 +8,9 @@ import (
|
||||||
"github.com/docker/cli/cli/connhelper"
|
"github.com/docker/cli/cli/connhelper"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ClientInfo struct {
|
type ClientInfo struct {
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Container struct {
|
type Container struct {
|
|
@ -19,13 +19,7 @@ type templateData struct {
|
||||||
|
|
||||||
//go:embed html/loading_page.html
|
//go:embed html/loading_page.html
|
||||||
var loadingPage []byte
|
var loadingPage []byte
|
||||||
var loadingPageTmpl = func() *template.Template {
|
var loadingPageTmpl = template.Must(template.New("loading_page").Parse(string(loadingPage)))
|
||||||
tmpl, err := template.New("loading").Parse(string(loadingPage))
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return tmpl
|
|
||||||
}()
|
|
||||||
|
|
||||||
const (
|
const (
|
||||||
htmlContentType = "text/html; charset=utf-8"
|
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) {
|
func (w *watcher) roundTrip(origRoundTrip roundTripFunc, req *http.Request) (*http.Response, error) {
|
||||||
// wake the container
|
// wake the container
|
||||||
w.wakeCh <- struct{}{}
|
select {
|
||||||
|
case w.wakeCh <- struct{}{}:
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
// target site is ready, passthrough
|
// target site is ready, passthrough
|
||||||
if w.ready.Load() {
|
if w.ready.Load() {
|
|
@ -9,11 +9,11 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
P "github.com/yusing/go-proxy/proxy"
|
P "github.com/yusing/go-proxy/internal/proxy"
|
||||||
PT "github.com/yusing/go-proxy/proxy/fields"
|
PT "github.com/yusing/go-proxy/internal/proxy/fields"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"time"
|
"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) {
|
func (c Client) Inspect(containerID string) (Container, E.NestedError) {
|
|
@ -4,9 +4,9 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
|
@ -3,7 +3,7 @@ package docker
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeLabel(namespace string, alias string, field string) string {
|
func makeLabel(namespace string, alias string, field string) string {
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNestedLabel(t *testing.T) {
|
func TestNestedLabel(t *testing.T) {
|
|
@ -3,7 +3,7 @@ package error
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBuilderEmpty(t *testing.T) {
|
func TestBuilderEmpty(t *testing.T) {
|
|
@ -1,6 +1,7 @@
|
||||||
package error
|
package error
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -13,6 +14,11 @@ type (
|
||||||
err error
|
err error
|
||||||
extras []nestedError
|
extras []nestedError
|
||||||
}
|
}
|
||||||
|
jsonNestedError struct {
|
||||||
|
Subject string
|
||||||
|
Err string
|
||||||
|
Extras []jsonNestedError
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
func From(err error) NestedError {
|
func From(err error) NestedError {
|
||||||
|
@ -22,6 +28,29 @@ func From(err error) NestedError {
|
||||||
return &nestedError{err: err}
|
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
|
// Check is a helper function that
|
||||||
// convert (T, error) to (T, NestedError).
|
// convert (T, error) to (T, NestedError).
|
||||||
func Check[T any](obj T, err error) (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
|
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 {
|
func (ne NestedError) NoError() bool {
|
||||||
return ne == nil
|
return ne == nil
|
||||||
}
|
}
|
||||||
|
@ -169,6 +215,14 @@ func errorf(format string, args ...any) NestedError {
|
||||||
return From(fmt.Errorf(format, args...))
|
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 {
|
func (ne NestedError) withError(err NestedError) NestedError {
|
||||||
if ne != nil && err != nil {
|
if ne != nil && err != nil {
|
||||||
ne.extras = append(ne.extras, *err)
|
ne.extras = append(ne.extras, *err)
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/error"
|
. "github.com/yusing/go-proxy/internal/error"
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestErrorIs(t *testing.T) {
|
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 (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
"slices"
|
||||||
|
|
||||||
gpHTTP "github.com/yusing/go-proxy/http"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func removeHop(h Header) {
|
func RemoveHop(h http.Header) {
|
||||||
reqUpType := gpHTTP.UpgradeType(h)
|
reqUpType := UpgradeType(h)
|
||||||
gpHTTP.RemoveHopByHopHeaders(h)
|
RemoveHopByHopHeaders(h)
|
||||||
|
|
||||||
if reqUpType != "" {
|
if reqUpType != "" {
|
||||||
h.Set("Connection", "Upgrade")
|
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 k, vv := range src {
|
||||||
for _, v := range vv {
|
for _, v := range vv {
|
||||||
dst.Add(k, v)
|
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 {
|
if allowed == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
|
@ -10,6 +10,7 @@ package http
|
||||||
// Copyright (c) 2024 yusing
|
// Copyright (c) 2024 yusing
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -129,20 +130,13 @@ type ReverseProxy struct {
|
||||||
// Response from the backend. It is called if the backend
|
// Response from the backend. It is called if the backend
|
||||||
// returns a response at all, with any HTTP status code.
|
// returns a response at all, with any HTTP status code.
|
||||||
// If the backend is unreachable, the optional ErrorHandler is
|
// 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
|
// If ModifyResponse returns an error, ErrorHandler is called
|
||||||
// with its error value. If ErrorHandler is nil, its default
|
// with its error value. If ErrorHandler is nil, its default
|
||||||
// implementation is used.
|
// implementation is used.
|
||||||
ModifyResponse func(*http.Response) error
|
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
|
ServeHTTP http.HandlerFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -255,9 +249,11 @@ func copyHeader(dst, src http.Header) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ReverseProxy) errorHandler(rw http.ResponseWriter, _ *http.Request, err error) {
|
func (p *ReverseProxy) errorHandler(rw http.ResponseWriter, r *http.Request, err error, writeHeader bool) {
|
||||||
logger.Errorf("http: proxy error: %s", err)
|
logger.Errorf("http proxy to %s failed: %s", r.URL.String(), err)
|
||||||
rw.WriteHeader(http.StatusBadGateway)
|
if writeHeader {
|
||||||
|
rw.WriteHeader(http.StatusBadGateway)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// modifyResponse conditionally runs the optional ModifyResponse hook
|
// 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 {
|
if err := p.ModifyResponse(res); err != nil {
|
||||||
res.Body.Close()
|
res.Body.Close()
|
||||||
p.errorHandler(rw, req, err)
|
p.errorHandler(rw, req, err, true)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -324,7 +320,7 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
reqUpType := UpgradeType(outreq.Header)
|
reqUpType := UpgradeType(outreq.Header)
|
||||||
if !IsPrint(reqUpType) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -384,8 +380,20 @@ func (p *ReverseProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
|
||||||
res, err := transport.RoundTrip(outreq)
|
res, err := transport.RoundTrip(outreq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.errorHandler(rw, outreq, err)
|
p.errorHandler(rw, outreq, err, false)
|
||||||
return
|
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)
|
// 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)
|
_, err = io.Copy(rw, res.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
defer res.Body.Close()
|
p.errorHandler(rw, req, err, true)
|
||||||
// note: removed
|
res.Body.Close()
|
||||||
// Since we're streaming the response, if we run into an error all we can do
|
return
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
res.Body.Close() // close now, instead of defer, to populate res.Trailer
|
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)
|
reqUpType := UpgradeType(req.Header)
|
||||||
resUpType := UpgradeType(res.Header)
|
resUpType := UpgradeType(res.Header)
|
||||||
if !IsPrint(resUpType) { // We know reqUpType is ASCII, it's checked by the caller.
|
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) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
backConn, ok := res.Body.(io.ReadWriteCloser)
|
backConn, ok := res.Body.(io.ReadWriteCloser)
|
||||||
if !ok {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
rc := http.NewResponseController(rw)
|
rc := http.NewResponseController(rw)
|
||||||
conn, brw, hijackErr := rc.Hijack()
|
conn, brw, hijackErr := rc.Hijack()
|
||||||
if errors.Is(hijackErr, http.ErrNotSupported) {
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -513,7 +514,7 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
|
||||||
defer close(backConnCloseCh)
|
defer close(backConnCloseCh)
|
||||||
|
|
||||||
if hijackErr != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
|
@ -523,11 +524,11 @@ func (p *ReverseProxy) handleUpgradeResponse(rw http.ResponseWriter, req *http.R
|
||||||
res.Header = rw.Header()
|
res.Header = rw.Header()
|
||||||
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
|
res.Body = nil // so res.Write only writes the headers; we have res.Body in backConn above
|
||||||
if err := res.Write(brw); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
if err := brw.Flush(); err != nil {
|
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
|
return
|
||||||
}
|
}
|
||||||
errc := make(chan error, 1)
|
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"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/common"
|
. "github.com/yusing/go-proxy/internal/common"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
|
@ -5,10 +5,10 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/internal/models"
|
||||||
T "github.com/yusing/go-proxy/proxy/fields"
|
T "github.com/yusing/go-proxy/internal/proxy/fields"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"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) {
|
func ValidateHTTPHeaders(headers map[string]string) (http.Header, E.NestedError) {
|
|
@ -1,7 +1,7 @@
|
||||||
package fields
|
package fields
|
||||||
|
|
||||||
import (
|
import (
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Host string
|
type Host string
|
|
@ -1,7 +1,7 @@
|
||||||
package fields
|
package fields
|
||||||
|
|
||||||
import (
|
import (
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PathMode string
|
type PathMode string
|
|
@ -3,7 +3,7 @@ package fields
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PathPattern string
|
type PathPattern string
|
|
@ -3,8 +3,8 @@ package fields
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
U "github.com/yusing/go-proxy/utils/testing"
|
U "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validPatterns = []string{
|
var validPatterns = []string{
|
|
@ -3,7 +3,7 @@ package fields
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Port int
|
type Port int
|
|
@ -1,7 +1,7 @@
|
||||||
package fields
|
package fields
|
||||||
|
|
||||||
import (
|
import (
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Scheme string
|
type Scheme string
|
|
@ -1,7 +1,7 @@
|
||||||
package fields
|
package fields
|
||||||
|
|
||||||
import (
|
import (
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Signal string
|
type Signal string
|
|
@ -1,7 +1,7 @@
|
||||||
package fields
|
package fields
|
||||||
|
|
||||||
import (
|
import (
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StopMethod string
|
type StopMethod string
|
|
@ -3,8 +3,8 @@ package fields
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StreamPort struct {
|
type StreamPort struct {
|
|
@ -3,8 +3,8 @@ package fields
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var validPorts = []string{
|
var validPorts = []string{
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StreamScheme struct {
|
type StreamScheme struct {
|
|
@ -3,7 +3,7 @@ package fields
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
)
|
)
|
||||||
|
|
||||||
func ValidateDurationPostitive(value string) (time.Duration, E.NestedError) {
|
func ValidateDurationPostitive(value string) (time.Duration, E.NestedError) {
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/internal/models"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
type DockerProvider struct {
|
type DockerProvider struct {
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
P "github.com/yusing/go-proxy/proxy"
|
P "github.com/yusing/go-proxy/internal/proxy"
|
||||||
T "github.com/yusing/go-proxy/proxy/fields"
|
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"}
|
var dummyNames = []string{"/a"}
|
|
@ -5,12 +5,12 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
M "github.com/yusing/go-proxy/models"
|
M "github.com/yusing/go-proxy/internal/models"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
type FileProvider struct {
|
type FileProvider struct {
|
||||||
|
@ -94,5 +94,5 @@ func (p *FileProvider) LoadRoutesImpl() (routes R.Routes, res E.NestedError) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *FileProvider) NewWatcher() W.Watcher {
|
func (p *FileProvider) NewWatcher() W.Watcher {
|
||||||
return W.NewFileWatcher(p.fileName)
|
return W.NewConfigFileWatcher(p.fileName)
|
||||||
}
|
}
|
|
@ -5,9 +5,9 @@ import (
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
R "github.com/yusing/go-proxy/route"
|
R "github.com/yusing/go-proxy/internal/route"
|
||||||
W "github.com/yusing/go-proxy/watcher"
|
W "github.com/yusing/go-proxy/internal/watcher"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
|
@ -1,7 +1,9 @@
|
||||||
package route
|
package route
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"slices"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -9,14 +11,14 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
"github.com/yusing/go-proxy/docker/idlewatcher"
|
"github.com/yusing/go-proxy/internal/docker/idlewatcher"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
. "github.com/yusing/go-proxy/http"
|
. "github.com/yusing/go-proxy/internal/http"
|
||||||
P "github.com/yusing/go-proxy/proxy"
|
P "github.com/yusing/go-proxy/internal/proxy"
|
||||||
PT "github.com/yusing/go-proxy/proxy/fields"
|
PT "github.com/yusing/go-proxy/internal/proxy/fields"
|
||||||
"github.com/yusing/go-proxy/route/middleware"
|
"github.com/yusing/go-proxy/internal/route/middleware"
|
||||||
F "github.com/yusing/go-proxy/utils/functional"
|
F "github.com/yusing/go-proxy/internal/utils/functional"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -170,10 +172,21 @@ func (u *URL) MarshalText() (text []byte, err error) {
|
||||||
func ProxyHandler(w http.ResponseWriter, r *http.Request) {
|
func ProxyHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
mux, err := findMuxFunc(r.Host)
|
mux, err := findMuxFunc(r.Host)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusNotFound)
|
|
||||||
logrus.Error(E.Failure("request").
|
logrus.Error(E.Failure("request").
|
||||||
Subjectf("%s %s", r.Method, r.URL.String()).
|
Subjectf("%s %s", r.Method, r.URL.String()).
|
||||||
With(err))
|
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
|
return
|
||||||
}
|
}
|
||||||
mux.ServeHTTP(w, r)
|
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"
|
"time"
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
gpHTTP "github.com/yusing/go-proxy/http"
|
gpHTTP "github.com/yusing/go-proxy/internal/http"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -91,7 +91,7 @@ func newForwardAuth() (fa *forwardAuth) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (fa *forwardAuth) forward(next http.Handler, w ResponseWriter, req *Request) {
|
func (fa *forwardAuth) forward(next http.Handler, w ResponseWriter, req *Request) {
|
||||||
removeHop(req.Header)
|
gpHTTP.RemoveHop(req.Header)
|
||||||
|
|
||||||
faReq, err := http.NewRequestWithContext(
|
faReq, err := http.NewRequestWithContext(
|
||||||
req.Context(),
|
req.Context(),
|
||||||
|
@ -105,10 +105,10 @@ func (fa *forwardAuth) forward(next http.Handler, w ResponseWriter, req *Request
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
copyHeader(faReq.Header, req.Header)
|
gpHTTP.CopyHeader(faReq.Header, req.Header)
|
||||||
removeHop(faReq.Header)
|
gpHTTP.RemoveHop(faReq.Header)
|
||||||
|
|
||||||
filterHeaders(faReq.Header, fa.AuthResponseHeaders)
|
gpHTTP.FilterHeaders(faReq.Header, fa.AuthResponseHeaders)
|
||||||
fa.setAuthHeaders(req, faReq)
|
fa.setAuthHeaders(req, faReq)
|
||||||
|
|
||||||
faResp, err := fa.client.Do(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 {
|
if faResp.StatusCode < http.StatusOK || faResp.StatusCode >= http.StatusMultipleChoices {
|
||||||
copyHeader(w.Header(), faResp.Header)
|
gpHTTP.CopyHeader(w.Header(), faResp.Header)
|
||||||
removeHop(w.Header())
|
gpHTTP.RemoveHop(w.Header())
|
||||||
|
|
||||||
redirectURL, err := faResp.Location()
|
redirectURL, err := faResp.Location()
|
||||||
if err != nil {
|
if err != nil {
|
|
@ -3,9 +3,9 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
gpHTTP "github.com/yusing/go-proxy/http"
|
gpHTTP "github.com/yusing/go-proxy/internal/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
||||||
|
@ -21,7 +21,7 @@ type (
|
||||||
|
|
||||||
BeforeFunc func(next http.Handler, w ResponseWriter, r *Request)
|
BeforeFunc func(next http.Handler, w ResponseWriter, r *Request)
|
||||||
RewriteFunc func(req *ProxyRequest)
|
RewriteFunc func(req *ProxyRequest)
|
||||||
ModifyResponseFunc func(res *Response) error
|
ModifyResponseFunc func(resp *Response) error
|
||||||
CloneWithOptFunc func(opts OptionsRaw, rp *ReverseProxy) (*Middleware, E.NestedError)
|
CloneWithOptFunc func(opts OptionsRaw, rp *ReverseProxy) (*Middleware, E.NestedError)
|
||||||
|
|
||||||
OptionsRaw = map[string]any
|
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) {
|
func PatchReverseProxy(rp *ReverseProxy, middlewares map[string]OptionsRaw) (res E.NestedError) {
|
||||||
befores := make([]BeforeFunc, 0, len(middlewares))
|
befores := make([]BeforeFunc, 0, len(middlewares))
|
||||||
rewrites := make([]RewriteFunc, 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")
|
invalidM := E.NewBuilder("invalid middlewares")
|
||||||
invalidOpts := E.NewBuilder("invalid options")
|
invalidOpts := E.NewBuilder("invalid options")
|
||||||
|
@ -96,7 +96,7 @@ func PatchReverseProxy(rp *ReverseProxy, middlewares map[string]OptionsRaw) (res
|
||||||
rewrites = append(rewrites, m.rewrite)
|
rewrites = append(rewrites, m.rewrite)
|
||||||
}
|
}
|
||||||
if m.modifyResponse != nil {
|
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 {
|
if len(rewrites) > 0 {
|
||||||
origRewrite := rp.Rewrite
|
if rp.Rewrite != nil {
|
||||||
|
rewrites = append([]RewriteFunc{rp.Rewrite}, rewrites...)
|
||||||
|
}
|
||||||
rp.Rewrite = func(req *ProxyRequest) {
|
rp.Rewrite = func(req *ProxyRequest) {
|
||||||
if origRewrite != nil {
|
|
||||||
origRewrite(req)
|
|
||||||
}
|
|
||||||
for _, rewrite := range rewrites {
|
for _, rewrite := range rewrites {
|
||||||
rewrite(req)
|
rewrite(req)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(modifyResponses) > 0 {
|
if len(modResps) > 0 {
|
||||||
origModifyResponse := rp.ModifyResponse
|
if rp.ModifyResponse != nil {
|
||||||
|
modResps = append([]ModifyResponseFunc{rp.ModifyResponse}, modResps...)
|
||||||
|
}
|
||||||
rp.ModifyResponse = func(res *Response) error {
|
rp.ModifyResponse = func(res *Response) error {
|
||||||
if origModifyResponse != nil {
|
b := E.NewBuilder("errors in middleware ModifyResponse")
|
||||||
return origModifyResponse(res)
|
for _, mr := range modResps {
|
||||||
|
b.AddE(mr(res))
|
||||||
}
|
}
|
||||||
for _, modifyResponse := range modifyResponses {
|
return b.Build().Error()
|
||||||
if err := modifyResponse(res); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
)
|
)
|
||||||
|
|
||||||
var middlewares map[string]*Middleware
|
var middlewares map[string]*Middleware
|
||||||
|
@ -14,15 +14,17 @@ func Get(name string) (middleware *Middleware, ok bool) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// initialize middleware names
|
// initialize middleware names and label parsers
|
||||||
func init() {
|
func init() {
|
||||||
middlewares = map[string]*Middleware{
|
middlewares = map[string]*Middleware{
|
||||||
"set_x_forwarded": SetXForwarded,
|
"set_x_forwarded": SetXForwarded,
|
||||||
"add_x_forwarded": AddXForwarded,
|
"add_x_forwarded": AddXForwarded,
|
||||||
"redirect_http": RedirectHTTP,
|
"redirect_http": RedirectHTTP,
|
||||||
"forward_auth": ForwardAuth.m,
|
"forward_auth": ForwardAuth.m,
|
||||||
"modify_response": ModifyResponse.m,
|
"modify_response": ModifyResponse.m,
|
||||||
"modify_request": ModifyRequest.m,
|
"modify_request": ModifyRequest.m,
|
||||||
|
"error_page": CustomErrorPage,
|
||||||
|
"custom_error_page": CustomErrorPage,
|
||||||
}
|
}
|
||||||
names := make(map[*Middleware][]string)
|
names := make(map[*Middleware][]string)
|
||||||
for name, m := range middlewares {
|
for name, m := range middlewares {
|
|
@ -1,9 +1,9 @@
|
||||||
package middleware
|
package middleware
|
||||||
|
|
||||||
import (
|
import (
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSetModifyRequest(t *testing.T) {
|
func TestSetModifyRequest(t *testing.T) {
|
|
@ -3,9 +3,9 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
D "github.com/yusing/go-proxy/docker"
|
D "github.com/yusing/go-proxy/internal/docker"
|
||||||
E "github.com/yusing/go-proxy/error"
|
E "github.com/yusing/go-proxy/internal/error"
|
||||||
U "github.com/yusing/go-proxy/utils"
|
U "github.com/yusing/go-proxy/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type (
|
type (
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"slices"
|
"slices"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSetModifyResponse(t *testing.T) {
|
func TestSetModifyResponse(t *testing.T) {
|
|
@ -3,7 +3,7 @@ package middleware
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
var RedirectHTTP = &Middleware{
|
var RedirectHTTP = &Middleware{
|
|
@ -4,8 +4,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/yusing/go-proxy/common"
|
"github.com/yusing/go-proxy/internal/common"
|
||||||
. "github.com/yusing/go-proxy/utils/testing"
|
. "github.com/yusing/go-proxy/internal/utils/testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRedirectToHTTPs(t *testing.T) {
|
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