Skip to content

Commit

Permalink
feat: ⚡ 重构支持 vercel
Browse files Browse the repository at this point in the history
  • Loading branch information
adams549659584 committed May 4, 2023
1 parent 91f3f44 commit 097f583
Show file tree
Hide file tree
Showing 11 changed files with 339 additions and 230 deletions.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License Copyright (c) 2023 adams549659584

Permission is hereby granted, free
of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to the
following conditions:

The above copyright notice and this permission notice
(including the next paragraph) shall be included in all copies or substantial
portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
26 changes: 15 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,21 +22,19 @@

![手机未登录](./docs/img/4.png)

### 演示站点:
## 演示站点:

> 甲骨文小鸡仔,轻虐
- https://bing.vcanbb.top

> railway
> Railway
- https://bing-railway.vcanbb.top

- https://go-proxy-bingai-production.up.railway.app



### 获取cookies
## 获取cookies

- 访问 https://www.bing.com/https://cn.bing.com/ ,登录

Expand All @@ -46,13 +44,15 @@

![获取Cookie](./docs/img/5.png)

### 部署
## 部署

> 需 https 域名 (自行配置 nginx 等)
> 支持 Linux (amd64 / arm64)
- docker 部署 , 参考 [Dockerfile](./docker/Dockerfile)[docker-compose.yml](./docker/docker-compose.yml)
### docker

> 参考 [Dockerfile](./docker/Dockerfile)[docker-compose.yml](./docker/docker-compose.yml)
示例

Expand All @@ -61,24 +61,28 @@
docker run -d -p 8080:8080 --name go-proxy-bingai --restart=unless-stopped adams549659584/go-proxy-bingai
```

- 直接下载 Release 运行
### Release

[Github Releases](https://github.com/adams549659584/go-proxy-bingai/releases) 下载适用于对应平台的压缩包,解压后可得到可执行文件 go-proxy-bingai,直接运行即可。

- Railway
### Railway

> 主要配置 Dockerfile 路径 及 端口就可以
```bash
PORT=8080
RAILWAY_DOCKERFILE_PATH=docker/Dockerfile
```
使用模板部署,点这里 => [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/uIckWS?referralCode=BBs747)
一键部署,点这里 => [![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/template/uIckWS?referralCode=BBs747)

![Railway 模板部署](./docs/img/railway-1.png)

自行使用 Railway 部署配置如下

![Railway 环境变量](./docs/img/railway-2.png)

![Railway 域名](./docs/img/railway-3.png)
![Railway 域名](./docs/img/railway-3.png)

### Vercel

一键部署,点这里 => [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/adams549659584/go-proxy-bingai&project-name=go-proxy-bingai&repository-name=go-proxy-bingai)
10 changes: 10 additions & 0 deletions api/chathub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package api

import (
"adams549659584/go-proxy-bingai/common"
"net/http"
)

func ChatHub(w http.ResponseWriter, r *http.Request) {
common.NewSingleHostReverseProxy(common.BING_CHAT_URL).ServeHTTP(w, r)
}
14 changes: 14 additions & 0 deletions api/index.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package api

import (
"adams549659584/go-proxy-bingai/common"
"net/http"
)

func Index(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/" {
http.Redirect(w, r, "/web/chat.html", http.StatusFound)
} else {
common.NewSingleHostReverseProxy(common.BING_URL).ServeHTTP(w, r)
}
}
10 changes: 10 additions & 0 deletions api/web.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package api

import (
"adams549659584/go-proxy-bingai/web"
"net/http"
)

func WebStatic(w http.ResponseWriter, r *http.Request) {
http.StripPrefix("/web/", http.FileServer(http.FS(web.WebFS))).ServeHTTP(w, r)
}
202 changes: 202 additions & 0 deletions common/proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
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
}
12 changes: 12 additions & 0 deletions common/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package common

import (
"math/rand"
"time"
)

func RandInt(min int, max int) int {
seed := time.Now().UnixNano()
rng := rand.New(rand.NewSource(seed))
return rng.Intn(max-min+1) + min
}
Loading

0 comments on commit 097f583

Please sign in to comment.