跳到主要内容

eo边缘函数迭代 修复不带斜杠乱码问题

最后更新于:

eo边缘函数迭代-修复不带斜杠乱码问题

故障现象

故障时间:2026年7月20日

@cusor auto模型

https://onedayxyy.cn/topics/oneimg-install/ 正常,页面能打开

https://onedayxyy.cn/topics/oneimg-install 没加/ ,怎么页面打开后就异常了呢?

回答

原因已经确认:不是页面坏了,是响应头 Content-Type 错了。

实测对比

URLContent-Type浏览器表现
/topics/oneimg-install/text/html正常渲染
/topics/oneimg-installtext/plain; charset=utf-8当成纯文本,整页源码

浏览器收到的仍是 HTML,但被标成 text/plain,所以按纯文本显示。

为什么会这样

站点走 EdgeOne 边缘函数(从 CNB 拉静态文件)。逻辑大致是:

  1. /:路径变成 .../oneimg-install/index.htmlgetContentType 认出 .html → 返回 text/html
  2. 不带 /:先请求无扩展名路径,失败后再 fallback 到 .../index.html 拿到内容,但 Content-Type 仍按原路径算(没有扩展名),getContentType 得不到结果,就落到 CNB 默认的 text/plain

对应文档里的这段:

 1  return new Response(resp.body, {
 2    status: resp.status,
 3    headers: {
 4      'Content-Type': getContentType(uri) || resp.headers.get('content-type'),
 5      'Cache-Control': 'public, max-age=3600'
 6    }
 7  })
 8}
 9
10function getContentType(uri) {
11  const map = {
12    html: 'text/html',
13    // ...
14  }
15  const ext = uri.split('.').pop()
16  return map[ext]
17}

/docs 同样有这个问题,说明是整站无尾斜杠路径的通病。

修法(改边缘函数)

任选其一,或一起做更稳:

方案 A:无扩展名路径强制当 HTML

1function getContentType(uri) {
2  const map = { html: 'text/html', css: 'text/css', /* ... */ }
3  if (!/\.[^/]+$/.test(uri)) return 'text/html; charset=utf-8'
4  const ext = uri.split('.').pop()
5  return map[ext]
6}

方案 B:无尾斜杠 301 到有斜杠(更规范)

1if (!uri.endsWith('/') && !isFile(uri)) {
2  return Response.redirect(`https://onedayxyy.cn/${uri}/`, 301)
3}

方案 C:fallback 到 index.html 后,用最终路径算 MIME

1'Content-Type': getContentType(target.replace(BASE + '/', '')) || 'text/html; charset=utf-8'

改完后在 EdgeOne 控制台更新边缘函数并清缓存即可。需要的话我可以按你当前线上那份函数代码,直接改好一版可贴的完整脚本。

这3个方案你更推荐哪个呢?

更推荐 方案 B(无尾斜杠 301 到有斜杠)

原因很简单:

  1. 对准根因的用法:Hugo pretty URL 的规范路径本来就是带 / 的;不带 / 应视为「写错了」,重定向比硬改 MIME 更干净。
  2. SEO:避免 /xxx/xxx/ 两套 URL 同内容。
  3. 副作用小:用 isFile() 排除 .css / .js / .png 等真实文件即可。

A / C 只是把错误路径「也能打开」,重复 URL 还在。若只想最快止血,可临时用 A;长期仍以 B 为准。

稳妥组合:B 为主,A 作兜底(万一漏了某条路径,至少不会再整页源码)。

稳妥组合:B 为主,A 作兜底(万一漏了某条路径,至少不会再整页源码)。 那按这个方式优化

文档里的边缘函数已按 B + A 更新。线上要生效,还需你到 EdgeOne 控制台粘贴保存。

改动要点

  1. B:无扩展名且无 /301 到带 /(保留 query)
  2. A:无扩展名路径强制 text/html,避免再整页源码
  3. fallback 到 index.html 后,用最终路径算 MIME

你需要做的

  1. 打开 EdgeOne 边缘函数,编辑当前站点函数
  2. 用下面整段替换后保存
  3. 清一下该路径缓存(或等 TTL)
 1addEventListener('fetch', event => {
 2  event.respondWith(handleRequest(event.request))
 3})
 4
 5const BASE = 'https://cnb.cool/onedayxyy/hugo-teek-public-dist/-/git/raw/dist'
 6
 7async function handleRequest(request) {
 8  const url = new URL(request.url)
 9  let pathname = url.pathname
10
11  // B:无扩展名且无尾斜杠 → 301 到带尾斜杠(保留 query)
12  if (pathname !== '/' && !pathname.endsWith('/') && !isFile(pathname)) {
13    const redirectUrl = new URL(request.url)
14    redirectUrl.pathname = pathname + '/'
15    return Response.redirect(redirectUrl.toString(), 301)
16  }
17
18  let uri = pathname
19
20  if (uri.startsWith('/')) uri = uri.slice(1)
21
22  if (!uri || uri === '') {
23    uri = 'index.html'
24  }
25
26  if (uri.endsWith('/')) {
27    uri = uri + 'index.html'
28  }
29
30  let target = `${BASE}/${uri}`
31
32  let resp = await fetch(target, {
33    headers: {
34      'User-Agent': 'curl/8.0.0',
35      'Accept': '*/*',
36      'Accept-Encoding': 'identity'
37    }
38  })
39
40  if (!resp.ok) {
41    if (!isFile(uri)) {
42      const indexUri = uri.endsWith('/') ? uri + 'index.html' : uri + '/index.html'
43      target = `${BASE}/${indexUri}`
44      resp = await fetch(target, {
45        headers: {
46          'User-Agent': 'curl/8.0.0',
47          'Accept': '*/*',
48          'Accept-Encoding': 'identity'
49        }
50      })
51      if (resp.ok) uri = indexUri
52    }
53  }
54
55  if (!resp.ok || !resp.body) {
56    return new Response('Page Not Found', {
57      status: 404,
58      headers: {
59        'Content-Type': 'text/html; charset=utf-8'
60      }
61    })
62  }
63
64  return new Response(resp.body, {
65    status: resp.status,
66    headers: {
67      'Content-Type': getContentType(uri) || resp.headers.get('content-type') || 'text/html; charset=utf-8',
68      'Cache-Control': 'public, max-age=3600'
69    }
70  })
71}
72
73function isFile(path) {
74  return /\.[^\/]+$/.test(path)
75}
76
77function getContentType(uri) {
78  const map = {
79    html: 'text/html; charset=utf-8',
80    css: 'text/css',
81    js: 'application/javascript',
82    json: 'application/json',
83    png: 'image/png',
84    jpg: 'image/jpeg',
85    jpeg: 'image/jpeg',
86    svg: 'image/svg+xml',
87    webp: 'image/webp',
88    avif: 'image/avif'
89  }
90  // A 兜底:无扩展名路径按 HTML 处理
91  if (!isFile(uri)) {
92    return 'text/html; charset=utf-8'
93  }
94  const ext = uri.split('.').pop().toLowerCase()
95  return map[ext]
96}

