问题现象

浏览器出现如下报错信息
image.png 检查发现, js 脚本的返回类型是 text/plain, 正常的值应该是 application/javascript

环境

  • go 1.17.6
  • gf 1.16.4

    解决方式

    调试

  • 查 浏览器开发者工具-> 网络 每个 js 文件返回的类型是 text/plain

  • 在程序里将返回 Header 的 Content-Type打印出来, 失败在静态代理时好像不过中间件

    1. server.BindMiddlewareDefault(func(r *ghttp.Request) {
    2. fmt.Println(r.Response.Header().Get("Content-Type"))
    3. })
  • 查 goframe 静态代理的代码实现方式, 找到 serveFileserveFileSearch, 然后自己去实现.

    1. server.BindMiddlewareDefault(func(r *ghttp.Request) {
    2. r.Response.ServeFile(gfile.Join("./public", r.URL.Path))
    3. })
  • 跟踪代码, 发现 goframe 在做文件代理时使用了 http.ServeContent, 这个库里实现 Content-Type又是如下的代码(具体位置 SDK中的 net/http/fs.go中,这个应该是官方库的问题.)

    1. ctypes, haveType := w.Header()["Content-Type"]
    2. var ctype string
    3. if !haveType {
    4. ctype = mime.TypeByExtension(filepath.Ext(name))
    5. if ctype == "" {
    6. // read a chunk to decide between utf-8 text and binary
    7. var buf [sniffLen]byte
    8. n, _ := io.ReadFull(content, buf[:])
    9. ctype = DetectContentType(buf[:n])
    10. _, err := content.Seek(0, io.SeekStart) // rewind to output whole file
    11. if err != nil {
    12. Error(w, "seeker can't seek", StatusInternalServerError)
    13. return
    14. }
    15. }
    16. w.Header().Set("Content-Type", ctype)
    17. } else if len(ctypes) > 0 {
    18. ctype = ctypes[0]
    19. }
  • 发现没有进入下一个中间件

    1. server.BindMiddlewareDefault(func(r *ghttp.Request) {
    2. r.Middleware.Next()
    3. fmt.Println(r.Response.Header().Get("Content-Type"))
    4. })
  • 输出的类型都是 text/plain与我们查到的 mime.TypeByExtension返回的情况不一致 应该是 test/javascript,断点到函数内去看对应的变量. 输出确实是 text/plain

  • ctypes生成代码可以看出,在本身有值时就不会去用 mime 库生成类型值.
  • 我们利用中间件为文件直接加上head

    1. server.BindMiddlewareDefault(func(r *ghttp.Request) {
    2. builtinTypesLower := map[string]string{
    3. ".avif": "image/avif",
    4. ".css": "text/css; charset=utf-8",
    5. ".gif": "image/gif",
    6. ".htm": "text/html; charset=utf-8",
    7. ".html": "text/html; charset=utf-8",
    8. ".jpeg": "image/jpeg",
    9. ".jpg": "image/jpeg",
    10. ".js": "text/javascript; charset=utf-8",
    11. ".json": "application/json",
    12. ".mjs": "text/javascript; charset=utf-8",
    13. ".pdf": "application/pdf",
    14. ".png": "image/png",
    15. ".svg": "image/svg+xml",
    16. ".wasm": "application/wasm",
    17. ".webp": "image/webp",
    18. ".xml": "text/xml; charset=utf-8",
    19. }
    20. typ, ok := builtinTypesLower[gfile.Ext(r.URL.Path)]
    21. if ok {
    22. r.Response.Header().Set("Content-Type", typ)
    23. }
    24. r.Middleware.Next()
    25. fmt.Println(r.URL.Path, ":", r.Response.Header().Get("Content-Type"))
    26. })
  • 这个实现不够完美, 没有利用goframe已实现的静态文件代理功能, 再看 Mine 库 找到 AddExtensionType.我们把 js 的对应类型改了即可.

    1. err := mime.AddExtensionType(".js", "application/javascript; charset=utf-8")
    2. if err != nil {
    3. fmt.Println(err)
    4. return
    5. }
  • 重新调试发现又不是下面的问题

  • mine 库返回的类型是 ~~`text/javascript` 和要求的 `application/javascript`不一致.~~
  • 下面说我给出的解决方案

    增加中间件, 并自己代理静态文件

    这是我想到的第一步解决, 看到那个函数实现就是判断是否已经设置过类型, 没有设置就会去查库, 那我们就在开始把 header 加上 让它不去查库. 这里做一个前置中间件根据文件类型把参数设置上即可, 而由于自带的静态文件代理不能通过中间件截取, 这里我们将自己代理静态文件 ```go // 下面是全局注册 server.BindMiddlewareDefault(ChangeType) server.BindMiddlewareDefault(func(req *ghttp.Request) { req.Response.ServeFile(gfile.Join(“./public”, req.URL.Path)) })

func ChangeType(r *ghttp.Request) {

  1. builtinTypesLower := map[string]string{
  2. ".avif": "image/avif",
  3. ".css": "text/css; charset=utf-8",
  4. ".gif": "image/gif",
  5. ".htm": "text/html; charset=utf-8",
  6. ".html": "text/html; charset=utf-8",
  7. ".jpeg": "image/jpeg",
  8. ".jpg": "image/jpeg",
  9. ".js": "application/javascript; charset=utf-8",
  10. ".json": "application/json",
  11. ".mjs": "text/javascript; charset=utf-8",
  12. ".pdf": "application/pdf",
  13. ".png": "image/png",
  14. ".svg": "image/svg+xml",
  15. ".wasm": "application/wasm",
  16. ".webp": "image/webp",
  17. ".xml": "text/xml; charset=utf-8",
  18. }
  19. typ, ok := builtinTypesLower[gfile.Ext(r.URL.Path)]
  20. if ok {
  21. r.Response.Header().Set("Content-Type", typ)
  22. fmt.Println(r.URL.Path, typ)
  23. }
  24. r.Middleware.Next()

}

  1. <a name="b1xbI"></a>
  2. ## 修改 Mine 库的默认值
  3. 上一个方案的写法, 会让我们自己去对静态代理做处理, 要做好就需要自己去实现 gf 框架实现的静态代理功能, 那能否再找到一个优雅的处理方式?<br />继续研究 Mine 库发现 `AddExtensionType`函数, 可以直接修改默认值. 将下述代码放在 main 函数开头即可解决此问题, 同时可以正常使用 gf 的静态文件代理功能.
  4. ```go
  5. err := mime.AddExtensionType(".js", "application/javascript; charset=utf-8")
  6. if err != nil {
  7. fmt.Println(err)
  8. return
  9. }