
cloudflare
cloudflare page部署vitepress
pages部署还是很简单的,支持自动部署,会分配一个ssl的域名 https://cloudflare-demo2.pages.dev/
cloudflare-demo2 是你的项目名称

自定义域名
设置也很简单, 在阿里云域名控制台设置下cname即可

function 接口
cf是支持接口的,支持两种写法,一个是page的function,另外一个是worker,这里介绍下function的使用。
创建接口
就是请求的 HTTP 方法决定了调用哪个函数:
| 导出函数名 | 对应的 HTTP 方法 | 典型使用场景 |
|---|---|---|
onRequestGet | GET | 获取数据、访问页面 |
onRequestPost | POST | 提交数据、创建资源 |
onRequestPut | PUT | 更新资源(全量) |
onRequestDelete | DELETE | 删除资源 |
onRequestPatch | PATCH | 更新资源(部分) |
onRequestOptions | OPTIONS | CORS 预检请求 |
onRequest (通用) | 所有方法 | 如果不区分方法,只导出一个 onRequest,则会处理所有请求。 |
在根目录下创建functions/api/fetch-html.ts
shell
// functions/api/fetch-html.ts
// Cloudflare Pages Function: GET /api/fetch-html?url=https://example.com
/**前端用法示例
* const res = await fetch(`/api/fetch-html?url=${encodeURIComponent(pageUrl)}`)
* const data = await res.json() as { ok: boolean; html: string }
*
* if (!data.ok) {
* // 提示用户:自动获取失败,请手动复制网页源码
* return
* }
* // data.html → Readability / Turndown
*/
/** 允许调用本接口的前端域名(按你的站点改,只保留协议和域名,端口自动匹配) */
const ALLOWED_HOSTS = [
'https://html2md.share888.top',
'http://localhost',
'http://127.0.0.1',
]
const FETCH_HEADERS: HeadersInit = {
'user-agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
accept:
'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8',
'cache-control': 'no-cache',
pragma: 'no-cache',
'upgrade-insecure-requests': '1',
}
type FetchHtmlResult = {
ok: boolean
html: string
}
function isAllowedOrigin(origin: string): boolean {
try {
const url = new URL(origin)
const host = `${url.protocol}//${url.hostname}`
return ALLOWED_HOSTS.includes(host)
} catch {
return false
}
}
function json(data: FetchHtmlResult, init: ResponseInit = {}, origin?: string | null) {
const headers = new Headers(init.headers)
headers.set('content-type', 'application/json; charset=utf-8')
headers.set('cache-control', 'no-store')
if (origin && isAllowedOrigin(origin)) {
headers.set('access-control-allow-origin', origin)
headers.set('vary', 'Origin')
}
return new Response(JSON.stringify(data), { ...init, headers })
}
function fail(origin?: string | null, status = 200) {
// 业务失败也返回 200 + ok:false,方便前端统一处理提示
return json({ ok: false, html: '' }, { status }, origin)
}
function getAllowedOrigin(request: Request): string | null {
const host = request.headers.get('Host') || ''
if (host.includes('localhost') || host.includes('127.0.0.1')) {
return `http://${host.split(':')[0]}`
}
const origin = request.headers.get('Origin')
if (origin) {
return isAllowedOrigin(origin) ? origin : null
}
const referer = request.headers.get('Referer')
if (referer) {
try {
const refOrigin = new URL(referer).origin
return isAllowedOrigin(refOrigin) ? refOrigin : null
} catch {
return null
}
}
return null
}
function isHttpUrl(value: string): URL | null {
try {
const u = new URL(value)
if (u.protocol !== 'http:' && u.protocol !== 'https:') return null
return u
} catch {
return null
}
}
export const onRequestOptions: PagesFunction = async (context) => {
const origin = getAllowedOrigin(context.request)
if (!origin) {
return new Response(null, { status: 403 })
}
return new Response(null, {
status: 204,
headers: {
'access-control-allow-origin': origin,
'access-control-allow-methods': 'GET, OPTIONS',
'access-control-allow-headers': 'content-type',
'access-control-max-age': '86400',
vary: 'Origin',
},
})
}
export const onRequestGet: PagesFunction = async (context) => {
const origin = getAllowedOrigin(context.request)
if (!origin) {
return json({ ok: false, html: '' }, { status: 403 })
}
const target = new URL(context.request.url).searchParams.get('url')?.trim()
if (!target) {
return fail(origin)
}
const parsed = isHttpUrl(target)
if (!parsed) {
return fail(origin)
}
try {
const res = await fetch(parsed.toString(), {
method: 'GET',
headers: {
...FETCH_HEADERS,
// 部分站点会校验 Referer
referer: parsed.origin + '/',
},
redirect: 'follow',
cf: {
// 降低被当成自动化脚本的概率;超时按需调整
cacheTtl: 0,
cacheEverything: false,
},
})
if (!res.ok) {
return fail(origin)
}
const contentType = (res.headers.get('content-type') || '').toLowerCase()
// 明显不是 HTML 时直接判失败(如图片、PDF、JSON)
if (
contentType &&
!contentType.includes('text/html') &&
!contentType.includes('application/xhtml') &&
!contentType.includes('text/plain')
) {
return fail(origin)
}
const html = await res.text()
if (!html.trim()) {
return fail(origin)
}
return json({ ok: true, html }, { status: 200 }, origin)
} catch {
return fail(origin)
}
}创建配置文件functions/tsconfig.json
shell
{
"compilerOptions": {
"target": "ES2022",
"module": "ESNext",
"moduleResolution": "Bundler",
"lib": ["ES2022"],
"types": ["@cloudflare/workers-types"],
"strict": true,
"noEmit": true,
"skipLibCheck": true,
"isolatedModules": true
},
"include": ["./**/*.ts"]
}增加依赖 package.json
wrangler 是cf专门为本地调试用的。
@cloudflare/workers-types 是cf内置需要的类型,不加接口会报错
shell
{
"name": "vue3-demo",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"dev:wrangler": "wrangler pages dev --port 8788 --proxy 5174", //新增
"build": "vite build"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20230914.0", //新增
"wrangler": "^3.28.0", //新增
"vite": "^4.5.3",
}
}修改配置vite.config.js
shell
export default ({command,mode}) => {
return {
server: {
port: 5174,
proxy: {
'/api': {
target: 'http://localhost:8788',
changeOrigin: true,
},
},
},
}
};