老版,有报错的那一版代码:

 1addEventListener('fetch', event => {
 2  event.respondWith(handleRequest(event.request))
 3})
 4
 5const BASE = 'https://cnb.cool/onedayxyy/hugo-teek-public-dist/-/git/raw/dist'
 6
 7async function handleRequest(request) {
 8  const url = new URL(request.url)
 9  let uri = url.pathname
10
11  // 去掉开头的 /
12  if (uri.startsWith('/')) uri = uri.slice(1)
13
14  if (!uri || uri === '') {
15    uri = 'index.html'
16  }
17
18  if (uri.endsWith('/')) {
19    uri = uri + 'index.html'
20  }
21
22  let target = `${BASE}/${uri}`
23
24  let resp = await fetch(target, {
25    headers: {
26      'User-Agent': 'curl/8.0.0',
27      'Accept': '*/*',
28      'Accept-Encoding': 'identity'
29    }
30  })
31
32  // fallback:目录尝试 index.html
33  if (!resp.ok) {
34    if (!isFile(uri)) {
35      target = `${BASE}/${uri}/index.html`
36      resp = await fetch(target, {
37        headers: {
38          'User-Agent': 'curl/8.0.0',
39          'Accept': '*/*',
40          'Accept-Encoding': 'identity'
41        }
42      })
43    }
44  }
45
46  if (!resp.ok || !resp.body) {
47    return new Response('Page Not Found', {
48      status: 404,
49      headers: {
50        'Content-Type': 'text/html; charset=utf-8'
51      }
52    })
53  }
54
55  return new Response(resp.body, {
56    status: resp.status,
57    headers: {
58      'Content-Type': getContentType(uri) || resp.headers.get('content-type'),
59      'Cache-Control': 'public, max-age=3600'
60    }
61  })
62}
63
64function isFile(path) {
65  return /\.[^\/]+$/.test(path)
66}
67
68function getContentType(uri) {
69  const map = {
70    html: 'text/html',
71    css: 'text/css',
72    js: 'application/javascript',
73    json: 'application/json',
74    png: 'image/png',
75    jpg: 'image/jpeg',
76    jpeg: 'image/jpeg',
77    svg: 'image/svg+xml',
78    webp: 'image/webp',
79    avif: 'image/avif'
80  }
81  const ext = uri.split('.').pop()
82  return map[ext]
83}

验证

1curl.exe -sI "https://onedayxyy.cn/topics/oneimg-install"
2# 期望:301,Location 带尾斜杠
3
4curl.exe -sI "https://onedayxyy.cn/topics/oneimg-install/"
5# 期望:200,Content-Type: text/html

部署文档 15...oneblog版本.md12...rz版本.md 已同步。你贴到控制台后告诉我,我可以帮你再测一遍。

验证

故障已解决:😊😊😊

 1Administrator@DESKTOP-7GA8OTS MINGW64 /d/hugo-teek-private (master)
 2$ curl.exe -sI "https://onedayxyy.cn/topics/oneimg-install"
 3HTTP/1.1 301 Moved Permanently
 4Location: https://onedayxyy.cn/topics/oneimg-install/
 5Content-Type: text/plain;charset=UTF-8
 6Content-Length: 0
 7X-NWS-LOG-UUID: 3833726266161877471
 8Connection: keep-alive
 9Server: TencentEdgeOne
10Date: Sun, 19 Jul 2026 16:43:11 GMT
11Strict-Transport-Security: max-age=16070400
12
13
14Administrator@DESKTOP-7GA8OTS MINGW64 /d/hugo-teek-private (master)
15$ curl.exe -sI "https://onedayxyy.cn/topics/oneimg-install/"
16HTTP/1.1 200 OK
17Content-Type: text/html; charset=utf-8
18Cache-Control: public, max-age=3600
19X-NWS-LOG-UUID: 9707798473786386672
20Connection: keep-alive
21Server: TencentEdgeOne
22Date: Sun, 19 Jul 2026 16:43:19 GMT
23Strict-Transport-Security: max-age=16070400
24Transfer-Encoding: chunked
最新文章

文档导航