mirror of
https://github.com/yusing/godoxy.git
synced 2026-01-24 02:48:08 +01:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1953d86c2 | ||
|
|
46f88964bf | ||
|
|
9d20fdb5c2 | ||
|
|
3cf108569b | ||
|
|
c55157193b | ||
|
|
c5886bd1e3 | ||
|
|
8c71d880cb |
20
.github/workflows/docker-image-compat.yml
vendored
20
.github/workflows/docker-image-compat.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Docker Image CI (compat)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- compat
|
||||
|
||||
jobs:
|
||||
build-compat:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy
|
||||
tag: latest-compat
|
||||
target: main
|
||||
build-compat-agent:
|
||||
uses: ./.github/workflows/docker-image.yml
|
||||
with:
|
||||
image_name: ${{ github.repository_owner }}/godoxy-agent
|
||||
tag: latest-compat
|
||||
target: agent
|
||||
1
.github/workflows/docker-image-nightly.yml
vendored
1
.github/workflows/docker-image-nightly.yml
vendored
@@ -8,7 +8,6 @@ on:
|
||||
- "**" # matches every branch
|
||||
- "!dependabot/*"
|
||||
- "!main" # excludes main
|
||||
- "!compat" # excludes compat branch
|
||||
|
||||
jobs:
|
||||
build-nightly:
|
||||
|
||||
37
.github/workflows/docker-image.yml
vendored
37
.github/workflows/docker-image.yml
vendored
@@ -45,37 +45,11 @@ jobs:
|
||||
attestations: write
|
||||
|
||||
steps:
|
||||
- name: Checkout (for tag resolution)
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Prepare
|
||||
run: |
|
||||
platform=${{ matrix.platform }}
|
||||
echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV
|
||||
|
||||
- name: Compute VERSION for build
|
||||
run: |
|
||||
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
|
||||
version="${GITHUB_REF_NAME}"
|
||||
cache_variant="release"
|
||||
elif [ "${GITHUB_REF_NAME}" = "main" ] || [ "${GITHUB_REF_NAME}" = "compat" ]; then
|
||||
git fetch --tags origin main
|
||||
version="$(git describe --tags --abbrev=0 origin/main 2>/dev/null || git describe --tags --abbrev=0 main 2>/dev/null || echo v0.0.0)"
|
||||
cache_variant="${GITHUB_REF_NAME}"
|
||||
else
|
||||
version="v$(date -u +'%Y%m%d-%H%M')"
|
||||
cache_variant="nightly"
|
||||
fi
|
||||
echo "VERSION_FOR_BUILD=$version" >> $GITHUB_ENV
|
||||
echo "CACHE_VARIANT=$cache_variant" >> $GITHUB_ENV
|
||||
if [ "${GITHUB_REF_TYPE}" = "branch" ]; then
|
||||
echo "BRANCH_FOR_BUILD=${GITHUB_REF_NAME}" >> $GITHUB_ENV
|
||||
else
|
||||
echo "BRANCH_FOR_BUILD=" >> $GITHUB_ENV
|
||||
fi
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
@@ -106,15 +80,14 @@ jobs:
|
||||
file: ${{ env.DOCKERFILE }}
|
||||
outputs: type=image,name=${{ env.REGISTRY }}/${{ inputs.image_name }},push-by-digest=true,name-canonical=true,push=true
|
||||
cache-from: |
|
||||
type=gha,scope=${{ github.workflow }}-${{ env.CACHE_VARIANT }}-${{ env.PLATFORM_PAIR }}
|
||||
type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image_name }}:buildcache-${{ env.CACHE_VARIANT }}-${{ env.PLATFORM_PAIR }}
|
||||
type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image_name }}:buildcache-${{ env.PLATFORM_PAIR }}
|
||||
# type=gha,scope=${{ github.workflow }}-${{ env.PLATFORM_PAIR }}
|
||||
cache-to: |
|
||||
type=gha,scope=${{ github.workflow }}-${{ env.CACHE_VARIANT }}-${{ env.PLATFORM_PAIR }},mode=max
|
||||
type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image_name }}:buildcache-${{ env.CACHE_VARIANT }}-${{ env.PLATFORM_PAIR }},mode=max
|
||||
type=registry,ref=${{ env.REGISTRY }}/${{ inputs.image_name }}:buildcache-${{ env.PLATFORM_PAIR }},mode=max
|
||||
# type=gha,scope=${{ github.workflow }}-${{ env.PLATFORM_PAIR }},mode=max
|
||||
build-args: |
|
||||
VERSION=${{ env.VERSION_FOR_BUILD }}
|
||||
VERSION=${{ github.ref_name }}
|
||||
MAKE_ARGS=${{ env.MAKE_ARGS }}
|
||||
BRANCH=${{ env.BRANCH_FOR_BUILD }}
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@v1
|
||||
|
||||
@@ -43,9 +43,6 @@ ENV VERSION=${VERSION}
|
||||
ARG MAKE_ARGS
|
||||
ENV MAKE_ARGS=${MAKE_ARGS}
|
||||
|
||||
ARG BRANCH
|
||||
ENV BRANCH=${BRANCH}
|
||||
|
||||
RUN --mount=type=cache,target=/root/.cache/go-build \
|
||||
--mount=type=cache,target=/root/go/pkg/mod \
|
||||
make ${MAKE_ARGS} docker=1 build
|
||||
|
||||
13
Makefile
13
Makefile
@@ -1,6 +1,5 @@
|
||||
shell := /bin/sh
|
||||
export VERSION ?= $(shell git describe --tags --abbrev=0)
|
||||
export BRANCH ?= $(shell git rev-parse --abbrev-ref HEAD)
|
||||
export BUILD_DATE ?= $(shell date -u +'%Y%m%d-%H%M')
|
||||
export GOOS = linux
|
||||
|
||||
@@ -9,12 +8,7 @@ REPO_URL ?= https://github.com/yusing/godoxy
|
||||
WEBUI_DIR ?= ../godoxy-webui
|
||||
DOCS_DIR ?= ${WEBUI_DIR}/wiki
|
||||
|
||||
ifneq ($(BRANCH), compat)
|
||||
GO_TAGS = sonic
|
||||
else
|
||||
GO_TAGS =
|
||||
endif
|
||||
|
||||
GO_TAGS = sonic
|
||||
LDFLAGS = -X github.com/yusing/goutils/version.version=${VERSION} -checklinkname=0
|
||||
|
||||
ifeq ($(agent), 1)
|
||||
@@ -143,6 +137,9 @@ benchmark:
|
||||
dev-run: build
|
||||
cd dev-data && ${BIN_PATH}
|
||||
|
||||
mtrace:
|
||||
${BIN_PATH} debug-ls-mtrace > mtrace.json
|
||||
|
||||
rapid-crash:
|
||||
docker run --restart=always --name test_crash -p 80 debian:bookworm-slim /bin/cat &&\
|
||||
sleep 3 &&\
|
||||
@@ -159,7 +156,7 @@ cloc:
|
||||
scc -w -i go --not-match '_test.go$$'
|
||||
|
||||
push-github:
|
||||
git push origin $(BRANCH)
|
||||
git push origin $(shell git rev-parse --abbrev-ref HEAD)
|
||||
|
||||
gen-swagger:
|
||||
# go install github.com/swaggo/swag/cmd/swag@latest
|
||||
|
||||
19
agent/go.mod
19
agent/go.mod
@@ -14,16 +14,15 @@ replace (
|
||||
|
||||
exclude github.com/containerd/nerdctl/mod/tigron v0.0.0
|
||||
|
||||
exclude github.com/yusing/godoxy/internal/utils v0.0.0-20250927032450-e2aeef3a863f
|
||||
|
||||
require (
|
||||
github.com/bytedance/sonic v1.14.2
|
||||
github.com/gin-gonic/gin v1.11.0
|
||||
github.com/gorilla/websocket v1.5.3
|
||||
github.com/pion/dtls/v3 v3.0.10
|
||||
github.com/pion/transport/v3 v3.1.1
|
||||
github.com/rs/zerolog v1.34.0
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/yusing/godoxy v0.24.3
|
||||
github.com/yusing/godoxy v0.24.1
|
||||
github.com/yusing/godoxy/socketproxy v0.0.0-00010101000000-000000000000
|
||||
github.com/yusing/goutils v0.7.0
|
||||
)
|
||||
@@ -32,7 +31,6 @@ require (
|
||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
@@ -40,8 +38,7 @@ require (
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/distribution/reference v0.6.0 // indirect
|
||||
github.com/docker/cli v29.1.5+incompatible // indirect
|
||||
github.com/docker/docker v28.5.2+incompatible // indirect
|
||||
github.com/docker/cli v29.1.4+incompatible // indirect
|
||||
github.com/docker/go-connections v0.6.0 // indirect
|
||||
github.com/docker/go-units v0.5.0 // indirect
|
||||
github.com/ebitengine/purego v0.9.1 // indirect
|
||||
@@ -66,8 +63,7 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||
github.com/moby/moby/api v1.52.0 // indirect
|
||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||
github.com/moby/term v0.5.2 // indirect
|
||||
github.com/moby/moby/client v0.2.1 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
@@ -75,7 +71,6 @@ require (
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0 // indirect
|
||||
@@ -91,13 +86,12 @@ require (
|
||||
github.com/valyala/fasthttp v1.69.0 // indirect
|
||||
github.com/yusing/ds v0.4.1 // indirect
|
||||
github.com/yusing/gointernals v0.1.16 // indirect
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260121154438-bc9e96ebf387 // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260121154438-bc9e96ebf387 // indirect
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260116021320-b12ef77f3743 // indirect
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260116021320-b12ef77f3743 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 // indirect
|
||||
go.opentelemetry.io/otel v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
@@ -105,7 +99,6 @@ require (
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
41
agent/go.sum
41
agent/go.sum
@@ -1,5 +1,3 @@
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||
github.com/PuerkitoBio/goquery v1.11.0 h1:jZ7pwMQXIITcUXNH83LLk+txlaEy6NVOfTuP43xxfqw=
|
||||
@@ -26,8 +24,6 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 h1:hWBGaQfbi0iVviX4ibC7bk8OKT5qNr4klBaCHVNvehc=
|
||||
github.com/coreos/go-oidc/v3 v3.17.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
@@ -41,10 +37,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao=
|
||||
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/cli v29.1.4+incompatible h1:AI8fwZhqsAsrqZnVv9h6lbexeW/LzNTasf6A4vcNN8M=
|
||||
github.com/docker/cli v29.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -101,8 +95,6 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPRA=
|
||||
github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8=
|
||||
github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg=
|
||||
github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU=
|
||||
@@ -138,19 +130,13 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
|
||||
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
||||
@@ -167,9 +153,8 @@ github.com/pion/transport/v3 v3.1.1 h1:Tr684+fnnKlhPceU+ICdrw6KKkTms+5qHMgw6bIkY
|
||||
github.com/pion/transport/v3 v3.1.1/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pires/go-proxyproto v0.9.0 h1:3Qg3CLxWx4wJOw5uxhTvc0VrgsJeerDbGTvexu4UK1E=
|
||||
github.com/pires/go-proxyproto v0.9.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
@@ -235,10 +220,6 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
@@ -247,8 +228,6 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
@@ -279,12 +258,6 @@ golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
@@ -295,3 +268,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
|
||||
@@ -102,6 +102,7 @@ func TestUDPServer_RejectInvalidClient(t *testing.T) {
|
||||
|
||||
srv := startUDPServer(t, certs)
|
||||
|
||||
|
||||
// Try to connect with a client cert from a different CA
|
||||
_, err = stream.NewUDPClient(srv.Addr.String(), dstAddr, certs.CaCert, invalidClientCert)
|
||||
require.Error(t, err, "expected error when connecting with client cert from different CA")
|
||||
|
||||
@@ -2,11 +2,11 @@ package agentproxy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
route "github.com/yusing/godoxy/internal/route/types"
|
||||
)
|
||||
|
||||
@@ -53,7 +53,7 @@ func proxyConfigFromHeaders(h http.Header) (cfg Config, err error) {
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(cfgJSON, &cfg)
|
||||
err = sonic.Unmarshal(cfgJSON, &cfg)
|
||||
return cfg, err
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ func (cfg *Config) SetAgentProxyConfigHeadersLegacy(h http.Header) {
|
||||
func (cfg *Config) SetAgentProxyConfigHeaders(h http.Header) {
|
||||
h.Set(HeaderXProxyHost, cfg.Host)
|
||||
h.Set(HeaderXProxyScheme, string(cfg.Scheme))
|
||||
cfgJSON, _ := json.Marshal(cfg.HTTPConfig)
|
||||
cfgJSON, _ := sonic.Marshal(cfg.HTTPConfig)
|
||||
cfgBase64 := base64.StdEncoding.EncodeToString(cfgJSON)
|
||||
h.Set(HeaderXProxyConfig, cfgBase64)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -9,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
healthcheck "github.com/yusing/godoxy/internal/health/check"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
@@ -73,7 +73,7 @@ func CheckHealth(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(result)
|
||||
sonic.ConfigDefault.NewEncoder(w).Encode(result)
|
||||
}
|
||||
|
||||
func parseMsOrDefault(msStr string) time.Duration {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -51,7 +51,7 @@ func NewAgentHandler() http.Handler {
|
||||
Runtime: env.Runtime,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(agentInfo)
|
||||
sonic.ConfigDefault.NewEncoder(w).Encode(agentInfo)
|
||||
})
|
||||
mux.HandleEndpoint("GET", agent.EndpointHealth, CheckHealth)
|
||||
mux.HandleEndpoint("GET", agent.EndpointSystemInfo, metricsHandler.ServeHTTP)
|
||||
|
||||
11
cmd/main.go
11
cmd/main.go
@@ -69,18 +69,9 @@ func main() {
|
||||
server.StartServer(task.RootTask("api_server", false), server.Options{
|
||||
Name: "api",
|
||||
HTTPAddr: common.APIHTTPAddr,
|
||||
Handler: api.NewHandler(true),
|
||||
Handler: api.NewHandler(),
|
||||
})
|
||||
|
||||
// Local API Handler is used for unauthenticated access.
|
||||
if common.LocalAPIHTTPAddr != "" {
|
||||
server.StartServer(task.RootTask("local_api_server", false), server.Options{
|
||||
Name: "local_api",
|
||||
HTTPAddr: common.LocalAPIHTTPAddr,
|
||||
Handler: api.NewHandler(false),
|
||||
})
|
||||
}
|
||||
|
||||
listenDebugServer()
|
||||
|
||||
uptime.Poller.Start()
|
||||
|
||||
50
go.mod
50
go.mod
@@ -13,12 +13,9 @@ replace (
|
||||
github.com/yusing/goutils/server => ./goutils/server
|
||||
)
|
||||
|
||||
exclude github.com/luthermonson/go-proxmox v0.3.0
|
||||
|
||||
require (
|
||||
github.com/PuerkitoBio/goquery v1.11.0 // parsing HTML for extract fav icon; modify_html middleware
|
||||
github.com/coreos/go-oidc/v3 v3.17.0 // oidc authentication
|
||||
github.com/docker/docker v28.5.2+incompatible // docker daemon
|
||||
github.com/fsnotify/fsnotify v1.9.0 // file watcher
|
||||
github.com/gin-gonic/gin v1.11.0 // api server
|
||||
github.com/go-acme/lego/v4 v4.31.0 // acme client
|
||||
@@ -27,7 +24,7 @@ require (
|
||||
github.com/gorilla/websocket v1.5.3 // websocket for API and agent
|
||||
github.com/gotify/server/v2 v2.8.0 // reference the Message struct for json response
|
||||
github.com/lithammer/fuzzysearch v1.1.8 // fuzzy search for searching icons and filtering metrics
|
||||
github.com/pires/go-proxyproto v0.9.0 // proxy protocol support
|
||||
github.com/pires/go-proxyproto v0.8.1 // proxy protocol support
|
||||
github.com/puzpuzpuz/xsync/v4 v4.3.0 // lock free map for concurrent operations
|
||||
github.com/rs/zerolog v1.34.0 // logging
|
||||
github.com/vincent-petithory/dataurl v1.0.0 // data url for fav icon
|
||||
@@ -40,29 +37,31 @@ require (
|
||||
|
||||
require (
|
||||
github.com/bytedance/gopkg v0.1.3 // xxhash64 for fast hash
|
||||
github.com/bytedance/sonic v1.14.2 // indirect; fast json parsing
|
||||
github.com/docker/cli v29.1.5+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
|
||||
github.com/bytedance/sonic v1.14.2 // fast json parsing
|
||||
github.com/docker/cli v29.1.4+incompatible // needs docker/cli/cli/connhelper connection helper for docker client
|
||||
github.com/goccy/go-yaml v1.19.2 // yaml parsing for different config files
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/luthermonson/go-proxmox v0.3.2
|
||||
github.com/oschwald/maxminddb-golang v1.13.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // jwt authentication
|
||||
github.com/luthermonson/go-proxmox v0.3.2 // proxmox API client
|
||||
github.com/moby/moby/api v1.52.0 // docker API
|
||||
github.com/moby/moby/client v0.2.1 // docker client
|
||||
github.com/oschwald/maxminddb-golang v1.13.1 // maxminddb for geoip database
|
||||
github.com/quic-go/quic-go v0.59.0 // http3 support
|
||||
github.com/shirou/gopsutil/v4 v4.25.12 // system information
|
||||
github.com/spf13/afero v1.15.0 // afero for file system operations
|
||||
github.com/stretchr/testify v1.11.1 // testing framework
|
||||
github.com/valyala/fasthttp v1.69.0 // fast http for health check
|
||||
github.com/yusing/ds v0.4.1 // data structures and algorithms
|
||||
github.com/yusing/godoxy/agent v0.0.0-20260121155423-6b752059da7d
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260122072701-630629a3fd8e
|
||||
github.com/yusing/godoxy/agent v0.0.0-20260116020954-edcde00dcc3a
|
||||
github.com/yusing/godoxy/internal/dnsproviders v0.0.0-20260116020954-edcde00dcc3a
|
||||
github.com/yusing/gointernals v0.1.16
|
||||
github.com/yusing/goutils v0.7.0
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260121154438-bc9e96ebf387
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260121154438-bc9e96ebf387
|
||||
github.com/yusing/goutils/server v0.0.0-20260121154438-bc9e96ebf387
|
||||
github.com/yusing/goutils/http/reverseproxy v0.0.0-20260116021320-b12ef77f3743
|
||||
github.com/yusing/goutils/http/websocket v0.0.0-20260116021320-b12ef77f3743
|
||||
github.com/yusing/goutils/server v0.0.0-20260116021320-b12ef77f3743
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.18.1 // indirect
|
||||
cloud.google.com/go/auth v0.18.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||
@@ -116,11 +115,11 @@ require (
|
||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
github.com/pkg/errors v0.9.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/quic-go/qpack v0.6.0 // indirect
|
||||
github.com/samber/lo v1.52.0 // indirect
|
||||
github.com/samber/slog-common v0.19.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.0 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.36 // indirect
|
||||
github.com/sirupsen/logrus v1.9.4 // indirect
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
@@ -136,8 +135,8 @@ require (
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/api v0.261.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||
google.golang.org/api v0.260.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
|
||||
google.golang.org/grpc v1.78.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||
@@ -145,10 +144,7 @@ require (
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require github.com/moby/moby/api v1.52.0
|
||||
|
||||
require (
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/andybalholm/brotli v1.2.0 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
@@ -158,7 +154,6 @@ require (
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/containerd/errdefs v1.0.0 // indirect
|
||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/fatih/color v1.18.0 // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/gin-contrib/sse v1.1.0 // indirect
|
||||
@@ -168,33 +163,28 @@ require (
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/goccy/go-json v0.10.5 // indirect
|
||||
github.com/google/go-querystring v1.2.0 // indirect
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect
|
||||
github.com/klauspost/compress v1.18.3 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/linode/linodego v1.64.0 // indirect
|
||||
github.com/lufia/plan9stats v0.0.0-20251013123823-9fd1530e3ec3 // indirect
|
||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
||||
github.com/morikuni/aec v1.0.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.1 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.1 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.0 // indirect
|
||||
github.com/pierrec/lz4/v4 v4.1.21 // indirect
|
||||
github.com/pion/dtls/v3 v3.0.10 // indirect
|
||||
github.com/pion/logging v0.2.4 // indirect
|
||||
github.com/pion/transport/v4 v4.0.1 // indirect
|
||||
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||
github.com/pquerna/otp v1.5.0 // indirect
|
||||
github.com/samber/slog-zerolog/v2 v2.9.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/tklauser/go-sysconf v0.3.16 // indirect
|
||||
github.com/tklauser/numcpus v0.11.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||
github.com/vultr/govultr/v3 v3.26.1 // indirect
|
||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
)
|
||||
|
||||
64
go.sum
64
go.sum
@@ -1,5 +1,5 @@
|
||||
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
|
||||
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
|
||||
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
|
||||
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
@@ -23,8 +23,6 @@ github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourceg
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resourcegraph/armresourcegraph v0.9.0/go.mod h1:wVEOJfGTj0oPAUGA1JuRAvz/lxXQsWW16axmHPP47Bk=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0/go.mod h1:5kakwfW5CjC9KK+Q4wjXAg+ShuIm2mBMua0ZFj2C8PE=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1 h1:WJTmL004Abzc5wDB5VtZG2PJk5ndYDgVacGqfirKxjM=
|
||||
github.com/AzureAD/microsoft-authentication-extensions-for-go/cache v0.1.1/go.mod h1:tCcJZ0uHAmvjsVYzEFivsRTN00oz5BEsRgQHu5JZ9WE=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 h1:XRzhVemXdgvJqCH0sFfrBUTnUJSBrBf7++ypk+twtRs=
|
||||
@@ -67,8 +65,6 @@ github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG
|
||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
||||
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=
|
||||
github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -80,10 +76,8 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||
github.com/djherbis/times v1.6.0 h1:w2ctJ92J8fBvWPxugmXIv7Nz7Q3iDMKNx9v5ocVH20c=
|
||||
github.com/djherbis/times v1.6.0/go.mod h1:gOHeRAz2h+VJNZ5Gmc/o7iD9k4wW7NMVqieYCY99oc0=
|
||||
github.com/docker/cli v29.1.5+incompatible h1:GckbANUt3j+lsnQ6eCcQd70mNSOismSHWt8vk2AX8ao=
|
||||
github.com/docker/cli v29.1.5+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/docker v28.5.2+incompatible h1:DBX0Y0zAjZbSrm1uzOkdr1onVghKaftjlSWt4AFexzM=
|
||||
github.com/docker/docker v28.5.2+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||
github.com/docker/cli v29.1.4+incompatible h1:AI8fwZhqsAsrqZnVv9h6lbexeW/LzNTasf6A4vcNN8M=
|
||||
github.com/docker/cli v29.1.4+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
||||
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
|
||||
@@ -165,8 +159,6 @@ github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aN
|
||||
github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gotify/server/v2 v2.8.0 h1:E3UDDn/3rFZi1sjZfbuhXNnxJP3ACZhdcw/iySegPRA=
|
||||
github.com/gotify/server/v2 v2.8.0/go.mod h1:6ci5adxcE2hf1v+2oowKiQmixOxXV8vU+CRLKP6sqZA=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg=
|
||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4=
|
||||
github.com/h2non/gock v1.2.0 h1:K6ol8rfrRkUOefooBC8elXoaNGYkpp7y2qcxGG6BzUE=
|
||||
github.com/h2non/gock v1.2.0/go.mod h1:tNhoxHYW2W42cYkYb1WqzdbYIieALC99kpYr7rH/BQk=
|
||||
github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslCrtky5vbi9dd7HrQPQIx6wqiw=
|
||||
@@ -226,27 +218,21 @@ github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3N
|
||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||
github.com/moby/moby/api v1.52.0 h1:00BtlJY4MXkkt84WhUZPRqt5TvPbgig2FZvTbe3igYg=
|
||||
github.com/moby/moby/api v1.52.0/go.mod h1:8mb+ReTlisw4pS6BRzCMts5M49W5M7bKt1cJy/YbAqc=
|
||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
||||
github.com/moby/moby/client v0.2.1 h1:1Grh1552mvv6i+sYOdY+xKKVTvzJegcVMhuXocyDz/k=
|
||||
github.com/moby/moby/client v0.2.1/go.mod h1:O+/tw5d4a1Ha/ZA/tPxIZJapJRUS6LNZ1wiVRxYHyUE=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||
github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0=
|
||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.1 h1:+fx2mbWeR8XX/vidwpRMepJMtRIYQP44Iezm2oeObVM=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.1/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.1 h1:GDhBiaIAm/QXLzHJ0ASDdY/6R/9w60+gk8lY5rgfxEQ=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.1/go.mod h1:EHScJdbM0gg5Is7e3C0ceRYAFMMsfP4Vf8sBRoxoTgk=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.0 h1:4MRzV6spwPHKct+4/ETqkEtr39Hq+0KvxhsgqbgQ2Bo=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.0 h1:RxraLVYX3eMUfQ1pDtJVvykEFGheky2YsrUt2HHRDcw=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.0/go.mod h1:JLMEKMX8IYPZ1TUSVHAVAbtnNSfP/I8OZQkAnfEMA0I=
|
||||
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
||||
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
||||
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
|
||||
@@ -267,11 +253,10 @@ github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
|
||||
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
|
||||
github.com/pion/transport/v4 v4.0.1 h1:sdROELU6BZ63Ab7FrOLn13M6YdJLY20wldXW2Cu2k8o=
|
||||
github.com/pion/transport/v4 v4.0.1/go.mod h1:nEuEA4AD5lPdcIegQDpVLgNoDGreqM/YqmEx3ovP4jM=
|
||||
github.com/pires/go-proxyproto v0.9.0 h1:3Qg3CLxWx4wJOw5uxhTvc0VrgsJeerDbGTvexu4UK1E=
|
||||
github.com/pires/go-proxyproto v0.9.0/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pires/go-proxyproto v0.8.1 h1:9KEixbdJfhrbtjpz/ZwCdWDD2Xem0NZ38qMYaASJgp0=
|
||||
github.com/pires/go-proxyproto v0.8.1/go.mod h1:ZKAAyp3cgy5Y5Mo4n9AlScrkCZwUy0g3Jf+slqQVcuU=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
|
||||
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
|
||||
@@ -329,8 +314,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY=
|
||||
github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4=
|
||||
github.com/ulikunitz/xz v0.5.11 h1:kpFauv27b6ynzBNT/Xy+1k+fK4WswhN/6PN5WhFAGw8=
|
||||
github.com/ulikunitz/xz v0.5.11/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY=
|
||||
github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14=
|
||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||
github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
|
||||
@@ -358,10 +343,6 @@ go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0 h1:ssfIgGN
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.64.0/go.mod h1:GQ/474YrbE4Jx8gZ4q5I4hrhUzM6UPzyrqJYV2AqPoQ=
|
||||
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
|
||||
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0 h1:aTL7F04bJHUlztTsNGJ2l+6he8c+y/b//eR0jjjemT4=
|
||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.38.0/go.mod h1:kldtb7jDTeol0l3ewcmd8SDvx3EmIE7lyvqbasU3QC4=
|
||||
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
|
||||
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
|
||||
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
|
||||
@@ -370,8 +351,6 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W
|
||||
go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew=
|
||||
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
|
||||
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
|
||||
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
|
||||
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
|
||||
@@ -423,7 +402,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
|
||||
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
@@ -471,14 +449,14 @@ golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.261.0 h1:3DoJ2GGibaCxNi1lhdScNMx9fTW87ujKHDgyHMMYdoA=
|
||||
google.golang.org/api v0.261.0/go.mod h1:nVH0ZK5C4tO0RdsMscleeTLY7I8m/Nt9IXxcXD2tfts=
|
||||
google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4=
|
||||
google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b h1:uA40e2M6fYRBf0+8uN5mLlqUtV192iiksiICIBkYJ1E=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:Xa7le7qx2vmqB/SzWUBa7KdMjpdpAHlh5QCSnjessQk=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
@@ -496,3 +474,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
||||
pgregory.net/rapid v1.2.0 h1:keKAYRcjm+e1F0oAuU5F5+YPAWcyxNNRK2wud503Gnk=
|
||||
pgregory.net/rapid v1.2.0/go.mod h1:PY5XlDGj0+V1FCq0o192FdRhpKHGTRIWBgqjDBTrq04=
|
||||
|
||||
2
goutils
2
goutils
Submodule goutils updated: bc9e96ebf3...900faa77c8
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
@@ -26,9 +27,9 @@ type Config struct {
|
||||
Log *accesslog.ACLLoggerConfig `json:"log"`
|
||||
|
||||
Notify struct {
|
||||
To []string `json:"to,omitempty"` // list of notification providers
|
||||
Interval time.Duration `json:"interval,omitempty"` // interval between notifications
|
||||
IncludeAllowed *bool `json:"include_allowed,omitzero"` // default: false
|
||||
To []string `json:"to"` // list of notification providers
|
||||
Interval time.Duration `json:"interval"` // interval between notifications
|
||||
IncludeAllowed *bool `json:"include_allowed"` // default: false
|
||||
} `json:"notify"`
|
||||
|
||||
config
|
||||
@@ -74,7 +75,8 @@ type ipLog struct {
|
||||
allowed bool
|
||||
}
|
||||
|
||||
type ContextKey struct{}
|
||||
// could be nil
|
||||
var ActiveConfig atomic.Pointer[Config]
|
||||
|
||||
const cacheTTL = 1 * time.Minute
|
||||
|
||||
@@ -290,16 +292,16 @@ func (c *Config) IPAllowed(ip net.IP) bool {
|
||||
}
|
||||
|
||||
ipAndStr := &maxmind.IPInfo{IP: ip, Str: ipStr}
|
||||
if c.Deny.Match(ipAndStr) {
|
||||
c.logAndNotify(ipAndStr, false)
|
||||
c.cacheRecord(ipAndStr, false)
|
||||
return false
|
||||
}
|
||||
if c.Allow.Match(ipAndStr) {
|
||||
c.logAndNotify(ipAndStr, true)
|
||||
c.cacheRecord(ipAndStr, true)
|
||||
return true
|
||||
}
|
||||
if c.Deny.Match(ipAndStr) {
|
||||
c.logAndNotify(ipAndStr, false)
|
||||
c.cacheRecord(ipAndStr, false)
|
||||
return false
|
||||
}
|
||||
|
||||
c.logAndNotify(ipAndStr, c.defaultAllow)
|
||||
c.cacheRecord(ipAndStr, c.defaultAllow)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package acl
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
@@ -13,7 +12,6 @@ type MatcherFunc func(*maxmind.IPInfo) bool
|
||||
|
||||
type Matcher struct {
|
||||
match MatcherFunc
|
||||
raw string
|
||||
}
|
||||
|
||||
type Matchers []Matcher
|
||||
@@ -48,7 +46,6 @@ func (matcher *Matcher) Parse(s string) error {
|
||||
if len(parts) != 2 {
|
||||
return errSyntax
|
||||
}
|
||||
matcher.raw = s
|
||||
|
||||
switch parts[0] {
|
||||
case MatcherTypeIP:
|
||||
@@ -82,18 +79,6 @@ func (matchers Matchers) Match(ip *maxmind.IPInfo) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (matchers Matchers) MarshalText() ([]byte, error) {
|
||||
if len(matchers) == 0 {
|
||||
return []byte("[]"), nil
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for _, m := range matchers {
|
||||
buf.WriteString(m.raw)
|
||||
buf.WriteByte('\n')
|
||||
}
|
||||
return buf.Bytes(), nil
|
||||
}
|
||||
|
||||
func matchIP(ip net.IP) MatcherFunc {
|
||||
return func(ip2 *maxmind.IPInfo) bool {
|
||||
return ip.Equal(ip2.IP)
|
||||
|
||||
@@ -2,12 +2,12 @@ package agentpool
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/valyala/fasthttp"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -63,7 +63,7 @@ func (cfg *Agent) DoHealthCheck(timeout time.Duration, query string) (ret Health
|
||||
ret.Detail = fmt.Sprintf("HTTP %d %s", status, resp.Body())
|
||||
return ret, nil
|
||||
} else {
|
||||
err = json.Unmarshal(resp.Body(), &ret)
|
||||
err = sonic.Unmarshal(resp.Body(), &ret)
|
||||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
@@ -2,8 +2,10 @@ package api
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"reflect"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gin-gonic/gin/codec/json"
|
||||
"github.com/gorilla/websocket"
|
||||
"github.com/rs/zerolog/log"
|
||||
apiV1 "github.com/yusing/godoxy/internal/api/v1"
|
||||
@@ -36,7 +38,7 @@ import (
|
||||
|
||||
// @externalDocs.description GoDoxy Docs
|
||||
// @externalDocs.url https://docs.godoxy.dev
|
||||
func NewHandler(requireAuth bool) *gin.Engine {
|
||||
func NewHandler() *gin.Engine {
|
||||
if !common.IsDebug {
|
||||
gin.SetMode("release")
|
||||
}
|
||||
@@ -45,9 +47,11 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
||||
r.Use(ErrorLoggingMiddleware())
|
||||
r.Use(NoCache())
|
||||
|
||||
log.Debug().Msg("gin codec json.API: " + reflect.TypeOf(json.API).Name())
|
||||
|
||||
r.GET("/api/v1/version", apiV1.Version)
|
||||
|
||||
if auth.IsEnabled() && requireAuth {
|
||||
if auth.IsEnabled() {
|
||||
v1Auth := r.Group("/api/v1/auth")
|
||||
{
|
||||
v1Auth.HEAD("/check", authApi.Check)
|
||||
@@ -60,7 +64,7 @@ func NewHandler(requireAuth bool) *gin.Engine {
|
||||
}
|
||||
|
||||
v1 := r.Group("/api/v1")
|
||||
if auth.IsEnabled() && requireAuth {
|
||||
if auth.IsEnabled() {
|
||||
v1.Use(AuthMiddleware())
|
||||
}
|
||||
if common.APISkipOriginCheck {
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
@@ -42,22 +43,22 @@ func GetContainer(c *gin.Context) {
|
||||
|
||||
defer dockerClient.Close()
|
||||
|
||||
cont, err := dockerClient.ContainerInspect(c.Request.Context(), id)
|
||||
cont, err := dockerClient.ContainerInspect(c.Request.Context(), id, client.ContainerInspectOptions{})
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to inspect container"))
|
||||
return
|
||||
}
|
||||
|
||||
var state ContainerState
|
||||
if cont.State != nil {
|
||||
state = cont.State.Status
|
||||
if cont.Container.State != nil {
|
||||
state = cont.Container.State.Status
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, &Container{
|
||||
Server: dockerCfg.URL,
|
||||
Name: cont.Name,
|
||||
ID: cont.ID,
|
||||
Image: cont.Image,
|
||||
Name: cont.Container.Name,
|
||||
ID: cont.Container.ID,
|
||||
Image: cont.Container.Image,
|
||||
State: state,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
|
||||
_ "github.com/yusing/goutils/apitypes"
|
||||
@@ -39,12 +40,12 @@ func GetContainers(ctx context.Context, dockerClients DockerClients) ([]Containe
|
||||
errs := gperr.NewBuilder("failed to get containers")
|
||||
containers := make([]Container, 0)
|
||||
for server, dockerClient := range dockerClients {
|
||||
conts, err := dockerClient.ContainerList(ctx, container.ListOptions{All: true})
|
||||
conts, err := dockerClient.ContainerList(ctx, client.ContainerListOptions{All: true})
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
continue
|
||||
}
|
||||
for _, cont := range conts {
|
||||
for _, cont := range conts.Items {
|
||||
containers = append(containers, Container{
|
||||
Server: server,
|
||||
Name: cont.Names[0],
|
||||
|
||||
@@ -4,8 +4,9 @@ import (
|
||||
"context"
|
||||
"sort"
|
||||
|
||||
dockerSystem "github.com/docker/docker/api/types/system"
|
||||
"github.com/gin-gonic/gin"
|
||||
dockerSystem "github.com/moby/moby/api/types/system"
|
||||
"github.com/moby/moby/client"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
|
||||
@@ -64,13 +65,13 @@ func GetDockerInfo(ctx context.Context, dockerClients DockerClients) ([]dockerIn
|
||||
|
||||
i := 0
|
||||
for name, dockerClient := range dockerClients {
|
||||
info, err := dockerClient.Info(ctx)
|
||||
info, err := dockerClient.Info(ctx, client.InfoOptions{})
|
||||
if err != nil {
|
||||
errs.Add(err)
|
||||
continue
|
||||
}
|
||||
info.Name = name
|
||||
dockerInfos[i] = toDockerInfo(info)
|
||||
info.Info.Name = name
|
||||
dockerInfos[i] = toDockerInfo(info.Info)
|
||||
i++
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,9 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/pkg/stdcopy"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/api/pkg/stdcopy"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
@@ -70,7 +70,7 @@ func Logs(c *gin.Context) {
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
|
||||
opts := container.LogsOptions{
|
||||
opts := client.ContainerLogsOptions{
|
||||
ShowStdout: queryParams.Stdout,
|
||||
ShowStderr: queryParams.Stderr,
|
||||
Since: queryParams.Since,
|
||||
|
||||
@@ -4,17 +4,23 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type RestartRequest struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
client.ContainerRestartOptions
|
||||
}
|
||||
|
||||
// @x-id "restart"
|
||||
// @BasePath /api/v1
|
||||
// @Summary Restart container
|
||||
// @Description Restart container by container id
|
||||
// @Tags docker
|
||||
// @Produce json
|
||||
// @Param request body StopRequest true "Request"
|
||||
// @Param request body RestartRequest true "Request"
|
||||
// @Success 200 {object} apitypes.SuccessResponse
|
||||
// @Failure 400 {object} apitypes.ErrorResponse "Invalid request"
|
||||
// @Failure 403 {object} apitypes.ErrorResponse
|
||||
@@ -22,7 +28,7 @@ import (
|
||||
// @Failure 500 {object} apitypes.ErrorResponse
|
||||
// @Router /docker/restart [post]
|
||||
func Restart(c *gin.Context) {
|
||||
var req StopRequest
|
||||
var req RestartRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, apitypes.Error("invalid request", err))
|
||||
return
|
||||
@@ -42,7 +48,7 @@ func Restart(c *gin.Context) {
|
||||
|
||||
defer client.Close()
|
||||
|
||||
err = client.ContainerRestart(c.Request.Context(), req.ID, req.StopOptions)
|
||||
_, err = client.ContainerRestart(c.Request.Context(), req.ID, req.ContainerRestartOptions)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to restart container"))
|
||||
return
|
||||
|
||||
@@ -3,15 +3,15 @@ package dockerapi
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type StartRequest struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
container.StartOptions
|
||||
client.ContainerStartOptions
|
||||
}
|
||||
|
||||
// @x-id "start"
|
||||
@@ -48,7 +48,7 @@ func Start(c *gin.Context) {
|
||||
|
||||
defer client.Close()
|
||||
|
||||
err = client.ContainerStart(c.Request.Context(), req.ID, req.StartOptions)
|
||||
_, err = client.ContainerStart(c.Request.Context(), req.ID, req.ContainerStartOptions)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to start container"))
|
||||
return
|
||||
|
||||
@@ -3,15 +3,15 @@ package dockerapi
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
)
|
||||
|
||||
type StopRequest struct {
|
||||
ID string `json:"id" binding:"required"`
|
||||
container.StopOptions
|
||||
client.ContainerStopOptions
|
||||
}
|
||||
|
||||
// @x-id "stop"
|
||||
@@ -48,7 +48,7 @@ func Stop(c *gin.Context) {
|
||||
|
||||
defer client.Close()
|
||||
|
||||
err = client.ContainerStop(c.Request.Context(), req.ID, req.StopOptions)
|
||||
_, err = client.ContainerStop(c.Request.Context(), req.ID, req.ContainerStopOptions)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to stop container"))
|
||||
return
|
||||
|
||||
@@ -4191,6 +4191,12 @@
|
||||
"RequestLoggerConfig": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"buffer_size": {
|
||||
"description": "Deprecated: buffer size is adjusted dynamically",
|
||||
"type": "integer",
|
||||
"x-nullable": false,
|
||||
"x-omitempty": false
|
||||
},
|
||||
"fields": {
|
||||
"$ref": "#/definitions/accesslog.Fields",
|
||||
"x-nullable": false,
|
||||
|
||||
@@ -879,6 +879,9 @@ definitions:
|
||||
type: object
|
||||
RequestLoggerConfig:
|
||||
properties:
|
||||
buffer_size:
|
||||
description: 'Deprecated: buffer size is adjusted dynamically'
|
||||
type: integer
|
||||
fields:
|
||||
$ref: '#/definitions/accesslog.Fields'
|
||||
filters:
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
@@ -236,7 +237,7 @@ func marshalSystemInfo(ws *websocket.Manager, agentName string, systemInfo any)
|
||||
defer bufFromPool.release(bufFromPool.RawMessage)
|
||||
}
|
||||
|
||||
err := json.NewEncoder(buf).Encode(map[string]any{
|
||||
err := sonic.ConfigDefault.NewEncoder(buf).Encode(map[string]any{
|
||||
agentName: systemInfo,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -109,7 +109,7 @@ type UserPassAuthCallbackRequest struct {
|
||||
|
||||
func (auth *UserPassAuth) PostAuthCallbackHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var creds UserPassAuthCallbackRequest
|
||||
err := json.NewDecoder(r.Body).Decode(&creds)
|
||||
err := sonic.ConfigDefault.NewDecoder(r.Body).Decode(&creds)
|
||||
if err != nil {
|
||||
http.Error(w, "invalid request", http.StatusBadRequest)
|
||||
return
|
||||
|
||||
@@ -30,11 +30,6 @@ var (
|
||||
APIHTTPPort,
|
||||
APIHTTPURL = env.GetAddrEnv("API_ADDR", "127.0.0.1:8888", "http")
|
||||
|
||||
LocalAPIHTTPAddr,
|
||||
LocalAPIHTTPHost,
|
||||
LocalAPIHTTPPort,
|
||||
LocalAPIHTTPURL = env.GetAddrEnv("LOCAL_API_ADDR", "", "http")
|
||||
|
||||
APIJWTSecure = env.GetEnvBool("API_JWT_SECURE", true)
|
||||
APIJWTSecret = decodeJWTKey(env.GetEnvString("API_JWT_SECRET", ""))
|
||||
APIJWTTokenTTL = env.GetEnvDuation("API_JWT_TOKEN_TTL", 24*time.Hour)
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
config "github.com/yusing/godoxy/internal/config/types"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/watcher"
|
||||
"github.com/yusing/godoxy/internal/watcher/events"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -60,15 +59,6 @@ func Load() error {
|
||||
|
||||
cfgWatcher = watcher.NewConfigFileWatcher(common.ConfigFileName)
|
||||
|
||||
// disable pool logging temporary since we already have pretty logging
|
||||
routes.HTTP.DisableLog(true)
|
||||
routes.Stream.DisableLog(true)
|
||||
|
||||
defer func() {
|
||||
routes.HTTP.DisableLog(false)
|
||||
routes.Stream.DisableLog(false)
|
||||
}()
|
||||
|
||||
initErr := state.InitFromFile(common.ConfigPath)
|
||||
err := errors.Join(initErr, state.StartProviders())
|
||||
if err != nil {
|
||||
|
||||
@@ -28,6 +28,7 @@ import (
|
||||
"github.com/yusing/godoxy/internal/maxmind"
|
||||
"github.com/yusing/godoxy/internal/notif"
|
||||
route "github.com/yusing/godoxy/internal/route/provider"
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -73,6 +74,7 @@ func SetState(state config.State) {
|
||||
|
||||
cfg := state.Value()
|
||||
config.ActiveState.Store(state)
|
||||
acl.ActiveConfig.Store(cfg.ACL)
|
||||
entrypoint.ActiveConfig.Store(&cfg.Entrypoint)
|
||||
homepage.ActiveConfig.Store(&cfg.Homepage)
|
||||
if autocertProvider := state.AutoCertProvider(); autocertProvider != nil {
|
||||
@@ -117,7 +119,7 @@ func (state *state) Init(data []byte) error {
|
||||
errs := g.Wait()
|
||||
// these won't benefit from running on goroutines
|
||||
errs.Add(state.initNotification())
|
||||
errs.Add(state.initACL())
|
||||
errs.Add(state.initAccessLogger())
|
||||
errs.Add(state.initEntrypoint())
|
||||
return errs.Error()
|
||||
}
|
||||
@@ -190,17 +192,12 @@ func (state *state) FlushTmpLog() {
|
||||
state.tmpLogBuf.Reset()
|
||||
}
|
||||
|
||||
// initACL initializes the ACL.
|
||||
func (state *state) initACL() error {
|
||||
// this one is connection level access logger, different from entrypoint access logger
|
||||
func (state *state) initAccessLogger() error {
|
||||
if !state.ACL.Valid() {
|
||||
return nil
|
||||
}
|
||||
err := state.ACL.Start(state.task)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
state.task.SetValue(acl.ContextKey{}, state.ACL)
|
||||
return nil
|
||||
return state.ACL.Start(state.task)
|
||||
}
|
||||
|
||||
func (state *state) initEntrypoint() error {
|
||||
@@ -322,6 +319,15 @@ func (state *state) storeProvider(p types.RouteProvider) {
|
||||
}
|
||||
|
||||
func (state *state) loadRouteProviders() error {
|
||||
// disable pool logging temporary since we will have pretty logging below
|
||||
routes.HTTP.DisableLog(true)
|
||||
routes.Stream.DisableLog(true)
|
||||
|
||||
defer func() {
|
||||
routes.HTTP.DisableLog(false)
|
||||
routes.Stream.DisableLog(false)
|
||||
}()
|
||||
|
||||
providers := &state.Providers
|
||||
errs := gperr.NewGroup("route provider errors")
|
||||
results := gperr.NewGroup("loaded route providers")
|
||||
|
||||
@@ -6,11 +6,11 @@ replace github.com/yusing/godoxy => ../..
|
||||
|
||||
require (
|
||||
github.com/go-acme/lego/v4 v4.31.0
|
||||
github.com/yusing/godoxy v0.24.3
|
||||
github.com/yusing/godoxy v0.24.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go/auth v0.18.1 // indirect
|
||||
cloud.google.com/go/auth v0.18.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect
|
||||
@@ -23,8 +23,12 @@ require (
|
||||
github.com/akamai/AkamaiOPEN-edgegrid-golang/v11 v11.1.0 // indirect
|
||||
github.com/benbjohnson/clock v1.3.5 // indirect
|
||||
github.com/boombuler/barcode v1.1.0 // indirect
|
||||
github.com/bytedance/gopkg v0.1.3 // indirect
|
||||
github.com/bytedance/sonic v1.14.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.4.0 // indirect
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.6 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fatih/structs v1.1.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
@@ -49,6 +53,7 @@ require (
|
||||
github.com/gotify/server/v2 v2.8.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
@@ -60,8 +65,8 @@ require (
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/nrdcg/goacmedns v0.2.0 // indirect
|
||||
github.com/nrdcg/goinwx v0.12.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.1 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.1 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.0 // indirect
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.0 // indirect
|
||||
github.com/nrdcg/porkbun v0.4.0 // indirect
|
||||
github.com/ovh/go-ovh v1.9.0 // indirect
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||
@@ -73,6 +78,7 @@ require (
|
||||
github.com/sony/gobreaker v1.0.0 // indirect
|
||||
github.com/stretchr/objx v0.5.3 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/vultr/govultr/v3 v3.26.1 // indirect
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect
|
||||
github.com/yusing/gointernals v0.1.16 // indirect
|
||||
@@ -83,6 +89,7 @@ require (
|
||||
go.opentelemetry.io/otel/metric v1.39.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.39.0 // indirect
|
||||
go.uber.org/ratelimit v0.3.1 // indirect
|
||||
golang.org/x/arch v0.23.0 // indirect
|
||||
golang.org/x/crypto v0.47.0 // indirect
|
||||
golang.org/x/mod v0.32.0 // indirect
|
||||
golang.org/x/net v0.49.0 // indirect
|
||||
@@ -91,8 +98,8 @@ require (
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
golang.org/x/text v0.33.0 // indirect
|
||||
golang.org/x/tools v0.41.0 // indirect
|
||||
google.golang.org/api v0.261.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 // indirect
|
||||
google.golang.org/api v0.260.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 // indirect
|
||||
google.golang.org/grpc v1.78.0 // indirect
|
||||
google.golang.org/protobuf v1.36.11 // indirect
|
||||
gopkg.in/ini.v1 v1.67.1 // indirect
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
|
||||
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
|
||||
cloud.google.com/go/auth v0.18.0 h1:wnqy5hrv7p3k7cShwAU/Br3nzod7fxoqG+k0VZ+/Pk0=
|
||||
cloud.google.com/go/auth v0.18.0/go.mod h1:wwkPM1AgE1f2u6dG443MiWoD8C3BtOywNsUMcUTVDRo=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
|
||||
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
|
||||
@@ -37,10 +37,18 @@ github.com/benbjohnson/clock v1.3.5/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/boombuler/barcode v1.1.0 h1:ChaYjBR63fr4LFyGn8E8nt7dBSt3MiU3zMOZqFvVkHo=
|
||||
github.com/boombuler/barcode v1.1.0/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
|
||||
github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M=
|
||||
github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM=
|
||||
github.com/bytedance/sonic v1.14.2 h1:k1twIoe97C1DtYUo+fZQy865IuHia4PR5RPiuGPPIIE=
|
||||
github.com/bytedance/sonic v1.14.2/go.mod h1:T80iDELeHiHKSc0C9tubFygiuXoGzrkjKzX2quAx980=
|
||||
github.com/bytedance/sonic/loader v0.4.0 h1:olZ7lEqcxtZygCK9EKYKADnpQoYkRQxaeY2NYzevs+o=
|
||||
github.com/bytedance/sonic/loader v0.4.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M=
|
||||
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
|
||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
@@ -111,6 +119,8 @@ github.com/jarcoal/httpmock v1.4.1 h1:0Ju+VCFuARfFlhVXFc2HxlcQkfB+Xq12/EotHko+x2
|
||||
github.com/jarcoal/httpmock v1.4.1/go.mod h1:ftW1xULwo+j0R0JJkJIIi7UKigZUXCLLanykgjwBXL0=
|
||||
github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU=
|
||||
github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b h1:udzkj9S/zlT5X367kqJis0QP7YMxobob6zhzq6Yre00=
|
||||
github.com/kolo/xmlrpc v0.0.0-20220921171641-a4b6fa1dd06b/go.mod h1:pcaDhQK0/NJZEvtCO0qQPPropqV0sJOJ6YW7X+9kRwM=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
@@ -140,10 +150,10 @@ github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0
|
||||
github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg=
|
||||
github.com/nrdcg/goinwx v0.12.0 h1:ujdUqDBnaRSFwzVnImvPHYw3w3m9XgmGImNUw1GyMb4=
|
||||
github.com/nrdcg/goinwx v0.12.0/go.mod h1:IrVKd3ZDbFiMjdPgML4CSxZAY9wOoqLvH44zv3NodJ0=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.1 h1:+fx2mbWeR8XX/vidwpRMepJMtRIYQP44Iezm2oeObVM=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.1/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.1 h1:GDhBiaIAm/QXLzHJ0ASDdY/6R/9w60+gk8lY5rgfxEQ=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.1/go.mod h1:EHScJdbM0gg5Is7e3C0ceRYAFMMsfP4Vf8sBRoxoTgk=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.0 h1:4MRzV6spwPHKct+4/ETqkEtr39Hq+0KvxhsgqbgQ2Bo=
|
||||
github.com/nrdcg/oci-go-sdk/common/v1065 v1065.106.0/go.mod h1:Gcs8GCaZXL3FdiDWgdnMxlOLEdRprJJnPYB22TX1jw8=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.0 h1:RxraLVYX3eMUfQ1pDtJVvykEFGheky2YsrUt2HHRDcw=
|
||||
github.com/nrdcg/oci-go-sdk/dns/v1065 v1065.106.0/go.mod h1:JLMEKMX8IYPZ1TUSVHAVAbtnNSfP/I8OZQkAnfEMA0I=
|
||||
github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw=
|
||||
github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54=
|
||||
github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE=
|
||||
@@ -178,8 +188,11 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
||||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/vultr/govultr/v3 v3.26.1 h1:G/M0rMQKwVSmL+gb0UgETbW5mcQi0Vf/o/ZSGdBCxJw=
|
||||
github.com/vultr/govultr/v3 v3.26.1/go.mod h1:9WwnWGCKnwDlNjHjtt+j+nP+0QWq6hQXzaHgddqrLWY=
|
||||
github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM=
|
||||
@@ -208,6 +221,8 @@ go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||
go.uber.org/ratelimit v0.3.1 h1:K4qVE+byfv/B3tC+4nYWP7v/6SimcO7HzHekoMNBma0=
|
||||
go.uber.org/ratelimit v0.3.1/go.mod h1:6euWsTB6U/Nb3X++xEUXA8ciPJvr19Q/0h1+oDcJhRk=
|
||||
golang.org/x/arch v0.23.0 h1:lKF64A2jF6Zd8L0knGltUnegD62JMFBiCPBmQpToHhg=
|
||||
golang.org/x/arch v0.23.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A=
|
||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||
@@ -234,14 +249,14 @@ golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
||||
google.golang.org/api v0.261.0 h1:3DoJ2GGibaCxNi1lhdScNMx9fTW87ujKHDgyHMMYdoA=
|
||||
google.golang.org/api v0.261.0/go.mod h1:nVH0ZK5C4tO0RdsMscleeTLY7I8m/Nt9IXxcXD2tfts=
|
||||
google.golang.org/api v0.260.0 h1:XbNi5E6bOVEj/uLXQRlt6TKuEzMD7zvW/6tNwltE4P4=
|
||||
google.golang.org/api v0.260.0/go.mod h1:Shj1j0Phr/9sloYrKomICzdYgsSDImpTxME8rGLaZ/o=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
|
||||
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516 h1:sNrWoksmOyF5bvJUcnmbeAmQi8baNhqg5IWaI3llQqU=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120221211-b8f7ae30c516/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3 h1:C4WAdL+FbjnGlpp2S+HMVhBeCq2Lcib4xZqfPNF6OoQ=
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20260114163908-3f89685c29c3/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
|
||||
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
|
||||
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
|
||||
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"unsafe"
|
||||
|
||||
"github.com/docker/cli/cli/connhelper"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
@@ -198,9 +198,7 @@ func NewClient(cfg types.DockerProviderConfig, unique ...bool) (*SharedClient, e
|
||||
opt = append(opt, client.WithTLSClientConfig(cfg.TLS.CAFile, cfg.TLS.CertFile, cfg.TLS.KeyFile))
|
||||
}
|
||||
|
||||
opt = append(opt, client.WithAPIVersionNegotiation())
|
||||
|
||||
client, err := client.NewClientWithOpts(opt...)
|
||||
client, err := client.New(opt...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,8 +11,9 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/agent/pkg/agent"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
@@ -98,18 +99,18 @@ func UpdatePorts(ctx context.Context, c *types.Container) error {
|
||||
}
|
||||
defer dockerClient.Close()
|
||||
|
||||
inspect, err := dockerClient.ContainerInspect(ctx, c.ContainerID)
|
||||
inspect, err := dockerClient.ContainerInspect(ctx, c.ContainerID, client.ContainerInspectOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for port := range inspect.Config.ExposedPorts {
|
||||
proto, portStr := nat.SplitProtoPort(string(port))
|
||||
for port := range inspect.Container.Config.ExposedPorts {
|
||||
proto, portStr := nat.SplitProtoPort(port.String())
|
||||
portInt, _ := nat.ParsePort(portStr)
|
||||
if portInt == 0 {
|
||||
continue
|
||||
}
|
||||
c.PublicPortMapping[portInt] = container.Port{
|
||||
c.PublicPortMapping[portInt] = container.PortSummary{
|
||||
PublicPort: uint16(portInt), //nolint:gosec
|
||||
PrivatePort: uint16(portInt), //nolint:gosec
|
||||
Type: proto,
|
||||
@@ -210,8 +211,8 @@ func setPrivateHostname(c *types.Container, helper containerHelper) {
|
||||
}
|
||||
if c.Network != "" {
|
||||
v, hasNetwork := helper.NetworkSettings.Networks[c.Network]
|
||||
if hasNetwork && v.IPAddress != "" {
|
||||
c.PrivateHostname = v.IPAddress
|
||||
if hasNetwork && v.IPAddress.IsValid() {
|
||||
c.PrivateHostname = v.IPAddress.String()
|
||||
return
|
||||
}
|
||||
var hasComposeNetwork bool
|
||||
@@ -219,9 +220,9 @@ func setPrivateHostname(c *types.Container, helper containerHelper) {
|
||||
if proj := DockerComposeProject(c); proj != "" {
|
||||
newNetwork := fmt.Sprintf("%s_%s", proj, c.Network)
|
||||
v, hasComposeNetwork = helper.NetworkSettings.Networks[newNetwork]
|
||||
if hasComposeNetwork && v.IPAddress != "" {
|
||||
if hasComposeNetwork && v.IPAddress.IsValid() {
|
||||
c.Network = newNetwork // update network to the new one
|
||||
c.PrivateHostname = v.IPAddress
|
||||
c.PrivateHostname = v.IPAddress.String()
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -234,9 +235,9 @@ func setPrivateHostname(c *types.Container, helper containerHelper) {
|
||||
}
|
||||
// fallback to first network if no network is specified
|
||||
for k, v := range helper.NetworkSettings.Networks {
|
||||
if v.IPAddress != "" {
|
||||
if v.IPAddress.IsValid() {
|
||||
c.Network = k // update network to the first network
|
||||
c.PrivateHostname = v.IPAddress
|
||||
c.PrivateHostname = v.IPAddress.String()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package docker
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/ds/ordered"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
|
||||
@@ -3,7 +3,7 @@ package docker
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
)
|
||||
|
||||
@@ -3,12 +3,12 @@ package docker
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
)
|
||||
|
||||
var listOptions = container.ListOptions{
|
||||
var listOptions = client.ContainerListOptions{
|
||||
// created|restarting|running|removing|paused|exited|dead
|
||||
// Filters: filters.NewArgs(
|
||||
// filters.Arg("status", "created"),
|
||||
@@ -31,7 +31,7 @@ func ListContainers(ctx context.Context, dockerCfg types.DockerProviderConfig) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return containers, nil
|
||||
return containers.Items, nil
|
||||
}
|
||||
|
||||
func IsErrConnectionFailed(err error) bool {
|
||||
|
||||
@@ -100,7 +100,7 @@ func (ep *Entrypoint) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
rec := accesslog.GetResponseRecorder(w)
|
||||
w = rec
|
||||
defer func() {
|
||||
ep.accessLogger.LogRequest(r, rec.Response())
|
||||
ep.accessLogger.Log(r, rec.Response())
|
||||
accesslog.PutResponseRecorder(rec)
|
||||
}()
|
||||
}
|
||||
|
||||
@@ -2,12 +2,13 @@ package healthcheck
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
@@ -43,7 +44,7 @@ func Docker(ctx context.Context, state *DockerHealthcheckState, timeout time.Dur
|
||||
defer cancel()
|
||||
|
||||
// the actual inspect response is intercepted and returned as RequestInterceptedError
|
||||
_, err := state.client.ContainerInspect(ctx, state.containerId)
|
||||
_, err := state.client.ContainerInspect(ctx, state.containerId, client.ContainerInspectOptions{})
|
||||
|
||||
var interceptedErr *httputils.RequestInterceptedError
|
||||
if !httputils.AsRequestInterceptedError(err, &interceptedErr) {
|
||||
@@ -105,7 +106,7 @@ func interceptDockerInspectResponse(resp *http.Response) (intercepted bool, err
|
||||
}
|
||||
|
||||
var state container.State
|
||||
err = json.Unmarshal(body, &state)
|
||||
err = sonic.Unmarshal(body, &state)
|
||||
release(body)
|
||||
if err != nil {
|
||||
return false, err
|
||||
|
||||
@@ -2,12 +2,12 @@ package iconlist
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
@@ -270,7 +270,7 @@ func UpdateWalkxCodeIcons(m IconMap) error {
|
||||
}
|
||||
|
||||
data := make(map[string][]string)
|
||||
err = json.Unmarshal(body, &data)
|
||||
err = sonic.Unmarshal(body, &data)
|
||||
release(body)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -349,7 +349,7 @@ func UpdateSelfhstIcons(m IconMap) error {
|
||||
}
|
||||
|
||||
data := make([]SelfhStIcon, 0)
|
||||
err = json.Unmarshal(body, &data) //nolint:musttag
|
||||
err = sonic.Unmarshal(body, &data) //nolint:musttag
|
||||
release(body)
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
@@ -2,11 +2,11 @@ package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/godoxy/internal/homepage/widgets"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
@@ -59,7 +59,7 @@ func jsonRequest[T any](ctx context.Context, client *Client, endpoint string, qu
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
err = json.NewDecoder(resp.Body).Decode(&result)
|
||||
err = sonic.ConfigDefault.NewDecoder(resp.Body).Decode(&result)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
|
||||
@@ -2,10 +2,11 @@ package qbittorrent
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
const endpointLogs = "/api/v2/log/main"
|
||||
@@ -44,7 +45,7 @@ func (l *LogEntry) Level() string {
|
||||
}
|
||||
|
||||
func (l *LogEntry) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
return sonic.Marshal(map[string]any{
|
||||
"id": l.ID,
|
||||
"timestamp": l.Timestamp,
|
||||
"level": l.Level(),
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package idlewatcher
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ type watcherDebug struct {
|
||||
|
||||
func (w watcherDebug) MarshalJSON() ([]byte, error) {
|
||||
state := w.state.Load()
|
||||
return json.Marshal(map[string]any{
|
||||
return sonic.Marshal(map[string]any{
|
||||
"name": w.Name(),
|
||||
"state": map[string]string{
|
||||
"status": string(state.status),
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package idlewatcher
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type WakeEvent struct {
|
||||
@@ -39,7 +40,7 @@ func (w *Watcher) newWakeEvent(eventType WakeEventType, message string, err erro
|
||||
}
|
||||
|
||||
func (e *WakeEvent) WriteSSE(w io.Writer) error {
|
||||
data, err := json.Marshal(e)
|
||||
data, err := sonic.Marshal(e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -3,7 +3,8 @@ package provider
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
idlewatcher "github.com/yusing/godoxy/internal/idlewatcher/types"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
@@ -17,7 +18,7 @@ type DockerProvider struct {
|
||||
containerID string
|
||||
}
|
||||
|
||||
var startOptions = container.StartOptions{}
|
||||
var startOptions = client.ContainerStartOptions{}
|
||||
|
||||
func NewDockerProvider(dockerCfg types.DockerProviderConfig, containerID string) (idlewatcher.Provider, error) {
|
||||
client, err := docker.NewClient(dockerCfg)
|
||||
@@ -32,34 +33,41 @@ func NewDockerProvider(dockerCfg types.DockerProviderConfig, containerID string)
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerPause(ctx context.Context) error {
|
||||
return p.client.ContainerPause(ctx, p.containerID)
|
||||
_, err := p.client.ContainerPause(ctx, p.containerID, client.ContainerPauseOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerUnpause(ctx context.Context) error {
|
||||
return p.client.ContainerUnpause(ctx, p.containerID)
|
||||
_, err := p.client.ContainerUnpause(ctx, p.containerID, client.ContainerUnpauseOptions{})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerStart(ctx context.Context) error {
|
||||
return p.client.ContainerStart(ctx, p.containerID, startOptions)
|
||||
_, err := p.client.ContainerStart(ctx, p.containerID, startOptions)
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerStop(ctx context.Context, signal types.ContainerSignal, timeout int) error {
|
||||
return p.client.ContainerStop(ctx, p.containerID, container.StopOptions{
|
||||
_, err := p.client.ContainerStop(ctx, p.containerID, client.ContainerStopOptions{
|
||||
Signal: string(signal),
|
||||
Timeout: &timeout,
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerKill(ctx context.Context, signal types.ContainerSignal) error {
|
||||
return p.client.ContainerKill(ctx, p.containerID, string(signal))
|
||||
_, err := p.client.ContainerKill(ctx, p.containerID, client.ContainerKillOptions{
|
||||
Signal: string(signal),
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (p *DockerProvider) ContainerStatus(ctx context.Context) (idlewatcher.ContainerStatus, error) {
|
||||
status, err := p.client.ContainerInspect(ctx, p.containerID)
|
||||
status, err := p.client.ContainerInspect(ctx, p.containerID, client.ContainerInspectOptions{})
|
||||
if err != nil {
|
||||
return idlewatcher.ContainerStatusError, err
|
||||
}
|
||||
switch status.State.Status {
|
||||
switch status.Container.State.Status {
|
||||
case container.StateRunning:
|
||||
return idlewatcher.ContainerStatusRunning, nil
|
||||
case container.StateExited, container.StateDead, container.StateRestarting:
|
||||
@@ -67,12 +75,12 @@ func (p *DockerProvider) ContainerStatus(ctx context.Context) (idlewatcher.Conta
|
||||
case container.StatePaused:
|
||||
return idlewatcher.ContainerStatusPaused, nil
|
||||
}
|
||||
return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(status.State.Status)
|
||||
return idlewatcher.ContainerStatusError, idlewatcher.ErrUnexpectedContainerStatus.Subject(string(status.Container.State.Status))
|
||||
}
|
||||
|
||||
func (p *DockerProvider) Watch(ctx context.Context) (eventCh <-chan watcher.Event, errCh <-chan gperr.Error) {
|
||||
return p.watcher.EventsWithOptions(ctx, watcher.DockerListOptions{
|
||||
Filters: watcher.NewDockerFilter(
|
||||
Filters: watcher.NewDockerFilters(
|
||||
watcher.DockerFilterContainer,
|
||||
watcher.DockerFilterContainerNameID(p.containerID),
|
||||
watcher.DockerFilterStart,
|
||||
|
||||
@@ -261,7 +261,7 @@ func loadNS[T store](ns namespace) T {
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
if err := json.NewDecoder(file).Decode(&store); err != nil {
|
||||
if err := sonic.ConfigDefault.NewDecoder(file).Decode(&store); err != nil {
|
||||
log.Err(err).Msg("failed to decode store")
|
||||
}
|
||||
|
||||
@@ -316,7 +316,7 @@ type MapStore[VT any] struct {
|
||||
|
||||
```go
|
||||
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(xsync.ToPlainMap(s.Map))
|
||||
return sonic.Marshal(xsync.ToPlainMap(s.Map))
|
||||
}
|
||||
```
|
||||
|
||||
@@ -325,7 +325,7 @@ func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
```go
|
||||
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
|
||||
tmp := make(map[string]VT)
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Map = xsync.NewMap[string, VT](xsync.WithPresize(len(tmp)))
|
||||
@@ -349,7 +349,7 @@ The jsonstore package integrates with:
|
||||
Errors are logged but don't prevent store usage:
|
||||
|
||||
```go
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
log.Err(err).
|
||||
Str("path", path).
|
||||
Msg("failed to load store")
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
@@ -65,7 +66,7 @@ func loadNS[T store](ns namespace) T {
|
||||
}
|
||||
} else {
|
||||
defer file.Close()
|
||||
if err := json.NewDecoder(file).Decode(&store); err != nil {
|
||||
if err := sonic.ConfigDefault.NewDecoder(file).Decode(&store); err != nil {
|
||||
log.Err(err).
|
||||
Str("path", path).
|
||||
Msg("failed to load store")
|
||||
@@ -112,12 +113,12 @@ func (s *MapStore[VT]) Initialize() {
|
||||
}
|
||||
|
||||
func (s MapStore[VT]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(xsync.ToPlainMap(s.Map))
|
||||
return sonic.Marshal(xsync.ToPlainMap(s.Map))
|
||||
}
|
||||
|
||||
func (s *MapStore[VT]) UnmarshalJSON(data []byte) error {
|
||||
tmp := make(map[string]VT)
|
||||
if err := json.Unmarshal(data, &tmp); err != nil {
|
||||
if err := sonic.Unmarshal(data, &tmp); err != nil {
|
||||
return err
|
||||
}
|
||||
s.Map = xsync.NewMap[string, VT](xsync.WithPresize(len(tmp)))
|
||||
@@ -133,10 +134,10 @@ func (obj *ObjectStore[Ptr]) Initialize() {
|
||||
}
|
||||
|
||||
func (obj ObjectStore[Ptr]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(obj.ptr)
|
||||
return sonic.Marshal(obj.ptr)
|
||||
}
|
||||
|
||||
func (obj *ObjectStore[Ptr]) UnmarshalJSON(data []byte) error {
|
||||
obj.Initialize()
|
||||
return json.Unmarshal(data, obj.ptr)
|
||||
return sonic.Unmarshal(data, obj.ptr)
|
||||
}
|
||||
|
||||
@@ -20,20 +20,25 @@ import (
|
||||
)
|
||||
|
||||
type (
|
||||
File interface {
|
||||
io.WriteCloser
|
||||
supportRotate
|
||||
Name() string
|
||||
AccessLogger interface {
|
||||
Log(req *http.Request, res *http.Response)
|
||||
LogError(req *http.Request, err error)
|
||||
LogACL(info *maxmind.IPInfo, blocked bool)
|
||||
|
||||
Config() *Config
|
||||
|
||||
Flush()
|
||||
Close() error
|
||||
}
|
||||
|
||||
fileAccessLogger struct {
|
||||
accessLogger struct {
|
||||
task *task.Task
|
||||
cfg *Config
|
||||
|
||||
writer BufferedWriter
|
||||
file File
|
||||
writeLock *sync.Mutex
|
||||
closed bool
|
||||
writer BufferedWriter
|
||||
supportRotate SupportRotate
|
||||
writeLock *sync.Mutex
|
||||
closed bool
|
||||
|
||||
writeCount int64
|
||||
bufSize int
|
||||
@@ -43,7 +48,32 @@ type (
|
||||
logger zerolog.Logger
|
||||
|
||||
RequestFormatter
|
||||
ACLLogFormatter
|
||||
ACLFormatter
|
||||
}
|
||||
|
||||
Writer interface {
|
||||
io.WriteCloser
|
||||
ShouldBeBuffered() bool
|
||||
Name() string // file name or path
|
||||
}
|
||||
|
||||
SupportRotate interface {
|
||||
io.Writer
|
||||
supportRotate
|
||||
Name() string
|
||||
}
|
||||
|
||||
AccessLogRotater interface {
|
||||
Rotate(result *RotateResult) (rotated bool, err error)
|
||||
}
|
||||
|
||||
RequestFormatter interface {
|
||||
// AppendRequestLog appends a log line to line with or without a trailing newline
|
||||
AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte
|
||||
}
|
||||
ACLFormatter interface {
|
||||
// AppendACLLog appends a log line to line with or without a trailing newline
|
||||
AppendACLLog(line []byte, info *maxmind.IPInfo, blocked bool) []byte
|
||||
}
|
||||
)
|
||||
|
||||
@@ -66,87 +96,112 @@ const (
|
||||
var bytesPool = synk.GetUnsizedBytesPool()
|
||||
var sizedPool = synk.GetSizedBytesPool()
|
||||
|
||||
func NewFileAccessLogger(parent task.Parent, file File, anyCfg AnyConfig) AccessLogger {
|
||||
func NewAccessLogger(parent task.Parent, cfg AnyConfig) (AccessLogger, error) {
|
||||
writers, err := cfg.Writers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewMultiAccessLogger(parent, cfg, writers), nil
|
||||
}
|
||||
|
||||
func NewMockAccessLogger(parent task.Parent, cfg *RequestLoggerConfig) AccessLogger {
|
||||
return NewAccessLoggerWithIO(parent, NewMockFile(true), cfg)
|
||||
}
|
||||
|
||||
func NewAccessLoggerWithIO(parent task.Parent, writer Writer, anyCfg AnyConfig) AccessLogger {
|
||||
cfg := anyCfg.ToConfig()
|
||||
if cfg.RotateInterval == 0 {
|
||||
cfg.RotateInterval = defaultRotateInterval
|
||||
}
|
||||
|
||||
name := file.Name()
|
||||
l := &fileAccessLogger{
|
||||
task: parent.Subtask("accesslog."+name, true),
|
||||
l := &accessLogger{
|
||||
task: parent.Subtask("accesslog."+writer.Name(), true),
|
||||
cfg: cfg,
|
||||
bufSize: InitialBufferSize,
|
||||
errRateLimiter: rate.NewLimiter(rate.Every(errRateLimit), errBurst),
|
||||
logger: log.With().Str("file", name).Logger(),
|
||||
logger: log.With().Str("file", writer.Name()).Logger(),
|
||||
}
|
||||
|
||||
l.writeLock, _ = writerLocks.LoadOrStore(name, &sync.Mutex{})
|
||||
l.writeLock, _ = writerLocks.LoadOrStore(writer.Name(), &sync.Mutex{})
|
||||
|
||||
l.writer = ioutils.NewBufferedWriter(file, InitialBufferSize)
|
||||
l.file = file
|
||||
if writer.ShouldBeBuffered() {
|
||||
l.writer = ioutils.NewBufferedWriter(writer, InitialBufferSize)
|
||||
} else {
|
||||
l.writer = NewUnbufferedWriter(writer)
|
||||
}
|
||||
|
||||
if supportRotate, ok := writer.(SupportRotate); ok {
|
||||
l.supportRotate = supportRotate
|
||||
}
|
||||
|
||||
if cfg.req != nil {
|
||||
fmt := CommonFormatter{cfg: &cfg.req.Fields}
|
||||
switch cfg.req.Format {
|
||||
case FormatCommon:
|
||||
l.RequestFormatter = CommonFormatter{cfg: &cfg.req.Fields}
|
||||
l.RequestFormatter = &fmt
|
||||
case FormatCombined:
|
||||
l.RequestFormatter = CombinedFormatter{CommonFormatter{cfg: &cfg.req.Fields}}
|
||||
l.RequestFormatter = &CombinedFormatter{fmt}
|
||||
case FormatJSON:
|
||||
l.RequestFormatter = JSONFormatter{cfg: &cfg.req.Fields}
|
||||
l.RequestFormatter = &JSONFormatter{fmt}
|
||||
default: // should not happen, validation has done by validate tags
|
||||
panic("invalid access log format")
|
||||
}
|
||||
} else {
|
||||
l.ACLFormatter = ACLLogFormatter{}
|
||||
}
|
||||
|
||||
go l.start()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) Config() *Config {
|
||||
func (l *accessLogger) Config() *Config {
|
||||
return l.cfg
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) LogRequest(req *http.Request, res *http.Response) {
|
||||
if !l.cfg.ShouldLogRequest(req, res) {
|
||||
func (l *accessLogger) shouldLog(req *http.Request, res *http.Response) bool {
|
||||
if !l.cfg.req.Filters.StatusCodes.CheckKeep(req, res) ||
|
||||
!l.cfg.req.Filters.Method.CheckKeep(req, res) ||
|
||||
!l.cfg.req.Filters.Headers.CheckKeep(req, res) ||
|
||||
!l.cfg.req.Filters.CIDR.CheckKeep(req, res) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *accessLogger) Log(req *http.Request, res *http.Response) {
|
||||
if !l.shouldLog(req, res) {
|
||||
return
|
||||
}
|
||||
|
||||
line := bytesPool.GetBuffer()
|
||||
defer bytesPool.PutBuffer(line)
|
||||
l.AppendRequestLog(line, req, res)
|
||||
// line is never empty
|
||||
if line.Bytes()[line.Len()-1] != '\n' {
|
||||
line.WriteByte('\n')
|
||||
line := bytesPool.Get()
|
||||
line = l.AppendRequestLog(line, req, res)
|
||||
if line[len(line)-1] != '\n' {
|
||||
line = append(line, '\n')
|
||||
}
|
||||
l.write(line.Bytes())
|
||||
l.write(line)
|
||||
bytesPool.Put(line)
|
||||
}
|
||||
|
||||
var internalErrorResponse = &http.Response{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Status: http.StatusText(http.StatusInternalServerError),
|
||||
func (l *accessLogger) LogError(req *http.Request, err error) {
|
||||
l.Log(req, &http.Response{StatusCode: http.StatusInternalServerError, Status: err.Error()})
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) LogError(req *http.Request, err error) {
|
||||
l.LogRequest(req, internalErrorResponse)
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) LogACL(info *maxmind.IPInfo, blocked bool) {
|
||||
line := bytesPool.GetBuffer()
|
||||
defer bytesPool.PutBuffer(line)
|
||||
l.AppendACLLog(line, info, blocked)
|
||||
// line is never empty
|
||||
if line.Bytes()[line.Len()-1] != '\n' {
|
||||
line.WriteByte('\n')
|
||||
func (l *accessLogger) LogACL(info *maxmind.IPInfo, blocked bool) {
|
||||
line := bytesPool.Get()
|
||||
line = l.AppendACLLog(line, info, blocked)
|
||||
if line[len(line)-1] != '\n' {
|
||||
line = append(line, '\n')
|
||||
}
|
||||
l.write(line.Bytes())
|
||||
l.write(line)
|
||||
bytesPool.Put(line)
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) ShouldRotate() bool {
|
||||
return l.cfg.Retention.IsValid()
|
||||
func (l *accessLogger) ShouldRotate() bool {
|
||||
return l.supportRotate != nil && l.cfg.Retention.IsValid()
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) Rotate(result *RotateResult) (rotated bool, err error) {
|
||||
func (l *accessLogger) Rotate(result *RotateResult) (rotated bool, err error) {
|
||||
if !l.ShouldRotate() {
|
||||
return false, nil
|
||||
}
|
||||
@@ -155,11 +210,11 @@ func (l *fileAccessLogger) Rotate(result *RotateResult) (rotated bool, err error
|
||||
l.writeLock.Lock()
|
||||
defer l.writeLock.Unlock()
|
||||
|
||||
rotated, err = rotateLogFile(l.file, l.cfg.Retention, result)
|
||||
rotated, err = rotateLogFile(l.supportRotate, l.cfg.Retention, result)
|
||||
return
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) handleErr(err error) {
|
||||
func (l *accessLogger) handleErr(err error) {
|
||||
if l.errRateLimiter.Allow() {
|
||||
gperr.LogError("failed to write access log", err, &l.logger)
|
||||
} else {
|
||||
@@ -168,7 +223,7 @@ func (l *fileAccessLogger) handleErr(err error) {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) start() {
|
||||
func (l *accessLogger) start() {
|
||||
defer func() {
|
||||
l.Flush()
|
||||
l.Close()
|
||||
@@ -204,7 +259,7 @@ func (l *fileAccessLogger) start() {
|
||||
}
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) Close() error {
|
||||
func (l *accessLogger) Close() error {
|
||||
l.writeLock.Lock()
|
||||
defer l.writeLock.Unlock()
|
||||
if l.closed {
|
||||
@@ -215,7 +270,7 @@ func (l *fileAccessLogger) Close() error {
|
||||
return l.writer.Close()
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) Flush() {
|
||||
func (l *accessLogger) Flush() {
|
||||
l.writeLock.Lock()
|
||||
defer l.writeLock.Unlock()
|
||||
if l.closed {
|
||||
@@ -224,7 +279,7 @@ func (l *fileAccessLogger) Flush() {
|
||||
l.writer.Flush()
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) write(data []byte) {
|
||||
func (l *accessLogger) write(data []byte) {
|
||||
l.writeLock.Lock()
|
||||
defer l.writeLock.Unlock()
|
||||
if l.closed {
|
||||
@@ -239,7 +294,7 @@ func (l *fileAccessLogger) write(data []byte) {
|
||||
atomic.AddInt64(&l.writeCount, int64(n))
|
||||
}
|
||||
|
||||
func (l *fileAccessLogger) adjustBuffer() {
|
||||
func (l *accessLogger) adjustBuffer() {
|
||||
wps := int(atomic.SwapInt64(&l.writeCount, 0)) / int(bufferAdjustInterval.Seconds())
|
||||
origBufSize := l.bufSize
|
||||
newBufSize := origBufSize
|
||||
@@ -1,7 +1,6 @@
|
||||
package accesslog_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
@@ -54,13 +53,13 @@ var (
|
||||
)
|
||||
|
||||
func fmtLog(cfg *RequestLoggerConfig) (ts string, line string) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
buf := make([]byte, 0, 1024)
|
||||
|
||||
t := time.Now()
|
||||
logger := NewMockAccessLogger(testTask, cfg)
|
||||
mockable.MockTimeNow(t)
|
||||
logger.(RequestFormatter).AppendRequestLog(buf, req, resp)
|
||||
return t.Format(LogTimeFormat), buf.String()
|
||||
buf = logger.(RequestFormatter).AppendRequestLog(buf, req, resp)
|
||||
return t.Format(LogTimeFormat), string(buf)
|
||||
}
|
||||
|
||||
func TestAccessLoggerCommon(t *testing.T) {
|
||||
@@ -142,6 +141,9 @@ func TestAccessLoggerJSON(t *testing.T) {
|
||||
expect.Equal(t, entry.UserAgent, ua)
|
||||
expect.Equal(t, len(entry.Headers), 0)
|
||||
expect.Equal(t, len(entry.Cookies), 0)
|
||||
if status >= 400 {
|
||||
expect.Equal(t, entry.Error, http.StatusText(status))
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAccessLoggerJSON(b *testing.B) {
|
||||
@@ -150,7 +152,7 @@ func BenchmarkAccessLoggerJSON(b *testing.B) {
|
||||
logger := NewMockAccessLogger(testTask, config)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -160,6 +162,6 @@ func BenchmarkAccessLoggerCombined(b *testing.B) {
|
||||
logger := NewMockAccessLogger(testTask, config)
|
||||
b.ResetTimer()
|
||||
for b.Loop() {
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,10 @@ type ReaderAtSeeker interface {
|
||||
|
||||
// BackScanner provides an interface to read a file backward line by line.
|
||||
type BackScanner struct {
|
||||
file ReaderAtSeeker
|
||||
size int64
|
||||
chunkBuf []byte
|
||||
file ReaderAtSeeker
|
||||
size int64
|
||||
chunkSize int
|
||||
chunkBuf []byte
|
||||
|
||||
offset int64
|
||||
chunk []byte
|
||||
@@ -26,25 +27,16 @@ type BackScanner struct {
|
||||
// NewBackScanner creates a new Scanner to read the file backward.
|
||||
// chunkSize determines the size of each read chunk from the end of the file.
|
||||
func NewBackScanner(file ReaderAtSeeker, fileSize int64, chunkSize int) *BackScanner {
|
||||
return newBackScanner(file, fileSize, sizedPool.GetSized(chunkSize))
|
||||
return newBackScanner(file, fileSize, make([]byte, chunkSize))
|
||||
}
|
||||
|
||||
func newBackScanner(file ReaderAtSeeker, fileSize int64, buf []byte) *BackScanner {
|
||||
return &BackScanner{
|
||||
file: file,
|
||||
size: fileSize,
|
||||
offset: fileSize,
|
||||
chunkBuf: buf,
|
||||
}
|
||||
}
|
||||
|
||||
// Release releases the buffer back to the pool.
|
||||
func (s *BackScanner) Release() {
|
||||
sizedPool.Put(s.chunkBuf)
|
||||
s.chunkBuf = nil
|
||||
if s.chunk != nil {
|
||||
sizedPool.Put(s.chunk)
|
||||
s.chunk = nil
|
||||
file: file,
|
||||
size: fileSize,
|
||||
offset: fileSize,
|
||||
chunkSize: len(buf),
|
||||
chunkBuf: buf,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,14 +64,13 @@ func (s *BackScanner) Scan() bool {
|
||||
// No more data to read; check remaining buffer
|
||||
if len(s.chunk) > 0 {
|
||||
s.line = s.chunk
|
||||
sizedPool.Put(s.chunk)
|
||||
s.chunk = nil
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
newOffset := max(0, s.offset-int64(len(s.chunkBuf)))
|
||||
newOffset := max(0, s.offset-int64(s.chunkSize))
|
||||
chunkSize := s.offset - newOffset
|
||||
chunk := s.chunkBuf[:chunkSize]
|
||||
|
||||
@@ -94,19 +85,8 @@ func (s *BackScanner) Scan() bool {
|
||||
}
|
||||
|
||||
// Prepend the chunk to the buffer
|
||||
if s.chunk == nil { // first chunk
|
||||
s.chunk = sizedPool.GetSized(2 * len(s.chunkBuf))
|
||||
copy(s.chunk, chunk[:n])
|
||||
s.chunk = s.chunk[:n]
|
||||
} else {
|
||||
neededSize := n + len(s.chunk)
|
||||
newChunk := sizedPool.GetSized(max(neededSize, 2*len(s.chunkBuf)))
|
||||
copy(newChunk, chunk[:n])
|
||||
copy(newChunk[n:], s.chunk)
|
||||
sizedPool.Put(s.chunk)
|
||||
s.chunk = newChunk[:neededSize]
|
||||
}
|
||||
|
||||
clone := append([]byte{}, chunk[:n]...)
|
||||
s.chunk = append(clone, s.chunk...)
|
||||
s.offset = newOffset
|
||||
|
||||
// Check for newline in the updated buffer
|
||||
@@ -131,3 +111,12 @@ func (s *BackScanner) Bytes() []byte {
|
||||
func (s *BackScanner) Err() error {
|
||||
return s.err
|
||||
}
|
||||
|
||||
func (s *BackScanner) Reset() error {
|
||||
_, err := s.file.Seek(0, io.SeekStart)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
*s = *newBackScanner(s.file, s.size, s.chunkBuf)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"math/rand/v2"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
|
||||
strutils "github.com/yusing/goutils/strings"
|
||||
"github.com/yusing/goutils/task"
|
||||
@@ -137,40 +135,88 @@ func TestBackScannerWithVaryingChunkSizes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
var logEntry = func() func() []byte {
|
||||
func logEntry() []byte {
|
||||
accesslog := NewMockAccessLogger(task.RootTask("test", false), &RequestLoggerConfig{
|
||||
Format: FormatJSON,
|
||||
})
|
||||
|
||||
contentTypes := []string{"application/json", "text/html", "text/plain", "application/xml", "application/x-www-form-urlencoded"}
|
||||
userAgents := []string{"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/120.0", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/120.0", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Firefox/120.0"}
|
||||
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD"}
|
||||
paths := []string{"/", "/about", "/contact", "/login", "/logout", "/register", "/profile"}
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
allocSize := rand.IntN(8192)
|
||||
w.Header().Set("Content-Type", contentTypes[rand.IntN(len(contentTypes))])
|
||||
w.Header().Set("Content-Length", strconv.Itoa(allocSize))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write([]byte("hello"))
|
||||
}))
|
||||
srv.URL = "http://localhost:8080"
|
||||
|
||||
return func() []byte {
|
||||
// make a request to the server
|
||||
req, _ := http.NewRequest(http.MethodGet, srv.URL, nil)
|
||||
res := httptest.NewRecorder()
|
||||
req.Header.Set("User-Agent", userAgents[rand.IntN(len(userAgents))])
|
||||
req.Method = methods[rand.IntN(len(methods))]
|
||||
req.URL.Path = paths[rand.IntN(len(paths))]
|
||||
// server the request
|
||||
srv.Config.Handler.ServeHTTP(res, req)
|
||||
b := bytes.NewBuffer(make([]byte, 0, 1024))
|
||||
accesslog.(RequestFormatter).AppendRequestLog(b, req, res.Result())
|
||||
return b.Bytes()
|
||||
defer srv.Close()
|
||||
// make a request to the server
|
||||
req, _ := http.NewRequest(http.MethodGet, srv.URL, nil)
|
||||
res := httptest.NewRecorder()
|
||||
// server the request
|
||||
srv.Config.Handler.ServeHTTP(res, req)
|
||||
b := accesslog.(RequestFormatter).AppendRequestLog(nil, req, res.Result())
|
||||
if b[len(b)-1] != '\n' {
|
||||
b = append(b, '\n')
|
||||
}
|
||||
}()
|
||||
return b
|
||||
}
|
||||
|
||||
func TestReset(t *testing.T) {
|
||||
file, err := afero.TempFile(afero.NewOsFs(), "", "accesslog")
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
line := logEntry()
|
||||
nLines := 1000
|
||||
for range nLines {
|
||||
_, err := file.Write(line)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write to temp file: %v", err)
|
||||
}
|
||||
}
|
||||
linesRead := 0
|
||||
stat, _ := file.Stat()
|
||||
s := NewBackScanner(file, stat.Size(), defaultChunkSize)
|
||||
for s.Scan() {
|
||||
linesRead++
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
t.Errorf("scanner error: %v", err)
|
||||
}
|
||||
expect.Equal(t, linesRead, nLines)
|
||||
err = s.Reset()
|
||||
if err != nil {
|
||||
t.Errorf("failed to reset scanner: %v", err)
|
||||
}
|
||||
|
||||
linesRead = 0
|
||||
for s.Scan() {
|
||||
linesRead++
|
||||
}
|
||||
if err := s.Err(); err != nil {
|
||||
t.Errorf("scanner error: %v", err)
|
||||
}
|
||||
expect.Equal(t, linesRead, nLines)
|
||||
}
|
||||
|
||||
// 100000 log entries.
|
||||
func BenchmarkBackScanner(b *testing.B) {
|
||||
mockFile := NewMockFile(false)
|
||||
line := logEntry()
|
||||
for range 100000 {
|
||||
_, _ = mockFile.Write(line)
|
||||
}
|
||||
for i := range 14 {
|
||||
chunkSize := (2 << i) * kilobyte
|
||||
scanner := NewBackScanner(mockFile, mockFile.MustSize(), chunkSize)
|
||||
name := strutils.FormatByteSize(chunkSize)
|
||||
b.ResetTimer()
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
_ = scanner.Reset()
|
||||
for scanner.Scan() {
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkBackScannerRealFile(b *testing.B) {
|
||||
file, err := afero.TempFile(afero.NewOsFs(), "", "accesslog")
|
||||
if err != nil {
|
||||
@@ -178,58 +224,51 @@ func BenchmarkBackScannerRealFile(b *testing.B) {
|
||||
}
|
||||
defer os.Remove(file.Name())
|
||||
|
||||
buf := bytes.NewBuffer(nil)
|
||||
for range 100000 {
|
||||
buf.Write(logEntry())
|
||||
for range 10000 {
|
||||
_, err = file.Write(logEntry())
|
||||
if err != nil {
|
||||
b.Fatalf("failed to write to temp file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
fSize := int64(buf.Len())
|
||||
_, err = file.Write(buf.Bytes())
|
||||
if err != nil {
|
||||
b.Fatalf("failed to write to file: %v", err)
|
||||
stat, _ := file.Stat()
|
||||
scanner := NewBackScanner(file, stat.Size(), 256*kilobyte)
|
||||
b.ResetTimer()
|
||||
for scanner.Scan() {
|
||||
}
|
||||
|
||||
// file position does not matter, Seek not needed
|
||||
|
||||
for i := range 12 {
|
||||
chunkSize := (2 << i) * kilobyte
|
||||
name := strutils.FormatByteSize(chunkSize)
|
||||
b.ResetTimer()
|
||||
b.Run(name, func(b *testing.B) {
|
||||
for b.Loop() {
|
||||
scanner := NewBackScanner(file, fSize, chunkSize)
|
||||
for scanner.Scan() {
|
||||
}
|
||||
scanner.Release()
|
||||
}
|
||||
})
|
||||
if err := scanner.Err(); err != nil {
|
||||
b.Errorf("scanner error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
BenchmarkBackScannerRealFile
|
||||
BenchmarkBackScannerRealFile/2_KiB
|
||||
BenchmarkBackScannerRealFile/2_KiB-10 21 51796773 ns/op 619 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/4_KiB
|
||||
BenchmarkBackScannerRealFile/4_KiB-10 36 32081281 ns/op 699 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/8_KiB
|
||||
BenchmarkBackScannerRealFile/8_KiB-10 57 22155619 ns/op 847 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/16_KiB
|
||||
BenchmarkBackScannerRealFile/16_KiB-10 62 21323125 ns/op 1449 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/32_KiB
|
||||
BenchmarkBackScannerRealFile/32_KiB-10 63 17534883 ns/op 2729 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/64_KiB
|
||||
BenchmarkBackScannerRealFile/64_KiB-10 73 17877029 ns/op 4617 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/128_KiB
|
||||
BenchmarkBackScannerRealFile/128_KiB-10 75 17797267 ns/op 8866 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/256_KiB
|
||||
BenchmarkBackScannerRealFile/256_KiB-10 67 16732108 ns/op 19691 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/512_KiB
|
||||
BenchmarkBackScannerRealFile/512_KiB-10 70 17121683 ns/op 37577 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/1_MiB
|
||||
BenchmarkBackScannerRealFile/1_MiB-10 51 19615791 ns/op 102930 B/op 1 allocs/op
|
||||
BenchmarkBackScannerRealFile/2_MiB
|
||||
BenchmarkBackScannerRealFile/2_MiB-10 26 41744928 ns/op 77595287 B/op 57 allocs/op
|
||||
BenchmarkBackScannerRealFile/4_MiB
|
||||
BenchmarkBackScannerRealFile/4_MiB-10 22 48081521 ns/op 79692224 B/op 49 allocs/op
|
||||
BenchmarkBackScanner
|
||||
BenchmarkBackScanner/2_KiB
|
||||
BenchmarkBackScanner/2_KiB-20 52 23254071 ns/op 67596663 B/op 26420 allocs/op
|
||||
BenchmarkBackScanner/4_KiB
|
||||
BenchmarkBackScanner/4_KiB-20 55 20961059 ns/op 62529378 B/op 13211 allocs/op
|
||||
BenchmarkBackScanner/8_KiB
|
||||
BenchmarkBackScanner/8_KiB-20 64 18242460 ns/op 62951141 B/op 6608 allocs/op
|
||||
BenchmarkBackScanner/16_KiB
|
||||
BenchmarkBackScanner/16_KiB-20 52 20162076 ns/op 62940256 B/op 3306 allocs/op
|
||||
BenchmarkBackScanner/32_KiB
|
||||
BenchmarkBackScanner/32_KiB-20 54 19247968 ns/op 67553645 B/op 1656 allocs/op
|
||||
BenchmarkBackScanner/64_KiB
|
||||
BenchmarkBackScanner/64_KiB-20 60 20909046 ns/op 64053342 B/op 827 allocs/op
|
||||
BenchmarkBackScanner/128_KiB
|
||||
BenchmarkBackScanner/128_KiB-20 68 17759890 ns/op 62201945 B/op 414 allocs/op
|
||||
BenchmarkBackScanner/256_KiB
|
||||
BenchmarkBackScanner/256_KiB-20 52 19531877 ns/op 61030487 B/op 208 allocs/op
|
||||
BenchmarkBackScanner/512_KiB
|
||||
BenchmarkBackScanner/512_KiB-20 54 19124656 ns/op 61030485 B/op 208 allocs/op
|
||||
BenchmarkBackScanner/1_MiB
|
||||
BenchmarkBackScanner/1_MiB-20 67 17078936 ns/op 61030495 B/op 208 allocs/op
|
||||
BenchmarkBackScanner/2_MiB
|
||||
BenchmarkBackScanner/2_MiB-20 66 18467421 ns/op 61030492 B/op 208 allocs/op
|
||||
BenchmarkBackScanner/4_MiB
|
||||
BenchmarkBackScanner/4_MiB-20 68 17214573 ns/op 61030486 B/op 208 allocs/op
|
||||
BenchmarkBackScanner/8_MiB
|
||||
BenchmarkBackScanner/8_MiB-20 57 18235229 ns/op 61030492 B/op 208 allocs/op
|
||||
BenchmarkBackScanner/16_MiB
|
||||
BenchmarkBackScanner/16_MiB-20 57 19343441 ns/op 61030499 B/op 208 allocs/op
|
||||
*/
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
@@ -10,15 +9,16 @@ import (
|
||||
|
||||
type (
|
||||
ConfigBase struct {
|
||||
Path string `json:"path,omitempty"`
|
||||
B int `json:"buffer_size"` // Deprecated: buffer size is adjusted dynamically
|
||||
Path string `json:"path"`
|
||||
Stdout bool `json:"stdout"`
|
||||
Retention *Retention `json:"retention" aliases:"keep"`
|
||||
RotateInterval time.Duration `json:"rotate_interval,omitempty" swaggertype:"primitive,integer"`
|
||||
} // @name AccessLoggerConfigBase
|
||||
}
|
||||
ACLLoggerConfig struct {
|
||||
ConfigBase
|
||||
LogAllowed bool `json:"log_allowed"`
|
||||
} // @name ACLLoggerConfig
|
||||
}
|
||||
RequestLoggerConfig struct {
|
||||
ConfigBase
|
||||
Format Format `json:"format" validate:"oneof=common combined json"`
|
||||
@@ -32,21 +32,21 @@ type (
|
||||
}
|
||||
AnyConfig interface {
|
||||
ToConfig() *Config
|
||||
Writers() ([]File, error)
|
||||
Writers() ([]Writer, error)
|
||||
}
|
||||
|
||||
Format string
|
||||
Filters struct {
|
||||
StatusCodes LogFilter[*StatusCodeRange] `json:"status_codes,omitzero"`
|
||||
Method LogFilter[HTTPMethod] `json:"method,omitzero"`
|
||||
Host LogFilter[Host] `json:"host,omitzero"`
|
||||
Headers LogFilter[*HTTPHeader] `json:"headers,omitzero"` // header exists or header == value
|
||||
CIDR LogFilter[*CIDR] `json:"cidr,omitzero"`
|
||||
StatusCodes LogFilter[*StatusCodeRange] `json:"status_codes"`
|
||||
Method LogFilter[HTTPMethod] `json:"method"`
|
||||
Host LogFilter[Host] `json:"host"`
|
||||
Headers LogFilter[*HTTPHeader] `json:"headers"` // header exists or header == value
|
||||
CIDR LogFilter[*CIDR] `json:"cidr"`
|
||||
}
|
||||
Fields struct {
|
||||
Headers FieldConfig `json:"headers,omitzero" aliases:"header"`
|
||||
Query FieldConfig `json:"query,omitzero" aliases:"queries"`
|
||||
Cookies FieldConfig `json:"cookies,omitzero" aliases:"cookie"`
|
||||
Headers FieldConfig `json:"headers" aliases:"header"`
|
||||
Query FieldConfig `json:"query" aliases:"queries"`
|
||||
Cookies FieldConfig `json:"cookies" aliases:"cookie"`
|
||||
}
|
||||
)
|
||||
|
||||
@@ -66,17 +66,17 @@ func (cfg *ConfigBase) Validate() gperr.Error {
|
||||
}
|
||||
|
||||
// Writers returns a list of writers for the config.
|
||||
func (cfg *ConfigBase) Writers() ([]File, error) {
|
||||
writers := make([]File, 0, 2)
|
||||
func (cfg *ConfigBase) Writers() ([]Writer, error) {
|
||||
writers := make([]Writer, 0, 2)
|
||||
if cfg.Path != "" {
|
||||
f, err := OpenFile(cfg.Path)
|
||||
io, err := NewFileIO(cfg.Path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
writers = append(writers, f)
|
||||
writers = append(writers, io)
|
||||
}
|
||||
if cfg.Stdout {
|
||||
writers = append(writers, stdout)
|
||||
writers = append(writers, NewStdout())
|
||||
}
|
||||
return writers, nil
|
||||
}
|
||||
@@ -95,16 +95,6 @@ func (cfg *RequestLoggerConfig) ToConfig() *Config {
|
||||
}
|
||||
}
|
||||
|
||||
func (cfg *Config) ShouldLogRequest(req *http.Request, res *http.Response) bool {
|
||||
if cfg.req == nil {
|
||||
return true
|
||||
}
|
||||
return cfg.req.Filters.StatusCodes.CheckKeep(req, res) &&
|
||||
cfg.req.Filters.Method.CheckKeep(req, res) &&
|
||||
cfg.req.Filters.Headers.CheckKeep(req, res) &&
|
||||
cfg.req.Filters.CIDR.CheckKeep(req, res)
|
||||
}
|
||||
|
||||
func DefaultRequestLoggerConfig() *RequestLoggerConfig {
|
||||
return &RequestLoggerConfig{
|
||||
ConfigBase: ConfigBase{
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
|
||||
)
|
||||
|
||||
type ConsoleLogger struct {
|
||||
cfg *Config
|
||||
|
||||
formatter ConsoleFormatter
|
||||
}
|
||||
|
||||
var stdoutLogger = func() *zerolog.Logger {
|
||||
l := zerolog.New(zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
||||
w.Out = os.Stdout
|
||||
w.TimeFormat = zerolog.TimeFieldFormat
|
||||
w.FieldsOrder = []string{
|
||||
"uri", "protocol", "type", "size",
|
||||
"useragent", "query", "headers", "cookies",
|
||||
"error", "iso_code", "time_zone"}
|
||||
})).With().Str("level", zerolog.InfoLevel.String()).Timestamp().Logger()
|
||||
return &l
|
||||
}()
|
||||
|
||||
// placeholder for console logger
|
||||
var stdout File = &sharedFileHandle{}
|
||||
|
||||
func NewConsoleLogger(cfg *Config) AccessLogger {
|
||||
if cfg == nil {
|
||||
panic("accesslog: NewConsoleLogger called with nil config")
|
||||
}
|
||||
l := &ConsoleLogger{
|
||||
cfg: cfg,
|
||||
}
|
||||
if cfg.req != nil {
|
||||
l.formatter = ConsoleFormatter{cfg: &cfg.req.Fields}
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *ConsoleLogger) Config() *Config {
|
||||
return l.cfg
|
||||
}
|
||||
|
||||
func (l *ConsoleLogger) LogRequest(req *http.Request, res *http.Response) {
|
||||
if !l.cfg.ShouldLogRequest(req, res) {
|
||||
return
|
||||
}
|
||||
|
||||
l.formatter.LogRequestZeroLog(stdoutLogger, req, res)
|
||||
}
|
||||
|
||||
func (l *ConsoleLogger) LogError(req *http.Request, err error) {
|
||||
log := stdoutLogger.With().Err(err).Logger()
|
||||
l.formatter.LogRequestZeroLog(&log, req, internalErrorResponse)
|
||||
}
|
||||
|
||||
func (l *ConsoleLogger) LogACL(info *maxmind.IPInfo, blocked bool) {
|
||||
ConsoleACLFormatter{}.LogACLZeroLog(stdoutLogger, info, blocked)
|
||||
}
|
||||
|
||||
func (l *ConsoleLogger) Flush() {
|
||||
// No-op for console logger
|
||||
}
|
||||
|
||||
func (l *ConsoleLogger) Close() error {
|
||||
// No-op for console logger
|
||||
return nil
|
||||
}
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"github.com/yusing/goutils/synk"
|
||||
)
|
||||
|
||||
type sharedFileHandle struct {
|
||||
*os.File
|
||||
type File struct {
|
||||
f *os.File
|
||||
|
||||
// os.File.Name() may not equal to key of `openedFiles`.
|
||||
// Store it for later delete from `openedFiles`.
|
||||
@@ -22,18 +22,18 @@ type sharedFileHandle struct {
|
||||
}
|
||||
|
||||
var (
|
||||
openedFiles = make(map[string]*sharedFileHandle)
|
||||
openedFiles = make(map[string]*File)
|
||||
openedFilesMu sync.Mutex
|
||||
)
|
||||
|
||||
// OpenFile creates a new file writer with cleaned path.
|
||||
// NewFileIO creates a new file writer with cleaned path.
|
||||
//
|
||||
// If the file is already opened, it will be returned.
|
||||
func OpenFile(path string) (File, error) {
|
||||
func NewFileIO(path string) (Writer, error) {
|
||||
openedFilesMu.Lock()
|
||||
defer openedFilesMu.Unlock()
|
||||
|
||||
var file *sharedFileHandle
|
||||
var file *File
|
||||
var err error
|
||||
|
||||
// make it absolute path, so that we can use it as key of `openedFiles` and shared lock
|
||||
@@ -53,38 +53,65 @@ func OpenFile(path string) (File, error) {
|
||||
return nil, fmt.Errorf("access log open error: %w", err)
|
||||
}
|
||||
if _, err := f.Seek(0, io.SeekEnd); err != nil {
|
||||
f.Close()
|
||||
return nil, fmt.Errorf("access log seek error: %w", err)
|
||||
}
|
||||
|
||||
file = &sharedFileHandle{File: f, path: path, refCount: synk.NewRefCounter()}
|
||||
file = &File{f: f, path: path, refCount: synk.NewRefCounter()}
|
||||
openedFiles[path] = file
|
||||
|
||||
log.Debug().Str("path", path).Msg("file opened")
|
||||
|
||||
go file.closeOnZero()
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (f *sharedFileHandle) Name() string {
|
||||
// Name returns the absolute path of the file.
|
||||
func (f *File) Name() string {
|
||||
return f.path
|
||||
}
|
||||
|
||||
func (f *sharedFileHandle) Close() error {
|
||||
func (f *File) ShouldBeBuffered() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (f *File) Write(p []byte) (n int, err error) {
|
||||
return f.f.Write(p)
|
||||
}
|
||||
|
||||
func (f *File) ReadAt(p []byte, off int64) (n int, err error) {
|
||||
return f.f.ReadAt(p, off)
|
||||
}
|
||||
|
||||
func (f *File) WriteAt(p []byte, off int64) (n int, err error) {
|
||||
return f.f.WriteAt(p, off)
|
||||
}
|
||||
|
||||
func (f *File) Seek(offset int64, whence int) (int64, error) {
|
||||
return f.f.Seek(offset, whence)
|
||||
}
|
||||
|
||||
func (f *File) Size() (int64, error) {
|
||||
stat, err := f.f.Stat()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return stat.Size(), nil
|
||||
}
|
||||
|
||||
func (f *File) Truncate(size int64) error {
|
||||
return f.f.Truncate(size)
|
||||
}
|
||||
|
||||
func (f *File) Close() error {
|
||||
f.refCount.Sub()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *sharedFileHandle) closeOnZero() {
|
||||
defer log.Debug().Str("path", f.path).Msg("file closed")
|
||||
func (f *File) closeOnZero() {
|
||||
defer log.Debug().
|
||||
Str("path", f.path).
|
||||
Msg("access log closed")
|
||||
|
||||
<-f.refCount.Zero()
|
||||
|
||||
openedFilesMu.Lock()
|
||||
delete(openedFiles, f.path)
|
||||
openedFilesMu.Unlock()
|
||||
err := f.File.Close()
|
||||
if err != nil {
|
||||
log.Error().Str("path", f.path).Err(err).Msg("failed to close file")
|
||||
}
|
||||
f.f.Close()
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yusing/goutils/task"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func TestConcurrentFileLoggersShareSameAccessLogIO(t *testing.T) {
|
||||
@@ -19,7 +18,7 @@ func TestConcurrentFileLoggersShareSameAccessLogIO(t *testing.T) {
|
||||
cfg.Path = "test.log"
|
||||
|
||||
loggerCount := runtime.GOMAXPROCS(0)
|
||||
accessLogIOs := make([]File, loggerCount)
|
||||
accessLogIOs := make([]Writer, loggerCount)
|
||||
|
||||
// make test log file
|
||||
file, err := os.Create(cfg.Path)
|
||||
@@ -29,20 +28,16 @@ func TestConcurrentFileLoggersShareSameAccessLogIO(t *testing.T) {
|
||||
assert.NoError(t, os.Remove(cfg.Path))
|
||||
})
|
||||
|
||||
var errs errgroup.Group
|
||||
var wg sync.WaitGroup
|
||||
for i := range loggerCount {
|
||||
errs.Go(func() error {
|
||||
file, err := OpenFile(cfg.Path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wg.Go(func() {
|
||||
file, err := NewFileIO(cfg.Path)
|
||||
assert.NoError(t, err)
|
||||
accessLogIOs[i] = file
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
err = errs.Wait()
|
||||
assert.NoError(t, err)
|
||||
wg.Wait()
|
||||
|
||||
firstIO := accessLogIOs[0]
|
||||
for _, io := range accessLogIOs {
|
||||
@@ -63,7 +58,7 @@ func TestConcurrentAccessLoggerLogAndFlush(t *testing.T) {
|
||||
loggers := make([]AccessLogger, loggerCount)
|
||||
|
||||
for i := range loggerCount {
|
||||
loggers[i] = NewFileAccessLogger(parent, file, cfg)
|
||||
loggers[i] = NewAccessLoggerWithIO(parent, file, cfg)
|
||||
}
|
||||
|
||||
req, _ := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
||||
@@ -92,7 +87,7 @@ func concurrentLog(logger AccessLogger, req *http.Request, resp *http.Response,
|
||||
var wg sync.WaitGroup
|
||||
for range n {
|
||||
wg.Go(func() {
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
if rand.IntN(2) == 0 {
|
||||
logger.Flush()
|
||||
}
|
||||
@@ -16,11 +16,9 @@ type (
|
||||
CommonFormatter struct {
|
||||
cfg *Fields
|
||||
}
|
||||
CombinedFormatter struct{ CommonFormatter }
|
||||
JSONFormatter struct{ cfg *Fields }
|
||||
ConsoleFormatter struct{ cfg *Fields }
|
||||
ACLLogFormatter struct{}
|
||||
ConsoleACLFormatter struct{}
|
||||
CombinedFormatter struct{ CommonFormatter }
|
||||
JSONFormatter struct{ CommonFormatter }
|
||||
ACLLogFormatter struct{}
|
||||
)
|
||||
|
||||
const LogTimeFormat = "02/Jan/2006:15:04:05 -0700"
|
||||
@@ -32,26 +30,24 @@ func scheme(req *http.Request) string {
|
||||
return "http"
|
||||
}
|
||||
|
||||
func appendRequestURI(line *bytes.Buffer, req *http.Request, query iter.Seq2[string, []string]) {
|
||||
func appendRequestURI(line []byte, req *http.Request, query iter.Seq2[string, []string]) []byte {
|
||||
uri := req.URL.EscapedPath()
|
||||
line.WriteString(uri)
|
||||
line = append(line, uri...)
|
||||
isFirst := true
|
||||
for k, v := range query {
|
||||
if isFirst {
|
||||
line.WriteByte('?')
|
||||
line = append(line, '?')
|
||||
isFirst = false
|
||||
} else {
|
||||
line.WriteByte('&')
|
||||
line = append(line, '&')
|
||||
}
|
||||
for i, val := range v {
|
||||
if i > 0 {
|
||||
line.WriteByte('&')
|
||||
}
|
||||
line.WriteString(k)
|
||||
line.WriteByte('=')
|
||||
line.WriteString(val)
|
||||
line = append(line, k...)
|
||||
line = append(line, '=')
|
||||
for _, v := range v {
|
||||
line = append(line, v...)
|
||||
}
|
||||
}
|
||||
return line
|
||||
}
|
||||
|
||||
func clientIP(req *http.Request) string {
|
||||
@@ -62,51 +58,50 @@ func clientIP(req *http.Request) string {
|
||||
return req.RemoteAddr
|
||||
}
|
||||
|
||||
func (f CommonFormatter) AppendRequestLog(line *bytes.Buffer, req *http.Request, res *http.Response) {
|
||||
func (f *CommonFormatter) AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte {
|
||||
query := f.cfg.Query.IterQuery(req.URL.Query())
|
||||
|
||||
line.WriteString(req.Host)
|
||||
line.WriteByte(' ')
|
||||
line = append(line, req.Host...)
|
||||
line = append(line, ' ')
|
||||
|
||||
line.WriteString(clientIP(req))
|
||||
line.WriteString(" - - [")
|
||||
line = append(line, clientIP(req)...)
|
||||
line = append(line, " - - ["...)
|
||||
|
||||
line.WriteString(mockable.TimeNow().Format(LogTimeFormat))
|
||||
line.WriteString("] \"")
|
||||
line = mockable.TimeNow().AppendFormat(line, LogTimeFormat)
|
||||
line = append(line, `] "`...)
|
||||
|
||||
line.WriteString(req.Method)
|
||||
line.WriteByte(' ')
|
||||
appendRequestURI(line, req, query)
|
||||
line.WriteByte(' ')
|
||||
line.WriteString(req.Proto)
|
||||
line.WriteByte('"')
|
||||
line.WriteByte(' ')
|
||||
line = append(line, req.Method...)
|
||||
line = append(line, ' ')
|
||||
line = appendRequestURI(line, req, query)
|
||||
line = append(line, ' ')
|
||||
line = append(line, req.Proto...)
|
||||
line = append(line, '"')
|
||||
line = append(line, ' ')
|
||||
|
||||
line.WriteString(strconv.FormatInt(int64(res.StatusCode), 10))
|
||||
line.WriteByte(' ')
|
||||
line.WriteString(strconv.FormatInt(res.ContentLength, 10))
|
||||
line = strconv.AppendInt(line, int64(res.StatusCode), 10)
|
||||
line = append(line, ' ')
|
||||
line = strconv.AppendInt(line, res.ContentLength, 10)
|
||||
return line
|
||||
}
|
||||
|
||||
func (f CombinedFormatter) AppendRequestLog(line *bytes.Buffer, req *http.Request, res *http.Response) {
|
||||
f.CommonFormatter.AppendRequestLog(line, req, res)
|
||||
line.WriteString(" \"")
|
||||
line.WriteString(req.Referer())
|
||||
line.WriteString("\" \"")
|
||||
line.WriteString(req.UserAgent())
|
||||
line.WriteByte('"')
|
||||
func (f *CombinedFormatter) AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte {
|
||||
line = f.CommonFormatter.AppendRequestLog(line, req, res)
|
||||
line = append(line, " \""...)
|
||||
line = append(line, req.Referer()...)
|
||||
line = append(line, "\" \""...)
|
||||
line = append(line, req.UserAgent()...)
|
||||
line = append(line, '"')
|
||||
return line
|
||||
}
|
||||
|
||||
func (f JSONFormatter) AppendRequestLog(line *bytes.Buffer, req *http.Request, res *http.Response) {
|
||||
logger := zerolog.New(line)
|
||||
f.LogRequestZeroLog(&logger, req, res)
|
||||
}
|
||||
|
||||
func (f JSONFormatter) LogRequestZeroLog(logger *zerolog.Logger, req *http.Request, res *http.Response) {
|
||||
func (f *JSONFormatter) AppendRequestLog(line []byte, req *http.Request, res *http.Response) []byte {
|
||||
query := f.cfg.Query.ZerologQuery(req.URL.Query())
|
||||
headers := f.cfg.Headers.ZerologHeaders(req.Header)
|
||||
cookies := f.cfg.Cookies.ZerologCookies(req.Cookies())
|
||||
contentType := res.Header.Get("Content-Type")
|
||||
|
||||
writer := bytes.NewBuffer(line)
|
||||
logger := zerolog.New(writer)
|
||||
event := logger.Info().
|
||||
Str("time", mockable.TimeNow().Format(LogTimeFormat)).
|
||||
Str("ip", clientIP(req)).
|
||||
@@ -124,33 +119,22 @@ func (f JSONFormatter) LogRequestZeroLog(logger *zerolog.Logger, req *http.Reque
|
||||
Object("headers", headers).
|
||||
Object("cookies", cookies)
|
||||
|
||||
if res.StatusCode >= 400 {
|
||||
if res.Status != "" {
|
||||
event.Str("error", res.Status)
|
||||
} else {
|
||||
event.Str("error", http.StatusText(res.StatusCode))
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: zerolog will append a newline to the buffer
|
||||
event.Send()
|
||||
return writer.Bytes()
|
||||
}
|
||||
|
||||
func (f ConsoleFormatter) LogRequestZeroLog(logger *zerolog.Logger, req *http.Request, res *http.Response) {
|
||||
contentType := res.Header.Get("Content-Type")
|
||||
|
||||
var reqURI bytes.Buffer
|
||||
appendRequestURI(&reqURI, req, f.cfg.Query.IterQuery(req.URL.Query()))
|
||||
|
||||
event := logger.Info().
|
||||
Bytes("uri", reqURI.Bytes()).
|
||||
Str("protocol", req.Proto).
|
||||
Str("type", contentType).
|
||||
Int64("size", res.ContentLength).
|
||||
Str("useragent", req.UserAgent())
|
||||
|
||||
// NOTE: zerolog will append a newline to the buffer
|
||||
event.Msgf("[%d] %s %s://%s from %s", res.StatusCode, req.Method, scheme(req), req.Host, clientIP(req))
|
||||
}
|
||||
|
||||
func (f ACLLogFormatter) AppendACLLog(line *bytes.Buffer, info *maxmind.IPInfo, blocked bool) {
|
||||
logger := zerolog.New(line)
|
||||
f.LogACLZeroLog(&logger, info, blocked)
|
||||
}
|
||||
|
||||
func (f ACLLogFormatter) LogACLZeroLog(logger *zerolog.Logger, info *maxmind.IPInfo, blocked bool) {
|
||||
func (f ACLLogFormatter) AppendACLLog(line []byte, info *maxmind.IPInfo, blocked bool) []byte {
|
||||
writer := bytes.NewBuffer(line)
|
||||
logger := zerolog.New(writer)
|
||||
event := logger.Info().
|
||||
Str("time", mockable.TimeNow().Format(LogTimeFormat)).
|
||||
Str("ip", info.Str)
|
||||
@@ -160,32 +144,10 @@ func (f ACLLogFormatter) LogACLZeroLog(logger *zerolog.Logger, info *maxmind.IPI
|
||||
event.Str("action", "allow")
|
||||
}
|
||||
if info.City != nil {
|
||||
if isoCode := info.City.Country.IsoCode; isoCode != "" {
|
||||
event.Str("iso_code", isoCode)
|
||||
}
|
||||
if timeZone := info.City.Location.TimeZone; timeZone != "" {
|
||||
event.Str("time_zone", timeZone)
|
||||
}
|
||||
event.Str("iso_code", info.City.Country.IsoCode)
|
||||
event.Str("time_zone", info.City.Location.TimeZone)
|
||||
}
|
||||
// NOTE: zerolog will append a newline to the buffer
|
||||
event.Send()
|
||||
}
|
||||
|
||||
func (f ConsoleACLFormatter) LogACLZeroLog(logger *zerolog.Logger, info *maxmind.IPInfo, blocked bool) {
|
||||
event := logger.Info()
|
||||
if info.City != nil {
|
||||
if isoCode := info.City.Country.IsoCode; isoCode != "" {
|
||||
event.Str("iso_code", isoCode)
|
||||
}
|
||||
if timeZone := info.City.Location.TimeZone; timeZone != "" {
|
||||
event.Str("time_zone", timeZone)
|
||||
}
|
||||
}
|
||||
action := "accepted"
|
||||
if blocked {
|
||||
action = "denied"
|
||||
}
|
||||
|
||||
// NOTE: zerolog will append a newline to the buffer
|
||||
event.Msgf("request %s from %s", action, info.Str)
|
||||
return writer.Bytes()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ type MockFile struct {
|
||||
buffered bool
|
||||
}
|
||||
|
||||
var _ File = (*MockFile)(nil)
|
||||
var _ SupportRotate = (*MockFile)(nil)
|
||||
|
||||
func NewMockFile(buffered bool) *MockFile {
|
||||
f, _ := afero.TempFile(afero.NewMemMapFs(), "", "")
|
||||
@@ -52,9 +52,14 @@ func (m *MockFile) NumLines() int {
|
||||
return count
|
||||
}
|
||||
|
||||
func (m *MockFile) MustSize() int64 {
|
||||
func (m *MockFile) Size() (int64, error) {
|
||||
stat, _ := m.Stat()
|
||||
return stat.Size()
|
||||
return stat.Size(), nil
|
||||
}
|
||||
|
||||
func (m *MockFile) MustSize() int64 {
|
||||
size, _ := m.Size()
|
||||
return size
|
||||
}
|
||||
|
||||
func (m *MockFile) Close() error {
|
||||
|
||||
@@ -15,21 +15,14 @@ type MultiAccessLogger struct {
|
||||
//
|
||||
// If there is only one writer, it will return a single AccessLogger.
|
||||
// Otherwise, it will return a MultiAccessLogger that writes to all the writers.
|
||||
func NewMultiAccessLogger(parent task.Parent, cfg AnyConfig, writers []File) AccessLogger {
|
||||
func NewMultiAccessLogger(parent task.Parent, cfg AnyConfig, writers []Writer) AccessLogger {
|
||||
if len(writers) == 1 {
|
||||
if writers[0] == stdout {
|
||||
return NewConsoleLogger(cfg.ToConfig())
|
||||
}
|
||||
return NewFileAccessLogger(parent, writers[0], cfg)
|
||||
return NewAccessLoggerWithIO(parent, writers[0], cfg)
|
||||
}
|
||||
|
||||
accessLoggers := make([]AccessLogger, len(writers))
|
||||
for i, writer := range writers {
|
||||
if writer == stdout {
|
||||
accessLoggers[i] = NewConsoleLogger(cfg.ToConfig())
|
||||
} else {
|
||||
accessLoggers[i] = NewFileAccessLogger(parent, writer, cfg)
|
||||
}
|
||||
accessLoggers[i] = NewAccessLoggerWithIO(parent, writer, cfg)
|
||||
}
|
||||
return &MultiAccessLogger{accessLoggers}
|
||||
}
|
||||
@@ -38,9 +31,9 @@ func (m *MultiAccessLogger) Config() *Config {
|
||||
return m.accessLoggers[0].Config()
|
||||
}
|
||||
|
||||
func (m *MultiAccessLogger) LogRequest(req *http.Request, res *http.Response) {
|
||||
func (m *MultiAccessLogger) Log(req *http.Request, res *http.Response) {
|
||||
for _, accessLogger := range m.accessLoggers {
|
||||
accessLogger.LogRequest(req, res)
|
||||
accessLogger.Log(req, res)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestNewMultiAccessLogger(t *testing.T) {
|
||||
testTask := task.RootTask("test", false)
|
||||
cfg := DefaultRequestLoggerConfig()
|
||||
|
||||
writers := []File{
|
||||
writers := []Writer{
|
||||
NewMockFile(true),
|
||||
NewMockFile(true),
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestMultiAccessLoggerConfig(t *testing.T) {
|
||||
cfg := DefaultRequestLoggerConfig()
|
||||
cfg.Format = FormatCommon
|
||||
|
||||
writers := []File{
|
||||
writers := []Writer{
|
||||
NewMockFile(true),
|
||||
NewMockFile(true),
|
||||
}
|
||||
@@ -48,7 +48,7 @@ func TestMultiAccessLoggerLog(t *testing.T) {
|
||||
|
||||
writer1 := NewMockFile(true)
|
||||
writer2 := NewMockFile(true)
|
||||
writers := []File{writer1, writer2}
|
||||
writers := []Writer{writer1, writer2}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
|
||||
@@ -68,7 +68,7 @@ func TestMultiAccessLoggerLog(t *testing.T) {
|
||||
ContentLength: 100,
|
||||
}
|
||||
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
logger.Flush()
|
||||
|
||||
expect.Equal(t, writer1.NumLines(), 1)
|
||||
@@ -81,7 +81,7 @@ func TestMultiAccessLoggerLogError(t *testing.T) {
|
||||
|
||||
writer1 := NewMockFile(true)
|
||||
writer2 := NewMockFile(true)
|
||||
writers := []File{writer1, writer2}
|
||||
writers := []Writer{writer1, writer2}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
|
||||
@@ -107,7 +107,7 @@ func TestMultiAccessLoggerLogACL(t *testing.T) {
|
||||
|
||||
writer1 := NewMockFile(true)
|
||||
writer2 := NewMockFile(true)
|
||||
writers := []File{writer1, writer2}
|
||||
writers := []Writer{writer1, writer2}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
|
||||
@@ -129,7 +129,7 @@ func TestMultiAccessLoggerFlush(t *testing.T) {
|
||||
|
||||
writer1 := NewMockFile(true)
|
||||
writer2 := NewMockFile(true)
|
||||
writers := []File{writer1, writer2}
|
||||
writers := []Writer{writer1, writer2}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
|
||||
@@ -143,7 +143,7 @@ func TestMultiAccessLoggerFlush(t *testing.T) {
|
||||
StatusCode: http.StatusOK,
|
||||
}
|
||||
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
logger.Flush()
|
||||
|
||||
expect.Equal(t, writer1.NumLines(), 1)
|
||||
@@ -156,7 +156,7 @@ func TestMultiAccessLoggerClose(t *testing.T) {
|
||||
|
||||
writer1 := NewMockFile(true)
|
||||
writer2 := NewMockFile(true)
|
||||
writers := []File{writer1, writer2}
|
||||
writers := []Writer{writer1, writer2}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
|
||||
@@ -170,7 +170,7 @@ func TestMultiAccessLoggerMultipleLogs(t *testing.T) {
|
||||
|
||||
writer1 := NewMockFile(true)
|
||||
writer2 := NewMockFile(true)
|
||||
writers := []File{writer1, writer2}
|
||||
writers := []Writer{writer1, writer2}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
|
||||
@@ -185,7 +185,7 @@ func TestMultiAccessLoggerMultipleLogs(t *testing.T) {
|
||||
resp := &http.Response{
|
||||
StatusCode: http.StatusOK,
|
||||
}
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
|
||||
logger.Flush()
|
||||
@@ -199,7 +199,7 @@ func TestMultiAccessLoggerSingleWriter(t *testing.T) {
|
||||
cfg := DefaultRequestLoggerConfig()
|
||||
|
||||
writer := NewMockFile(true)
|
||||
writers := []File{writer}
|
||||
writers := []Writer{writer}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
expect.NotNil(t, logger)
|
||||
@@ -214,7 +214,7 @@ func TestMultiAccessLoggerSingleWriter(t *testing.T) {
|
||||
StatusCode: http.StatusOK,
|
||||
}
|
||||
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
logger.Flush()
|
||||
|
||||
expect.Equal(t, writer.NumLines(), 1)
|
||||
@@ -226,7 +226,7 @@ func TestMultiAccessLoggerMixedOperations(t *testing.T) {
|
||||
|
||||
writer1 := NewMockFile(true)
|
||||
writer2 := NewMockFile(true)
|
||||
writers := []File{writer1, writer2}
|
||||
writers := []Writer{writer1, writer2}
|
||||
|
||||
logger := NewMultiAccessLogger(testTask, cfg, writers)
|
||||
|
||||
@@ -241,7 +241,7 @@ func TestMultiAccessLoggerMixedOperations(t *testing.T) {
|
||||
StatusCode: http.StatusOK,
|
||||
}
|
||||
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
logger.Flush()
|
||||
|
||||
info := &maxmind.IPInfo{
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
)
|
||||
|
||||
type Retention struct {
|
||||
Days uint64 `json:"days,omitempty"`
|
||||
Last uint64 `json:"last,omitempty"`
|
||||
KeepSize uint64 `json:"keep_size,omitempty"`
|
||||
Days uint64 `json:"days"`
|
||||
Last uint64 `json:"last"`
|
||||
KeepSize uint64 `json:"keep_size"`
|
||||
} // @name LogRetention
|
||||
|
||||
var (
|
||||
@@ -20,7 +20,7 @@ var (
|
||||
)
|
||||
|
||||
// see back_scanner_test.go#L210 for benchmarks
|
||||
var defaultChunkSize = 32 * kilobyte
|
||||
var defaultChunkSize = 256 * kilobyte
|
||||
|
||||
// Syntax:
|
||||
//
|
||||
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
@@ -18,7 +17,7 @@ type supportRotate interface {
|
||||
io.ReaderAt
|
||||
io.WriterAt
|
||||
Truncate(size int64) error
|
||||
Stat() (fs.FileInfo, error)
|
||||
Size() (int64, error)
|
||||
}
|
||||
|
||||
type RotateResult struct {
|
||||
@@ -94,11 +93,10 @@ func rotateLogFileByPolicy(file supportRotate, config *Retention, result *Rotate
|
||||
return false, nil // should not happen
|
||||
}
|
||||
|
||||
stat, err := file.Stat()
|
||||
fileSize, err := file.Size()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fileSize := stat.Size()
|
||||
|
||||
// nothing to rotate, return the nothing
|
||||
if fileSize == 0 {
|
||||
@@ -106,7 +104,6 @@ func rotateLogFileByPolicy(file supportRotate, config *Retention, result *Rotate
|
||||
}
|
||||
|
||||
s := NewBackScanner(file, fileSize, defaultChunkSize)
|
||||
defer s.Release()
|
||||
result.OriginalSize = fileSize
|
||||
|
||||
// Store the line positions and sizes we want to keep
|
||||
@@ -219,17 +216,16 @@ func fileContentMove(file supportRotate, srcPos, dstPos int64, size int) error {
|
||||
//
|
||||
// Invalid lines will not be detected and included in the result.
|
||||
func rotateLogFileBySize(file supportRotate, config *Retention, result *RotateResult) (rotated bool, err error) {
|
||||
stat, err := file.Stat()
|
||||
filesize, err := file.Size()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
fileSize := stat.Size()
|
||||
|
||||
result.OriginalSize = fileSize
|
||||
result.OriginalSize = filesize
|
||||
|
||||
keepSize := int64(config.KeepSize)
|
||||
if keepSize >= fileSize {
|
||||
result.NumBytesKeep = fileSize
|
||||
if keepSize >= filesize {
|
||||
result.NumBytesKeep = filesize
|
||||
return false, nil
|
||||
}
|
||||
result.NumBytesKeep = keepSize
|
||||
|
||||
@@ -57,13 +57,13 @@ func TestRotateKeepLast(t *testing.T) {
|
||||
t.Run(string(format)+" keep last", func(t *testing.T) {
|
||||
file := NewMockFile(true)
|
||||
mockable.MockTimeNow(testTime)
|
||||
logger := NewFileAccessLogger(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
Format: format,
|
||||
})
|
||||
expect.Nil(t, logger.Config().Retention)
|
||||
|
||||
for range 10 {
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
logger.Flush()
|
||||
|
||||
@@ -87,14 +87,14 @@ func TestRotateKeepLast(t *testing.T) {
|
||||
|
||||
t.Run(string(format)+" keep days", func(t *testing.T) {
|
||||
file := NewMockFile(true)
|
||||
logger := NewFileAccessLogger(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
Format: format,
|
||||
})
|
||||
expect.Nil(t, logger.Config().Retention)
|
||||
nLines := 10
|
||||
for i := range nLines {
|
||||
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
logger.Flush()
|
||||
expect.Equal(t, file.NumLines(), nLines)
|
||||
@@ -133,14 +133,14 @@ func TestRotateKeepFileSize(t *testing.T) {
|
||||
for _, format := range ReqLoggerFormats {
|
||||
t.Run(string(format)+" keep size no rotation", func(t *testing.T) {
|
||||
file := NewMockFile(true)
|
||||
logger := NewFileAccessLogger(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
Format: format,
|
||||
})
|
||||
expect.Nil(t, logger.Config().Retention)
|
||||
nLines := 10
|
||||
for i := range nLines {
|
||||
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
logger.Flush()
|
||||
expect.Equal(t, file.NumLines(), nLines)
|
||||
@@ -165,14 +165,14 @@ func TestRotateKeepFileSize(t *testing.T) {
|
||||
|
||||
t.Run("keep size with rotation", func(t *testing.T) {
|
||||
file := NewMockFile(true)
|
||||
logger := NewFileAccessLogger(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
Format: FormatJSON,
|
||||
})
|
||||
expect.Nil(t, logger.Config().Retention)
|
||||
nLines := 100
|
||||
for i := range nLines {
|
||||
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
logger.Flush()
|
||||
expect.Equal(t, file.NumLines(), nLines)
|
||||
@@ -199,14 +199,14 @@ func TestRotateSkipInvalidTime(t *testing.T) {
|
||||
for _, format := range ReqLoggerFormats {
|
||||
t.Run(string(format), func(t *testing.T) {
|
||||
file := NewMockFile(true)
|
||||
logger := NewFileAccessLogger(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
Format: format,
|
||||
})
|
||||
expect.Nil(t, logger.Config().Retention)
|
||||
nLines := 10
|
||||
for i := range nLines {
|
||||
mockable.MockTimeNow(testTime.AddDate(0, 0, -nLines+i+1))
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
logger.Flush()
|
||||
|
||||
n, err := file.Write([]byte("invalid time\n"))
|
||||
@@ -241,7 +241,7 @@ func BenchmarkRotate(b *testing.B) {
|
||||
for _, retention := range tests {
|
||||
b.Run(fmt.Sprintf("retention_%s", retention.String()), func(b *testing.B) {
|
||||
file := NewMockFile(true)
|
||||
logger := NewFileAccessLogger(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
ConfigBase: ConfigBase{
|
||||
Retention: retention,
|
||||
},
|
||||
@@ -249,7 +249,7 @@ func BenchmarkRotate(b *testing.B) {
|
||||
})
|
||||
for i := range 100 {
|
||||
mockable.MockTimeNow(testTime.AddDate(0, 0, -100+i+1))
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
}
|
||||
logger.Flush()
|
||||
content := file.Content()
|
||||
@@ -275,7 +275,7 @@ func BenchmarkRotateWithInvalidTime(b *testing.B) {
|
||||
for _, retention := range tests {
|
||||
b.Run(fmt.Sprintf("retention_%s", retention.String()), func(b *testing.B) {
|
||||
file := NewMockFile(true)
|
||||
logger := NewFileAccessLogger(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
logger := NewAccessLoggerWithIO(task.RootTask("test", false), file, &RequestLoggerConfig{
|
||||
ConfigBase: ConfigBase{
|
||||
Retention: retention,
|
||||
},
|
||||
@@ -283,7 +283,7 @@ func BenchmarkRotateWithInvalidTime(b *testing.B) {
|
||||
})
|
||||
for i := range 10000 {
|
||||
mockable.MockTimeNow(testTime.AddDate(0, 0, -10000+i+1))
|
||||
logger.LogRequest(req, resp)
|
||||
logger.Log(req, resp)
|
||||
if i%10 == 0 {
|
||||
_, _ = file.Write([]byte("invalid time\n"))
|
||||
}
|
||||
|
||||
32
internal/logging/accesslog/stdout.go
Normal file
32
internal/logging/accesslog/stdout.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/yusing/godoxy/internal/logging"
|
||||
)
|
||||
|
||||
type Stdout struct {
|
||||
logger zerolog.Logger
|
||||
}
|
||||
|
||||
func NewStdout() Writer {
|
||||
return &Stdout{logger: logging.NewLoggerWithFixedLevel(zerolog.InfoLevel, os.Stdout)}
|
||||
}
|
||||
|
||||
func (s Stdout) Name() string {
|
||||
return "stdout"
|
||||
}
|
||||
|
||||
func (s Stdout) ShouldBeBuffered() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (s Stdout) Write(p []byte) (n int, err error) {
|
||||
return s.logger.Write(p)
|
||||
}
|
||||
|
||||
func (s Stdout) Close() error {
|
||||
return nil
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
package accesslog
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
maxmind "github.com/yusing/godoxy/internal/maxmind/types"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
type (
|
||||
AccessLogger interface {
|
||||
LogRequest(req *http.Request, res *http.Response)
|
||||
LogError(req *http.Request, err error)
|
||||
LogACL(info *maxmind.IPInfo, blocked bool)
|
||||
|
||||
Config() *Config
|
||||
|
||||
Flush()
|
||||
Close() error
|
||||
}
|
||||
|
||||
AccessLogRotater interface {
|
||||
Rotate(result *RotateResult) (rotated bool, err error)
|
||||
}
|
||||
|
||||
RequestFormatter interface {
|
||||
// AppendRequestLog appends a log line to line with or without a trailing newline
|
||||
AppendRequestLog(line *bytes.Buffer, req *http.Request, res *http.Response)
|
||||
}
|
||||
RequestFormatterZeroLog interface {
|
||||
// LogRequestZeroLog logs a request log to the logger
|
||||
LogRequestZeroLog(logger *zerolog.Logger, req *http.Request, res *http.Response)
|
||||
}
|
||||
ACLFormatter interface {
|
||||
// AppendACLLog appends a log line to line with or without a trailing newline
|
||||
AppendACLLog(line *bytes.Buffer, info *maxmind.IPInfo, blocked bool)
|
||||
// LogACLZeroLog logs an ACL log to the logger
|
||||
LogACLZeroLog(logger *zerolog.Logger, info *maxmind.IPInfo, blocked bool)
|
||||
}
|
||||
)
|
||||
|
||||
func NewAccessLogger(parent task.Parent, cfg AnyConfig) (AccessLogger, error) {
|
||||
writers, err := cfg.Writers()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewMultiAccessLogger(parent, cfg, writers), nil
|
||||
}
|
||||
|
||||
func NewMockAccessLogger(parent task.Parent, cfg *RequestLoggerConfig) AccessLogger {
|
||||
return NewFileAccessLogger(parent, NewMockFile(true), cfg)
|
||||
}
|
||||
@@ -92,7 +92,7 @@ func NewLogger(out ...io.Writer) zerolog.Logger {
|
||||
return zerolog.New(writer).Level(level).With().Timestamp().Logger()
|
||||
}
|
||||
|
||||
func NewLoggerWithFixedLevel(lvl zerolog.Level, out ...io.Writer) zerolog.Logger {
|
||||
func NewLoggerWithFixedLevel(level zerolog.Level, out ...io.Writer) zerolog.Logger {
|
||||
writer := zerolog.NewConsoleWriter(func(w *zerolog.ConsoleWriter) {
|
||||
w.Out = multiLevelWriter(out...)
|
||||
w.TimeFormat = timeFmt
|
||||
@@ -103,5 +103,5 @@ func NewLoggerWithFixedLevel(lvl zerolog.Level, out ...io.Writer) zerolog.Logger
|
||||
return fmtMessage(msgI.(string))
|
||||
}
|
||||
})
|
||||
return zerolog.New(writer).Level(level).With().Str("level", lvl.String()).Timestamp().Logger()
|
||||
return zerolog.New(writer).Level(level).With().Str("level", level.String()).Timestamp().Logger()
|
||||
}
|
||||
|
||||
@@ -2,31 +2,42 @@ package memlogger
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"io"
|
||||
"slices"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
apitypes "github.com/yusing/goutils/apitypes"
|
||||
"github.com/yusing/goutils/http/websocket"
|
||||
)
|
||||
|
||||
type memLogger struct {
|
||||
buf *bytes.Buffer
|
||||
bufLock sync.RWMutex
|
||||
type logEntryRange struct {
|
||||
Start, End int
|
||||
}
|
||||
|
||||
channelLock sync.RWMutex
|
||||
listeners *xsync.Map[chan []byte, struct{}]
|
||||
type memLogger struct {
|
||||
*bytes.Buffer
|
||||
sync.RWMutex
|
||||
|
||||
notifyLock sync.RWMutex
|
||||
connChans *xsync.Map[chan *logEntryRange, struct{}]
|
||||
listeners *xsync.Map[chan []byte, struct{}]
|
||||
}
|
||||
|
||||
type MemLogger io.Writer
|
||||
|
||||
const (
|
||||
maxMemLogSize = 16 * 1024
|
||||
truncateSize = maxMemLogSize / 2
|
||||
listenerChanBufSize = 64
|
||||
maxMemLogSize = 16 * 1024
|
||||
truncateSize = maxMemLogSize / 2
|
||||
initialWriteChunkSize = 4 * 1024
|
||||
writeTimeout = 10 * time.Second
|
||||
)
|
||||
|
||||
var memLoggerInstance = &memLogger{
|
||||
buf: bytes.NewBuffer(make([]byte, 0, maxMemLogSize)),
|
||||
Buffer: bytes.NewBuffer(make([]byte, maxMemLogSize)),
|
||||
connChans: xsync.NewMap[chan *logEntryRange, struct{}](),
|
||||
listeners: xsync.NewMap[chan []byte, struct{}](),
|
||||
}
|
||||
|
||||
@@ -34,6 +45,10 @@ func GetMemLogger() MemLogger {
|
||||
return memLoggerInstance
|
||||
}
|
||||
|
||||
func HandlerFunc() gin.HandlerFunc {
|
||||
return memLoggerInstance.ServeHTTP
|
||||
}
|
||||
|
||||
func Events() (<-chan []byte, func()) {
|
||||
return memLoggerInstance.events()
|
||||
}
|
||||
@@ -41,90 +56,136 @@ func Events() (<-chan []byte, func()) {
|
||||
// Write implements io.Writer.
|
||||
func (m *memLogger) Write(p []byte) (n int, err error) {
|
||||
n = len(p)
|
||||
if n == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
m.truncateIfNeeded(n)
|
||||
|
||||
err = m.writeBuf(p)
|
||||
pos, err := m.writeBuf(p)
|
||||
if err != nil {
|
||||
// not logging the error here, it will cause Run to be called again = infinite loop
|
||||
return n, err
|
||||
}
|
||||
|
||||
if m.listeners.Size() == 0 {
|
||||
return n, nil
|
||||
m.notifyWS(pos, n)
|
||||
return n, err
|
||||
}
|
||||
|
||||
func (m *memLogger) ServeHTTP(c *gin.Context) {
|
||||
manager, err := websocket.NewManagerWithUpgrade(c)
|
||||
if err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to create websocket manager"))
|
||||
return
|
||||
}
|
||||
|
||||
msg := slices.Clone(p)
|
||||
m.notifyWS(msg)
|
||||
return n, nil
|
||||
logCh := make(chan *logEntryRange)
|
||||
m.connChans.Store(logCh, struct{}{})
|
||||
|
||||
defer func() {
|
||||
manager.Close()
|
||||
m.notifyLock.Lock()
|
||||
m.connChans.Delete(logCh)
|
||||
close(logCh)
|
||||
m.notifyLock.Unlock()
|
||||
}()
|
||||
|
||||
if err := m.wsInitial(manager); err != nil {
|
||||
c.Error(apitypes.InternalServerError(err, "failed to send initial log"))
|
||||
return
|
||||
}
|
||||
|
||||
m.wsStreamLog(c.Request.Context(), manager, logCh)
|
||||
}
|
||||
|
||||
func (m *memLogger) truncateIfNeeded(n int) {
|
||||
m.bufLock.RLock()
|
||||
needTruncate := m.buf.Len()+n > maxMemLogSize
|
||||
m.bufLock.RUnlock()
|
||||
m.RLock()
|
||||
needTruncate := m.Len()+n > maxMemLogSize
|
||||
m.RUnlock()
|
||||
|
||||
if !needTruncate {
|
||||
return
|
||||
}
|
||||
if needTruncate {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
needTruncate = m.Len()+n > maxMemLogSize
|
||||
if !needTruncate {
|
||||
return
|
||||
}
|
||||
|
||||
m.bufLock.Lock()
|
||||
defer m.bufLock.Unlock()
|
||||
|
||||
discard := m.buf.Len() - truncateSize
|
||||
if discard > 0 {
|
||||
_ = m.buf.Next(discard)
|
||||
m.Truncate(truncateSize)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memLogger) notifyWS(msg []byte) {
|
||||
if len(msg) == 0 || m.listeners.Size() == 0 {
|
||||
func (m *memLogger) notifyWS(pos, n int) {
|
||||
if m.connChans.Size() == 0 && m.listeners.Size() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
m.channelLock.RLock()
|
||||
defer m.channelLock.RUnlock()
|
||||
timeout := time.NewTimer(3 * time.Second)
|
||||
defer timeout.Stop()
|
||||
|
||||
for ch := range m.listeners.Range {
|
||||
m.notifyLock.RLock()
|
||||
defer m.notifyLock.RUnlock()
|
||||
|
||||
m.connChans.Range(func(ch chan *logEntryRange, _ struct{}) bool {
|
||||
select {
|
||||
case ch <- msg:
|
||||
default:
|
||||
case ch <- &logEntryRange{pos, pos + n}:
|
||||
return true
|
||||
case <-timeout.C:
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
if m.listeners.Size() > 0 {
|
||||
msg := m.Bytes()[pos : pos+n]
|
||||
m.listeners.Range(func(ch chan []byte, _ struct{}) bool {
|
||||
select {
|
||||
case <-timeout.C:
|
||||
return false
|
||||
case ch <- msg:
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memLogger) writeBuf(b []byte) (err error) {
|
||||
m.bufLock.Lock()
|
||||
defer m.bufLock.Unlock()
|
||||
|
||||
_, err = m.buf.Write(b)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if m.buf.Len() > maxMemLogSize {
|
||||
discard := m.buf.Len() - maxMemLogSize
|
||||
if discard > 0 {
|
||||
_ = m.buf.Next(discard)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (m *memLogger) writeBuf(b []byte) (pos int, err error) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
pos = m.Len()
|
||||
_, err = m.Buffer.Write(b)
|
||||
return pos, err
|
||||
}
|
||||
|
||||
func (m *memLogger) events() (logs <-chan []byte, cancel func()) {
|
||||
ch := make(chan []byte, listenerChanBufSize)
|
||||
m.channelLock.Lock()
|
||||
defer m.channelLock.Unlock()
|
||||
ch := make(chan []byte)
|
||||
m.notifyLock.Lock()
|
||||
defer m.notifyLock.Unlock()
|
||||
m.listeners.Store(ch, struct{}{})
|
||||
|
||||
return ch, func() {
|
||||
m.channelLock.Lock()
|
||||
defer m.channelLock.Unlock()
|
||||
m.notifyLock.Lock()
|
||||
defer m.notifyLock.Unlock()
|
||||
m.listeners.Delete(ch)
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *memLogger) wsInitial(manager *websocket.Manager) error {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
|
||||
return manager.WriteData(websocket.TextMessage, m.Bytes(), writeTimeout)
|
||||
}
|
||||
|
||||
func (m *memLogger) wsStreamLog(ctx context.Context, manager *websocket.Manager, ch <-chan *logEntryRange) {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case logRange := <-ch:
|
||||
m.RLock()
|
||||
msg := m.Bytes()[logRange.Start:logRange.End]
|
||||
err := manager.WriteData(websocket.TextMessage, msg, writeTimeout)
|
||||
m.RUnlock()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package period
|
||||
import (
|
||||
"encoding/json"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type Entries[T any] struct {
|
||||
@@ -73,7 +75,7 @@ type entriesJSON[T any] struct {
|
||||
}
|
||||
|
||||
func (e *Entries[T]) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(entriesJSON[T]{
|
||||
return sonic.Marshal(entriesJSON[T]{
|
||||
Entries: e.Get(),
|
||||
Interval: e.interval,
|
||||
})
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/rs/zerolog/log"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
"github.com/yusing/goutils/synk"
|
||||
@@ -97,7 +98,7 @@ func (p *Poller[T, AggregateT]) save() error {
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
err = json.NewEncoder(f).Encode(p.period)
|
||||
err = sonic.ConfigDefault.NewEncoder(f).Encode(p.period)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/shirou/gopsutil/v4/disk"
|
||||
"github.com/shirou/gopsutil/v4/mem"
|
||||
"github.com/shirou/gopsutil/v4/net"
|
||||
@@ -81,12 +82,12 @@ var (
|
||||
|
||||
func TestSystemInfo(t *testing.T) {
|
||||
// Test marshaling
|
||||
data, err := json.Marshal(testInfo)
|
||||
data, err := sonic.Marshal(testInfo)
|
||||
expect.NoError(t, err)
|
||||
|
||||
// Test unmarshaling back
|
||||
var decoded SystemInfo
|
||||
err = json.Unmarshal(data, &decoded)
|
||||
err = sonic.Unmarshal(data, &decoded)
|
||||
expect.NoError(t, err)
|
||||
|
||||
// Compare original and decoded
|
||||
@@ -126,7 +127,7 @@ func TestSerialize(t *testing.T) {
|
||||
for _, query := range allQueries {
|
||||
t.Run(string(query), func(t *testing.T) {
|
||||
_, result := aggregate(entries, url.Values{"aggregate": []string{string(query)}})
|
||||
s, err := json.Marshal(result)
|
||||
s, err := sonic.Marshal(result)
|
||||
expect.NoError(t, err)
|
||||
var v []map[string]any
|
||||
expect.NoError(t, json.Unmarshal(s, &v))
|
||||
|
||||
@@ -2,11 +2,11 @@ package uptime
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"slices"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/lithammer/fuzzysearch/fuzzy"
|
||||
"github.com/yusing/godoxy/internal/metrics/period"
|
||||
metricsutils "github.com/yusing/godoxy/internal/metrics/utils"
|
||||
@@ -50,7 +50,7 @@ func getStatuses(ctx context.Context, _ StatusByAlias) (StatusByAlias, error) {
|
||||
}
|
||||
|
||||
func (s *Status) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
return sonic.Marshal(map[string]any{
|
||||
"status": s.Status.String(),
|
||||
"latency": s.Latency,
|
||||
"timestamp": s.Timestamp,
|
||||
@@ -164,5 +164,5 @@ func (rs RouteStatuses) aggregate(limit int, offset int) Aggregated {
|
||||
}
|
||||
|
||||
func (result Aggregated) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal([]RouteAggregate(result))
|
||||
return sonic.Marshal([]RouteAggregate(result))
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ type (
|
||||
|
||||
task *task.Task
|
||||
|
||||
pool *pool.Pool[types.LoadBalancerServer]
|
||||
pool pool.Pool[types.LoadBalancerServer]
|
||||
poolMu sync.Mutex
|
||||
|
||||
sumWeight int
|
||||
|
||||
@@ -3,7 +3,6 @@ package captcha
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -12,6 +11,7 @@ import (
|
||||
|
||||
_ "embed"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
@@ -70,7 +70,7 @@ func (p *HcaptchaProvider) Verify(r *http.Request) error {
|
||||
Success bool `json:"success"`
|
||||
Error []string `json:"error-codes"`
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(&respData); err != nil {
|
||||
if err := sonic.ConfigDefault.NewDecoder(resp.Body).Decode(&respData); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
||||
@@ -1,203 +0,0 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/yusing/godoxy/internal/route/routes"
|
||||
httputils "github.com/yusing/goutils/http"
|
||||
ioutils "github.com/yusing/goutils/io"
|
||||
)
|
||||
|
||||
type (
|
||||
crowdsecMiddleware struct {
|
||||
CrowdsecMiddlewareOpts
|
||||
}
|
||||
|
||||
CrowdsecMiddlewareOpts struct {
|
||||
Route string `json:"route" validate:"required"` // route name (alias) or IP address
|
||||
Port int `json:"port"` // port number (optional if using route name)
|
||||
APIKey string `json:"api_key" validate:"required"` // API key for CrowdSec AppSec (mandatory)
|
||||
Endpoint string `json:"endpoint"` // default: "/"
|
||||
LogBlocked bool `json:"log_blocked"` // default: false
|
||||
Timeout time.Duration `json:"timeout"` // default: 5 seconds
|
||||
|
||||
httpClient *http.Client
|
||||
}
|
||||
)
|
||||
|
||||
var Crowdsec = NewMiddleware[crowdsecMiddleware]()
|
||||
|
||||
func (m *crowdsecMiddleware) setup() {
|
||||
m.CrowdsecMiddlewareOpts = CrowdsecMiddlewareOpts{
|
||||
Route: "",
|
||||
Port: 7422, // default port for CrowdSec AppSec
|
||||
APIKey: "",
|
||||
Endpoint: "/",
|
||||
LogBlocked: false,
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *crowdsecMiddleware) finalize() error {
|
||||
if !strings.HasPrefix(m.Endpoint, "/") {
|
||||
return fmt.Errorf("endpoint must start with /")
|
||||
}
|
||||
if m.Timeout == 0 {
|
||||
m.Timeout = 5 * time.Second
|
||||
}
|
||||
m.httpClient = &http.Client{
|
||||
Timeout: m.Timeout,
|
||||
// do not follow redirects
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// before implements RequestModifier.
|
||||
func (m *crowdsecMiddleware) before(w http.ResponseWriter, r *http.Request) (proceed bool) {
|
||||
// Build CrowdSec URL
|
||||
crowdsecURL, err := m.buildCrowdSecURL()
|
||||
if err != nil {
|
||||
Crowdsec.LogError(r).Err(err).Msg("failed to build CrowdSec URL")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
|
||||
// Determine HTTP method: GET for requests without body, POST for requests with body
|
||||
method := http.MethodGet
|
||||
var body io.Reader
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
method = http.MethodPost
|
||||
// Read the body
|
||||
bodyBytes, release, err := httputils.ReadAllRequestBody(r)
|
||||
if err != nil {
|
||||
Crowdsec.LogError(r).Err(err).Msg("failed to read request body")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
r.Body = ioutils.NewHookReadCloser(io.NopCloser(bytes.NewReader(bodyBytes)), func() {
|
||||
release(bodyBytes)
|
||||
})
|
||||
body = bytes.NewReader(bodyBytes)
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(r.Context(), m.Timeout)
|
||||
defer cancel()
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, crowdsecURL, body)
|
||||
if err != nil {
|
||||
Crowdsec.LogError(r).Err(err).Msg("failed to create CrowdSec request")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
|
||||
// Get remote IP
|
||||
remoteIP, _, err := net.SplitHostPort(r.RemoteAddr)
|
||||
if err != nil {
|
||||
remoteIP = r.RemoteAddr
|
||||
}
|
||||
|
||||
// Get HTTP version in integer form (10, 11, 20, etc.)
|
||||
httpVersion := m.getHTTPVersion(r)
|
||||
|
||||
// Copy original headers
|
||||
req.Header = r.Header.Clone()
|
||||
|
||||
// Overwrite CrowdSec required headers to prevent spoofing
|
||||
req.Header.Set("X-Crowdsec-Appsec-Ip", remoteIP)
|
||||
req.Header.Set("X-Crowdsec-Appsec-Uri", r.URL.RequestURI())
|
||||
req.Header.Set("X-Crowdsec-Appsec-Host", r.Host)
|
||||
req.Header.Set("X-Crowdsec-Appsec-Verb", r.Method)
|
||||
req.Header.Set("X-Crowdsec-Appsec-Api-Key", m.APIKey)
|
||||
req.Header.Set("X-Crowdsec-Appsec-User-Agent", r.UserAgent())
|
||||
req.Header.Set("X-Crowdsec-Appsec-Http-Version", httpVersion)
|
||||
|
||||
// Make request to CrowdSec
|
||||
resp, err := m.httpClient.Do(req)
|
||||
if err != nil {
|
||||
Crowdsec.LogError(r).Err(err).Msg("failed to connect to CrowdSec server")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Handle response codes
|
||||
switch resp.StatusCode {
|
||||
case http.StatusOK:
|
||||
// Request is allowed
|
||||
return true
|
||||
case http.StatusForbidden:
|
||||
// Request is blocked by CrowdSec
|
||||
if m.LogBlocked {
|
||||
Crowdsec.LogWarn(r).
|
||||
Str("ip", remoteIP).
|
||||
Msg("request blocked by CrowdSec")
|
||||
}
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return false
|
||||
case http.StatusInternalServerError:
|
||||
// CrowdSec server error
|
||||
bodyBytes, release, err := httputils.ReadAllBody(resp)
|
||||
if err == nil {
|
||||
defer release(bodyBytes)
|
||||
Crowdsec.LogError(r).
|
||||
Str("crowdsec_response", string(bodyBytes)).
|
||||
Msg("CrowdSec server error")
|
||||
}
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return false
|
||||
default:
|
||||
// Unexpected response code
|
||||
Crowdsec.LogWarn(r).
|
||||
Int("status_code", resp.StatusCode).
|
||||
Msg("unexpected response from CrowdSec server")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// buildCrowdSecURL constructs the CrowdSec server URL based on route or IP configuration
|
||||
func (m *crowdsecMiddleware) buildCrowdSecURL() (string, error) {
|
||||
// Try to get route first
|
||||
if m.Route != "" {
|
||||
if route, ok := routes.HTTP.Get(m.Route); ok {
|
||||
// Using route name
|
||||
targetURL := *route.TargetURL()
|
||||
targetURL.Path = m.Endpoint
|
||||
return targetURL.String(), nil
|
||||
}
|
||||
|
||||
// If not found in routes, assume it's an IP address
|
||||
if m.Port == 0 {
|
||||
return "", fmt.Errorf("port must be specified when using IP address")
|
||||
}
|
||||
return fmt.Sprintf("http://%s%s", net.JoinHostPort(m.Route, strconv.Itoa(m.Port)), m.Endpoint), nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("route or IP address must be specified")
|
||||
}
|
||||
|
||||
func (m *crowdsecMiddleware) getHTTPVersion(r *http.Request) string {
|
||||
switch {
|
||||
case r.ProtoMajor == 1 && r.ProtoMinor == 0:
|
||||
return "10"
|
||||
case r.ProtoMajor == 1 && r.ProtoMinor == 1:
|
||||
return "11"
|
||||
case r.ProtoMajor == 2:
|
||||
return "20"
|
||||
case r.ProtoMajor == 3:
|
||||
return "30"
|
||||
default:
|
||||
return strconv.Itoa(r.ProtoMajor*10 + r.ProtoMinor)
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"maps"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"sort"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/serialization"
|
||||
@@ -152,7 +152,7 @@ func (m *Middleware) MarshalJSON() ([]byte, error) {
|
||||
commonOptions
|
||||
any
|
||||
}
|
||||
return json.MarshalIndent(map[string]any{
|
||||
return sonic.MarshalIndent(map[string]any{
|
||||
"name": m.name,
|
||||
"options": allOptions{
|
||||
commonOptions: m.commonOptions,
|
||||
|
||||
@@ -19,7 +19,6 @@ var allMiddlewares = map[string]*Middleware{
|
||||
|
||||
"oidc": OIDC,
|
||||
"forwardauth": ForwardAuth,
|
||||
"crowdsec": Crowdsec,
|
||||
|
||||
"request": ModifyRequest,
|
||||
"modifyrequest": ModifyRequest,
|
||||
|
||||
@@ -3,12 +3,12 @@ package middleware
|
||||
import (
|
||||
"bytes"
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/godoxy/internal/common"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -24,7 +24,7 @@ func init() {
|
||||
return
|
||||
}
|
||||
tmp := map[string]string{}
|
||||
err := json.Unmarshal(testHeadersRaw, &tmp)
|
||||
err := sonic.Unmarshal(testHeadersRaw, &tmp)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package nettypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
urlPkg "net/url"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
)
|
||||
|
||||
type URL struct {
|
||||
@@ -46,7 +47,7 @@ func (u *URL) MarshalJSON() (text []byte, err error) {
|
||||
if u == nil {
|
||||
return []byte("null"), nil
|
||||
}
|
||||
return json.Marshal(u.URL.String())
|
||||
return sonic.Marshal(u.URL.String())
|
||||
}
|
||||
|
||||
func (u *URL) Equals(other *URL) bool {
|
||||
|
||||
@@ -2,9 +2,9 @@ package notif
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
@@ -69,7 +69,7 @@ func (f FieldsBody) Format(format LogFormat) ([]byte, error) {
|
||||
}
|
||||
return msg.Bytes(), nil
|
||||
case LogFormatRawJSON:
|
||||
return json.Marshal(f)
|
||||
return sonic.Marshal(f)
|
||||
}
|
||||
return f.Format(LogFormatMarkdown)
|
||||
}
|
||||
@@ -87,7 +87,7 @@ func (l ListBody) Format(format LogFormat) ([]byte, error) {
|
||||
}
|
||||
return msg.Bytes(), nil
|
||||
case LogFormatRawJSON:
|
||||
return json.Marshal(l)
|
||||
return sonic.Marshal(l)
|
||||
}
|
||||
return l.Format(LogFormatMarkdown)
|
||||
}
|
||||
@@ -97,7 +97,7 @@ func (m MessageBody) Format(format LogFormat) ([]byte, error) {
|
||||
case LogFormatPlain, LogFormatMarkdown:
|
||||
return []byte(m), nil
|
||||
case LogFormatRawJSON:
|
||||
return json.Marshal(m)
|
||||
return sonic.Marshal(m)
|
||||
}
|
||||
return []byte(m), nil
|
||||
}
|
||||
@@ -105,7 +105,7 @@ func (m MessageBody) Format(format LogFormat) ([]byte, error) {
|
||||
func (m MessageBodyBytes) Format(format LogFormat) ([]byte, error) {
|
||||
switch format {
|
||||
case LogFormatRawJSON:
|
||||
return json.Marshal(string(m))
|
||||
return sonic.Marshal(string(m))
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
@@ -113,7 +113,7 @@ func (m MessageBodyBytes) Format(format LogFormat) ([]byte, error) {
|
||||
func (e errorBody) Format(format LogFormat) ([]byte, error) {
|
||||
switch format {
|
||||
case LogFormatRawJSON:
|
||||
return json.Marshal(e.Error)
|
||||
return sonic.Marshal(e.Error)
|
||||
case LogFormatPlain:
|
||||
return gperr.Plain(e.Error), nil
|
||||
case LogFormatMarkdown:
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package notif
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/gotify/server/v2/model"
|
||||
"github.com/rs/zerolog"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -65,7 +65,7 @@ func (client *GotifyClient) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
data, err := sonic.Marshal(msg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -76,7 +76,7 @@ func (client *GotifyClient) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
// fmtError implements Provider.
|
||||
func (client *GotifyClient) fmtError(respBody io.Reader) error {
|
||||
var errm model.Error
|
||||
err := json.NewDecoder(respBody).Decode(&errm)
|
||||
err := sonic.ConfigDefault.NewDecoder(respBody).Decode(&errm)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode err response: %w", err)
|
||||
}
|
||||
|
||||
@@ -2,11 +2,11 @@ package notif
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
@@ -99,7 +99,7 @@ func (webhook *Webhook) fmtError(respBody io.Reader) error {
|
||||
}
|
||||
|
||||
func (webhook *Webhook) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
title, err := json.Marshal(logMsg.Title)
|
||||
title, err := sonic.Marshal(logMsg.Title)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -118,7 +118,7 @@ func (webhook *Webhook) MarshalMessage(logMsg *LogMessage) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
if webhook.MIMEType == MimeTypeJSON {
|
||||
message, err = json.Marshal(string(message))
|
||||
message, err = sonic.Marshal(string(message))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -147,5 +147,5 @@ func validateJSONPayload(payload string) bool {
|
||||
"$color", "",
|
||||
)
|
||||
payload = replacer.Replace(payload)
|
||||
return json.Valid([]byte(payload))
|
||||
return sonic.Valid([]byte(payload))
|
||||
}
|
||||
|
||||
@@ -2,9 +2,9 @@ package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/luthermonson/go-proxmox"
|
||||
)
|
||||
|
||||
@@ -44,7 +44,7 @@ func (c *Client) Name() string {
|
||||
}
|
||||
|
||||
func (c *Client) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
return sonic.Marshal(map[string]any{
|
||||
"version": c.Version,
|
||||
"cluster": map[string]any{
|
||||
"name": c.Cluster.Name,
|
||||
|
||||
@@ -2,10 +2,10 @@ package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/luthermonson/go-proxmox"
|
||||
"github.com/yusing/goutils/pool"
|
||||
)
|
||||
@@ -43,7 +43,7 @@ func (n *Node) String() string {
|
||||
}
|
||||
|
||||
func (n *Node) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(map[string]any{
|
||||
return sonic.Marshal(map[string]any{
|
||||
"name": n.name,
|
||||
"id": n.id,
|
||||
})
|
||||
|
||||
@@ -143,13 +143,8 @@ func (s *FileServer) RootPath() string {
|
||||
|
||||
// ServeHTTP implements http.Handler.
|
||||
func (s *FileServer) ServeHTTP(w http.ResponseWriter, req *http.Request) {
|
||||
if s.accessLogger != nil {
|
||||
rec := accesslog.GetResponseRecorder(w)
|
||||
w = rec
|
||||
defer func() {
|
||||
s.accessLogger.LogRequest(req, rec.Response())
|
||||
accesslog.PutResponseRecorder(rec)
|
||||
}()
|
||||
}
|
||||
s.handler.ServeHTTP(w, req)
|
||||
if s.accessLogger != nil {
|
||||
s.accessLogger.Log(req, req.Response)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,8 @@ package provider
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
expect "github.com/yusing/goutils/testing"
|
||||
@@ -26,7 +26,7 @@ func TestParseDockerLabels(t *testing.T) {
|
||||
Names: []string{"container"},
|
||||
Labels: labels,
|
||||
State: "running",
|
||||
Ports: []container.Port{
|
||||
Ports: []container.PortSummary{
|
||||
{Type: "tcp", PrivatePort: 1234, PublicPort: 1234},
|
||||
},
|
||||
}, types.DockerProviderConfig{URL: "unix:///var/run/docker.sock"}),
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/network"
|
||||
"github.com/docker/docker/client"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/moby/moby/api/types/network"
|
||||
"github.com/moby/moby/client"
|
||||
D "github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/route"
|
||||
routeTypes "github.com/yusing/godoxy/internal/route/types"
|
||||
@@ -275,7 +276,7 @@ func TestPrivateIPLocalhost(t *testing.T) {
|
||||
NetworkSettings: &container.NetworkSettingsSummary{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"network": {
|
||||
IPAddress: testDockerIP,
|
||||
IPAddress: netip.MustParseAddr(testDockerIP),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -293,7 +294,7 @@ func TestPrivateIPRemote(t *testing.T) {
|
||||
NetworkSettings: &container.NetworkSettingsSummary{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"network": {
|
||||
IPAddress: testDockerIP,
|
||||
IPAddress: netip.MustParseAddr(testDockerIP),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -315,11 +316,11 @@ func TestStreamDefaultValues(t *testing.T) {
|
||||
NetworkSettings: &container.NetworkSettingsSummary{
|
||||
Networks: map[string]*network.EndpointSettings{
|
||||
"network": {
|
||||
IPAddress: privIP,
|
||||
IPAddress: netip.MustParseAddr(privIP),
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: []container.Port{
|
||||
Ports: []container.PortSummary{
|
||||
{Type: "udp", PrivatePort: privPort, PublicPort: pubPort},
|
||||
},
|
||||
}
|
||||
@@ -372,7 +373,7 @@ func TestImplicitExcludeDatabase(t *testing.T) {
|
||||
t.Run("exposed port detection", func(t *testing.T) {
|
||||
r, ok := makeRoutes(&container.Summary{
|
||||
Names: dummyNames,
|
||||
Ports: []container.Port{
|
||||
Ports: []container.PortSummary{
|
||||
{Type: "tcp", PrivatePort: 5432, PublicPort: 5432},
|
||||
},
|
||||
})["a"]
|
||||
|
||||
@@ -50,7 +50,7 @@ func openFile(path string) (io.WriteCloser, gperr.Error) {
|
||||
return noopWriteCloser{buf}, nil
|
||||
}
|
||||
|
||||
f, err := accesslog.OpenFile(path)
|
||||
f, err := accesslog.NewFileIO(path)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidArguments.With(err)
|
||||
}
|
||||
|
||||
@@ -244,7 +244,7 @@ func (rules Rules) BuildHandler(up http.HandlerFunc) http.HandlerFunc {
|
||||
}
|
||||
|
||||
func appendRuleError(rm *httputils.ResponseModifier, rule *Rule, err error) {
|
||||
// rm.AppendError("rule: %s, error: %w", rule.Name, err)
|
||||
rm.AppendError("rule: %s, error: %w", rule.Name, err)
|
||||
}
|
||||
|
||||
func isTerminatingHandler(handler CommandHandler) bool {
|
||||
|
||||
@@ -6,7 +6,6 @@ import (
|
||||
|
||||
"github.com/pires/go-proxyproto"
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/acl"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
"github.com/yusing/godoxy/internal/entrypoint"
|
||||
@@ -51,14 +50,12 @@ func (s *TCPTCPStream) ListenAndServe(ctx context.Context, preDial, onRead netty
|
||||
return
|
||||
}
|
||||
|
||||
if acl, ok := ctx.Value(acl.ContextKey{}).(*acl.Config); ok {
|
||||
log.Debug().Str("listener", s.listener.Addr().String()).Msg("wrapping listener with ACL")
|
||||
s.listener = acl.WrapTCP(s.listener)
|
||||
}
|
||||
|
||||
if proxyProto := entrypoint.ActiveConfig.Load().SupportProxyProtocol; proxyProto {
|
||||
s.listener = &proxyproto.Listener{Listener: s.listener}
|
||||
}
|
||||
if acl := acl.ActiveConfig.Load(); acl != nil {
|
||||
s.listener = acl.WrapTCP(s.listener)
|
||||
}
|
||||
|
||||
s.preDial = preDial
|
||||
s.onRead = onRead
|
||||
|
||||
@@ -10,7 +10,6 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/rs/zerolog"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/acl"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
nettypes "github.com/yusing/godoxy/internal/net/types"
|
||||
@@ -82,8 +81,7 @@ func (s *UDPUDPStream) ListenAndServe(ctx context.Context, preDial, onRead netty
|
||||
return
|
||||
}
|
||||
s.listener = l
|
||||
if acl, ok := ctx.Value(acl.ContextKey{}).(*acl.Config); ok {
|
||||
log.Debug().Str("listener", s.listener.LocalAddr().String()).Msg("wrapping listener with ACL")
|
||||
if acl := acl.ActiveConfig.Load(); acl != nil {
|
||||
s.listener = acl.WrapUDP(s.listener)
|
||||
}
|
||||
s.preDial = preDial
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package route
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strconv"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
)
|
||||
|
||||
@@ -57,7 +57,7 @@ func (s Scheme) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (s *Scheme) UnmarshalJSON(data []byte) error {
|
||||
var v string
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
if err := sonic.Unmarshal(data, &v); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.Parse(v)
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package serialization
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"os"
|
||||
"reflect"
|
||||
@@ -11,6 +10,7 @@ import (
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/puzpuzpuz/xsync/v4"
|
||||
@@ -32,8 +32,8 @@ func ToSerializedObject[VT any](m map[string]VT) SerializedObject {
|
||||
}
|
||||
|
||||
func init() {
|
||||
strutils.SetJSONMarshaler(json.Marshal)
|
||||
strutils.SetJSONUnmarshaler(json.Unmarshal)
|
||||
strutils.SetJSONMarshaler(sonic.Marshal)
|
||||
strutils.SetJSONUnmarshaler(sonic.Unmarshal)
|
||||
strutils.SetYAMLMarshaler(yaml.Marshal)
|
||||
strutils.SetYAMLUnmarshaler(yaml.Unmarshal)
|
||||
}
|
||||
@@ -677,7 +677,7 @@ func loadSerialized[T any](path string, dst *T, deserialize func(data []byte, ds
|
||||
}
|
||||
|
||||
func SaveJSON[T any](path string, src *T, perm os.FileMode) error {
|
||||
data, err := json.Marshal(src)
|
||||
data, err := sonic.Marshal(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -685,7 +685,7 @@ func SaveJSON[T any](path string, src *T, perm os.FileMode) error {
|
||||
}
|
||||
|
||||
func LoadJSONIfExist[T any](path string, dst *T) error {
|
||||
err := loadSerialized(path, dst, json.Unmarshal)
|
||||
err := loadSerialized(path, dst, sonic.Unmarshal)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/moby/moby/api/types/container"
|
||||
"github.com/yusing/ds/ordered"
|
||||
"github.com/yusing/godoxy/internal/agentpool"
|
||||
gperr "github.com/yusing/goutils/errs"
|
||||
@@ -12,7 +11,7 @@ import (
|
||||
type (
|
||||
LabelMap = map[string]any
|
||||
|
||||
PortMapping = map[int]container.Port
|
||||
PortMapping = map[int]container.PortSummary
|
||||
Container struct {
|
||||
DockerCfg DockerProviderConfig `json:"docker_cfg"`
|
||||
Image *ContainerImage `json:"image"`
|
||||
@@ -70,5 +69,5 @@ func (e *ContainerError) Unwrap() error {
|
||||
|
||||
func (e *ContainerError) MarshalJSON() ([]byte, error) {
|
||||
err := e.errs.Error().(gperr.PlainError)
|
||||
return json.Marshal(string(err.Plain()))
|
||||
return sonic.Marshal(string(err.Plain()))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/bytedance/sonic"
|
||||
"github.com/yusing/goutils/task"
|
||||
)
|
||||
|
||||
@@ -161,7 +162,7 @@ func (s HealthStatus) MarshalJSON() ([]byte, error) {
|
||||
|
||||
func (s *HealthStatus) UnmarshalJSON(data []byte) error {
|
||||
var v string
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
if err := sonic.Unmarshal(data, &v); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal health status: %w", err)
|
||||
}
|
||||
|
||||
@@ -177,7 +178,7 @@ func (jsonRepr *HealthJSONRepr) MarshalJSON() ([]byte, error) {
|
||||
if url == "http://:0" {
|
||||
url = ""
|
||||
}
|
||||
return json.Marshal(HealthJSON{
|
||||
return sonic.Marshal(HealthJSON{
|
||||
Name: jsonRepr.Name,
|
||||
Config: jsonRepr.Config,
|
||||
Started: jsonRepr.Started.Unix(),
|
||||
|
||||
@@ -17,7 +17,7 @@ type (
|
||||
IdlewatcherConfigBase struct {
|
||||
// 0: no idle watcher.
|
||||
// Positive: idle watcher with idle timeout.
|
||||
// Negative: idle watcher as a dependency. IdleTimeout time.Duration `json:"idle_timeout" json_ext:"duration"`
|
||||
// Negative: idle watcher as a dependency.
|
||||
IdleTimeout time.Duration `json:"idle_timeout"`
|
||||
WakeTimeout time.Duration `json:"wake_timeout"`
|
||||
StopTimeout time.Duration `json:"stop_timeout"`
|
||||
|
||||
@@ -8,17 +8,18 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
configDirWatcher *DirWatcher
|
||||
configDirWatcherInitOnce sync.Once
|
||||
configDirWatcher *DirWatcher
|
||||
configDirWatcherMu sync.Mutex
|
||||
)
|
||||
|
||||
func initConfigDirWatcher() {
|
||||
t := task.RootTask("config_dir_watcher", false)
|
||||
configDirWatcher = NewDirectoryWatcher(t, common.ConfigBasePath)
|
||||
}
|
||||
|
||||
// create a new file watcher for file under ConfigBasePath.
|
||||
func NewConfigFileWatcher(filename string) Watcher {
|
||||
configDirWatcherInitOnce.Do(initConfigDirWatcher)
|
||||
configDirWatcherMu.Lock()
|
||||
defer configDirWatcherMu.Unlock()
|
||||
|
||||
if configDirWatcher == nil {
|
||||
t := task.RootTask("config_dir_watcher", false)
|
||||
configDirWatcher = NewDirectoryWatcher(t, common.ConfigBasePath)
|
||||
}
|
||||
return configDirWatcher.Add(filename)
|
||||
}
|
||||
|
||||
@@ -5,9 +5,8 @@ import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
dockerEvents "github.com/docker/docker/api/types/events"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/client"
|
||||
dockerEvents "github.com/moby/moby/api/types/events"
|
||||
"github.com/moby/moby/client"
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/yusing/godoxy/internal/docker"
|
||||
"github.com/yusing/godoxy/internal/types"
|
||||
@@ -19,23 +18,42 @@ type (
|
||||
DockerWatcher struct {
|
||||
cfg types.DockerProviderConfig
|
||||
}
|
||||
DockerListOptions = dockerEvents.ListOptions
|
||||
DockerListOptions = client.EventsListOptions
|
||||
DockerFilters = client.Filters
|
||||
)
|
||||
|
||||
type DockerFilter struct {
|
||||
Term string
|
||||
Values []string
|
||||
}
|
||||
|
||||
func NewDockerFilter(term string, values ...string) DockerFilter {
|
||||
return DockerFilter{
|
||||
Term: term,
|
||||
Values: values,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDockerFilters(filters ...DockerFilter) client.Filters {
|
||||
f := make(client.Filters, len(filters))
|
||||
for _, filter := range filters {
|
||||
f.Add(filter.Term, filter.Values...)
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
// https://docs.docker.com/reference/api/engine/version/v1.47/#tag/System/operation/SystemPingHead
|
||||
var (
|
||||
DockerFilterContainer = filters.Arg("type", string(dockerEvents.ContainerEventType))
|
||||
DockerFilterStart = filters.Arg("event", string(dockerEvents.ActionStart))
|
||||
DockerFilterStop = filters.Arg("event", string(dockerEvents.ActionStop))
|
||||
DockerFilterDie = filters.Arg("event", string(dockerEvents.ActionDie))
|
||||
DockerFilterDestroy = filters.Arg("event", string(dockerEvents.ActionDestroy))
|
||||
DockerFilterKill = filters.Arg("event", string(dockerEvents.ActionKill))
|
||||
DockerFilterPause = filters.Arg("event", string(dockerEvents.ActionPause))
|
||||
DockerFilterUnpause = filters.Arg("event", string(dockerEvents.ActionUnPause))
|
||||
DockerFilterContainer = NewDockerFilter("type", string(dockerEvents.ContainerEventType))
|
||||
DockerFilterStart = NewDockerFilter("event", string(dockerEvents.ActionStart))
|
||||
DockerFilterStop = NewDockerFilter("event", string(dockerEvents.ActionStop))
|
||||
DockerFilterDie = NewDockerFilter("event", string(dockerEvents.ActionDie))
|
||||
DockerFilterDestroy = NewDockerFilter("event", string(dockerEvents.ActionDestroy))
|
||||
DockerFilterKill = NewDockerFilter("event", string(dockerEvents.ActionKill))
|
||||
DockerFilterPause = NewDockerFilter("event", string(dockerEvents.ActionPause))
|
||||
DockerFilterUnpause = NewDockerFilter("event", string(dockerEvents.ActionUnPause))
|
||||
|
||||
NewDockerFilter = filters.NewArgs
|
||||
|
||||
optionsDefault = DockerListOptions{Filters: NewDockerFilter(
|
||||
optionsDefault = DockerListOptions{Filters: NewDockerFilters(
|
||||
DockerFilterContainer,
|
||||
DockerFilterStart,
|
||||
// DockerFilterStop,
|
||||
@@ -54,8 +72,8 @@ var (
|
||||
}
|
||||
)
|
||||
|
||||
func DockerFilterContainerNameID(nameOrID string) filters.KeyValuePair {
|
||||
return filters.Arg("container", nameOrID)
|
||||
func DockerFilterContainerNameID(nameOrID string) DockerFilter {
|
||||
return NewDockerFilter("container", nameOrID)
|
||||
}
|
||||
|
||||
func NewDockerWatcher(dockerCfg types.DockerProviderConfig) DockerWatcher {
|
||||
@@ -85,15 +103,15 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||
client.Close()
|
||||
}()
|
||||
|
||||
cEventCh, cErrCh := client.Events(ctx, options)
|
||||
chs := client.Events(ctx, options)
|
||||
defer log.Debug().Str("host", client.DaemonHost()).Msg("docker watcher closed")
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case msg := <-cEventCh:
|
||||
case msg := <-chs.Messages:
|
||||
w.handleEvent(msg, eventCh)
|
||||
case err := <-cErrCh:
|
||||
case err := <-chs.Err:
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
@@ -122,7 +140,7 @@ func (w DockerWatcher) EventsWithOptions(ctx context.Context, options DockerList
|
||||
// connection successful, trigger reload (reload routes)
|
||||
eventCh <- reloadTrigger
|
||||
// reopen event channel
|
||||
cEventCh, cErrCh = client.Events(ctx, options)
|
||||
chs = client.Events(ctx, options)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
@@ -3,7 +3,7 @@ package events
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
dockerEvents "github.com/docker/docker/api/types/events"
|
||||
dockerEvents "github.com/moby/moby/api/types/events"
|
||||
)
|
||||
|
||||
type (
|
||||
|
||||
Reference in New Issue
Block a user