ChatGPT代理(国内访问不通)

使用golang 实现请求转发代理

  1. 加密请求,满足条件才能继续请求,避免流量被滥用
  2. token配置到后端,避免token暴露风险
package main

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"log"
	"math/rand"
	"net/http"
	"net/url"
	"strings"
	"time"

	"github.com/gogf/gf/v2/crypto/gmd5"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/util/gconv"
)

var (
	target = "https://api.openai.com" // 目标域名
)

func main() {
	// fc.StartHttp(handleRequest)
	http.HandleFunc("/", handleRequest)
	http.ListenAndServe(":9000", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
	// 过滤无效URL
	_, err := url.Parse(r.URL.String())
	ctx := r.Context()
	if err != nil {
		g.Log().Infof(ctx, "Error parsing URL: ", err.Error())
		http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
		return
	}

	ts := gconv.Int64(r.Header.Get("proxy-ts"))
	nowTs := time.Now().Unix()
	mistake := g.Config().MustGet(context.Background(), "mistake").Int64()
	if (nowTs-ts < 0 && ts-nowTs > mistake) || nowTs-ts > mistake {
		g.Log().Infof(ctx, "timestamp err")
		http.Error(w, "Error creating proxy request", http.StatusForbidden)
		return
	}
	sign := r.Header.Get("proxy-sign")

	data, err := io.ReadAll(r.Body)
	if err != nil {
		g.Log().Infof(ctx, "read body err")
		http.Error(w, "Error creating proxy request", http.StatusForbidden)
		return
	}
	r.Body.Close() // NOTE 原始的 Body 无需手动关闭,会在 response.reqBody中自动关闭的.
	br := bytes.NewReader(data)
	r.Body = io.NopCloser(br)
    // 计算服务端sign 比较是否匹配
	signC := sign(r)
	if sign != signC {
		g.Log().Infof(ctx, "sign err")
		http.Error(w, "Error creating proxy request", http.StatusForbidden)
		return
	}
	r.Header.Del("proxy-ts")
	r.Header.Del("proxy-sign")
	tokens := g.Config().MustGet(context.Background(), "tokens").Strings()
	token := tokens[rand.Intn(len(tokens))]
	r.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
	// 去掉环境前缀(针对腾讯云,如果包含的话,目前我只用到了test和release)
	newPath := strings.Replace(r.URL.Path, "/release", "", 1)
	newPath = strings.Replace(newPath, "/test", "", 1)
	newPath = strings.Replace(newPath, "/openai-proxy", "", 1)

	// 拼接目标URL
	targetURL := target + newPath

	// 创建代理HTTP请求
	proxyReq, err := http.NewRequest(r.Method, targetURL, r.Body)
	if err != nil {
		g.Log().Infof(ctx, "sign err: %v", err)
		http.Error(w, "Error creating proxy request", http.StatusInternalServerError)
		return
	}

	// 将原始请求头复制到新请求中
	for headerKey, headerValues := range r.Header {
		for _, headerValue := range headerValues {
			proxyReq.Header.Add(headerKey, headerValue)
		}
	}

	// 默认超时时间设置为60s
	client := &http.Client{
		Timeout: 60 * time.Second,
	}

	// 向 OpenAI 发起代理请求
	resp, err := client.Do(proxyReq)
	if err != nil {
		log.Println("Error sending proxy request: ", err.Error())
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	// 将响应头复制到代理响应头中
	for key, values := range resp.Header {
		for _, value := range values {
			w.Header().Add(key, value)
		}
	}

	// 将响应状态码设置为原始响应状态码
	w.WriteHeader(resp.StatusCode)

	// 将响应实体写入到响应流中(支持流式响应)
	buf := make([]byte, 1024)
	for {
		if n, err := resp.Body.Read(buf); err == io.EOF || n == 0 {
			return
		} else if err != nil {
			log.Println("error while reading respbody: ", err.Error())
			http.Error(w, err.Error(), http.StatusInternalServerError)
			return
		} else {
			if _, err = w.Write(buf[:n]); err != nil {
				log.Println("error while writing resp: ", err.Error())
				http.Error(w, err.Error(), http.StatusInternalServerError)
				return
			}
			w.(http.Flusher).Flush()
		}
	}
}

解决国内访问不通

  1. 使用香港机器(有概率被封,推荐搭配一个国内服务器作为中转,屏蔽其他ip)
  2. 使用云函数,一般云函数提供的域名不会被封,选择阿里云或者腾讯云搭建,推荐在香港地区创建云函数 云函数

ChatGPT账号

  1. 建议自行获取,就不做广告了
  2. 可以尝试azure提供的open-ai,不限制访问