mirror of
https://github.com/woodchen-ink/proxy-go.git
synced 2025-07-19 08:51:55 +08:00
feat: add compression support and update deployment workflow
add brotli and gzip compression support, update docker-compose and deployment script
This commit is contained in:
parent
380d993b6d
commit
723b73d748
13
.github/workflows/docker-build.yml
vendored
13
.github/workflows/docker-build.yml
vendored
@ -83,4 +83,17 @@ jobs:
|
|||||||
tags: |
|
tags: |
|
||||||
woodchen/proxy-go:latest
|
woodchen/proxy-go:latest
|
||||||
woodchen/proxy-go:${{ steps.date.outputs.date }}
|
woodchen/proxy-go:${{ steps.date.outputs.date }}
|
||||||
|
- name: Execute deployment commands
|
||||||
|
uses: appleboy/ssh-action@master
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.SERVER_HOST }}
|
||||||
|
username: root
|
||||||
|
key: ${{ secrets.SERVER_SSH_KEY }}
|
||||||
|
script: |
|
||||||
|
docker pull woodchen/proxy-go:latest
|
||||||
|
|
||||||
|
docker stop proxy-go || true
|
||||||
|
docker rm proxy-go || true
|
||||||
|
|
||||||
|
docker compose -f /opt/1panel/docker/compose/proxy-go/docker-compose.yml up -d
|
||||||
|
|
||||||
|
59
cmd/proxy/main.go
Normal file
59
cmd/proxy/main.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"proxy-go/internal/compression"
|
||||||
|
"proxy-go/internal/config"
|
||||||
|
"proxy-go/internal/handler"
|
||||||
|
"proxy-go/internal/middleware"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// 加载配置
|
||||||
|
cfg, err := config.Load("data/config.json")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("Error loading config:", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建压缩管理器,直接使用配置文件中的压缩配置
|
||||||
|
compManager := compression.NewManager(compression.Config{
|
||||||
|
Gzip: compression.CompressorConfig(cfg.Compression.Gzip),
|
||||||
|
Brotli: compression.CompressorConfig(cfg.Compression.Brotli),
|
||||||
|
})
|
||||||
|
|
||||||
|
// 创建代理处理器
|
||||||
|
proxyHandler := handler.NewProxyHandler(cfg.MAP)
|
||||||
|
|
||||||
|
// 添加中间件
|
||||||
|
var handler http.Handler = proxyHandler
|
||||||
|
if cfg.Compression.Gzip.Enabled || cfg.Compression.Brotli.Enabled {
|
||||||
|
handler = middleware.CompressionMiddleware(compManager)(handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建服务器
|
||||||
|
server := &http.Server{
|
||||||
|
Addr: ":80",
|
||||||
|
Handler: handler,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 优雅关闭
|
||||||
|
go func() {
|
||||||
|
sigChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-sigChan
|
||||||
|
log.Println("Shutting down server...")
|
||||||
|
if err := server.Close(); err != nil {
|
||||||
|
log.Printf("Error during server shutdown: %v\n", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
// 启动服务器
|
||||||
|
log.Println("Starting proxy server on :80")
|
||||||
|
if err := server.ListenAndServe(); err != http.ErrServerClosed {
|
||||||
|
log.Fatal("Error starting server:", err)
|
||||||
|
}
|
||||||
|
}
|
@ -2,5 +2,15 @@
|
|||||||
"MAP": {
|
"MAP": {
|
||||||
"/path1": "https://path1.com/path/path/path",
|
"/path1": "https://path1.com/path/path/path",
|
||||||
"/path2": "https://path2.com"
|
"/path2": "https://path2.com"
|
||||||
|
},
|
||||||
|
"Compression": {
|
||||||
|
"Gzip": {
|
||||||
|
"Enabled": true,
|
||||||
|
"Level": 6
|
||||||
|
},
|
||||||
|
"Brotli": {
|
||||||
|
"Enabled": true,
|
||||||
|
"Level": 4
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
2
go.mod
2
go.mod
@ -1,3 +1,5 @@
|
|||||||
module proxy-go
|
module proxy-go
|
||||||
|
|
||||||
go 1.23.1
|
go 1.23.1
|
||||||
|
|
||||||
|
require github.com/andybalholm/brotli v1.1.1
|
||||||
|
4
go.sum
Normal file
4
go.sum
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
|
||||||
|
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
|
||||||
|
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
|
||||||
|
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
|
23
internal/compression/brotli.go
Normal file
23
internal/compression/brotli.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
package compression
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
|
||||||
|
"github.com/andybalholm/brotli"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BrotliCompressor struct {
|
||||||
|
level int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBrotliCompressor(level int) *BrotliCompressor {
|
||||||
|
// 确保level在有效范围内 (0-11)
|
||||||
|
if level < 0 || level > 11 {
|
||||||
|
level = brotli.DefaultCompression
|
||||||
|
}
|
||||||
|
return &BrotliCompressor{level: level}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BrotliCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
|
||||||
|
return brotli.NewWriterLevel(w, b.level), nil
|
||||||
|
}
|
22
internal/compression/gzip.go
Normal file
22
internal/compression/gzip.go
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
package compression
|
||||||
|
|
||||||
|
import (
|
||||||
|
"compress/gzip"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type GzipCompressor struct {
|
||||||
|
level int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewGzipCompressor(level int) *GzipCompressor {
|
||||||
|
// 确保level在有效范围内
|
||||||
|
if level < gzip.DefaultCompression || level > gzip.BestCompression {
|
||||||
|
level = gzip.DefaultCompression
|
||||||
|
}
|
||||||
|
return &GzipCompressor{level: level}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GzipCompressor) Compress(w io.Writer) (io.WriteCloser, error) {
|
||||||
|
return gzip.NewWriterLevel(w, g.level)
|
||||||
|
}
|
41
internal/compression/manager.go
Normal file
41
internal/compression/manager.go
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
package compression
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
type compressionManager struct {
|
||||||
|
gzip Compressor
|
||||||
|
brotli Compressor
|
||||||
|
config Config
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewManager 创建新的压缩管理器
|
||||||
|
func NewManager(config Config) Manager {
|
||||||
|
m := &compressionManager{
|
||||||
|
config: config,
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Gzip.Enabled {
|
||||||
|
m.gzip = NewGzipCompressor(config.Gzip.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.Brotli.Enabled {
|
||||||
|
m.brotli = NewBrotliCompressor(config.Brotli.Level)
|
||||||
|
}
|
||||||
|
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectCompressor 实现 Manager 接口
|
||||||
|
func (m *compressionManager) SelectCompressor(acceptEncoding string) (Compressor, CompressionType) {
|
||||||
|
// 优先选择 brotli
|
||||||
|
if m.brotli != nil && strings.Contains(acceptEncoding, string(CompressionBrotli)) {
|
||||||
|
return m.brotli, CompressionBrotli
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其次选择 gzip
|
||||||
|
if m.gzip != nil && strings.Contains(acceptEncoding, string(CompressionGzip)) {
|
||||||
|
return m.gzip, CompressionGzip
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, ""
|
||||||
|
}
|
35
internal/compression/types.go
Normal file
35
internal/compression/types.go
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
package compression
|
||||||
|
|
||||||
|
import "io"
|
||||||
|
|
||||||
|
// Compressor 定义压缩器接口
|
||||||
|
type Compressor interface {
|
||||||
|
Compress(w io.Writer) (io.WriteCloser, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressionType 表示压缩类型
|
||||||
|
type CompressionType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
CompressionGzip CompressionType = "gzip"
|
||||||
|
CompressionBrotli CompressionType = "br"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Config 压缩配置结构体
|
||||||
|
type Config struct {
|
||||||
|
Gzip CompressorConfig `json:"Gzip"`
|
||||||
|
Brotli CompressorConfig `json:"Brotli"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// CompressorConfig 单个压缩器的配置
|
||||||
|
type CompressorConfig struct {
|
||||||
|
Enabled bool `json:"Enabled"`
|
||||||
|
Level int `json:"Level"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager 压缩管理器接口
|
||||||
|
type Manager interface {
|
||||||
|
// SelectCompressor 根据 Accept-Encoding 头选择合适的压缩器
|
||||||
|
// 返回选中的压缩器和对应的压缩类型
|
||||||
|
SelectCompressor(acceptEncoding string) (Compressor, CompressionType)
|
||||||
|
}
|
20
internal/config/config.go
Normal file
20
internal/config/config.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Load(path string) (*Config, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
if err := json.Unmarshal(data, &config); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &config, nil
|
||||||
|
}
|
16
internal/config/types.go
Normal file
16
internal/config/types.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
MAP map[string]string `json:"MAP"`
|
||||||
|
Compression CompressionConfig `json:"Compression"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompressionConfig struct {
|
||||||
|
Gzip CompressorConfig `json:"Gzip"`
|
||||||
|
Brotli CompressorConfig `json:"Brotli"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type CompressorConfig struct {
|
||||||
|
Enabled bool `json:"Enabled"`
|
||||||
|
Level int `json:"Level"`
|
||||||
|
}
|
107
internal/handler/proxy.go
Normal file
107
internal/handler/proxy.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package handler
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ProxyHandler struct {
|
||||||
|
pathMap map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewProxyHandler(pathMap map[string]string) *ProxyHandler {
|
||||||
|
return &ProxyHandler{
|
||||||
|
pathMap: pathMap,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *ProxyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 处理根路径请求
|
||||||
|
if r.URL.Path == "/" {
|
||||||
|
w.WriteHeader(http.StatusOK)
|
||||||
|
fmt.Fprint(w, "Welcome to CZL proxy.")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查找匹配的代理路径
|
||||||
|
var matchedPrefix string
|
||||||
|
var targetBase string
|
||||||
|
for prefix, target := range h.pathMap {
|
||||||
|
if strings.HasPrefix(r.URL.Path, prefix) {
|
||||||
|
matchedPrefix = prefix
|
||||||
|
targetBase = target
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有匹配的路径,返回 404
|
||||||
|
if matchedPrefix == "" {
|
||||||
|
http.NotFound(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建目标 URL
|
||||||
|
targetPath := strings.TrimPrefix(r.URL.Path, matchedPrefix)
|
||||||
|
targetURL := targetBase + targetPath
|
||||||
|
|
||||||
|
// 创建新的请求
|
||||||
|
proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error creating proxy request", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制原始请求的 header
|
||||||
|
copyHeader(proxyReq.Header, r.Header)
|
||||||
|
|
||||||
|
// 设置一些必要的头部
|
||||||
|
proxyReq.Header.Set("X-Forwarded-Host", r.Host)
|
||||||
|
proxyReq.Header.Set("X-Real-IP", getClientIP(r))
|
||||||
|
|
||||||
|
// 发送代理请求
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(proxyReq)
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Error forwarding request", http.StatusBadGateway)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
// 复制响应 header
|
||||||
|
copyHeader(w.Header(), resp.Header)
|
||||||
|
|
||||||
|
// 设置响应状态码
|
||||||
|
w.WriteHeader(resp.StatusCode)
|
||||||
|
|
||||||
|
// 复制响应体
|
||||||
|
if _, err := io.Copy(w, resp.Body); err != nil {
|
||||||
|
// 这里只记录错误,不返回给客户端,因为响应头已经发送
|
||||||
|
fmt.Printf("Error copying response: %v\n", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func copyHeader(dst, src http.Header) {
|
||||||
|
for k, vv := range src {
|
||||||
|
for _, v := range vv {
|
||||||
|
dst.Add(k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getClientIP(r *http.Request) string {
|
||||||
|
// 检查各种可能的请求头
|
||||||
|
if ip := r.Header.Get("X-Real-IP"); ip != "" {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
if ip := r.Header.Get("X-Forwarded-For"); ip != "" {
|
||||||
|
return strings.Split(ip, ",")[0]
|
||||||
|
}
|
||||||
|
// 从RemoteAddr获取
|
||||||
|
if ip, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
|
||||||
|
return ip
|
||||||
|
}
|
||||||
|
return r.RemoteAddr
|
||||||
|
}
|
174
internal/middleware/compression.go
Normal file
174
internal/middleware/compression.go
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
package middleware
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"proxy-go/internal/compression"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultBufferSize = 32 * 1024 // 32KB
|
||||||
|
)
|
||||||
|
|
||||||
|
type CompressResponseWriter struct {
|
||||||
|
http.ResponseWriter
|
||||||
|
compressor compression.Compressor
|
||||||
|
writer io.WriteCloser
|
||||||
|
bufferedWriter *bufio.Writer
|
||||||
|
statusCode int
|
||||||
|
written bool
|
||||||
|
compressed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func CompressionMiddleware(manager compression.Manager) func(http.Handler) http.Handler {
|
||||||
|
return func(next http.Handler) http.Handler {
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
// 检查源站是否已经压缩
|
||||||
|
if r.Header.Get("Content-Encoding") != "" {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择压缩器
|
||||||
|
compressor, encoding := manager.SelectCompressor(r.Header.Get("Accept-Encoding"))
|
||||||
|
if compressor == nil {
|
||||||
|
next.ServeHTTP(w, r)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cw := &CompressResponseWriter{
|
||||||
|
ResponseWriter: w,
|
||||||
|
compressor: compressor,
|
||||||
|
statusCode: 0,
|
||||||
|
written: false,
|
||||||
|
compressed: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置Content-Encoding header
|
||||||
|
cw.Header().Set("Content-Encoding", string(encoding))
|
||||||
|
cw.Header().Add("Vary", "Accept-Encoding")
|
||||||
|
|
||||||
|
defer func() {
|
||||||
|
if cw.writer != nil {
|
||||||
|
if cw.bufferedWriter != nil {
|
||||||
|
cw.bufferedWriter.Flush()
|
||||||
|
}
|
||||||
|
cw.writer.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
next.ServeHTTP(cw, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *CompressResponseWriter) WriteHeader(statusCode int) {
|
||||||
|
if cw.written {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cw.statusCode = statusCode
|
||||||
|
cw.written = true
|
||||||
|
|
||||||
|
// 某些状态码不应该压缩
|
||||||
|
if !shouldCompressForStatus(statusCode) {
|
||||||
|
cw.compressed = false
|
||||||
|
cw.Header().Del("Content-Encoding")
|
||||||
|
cw.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查内容类型是否应该压缩
|
||||||
|
if !shouldCompressType(cw.Header().Get("Content-Type")) {
|
||||||
|
cw.compressed = false
|
||||||
|
cw.Header().Del("Content-Encoding")
|
||||||
|
cw.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cw.compressed = true
|
||||||
|
cw.Header().Del("Content-Length") // 因为内容将被压缩,原长度不再有效
|
||||||
|
cw.ResponseWriter.WriteHeader(statusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cw *CompressResponseWriter) Write(b []byte) (int, error) {
|
||||||
|
if !cw.written {
|
||||||
|
cw.WriteHeader(http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cw.compressed {
|
||||||
|
return cw.ResponseWriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 延迟初始化压缩写入器
|
||||||
|
if cw.writer == nil {
|
||||||
|
var err error
|
||||||
|
cw.writer, err = cw.compressor.Compress(cw.ResponseWriter)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
cw.bufferedWriter = bufio.NewWriterSize(cw.writer, defaultBufferSize)
|
||||||
|
}
|
||||||
|
|
||||||
|
return cw.bufferedWriter.Write(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现 http.Hijacker 接口
|
||||||
|
func (cw *CompressResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
|
||||||
|
if hj, ok := cw.ResponseWriter.(http.Hijacker); ok {
|
||||||
|
return hj.Hijack()
|
||||||
|
}
|
||||||
|
return nil, nil, http.ErrNotSupported
|
||||||
|
}
|
||||||
|
|
||||||
|
// 实现 http.Flusher 接口
|
||||||
|
func (cw *CompressResponseWriter) Flush() {
|
||||||
|
if cw.bufferedWriter != nil {
|
||||||
|
cw.bufferedWriter.Flush()
|
||||||
|
}
|
||||||
|
if f, ok := cw.ResponseWriter.(http.Flusher); ok {
|
||||||
|
f.Flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否应该对该状态码的响应进行压缩
|
||||||
|
func shouldCompressForStatus(status int) bool {
|
||||||
|
// 只压缩成功的响应
|
||||||
|
return status == http.StatusOK ||
|
||||||
|
status == http.StatusCreated ||
|
||||||
|
status == http.StatusAccepted ||
|
||||||
|
status == http.StatusNonAuthoritativeInfo ||
|
||||||
|
status == http.StatusNoContent ||
|
||||||
|
status == http.StatusPartialContent
|
||||||
|
}
|
||||||
|
|
||||||
|
// 判断是否应该对该内容类型进行压缩
|
||||||
|
func shouldCompressType(contentType string) bool {
|
||||||
|
// 解析内容类型
|
||||||
|
mimeType, _, err := mime.ParseMediaType(contentType)
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
compressibleTypes := map[string]bool{
|
||||||
|
"text/": true,
|
||||||
|
"application/javascript": true,
|
||||||
|
"application/json": true,
|
||||||
|
"application/xml": true,
|
||||||
|
"application/x-yaml": true,
|
||||||
|
"image/svg+xml": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否是可压缩类型
|
||||||
|
for prefix := range compressibleTypes {
|
||||||
|
if strings.HasPrefix(mimeType, prefix) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
105
main.go
105
main.go
@ -1,105 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"log"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Config 结构体用于解析配置文件
|
|
||||||
type Config struct {
|
|
||||||
MAP map[string]string `json:"MAP"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
// 读取配置文件
|
|
||||||
configFile, err := os.ReadFile("data/config.json")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal("Error reading config file:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 解析配置文件
|
|
||||||
var config Config
|
|
||||||
if err := json.Unmarshal(configFile, &config); err != nil {
|
|
||||||
log.Fatal("Error parsing config file:", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 HTTP 处理函数
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
// 处理根路径请求
|
|
||||||
if r.URL.Path == "/" {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
fmt.Fprint(w, "Welcome to CZL proxy.")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查找匹配的代理路径
|
|
||||||
var matchedPrefix string
|
|
||||||
var targetBase string
|
|
||||||
for prefix, target := range config.MAP {
|
|
||||||
if strings.HasPrefix(r.URL.Path, prefix) {
|
|
||||||
matchedPrefix = prefix
|
|
||||||
targetBase = target
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果没有匹配的路径,返回 404
|
|
||||||
if matchedPrefix == "" {
|
|
||||||
http.NotFound(w, r)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 构建目标 URL
|
|
||||||
targetPath := strings.TrimPrefix(r.URL.Path, matchedPrefix)
|
|
||||||
targetURL := targetBase + targetPath
|
|
||||||
|
|
||||||
// 创建新的请求
|
|
||||||
proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Error creating proxy request", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// 复制原始请求的 header
|
|
||||||
for header, values := range r.Header {
|
|
||||||
for _, value := range values {
|
|
||||||
proxyReq.Header.Add(header, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 发送代理请求
|
|
||||||
client := &http.Client{}
|
|
||||||
resp, err := client.Do(proxyReq)
|
|
||||||
if err != nil {
|
|
||||||
http.Error(w, "Error forwarding request", http.StatusBadGateway)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
|
|
||||||
// 复制响应 header
|
|
||||||
for header, values := range resp.Header {
|
|
||||||
for _, value := range values {
|
|
||||||
w.Header().Add(header, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置响应状态码
|
|
||||||
w.WriteHeader(resp.StatusCode)
|
|
||||||
|
|
||||||
// 复制响应体
|
|
||||||
if _, err := io.Copy(w, resp.Body); err != nil {
|
|
||||||
log.Printf("Error copying response: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// 启动服务器
|
|
||||||
log.Println("Starting proxy server on :80")
|
|
||||||
if err := http.ListenAndServe(":80", nil); err != nil {
|
|
||||||
log.Fatal("Error starting server:", err)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user