tony
发布于 2024-07-08 / 33 阅读
0
0

Proxy for LLM

名词解释

正向代理

为了简单、形象方便理解,可以简单粗暴的理解,将代理方式放在客户端(pc/phone)上的代理为正向代理。访问数据的链路有端上通过隧道(tunnel)与Target进行通信交互。

典型应用:

chrome via socks5

某火箭

反向代理

与正向代理相反,将代理节点后置,放在服务端上实现。典型应用:Nginx

其中正向代理使用不当就是不合规的问题。反向代理+限定target站点的访问实现来说合规问题风险会稍微小一点。这里重点讨论反向代理,对正向代理的内容不做过多赘述。

Proxy的选型

服务端环境的选择

vps 不推荐,原因:繁琐

云函数:

腾讯云 倾向选择

阿里云 备用选择

实现语言的选择

Nodejs

Golang

代码参考

package main

import (
	"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
)

func setupCORS(r *gin.Engine) {
	r.Use(func(c *gin.Context) {
		c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
		c.Writer.Header().Set("Access-Control-Max-Age", "86400")
		c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
		c.Writer.Header().Set("Access-Control-Allow-Headers", "*")

		if c.Request.Method == "OPTIONS" {
			c.AbortWithStatus(http.StatusOK)
			return
		}

		c.Next()
	})
}

func filterHeaders(r *http.Request) {
	for key := range r.Header {
		if strings.Contains(key, "X-Scf") {
			r.Header.Del(key)
		}
	}
}

func main() {
	gin.SetMode(gin.ReleaseMode)
	r := gin.Default()
	setupCORS(r)

	// 目标服务器地址
	target := "https://api.anthropic.com"
	targetURL, err := url.Parse(target)
	if err != nil {
		panic(err) // 如果 URL 解析有误,则终止程序
	}

	//proxy := goproxy.NewProxyHttpServer()

	r.Any("/*proxyPath", func(c *gin.Context) {
		proxyURL := targetURL.ResolveReference(c.Request.URL)
		proxyReq, err := http.NewRequest(c.Request.Method, proxyURL.String(), c.Request.Body)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		filterHeaders(c.Request)
		originAuth := c.Request.Header.Get("x-api-key")
		theIp := c.ClientIP()
		if strings.Contains(originAuth, "sk-xx") || strings.Contains(originAuth, "sk-xx2") ||
			theIp == "ip01" || theIp == "127.0.0.1" || theIp == "ip02" {
			c.Request.Header.Set("x-api-key", "claude or openai key here")
			c.Request.Header.Del("origin")
		}

		proxyReq.Header = c.Request.Header

		// 打印请求内容
		dump, err := httputil.DumpRequest(proxyReq, true)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}
		fmt.Println("---请求body--- \\n")
		fmt.Println(string(dump))

		proxyRes, err := http.DefaultClient.Do(proxyReq)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
			return
		}

		// 复制所有响应头
		for k, values := range proxyRes.Header {
			for _, v := range values {
				c.Writer.Header().Add(k, v)
			}
		}

		c.Writer.WriteHeader(proxyRes.StatusCode)
		// 将响应体复制到客户端响应中
		_, err = io.Copy(c.Writer, proxyRes.Body)
		if err != nil {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to copy proxy response"})
			return
		}
	})
	package main

	import (
		"fmt"
	"github.com/gin-gonic/gin"
	"io"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
	)

	func setupCORS(r *gin.Engine) {
		r.Use(func(c *gin.Context) {
			c.Writer.Header().Set("Access-Control-Allow-Origin", "*")
			c.Writer.Header().Set("Access-Control-Max-Age", "86400")
			c.Writer.Header().Set("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
			c.Writer.Header().Set("Access-Control-Allow-Headers", "*")

			if c.Request.Method == "OPTIONS" {
				c.AbortWithStatus(http.StatusOK)
				return
			}

			c.Next()
		})
	}

	func filterHeaders(r *http.Request) {
		for key := range r.Header {
			if strings.Contains(key, "X-Scf") {
				r.Header.Del(key)
			}
		}
	}

	func main() {
		gin.SetMode(gin.ReleaseMode)
		r := gin.Default()
		setupCORS(r)

		// 目标服务器地址
		target := "https://api.anthropic.com"
		targetURL, err := url.Parse(target)
		if err != nil {
			panic(err) // 如果 URL 解析有误,则终止程序
		}

		//proxy := goproxy.NewProxyHttpServer()

		r.Any("/*proxyPath", func(c *gin.Context) {
			proxyURL := targetURL.ResolveReference(c.Request.URL)
			proxyReq, err := http.NewRequest(c.Request.Method, proxyURL.String(), c.Request.Body)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
			filterHeaders(c.Request)
			originAuth := c.Request.Header.Get("x-api-key")
			theIp := c.ClientIP()
			if strings.Contains(originAuth, "sk-xx") || strings.Contains(originAuth, "sk-xx01") ||
				theIp == "ip01" || theIp == "127.0.0.1" || theIp == "ip02" {
				c.Request.Header.Set("x-api-key", "claude or openai key here")
				c.Request.Header.Del("origin")
			}

			proxyReq.Header = c.Request.Header

			// 打印请求内容
			dump, err := httputil.DumpRequest(proxyReq, true)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
			fmt.Println("---请求body--- \\n")
			fmt.Println(string(dump))

			proxyRes, err := http.DefaultClient.Do(proxyReq)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}

			// 复制所有响应头
			for k, values := range proxyRes.Header {
				for _, v := range values {
					c.Writer.Header().Add(k, v)
				}
			}

			c.Writer.WriteHeader(proxyRes.StatusCode)
			// 将响应体复制到客户端响应中
			_, err = io.Copy(c.Writer, proxyRes.Body)
			if err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to copy proxy response"})
				return
			}
		})

		r.Run(":9000") // 启动服务器在 9000 端口
	}

	r.Run(":9000") // 启动服务器在 9000 端口
}

# build.sh
GOOS=linux GOARCH=amd64 go build -o main main.go
zip main.zip main scf_bootstrap

#!/bin/bash
#scf_bootstrap
./main

腾讯面板

腾讯云函数配置选择

选择go模版,上传代码选择zip包上传即可

注意事项

初始化、timeout的时间需要勾选

域名配置

触发器里面升级为api网关,再到网关里面配置域名


评论