代码展示
不是标题党,直接上代码 🫣:
export default async function(req: Request): Promise<Response> {
return fetch("https://github.com/login/oauth/access_token", {
method: "POST",
headers: req.headers,
body: req.body,
});
}
之后每次调用 https://github.com/login/oauth/access_token
接口时,把 API 地址换成我们的服务器地址,即可完成对该接口的代理。
背景
使用 Github OAuth 授权流程的小伙伴一定不陌生,整个流程分为 3 步:
- 客户端:浏览器重定向到 Github 授权页面。
- Github 重定向:用户授权后,Github 重定向到我们的服务端并携带
code
参数。 - 服务端:拿着
code
调用 Github 的/login/oauth/access_token
接口,获取access_token
。
拿到 access_token
后,就可以用它调用 Github 的 API 服务了。
由于国内现状,Github 对于我们来说是不可能畅通无阻的访问的。
这里存在两个需要考虑的网络环境:客户端、服务端。
客户端即用户的网络我们无法控制,网络不行就没法重定向到 Github 的网页。
如果你的产品受众人群不具备访问 Github 的能力,那还是添加一些诸如邮箱、密码、微信扫码登录对国内用户友好的登录方式。
而对于后者服务端的网络,由于我的服务器位于境内,所以也存在无法畅通连接 Github 服务的问题。
为了解决这个问题,一种方案是在服务器上安装一些代理应用,但是仅仅只是为了保证 https://github.com/login/oauth/access_token
这一接口能连通就安装代理应用,有点小题大做了,那可不可以仅仅实现这一个接口的代理?
带着这一目的,我调研了一些提供 FasS(Function as a Service)的云服务,很快写出了上面的代码,经过实际验证,确实是可行的。
技术解析
实现接口代理功能只需几行代码,简单强大,这受益于 Web 标准的不断普及。
这背后有一个名为 WinterCG 的区组织,他们主要的工作是推动跨平台 JavaScript 运行时环境中的 Web API 标准化。
——WinterCG 组织的参与者
WinterCG 核心目标之一,就是在各种非浏览器环境中(Node.js、Deno、边缘计算等)提供一致的标准,使开发者可以在不同的运行时中使用相同的 API 构建应用。
Fetch API 就是其中之一,它支持的对象有:fetch
Response
Request
Headers
等。
只要运行环境支持这些标准,上述代码就可以正常运行。
fetch()
可以接收来自 Request
对象的 headers
和 body
,并且直接返回 Promise<Response>
非常简洁方便。
接下来,我们通过 Express 结合 fetch
实现相同的代理功能来感受下它的不同。
const express = require('express');
const fetch = require('node-fetch');
const app = express();
// 解析 JSON 请求体
app.use(express.json());
app.post('/proxy', async (req, res) => {
// 发送请求到 GitHub OAuth 端点
const response = await fetch('https://github.com/login/oauth/access_token', {
method: 'POST',
headers: req.headers, // 使用客户端请求中的 headers
body: JSON.stringify(req.body), // 将请求体转为 JSON 字符串发送
});
// 读取响应并返回给客户端
const data = await response.json();
res.json(data);
});
app.listen(3000, () => {
console.log('Server running on <http://localhost:3000>');
});
通过 Express 实现相同功能显然代码量更多,并且需要处理一些潜在的兼容性问题。例如,如果代理接口返回中含有 Transfer-Encoding: chunked
,再用 response.json()
处理响应就会出现错误。
而使用 fetch()
直接返回响应对象的方式则不需要关心这些细节,它能自动处理这些复杂情况。
此外,使用 fetch()
发送请求时,请求头中的 Host
会自动的根据请求地址设置正确的值,并且不允许我们手动设置,可见: MDN: 禁止修改的标头 。
在代理接口应用场景中,传入的请求头中的 Host
即为我们代理服务器的 host 地址,如果没有 fetch API
自动帮我们设置正确的 Host
,请求是一定会失败的。
这一点也是我在用 Rust 实现相同的代理功能时发现的,可见标准化 Web API 为我们省了多少麻烦。
应用在 auth.js 中
auth.js
是 JavaScript 生态中最为流行的身份验证库,可快速接入众多第三方身份验证服务商(如 Google、Github、Gitlab 等)。
下面代码展示了如何使用代理 API 获取 Github Token:
import { NextAuthConfig } from 'next-auth'
import GithubProvider from 'next-auth/providers/github'
const githubProvider = GithubProvider({})
// 有代理优先使用代理
githubProvider.token = process.env.FETCH_GITHUB_TOKEN_PROXY || githubProvider.token
export const authConfig = {
providers: [githubProvider],
session: { strategy: 'jwt' },
// ...
} satisfies NextAuthConfig
通过这种方式,可以将代理服务用在 auth.js
中用来获取 Github Token。
总结
在现代 Web 开发中,标准化的 API 让我们的开发流程变得更加简单高效。通过 fetch
等标准的 Web API,跨平台的运行时环境可以一致地处理网络请求、响应等操作,大大减少了代码的复杂性。