事實證明 ChatGPT 是足夠火爆的,火爆到什麼程度呢,其 API 一經推出便獲得了 GFW 的認證。在 Twitter 上看到很多人都在為解決無法正常通路 OpenAI 的 API 而苦惱,最常見解決方案是使用一台伺服器來進行反向代理,但這樣又徒增了一些成本。因為之前在公司的業務上遇到過類似問題,當時老闆找到了一個還不錯的幾乎零成本解決方案,試了一下現在仍然可以用來解決 OpenAI 的 API 無法通路的問題,是以在這裡推薦給大家。
該方案的主要思路是使用 Cloudflare 的 Workers 來代理 OpenAI 的 API 位址,配合自己的域名即可在境内實作通路。因為 Cloudflare Workers 有每天免費 10 萬次的請求額度,也有可以免費注冊的域名,是以幾乎可以說是零成本。而且該方法理論上支援所有被認證的網站,而不隻是 OpenAI。
使用這個方案需要你有以下東西:
- 一個沒有被 GFW 認證的域名,相信對于大家來說注冊域名不是啥大問題)
- 一個 Cloudflare 賬号(當然也可以現注冊)
太長不看
- 建立一個 Cloudflare Worker
- 将 https://gist.github.com/noobnooc/d0407b5fb81cff9d36f981170b99d4e6 裡的代碼粘貼到 Worker 中并部署
- 給 Worker 綁定一個沒有被 GFW 認證的域名
- 使用自己的域名代替 api.openai.com
如果具體步驟有問題,可以參考下面的詳細版教程。
将域名 NS 轉到 Cloudflare
如果域名已經托管在 Cloudflare 的忽略這一步即可。
⚠️ 經評論區指出,Cloudflare Workers 的域名綁定僅支援托管在 Cloudflare 上的域名。由于本人常年是把域名托管在 Cloudflare 的沒有注意到這一點,是以得先将域名的 NS 轉到 Cloudflare,如果介意将域名轉到 Cloudflare 的話,可以考慮使用 nginx 反代、Docker 容器等其他方法 。
沒有 Cloudflare 賬号的話可以注冊一個,具體注冊細節就不多說了。注冊或登入到 Cloudflare 的管理界面後,點選側邊欄的 “Websites” ,然後點選 “Add a Site” 按鈕準備将域名轉到 Cloudflare:
建立一個 Cloudflare Worker
登入到 Cloudflare 的管理界面後,點選側邊欄的 “Workers” 選項,然後點選 “Create a Service” 建立一個 Worker。
然後在建立界面中輸入 “Service name” 後點選 “Create Service” 按鈕建立 Worker。“Select a starter” 項先不用管。
至此 Cloudflare 的 Worker 便建立好了,下面開始修改 Worker 的代碼,使其能代理 OpenAI 的 API。
修改 Cloudflare Worker 的代碼
在 Worker 的管理界面,點選右上角的 “Quick Edit” 按鈕編輯代碼 Worker 的代碼。
在 “Enter your site (example.com)” 處輸入要轉入的域名後,點選 “Add Site”:
根據 Cloudflare 的提示,在域名注冊商處将 NS 修改到 Cloudflare 指定的位址,等待域名解析成功後,即可進行後續操作。
在左側的代碼編輯器中,删除現有的所有代碼,然後複制粘貼以下内容到代碼編輯器:
// Website you intended to retrieve for users.
const upstream = 'api.openai.com'
// Custom pathname for the upstream website.
const upstream_path = '/'
// Website you intended to retrieve for users using mobile devices.
const upstream_mobile = upstream
// Countries and regions where you wish to suspend your service.
const blocked_region = []
// IP addresses which you wish to block from using your service.
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']
// Whether to use HTTPS protocol for upstream address.
const https = true
// Whether to disable cache.
const disable_cache = false
// Replace texts.
const replace_dict = {
'$upstream': '$custom_domain',
}
addEventListener('fetch', event => {
event.respondWith(fetchAndApply(event.request));
})
async function fetchAndApply(request) {
const region = request.headers.get('cf-ipcountry').toUpperCase();
const ip_address = request.headers.get('cf-connecting-ip');
const user_agent = request.headers.get('user-agent');
let response = null;
let url = new URL(request.url);
let url_hostname = url.hostname;
if (https == true) {
url.protocol = 'https:';
} else {
url.protocol = 'http:';
}
if (await device_status(user_agent)) {
var upstream_domain = upstream;
} else {
var upstream_domain = upstream_mobile;
}
url.host = upstream_domain;
if (url.pathname == '/') {
url.pathname = upstream_path;
} else {
url.pathname = upstream_path + url.pathname;
}
if (blocked_region.includes(region)) {
response = new Response('Access denied: WorkersProxy is not available in your region yet.', {
status: 403
});
} else if (blocked_ip_address.includes(ip_address)) {
response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', {
status: 403
});
} else {
let method = request.method;
let request_headers = request.headers;
let new_request_headers = new Headers(request_headers);
new_request_headers.set('Host', upstream_domain);
new_request_headers.set('Referer', url.protocol + '//' + url_hostname);
let original_response = await fetch(url.href, {
method: method,
headers: new_request_headers,
body: request.body
})
connection_upgrade = new_request_headers.get("Upgrade");
if (connection_upgrade && connection_upgrade.toLowerCase() == "websocket") {
return original_response;
}
let original_response_clone = original_response.clone();
let original_text = null;
let response_headers = original_response.headers;
let new_response_headers = new Headers(response_headers);
let status = original_response.status;
if (disable_cache) {
new_response_headers.set('Cache-Control', 'no-store');
}
new_response_headers.set('access-control-allow-origin', '*');
new_response_headers.set('access-control-allow-credentials', true);
new_response_headers.delete('content-security-policy');
new_response_headers.delete('content-security-policy-report-only');
new_response_headers.delete('clear-site-data');
if (new_response_headers.get("x-pjax-url")) {
new_response_headers.set("x-pjax-url", response_headers.get("x-pjax-url").replace("//" + upstream_domain, "//" + url_hostname));
}
const content_type = new_response_headers.get('content-type');
if (content_type != null && content_type.includes('text/html') && content_type.includes('UTF-8')) {
original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
} else {
original_text = original_response_clone.body
}
response = new Response(original_text, {
status,
headers: new_response_headers
})
}
return response;
}
async function replace_response_text(response, upstream_domain, host_name) {
let text = await response.text()
var i, j;
for (i in replace_dict) {
j = replace_dict[i]
if (i == '$upstream') {
i = upstream_domain
} else if (i == '$custom_domain') {
i = host_name
}
if (j == '$upstream') {
j = upstream_domain
} else if (j == '$custom_domain') {
j = host_name
}
let re = new RegExp(i, 'g')
text = text.replace(re, j);
}
return text;
}
async function device_status(user_agent_info) {
var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
var flag = true;
for (var v = 0; v < agents.length; v++) {
if (user_agent_info.indexOf(agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}
最後點選編輯器右下角的 “Save and deploy” 按鈕部署該代碼,在彈出的對話框中繼續選擇 “Save and deploy” 确認部署。
至此,便可以使用該 worker 的位址來代替 OpenAI 的 API 位址了。比如想要請求 ChatGPT 的 API 時,把官方文檔中的 https://api.openai.com/v1/chat/completions 替換成 https://openai.workers.dev 即可(注意這個位址并不存在,是需要換成自己剛剛建立的 Worker 的位址)。
但是你可能會發現,這樣做了依然還是沒有解決問題,因為 Cloudflare Workers 的 workers.dev 域名也是被 GFW 認證過的。但是好在隻是認證了 workers.dev 域名,而 ip 還是幸存的狀态,是以我們可以給 Worker 綁定一個自己的域名。
綁定域名
在 Cloudflare Workers 的管理界面中,點選 “Triggers” 頁籤,然後點選 “Custom Domians” 中的 “Add Custom Domain” 按鈕以綁定域名。
輸入域名後點選 “Add Custom Domain”目前隻支援 NS 托管在 Cloudflare 上的域名,如果不介意,可以點選 Cloudflare 側邊欄的 “Websites”,然後點選 “Add a Site” 按鈕,根據提示将域名的 NS 記錄指定到 Cloudflare。
至此便大功告成。等待片刻,應該就可以通過你自己的域名來代替 OpenAI 的 API 位址了,比如在本文的例子中,想要請求 ChatGPT 的 API ,即是把官方 API 位址 https://api.openai.com/v1/chat/completions 換為我自己的域名,其他參數均參照官方示例即可。由于 Cloudflare 有每天免費 10 萬次的請求額度,是以輕度使用基本是零成本的