go-proxy-bingai/common/proxy.go
2023-05-04 11:29:40 +08:00

203 lines
6.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package common
import (
"bytes"
"compress/gzip"
"fmt"
"io"
"log"
"net/http"
"net/http/httputil"
"net/url"
"strconv"
"strings"
"github.com/andybalholm/brotli"
)
var (
BING_CHAT_DOMAIN = "https://sydney.bing.com"
BING_CHAT_URL, _ = url.Parse(BING_CHAT_DOMAIN + "/sydney/ChatHub")
BING_URL, _ = url.Parse("https://www.bing.com")
KEEP_HEADERS = map[string]bool{
"Accept": true,
"Accept-Encoding": true,
"Accept-Language": true,
"Referer": true,
"Connection": true,
"Cookie": true,
"Upgrade": true,
"User-Agent": true,
"Sec-Websocket-Extensions": true,
"Sec-Websocket-Key": true,
"Sec-Websocket-Version": true,
"X-Request-Id": true,
"X-Forwarded-For": true,
}
)
func NewSingleHostReverseProxy(target *url.URL) *httputil.ReverseProxy {
originalScheme := "http"
httpsSchemeName := "https"
var originalHost string
var originalPath string
director := func(req *http.Request) {
if req.URL.Scheme == httpsSchemeName || req.Header.Get("X-Forwarded-Proto") == httpsSchemeName {
originalScheme = httpsSchemeName
}
originalHost = req.Host
originalPath = req.URL.Path
req.URL.Scheme = target.Scheme
req.URL.Host = target.Host
req.Host = target.Host
req.Header.Set("Referer", fmt.Sprintf("%s/search?q=Bing+AI", BING_URL.String()))
// 随机ip
randIp := fmt.Sprintf("%d.%d.%d.%d", RandInt(1, 10), RandInt(1, 255), RandInt(1, 255), RandInt(1, 255))
req.Header.Set("X-Forwarded-For", randIp)
for hKey, _ := range req.Header {
if _, isExist := KEEP_HEADERS[hKey]; !isExist {
req.Header.Del(hKey)
}
}
// reqHeaderByte, _ := json.Marshal(req.Header)
// log.Println("剩余请求头 ", string(reqHeaderByte))
}
//改写返回信息
modifyFunc := func(res *http.Response) error {
contentType := res.Header.Get("Content-Type")
if strings.Contains(contentType, "text/javascript") {
contentEncoding := res.Header.Get("Content-Encoding")
switch contentEncoding {
case "gzip":
// log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath)
modifyGzipBody(res, originalScheme, originalHost)
case "br":
// log.Println("ContentEncoding : ", contentEncoding, " Path : ", originalPath)
modifyBrBody(res, originalScheme, originalHost)
default:
log.Println("ContentEncoding default : ", contentEncoding, " Path : ", originalPath)
modifyDefaultBody(res, originalScheme, originalHost)
}
}
// 修改响应 cookie 域
// resCookies := res.Header.Values("Set-Cookie")
// if len(resCookies) > 0 {
// for i, v := range resCookies {
// resCookies[i] = strings.ReplaceAll(strings.ReplaceAll(v, ".bing.com", originalHost), "bing.com", originalHost)
// }
// }
res.Header.Del("Set-Cookie")
return nil
}
errorHandler := func(res http.ResponseWriter, req *http.Request, err error) {
log.Println("代理异常 ", err)
res.Write([]byte(err.Error()))
}
// 代理请求 请求回来的内容 报错自动调用
return &httputil.ReverseProxy{Director: director, ModifyResponse: modifyFunc, ErrorHandler: errorHandler}
}
func replaceResBody(originalBody string, originalScheme string, originalHost string) string {
modifiedBodyStr := originalBody
originalDomain := fmt.Sprintf("%s://%s", originalScheme, originalHost)
if strings.Contains(modifiedBodyStr, BING_URL.String()) {
modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_URL.String(), originalDomain)
}
// 对话暂时支持国内网络,而且 Vercel 还不支持 Websocket ,先不用
// if strings.Contains(modifiedBodyStr, BING_CHAT_DOMAIN) {
// modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, BING_CHAT_DOMAIN, originalDomain)
// }
// if strings.Contains(modifiedBodyStr, "https://www.bingapis.com") {
// modifiedBodyStr = strings.ReplaceAll(modifiedBodyStr, "https://www.bingapis.com", "https://bing.vcanbb.top")
// }
return modifiedBodyStr
}
func modifyGzipBody(res *http.Response, originalScheme string, originalHost string) error {
gz, err := gzip.NewReader(res.Body)
if err != nil {
return err
}
defer gz.Close()
bodyByte, err := io.ReadAll(gz)
if err != nil {
return err
}
originalBody := string(bodyByte)
modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost)
// 修改响应内容
modifiedBody := []byte(modifiedBodyStr)
// gzip 压缩
var buf bytes.Buffer
writer := gzip.NewWriter(&buf)
defer writer.Close()
_, err = writer.Write(modifiedBody)
if err != nil {
return err
}
// 修改 Content-Length 头
res.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
// 修改响应内容
res.Body = io.NopCloser(bytes.NewReader(buf.Bytes()))
return nil
}
func modifyBrBody(res *http.Response, originalScheme string, originalHost string) error {
reader := brotli.NewReader(res.Body)
var uncompressed bytes.Buffer
uncompressed.ReadFrom(reader)
originalBody := uncompressed.String()
modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost)
// 修改响应内容
modifiedBody := []byte(modifiedBodyStr)
// br 压缩
var buf bytes.Buffer
writer := brotli.NewWriter(&buf)
writer.Write(modifiedBody)
writer.Close()
// 修改 Content-Length 头
// res.ContentLength = int64(buf.Len())
res.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
// 修改响应内容
res.Body = io.NopCloser(bytes.NewReader(buf.Bytes()))
return nil
}
func modifyDefaultBody(res *http.Response, originalScheme string, originalHost string) error {
bodyByte, err := io.ReadAll(res.Body)
if err != nil {
return err
}
originalBody := string(bodyByte)
modifiedBodyStr := replaceResBody(originalBody, originalScheme, originalHost)
// 修改响应内容
modifiedBody := []byte(modifiedBodyStr)
// 修改 Content-Length 头
// res.ContentLength = int64(buf.Len())
res.Header.Set("Content-Length", strconv.Itoa(len(modifiedBody)))
// 修改响应内容
res.Body = io.NopCloser(bytes.NewReader(modifiedBody))
return nil
}