mirror of
https://github.com/wgh136/nysoure.git
synced 2025-12-15 15:31:16 +00:00
feat: Implement proxy functionality
This commit is contained in:
@@ -9,14 +9,22 @@ services:
|
|||||||
- app_data:/var/lib/nysoure
|
- app_data:/var/lib/nysoure
|
||||||
depends_on:
|
depends_on:
|
||||||
- db
|
- db
|
||||||
|
- tendis
|
||||||
environment:
|
environment:
|
||||||
- DB_HOST=db
|
- DB_HOST=db
|
||||||
- DB_PORT=3306
|
- DB_PORT=3306
|
||||||
- DB_USER=nysoure
|
- DB_USER=nysoure
|
||||||
- DB_PASSWORD=nysoure_password
|
- DB_PASSWORD=nysoure_password
|
||||||
- DB_NAME=nysoure
|
- DB_NAME=nysoure
|
||||||
|
- REDIS_HOST=tendis
|
||||||
|
- REDIS_PORT=6379
|
||||||
- BANNED_REDIRECT_DOMAINS=example.com,example.org
|
- BANNED_REDIRECT_DOMAINS=example.com,example.org
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
db:
|
db:
|
||||||
image: mariadb:latest
|
image: mariadb:latest
|
||||||
@@ -30,7 +38,26 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "3306"
|
- "3306"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
|
tendis:
|
||||||
|
image: tendis/tendis:latest
|
||||||
|
volumes:
|
||||||
|
- tendis_data:/data
|
||||||
|
ports:
|
||||||
|
- "6379"
|
||||||
|
restart: unless-stopped
|
||||||
|
logging:
|
||||||
|
driver: "json-file"
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
app_data:
|
app_data:
|
||||||
db_data:
|
db_data:
|
||||||
|
tendis_data:
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -14,6 +14,7 @@ require (
|
|||||||
github.com/blevesearch/bleve v1.0.14
|
github.com/blevesearch/bleve v1.0.14
|
||||||
github.com/chai2010/webp v1.4.0
|
github.com/chai2010/webp v1.4.0
|
||||||
github.com/disintegration/imaging v1.6.2
|
github.com/disintegration/imaging v1.6.2
|
||||||
|
github.com/redis/go-redis/v9 v9.17.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
gorm.io/driver/mysql v1.6.0
|
gorm.io/driver/mysql v1.6.0
|
||||||
)
|
)
|
||||||
@@ -30,8 +31,10 @@ require (
|
|||||||
github.com/blevesearch/zap/v13 v13.0.6 // indirect
|
github.com/blevesearch/zap/v13 v13.0.6 // indirect
|
||||||
github.com/blevesearch/zap/v14 v14.0.5 // indirect
|
github.com/blevesearch/zap/v14 v14.0.5 // indirect
|
||||||
github.com/blevesearch/zap/v15 v15.0.3 // indirect
|
github.com/blevesearch/zap/v15 v15.0.3 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/couchbase/vellum v1.0.2 // indirect
|
github.com/couchbase/vellum v1.0.2 // indirect
|
||||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 // indirect
|
github.com/glycerine/go-unsnap-stream v0.0.0-20181221182339-f9677308dec2 // indirect
|
||||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||||
github.com/golang/protobuf v1.3.2 // indirect
|
github.com/golang/protobuf v1.3.2 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -29,6 +29,12 @@ github.com/blevesearch/zap/v14 v14.0.5 h1:NdcT+81Nvmp2zL+NhwSvGSLh7xNgGL8QRVZ67n
|
|||||||
github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY=
|
github.com/blevesearch/zap/v14 v14.0.5/go.mod h1:bWe8S7tRrSBTIaZ6cLRbgNH4TUDaC9LZSpRGs85AsGY=
|
||||||
github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
|
github.com/blevesearch/zap/v15 v15.0.3 h1:Ylj8Oe+mo0P25tr9iLPp33lN6d4qcztGjaIsP51UxaY=
|
||||||
github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
|
github.com/blevesearch/zap/v15 v15.0.3/go.mod h1:iuwQrImsh1WjWJ0Ue2kBqY83a0rFtJTqfa9fp1rbVVU=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
|
||||||
|
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
|
||||||
|
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
|
||||||
|
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
|
||||||
|
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/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko=
|
github.com/chai2010/webp v1.4.0 h1:6DA2pkkRUPnbOHvvsmGI3He1hBKf/bkRlniAiSGuEko=
|
||||||
github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
github.com/chai2010/webp v1.4.0/go.mod h1:0XVwvZWdjjdxpUEIf7b9g9VkHFnInUSYujwqTLEuldU=
|
||||||
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
|
||||||
@@ -46,6 +52,8 @@ github.com/cznic/strutil v0.0.0-20181122101858-275e90344537/go.mod h1:AHHPPPXTw0
|
|||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
|
||||||
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
|
||||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
@@ -135,6 +143,8 @@ github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJ
|
|||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
github.com/rcrowley/go-metrics v0.0.0-20190826022208-cac0b30c2563/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.0 h1:K6E+ZlYN95KSMmZeEQPbU/c++wfmEvfFB17yEAq/VhM=
|
||||||
|
github.com/redis/go-redis/v9 v9.17.0/go.mod h1:u410H11HMLoB+TP67dz8rL9s6QW2j76l0//kSOd3370=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
|
||||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||||
|
|||||||
3
main.go
3
main.go
@@ -38,7 +38,8 @@ func main() {
|
|||||||
api.AddCommentRoutes(apiG)
|
api.AddCommentRoutes(apiG)
|
||||||
api.AddConfigRoutes(apiG)
|
api.AddConfigRoutes(apiG)
|
||||||
api.AddActivityRoutes(apiG)
|
api.AddActivityRoutes(apiG)
|
||||||
api.AddCollectionRoutes(apiG) // 新增
|
api.AddCollectionRoutes(apiG)
|
||||||
|
api.AddProxyRoutes(apiG)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Fatal(app.Listen(":3000"))
|
log.Fatal(app.Listen(":3000"))
|
||||||
|
|||||||
124
server/api/proxy.go
Normal file
124
server/api/proxy.go
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"io"
|
||||||
|
"log/slog"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"nysoure/server/cache"
|
||||||
|
"nysoure/server/model"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/gofiber/fiber/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
allowedUrlRegexps []*regexp.Regexp
|
||||||
|
)
|
||||||
|
|
||||||
|
type proxyResponse struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
ContentType string `json:"content_type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
regexps := os.Getenv("ALLOWED_URL_REGEXPS")
|
||||||
|
for _, expr := range strings.Split(regexps, ",") {
|
||||||
|
if expr == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
re, err := regexp.Compile(expr)
|
||||||
|
if err != nil {
|
||||||
|
panic("Invalid regex in ALLOWED_URL_REGEXPS: " + expr)
|
||||||
|
}
|
||||||
|
allowedUrlRegexps = append(allowedUrlRegexps, re)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleProxyCall(c fiber.Ctx) error {
|
||||||
|
uriBase64 := c.Query("uri")
|
||||||
|
if uriBase64 == "" {
|
||||||
|
return model.NewRequestError("Missing uri parameter")
|
||||||
|
}
|
||||||
|
uriStr, err := base64.URLEncoding.DecodeString(uriBase64)
|
||||||
|
if err != nil {
|
||||||
|
return model.NewRequestError("Invalid base64 encoding")
|
||||||
|
}
|
||||||
|
uri, err := url.Parse(string(uriStr))
|
||||||
|
if err != nil {
|
||||||
|
return model.NewRequestError("Invalid URL")
|
||||||
|
}
|
||||||
|
allowed := false
|
||||||
|
for _, re := range allowedUrlRegexps {
|
||||||
|
if re.MatchString(uri.String()) {
|
||||||
|
allowed = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !allowed {
|
||||||
|
return model.NewRequestError("URL not allowed")
|
||||||
|
}
|
||||||
|
|
||||||
|
var resp *proxyResponse
|
||||||
|
|
||||||
|
rawVal, err := cache.Get("proxy:" + uri.String())
|
||||||
|
if err == nil {
|
||||||
|
var r proxyResponse
|
||||||
|
err = json.Unmarshal([]byte(rawVal), &r)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(c, "Failed to unmarshal cached proxy response", "error", err)
|
||||||
|
return model.NewInternalServerError("Error")
|
||||||
|
}
|
||||||
|
resp = &r
|
||||||
|
} else {
|
||||||
|
resp, err = proxy(uri)
|
||||||
|
if err != nil {
|
||||||
|
slog.ErrorContext(c, "Proxy request failed", "error", err)
|
||||||
|
return model.NewInternalServerError("Error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
c.Response().Header.SetContentType(resp.ContentType)
|
||||||
|
return c.Send([]byte(rawVal))
|
||||||
|
}
|
||||||
|
|
||||||
|
func proxy(uri *url.URL) (*proxyResponse, error) {
|
||||||
|
client := http.Client{}
|
||||||
|
req, err := http.NewRequest("GET", uri.String(), nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
contentType := resp.Header.Get("Content-Type")
|
||||||
|
data, err := io.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
proxyResp := &proxyResponse{
|
||||||
|
Content: string(data),
|
||||||
|
ContentType: contentType,
|
||||||
|
}
|
||||||
|
|
||||||
|
j, err := json.Marshal(proxyResp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
err = cache.Set("proxy:"+uri.String(), string(j), 24*time.Hour)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Failed to cache proxy response", "error", err)
|
||||||
|
}
|
||||||
|
return proxyResp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddProxyRoutes(router fiber.Router) {
|
||||||
|
router.Get("/proxy", handleProxyCall)
|
||||||
|
}
|
||||||
42
server/cache/cache.go
vendored
Normal file
42
server/cache/cache.go
vendored
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
package cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/redis/go-redis/v9"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
client *redis.Client
|
||||||
|
ctx = context.Background()
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
host := os.Getenv("REDIS_HOST")
|
||||||
|
port := os.Getenv("REDIS_PORT")
|
||||||
|
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
if port == "" {
|
||||||
|
port = "6379"
|
||||||
|
}
|
||||||
|
|
||||||
|
client = redis.NewClient(&redis.Options{
|
||||||
|
Addr: host + ":" + port,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(key string) (string, error) {
|
||||||
|
val, err := client.Get(ctx, key).Result()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return val, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func Set(key, value string, expiration time.Duration) error {
|
||||||
|
return client.Set(ctx, key, value, expiration).Err()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user