🚀四种方案解决浏览器地址栏预览txt文本乱码问题🚀Content-Type: text/plain;没有charset=utf-8

问题描述

  • 前段时间,笔者接手了一个老项目
  • 其中用户会上传一些资源,比如图片、视频啥的,其中还有txt文本文档
  • 用户提出,需要再加上一个txt文本预览的功能
  • 笔者看了一下代码,原本的图片、视频预览功能,是使用iframe实现的
  • 只需要把后端返回的文本资源的url丢到iframe的src里面,就能够自动给渲染出来
  • 结果,笔者这样操作以后,直接乱码了,如下图:
  • 但是这个txt下载下来,打开是没有问题的,如下图
  • 这里,为了方便大家看效果,笔者用自己的服务器提供了一个乱码的txt文本连接
  • 乱码文本链接,点击看效果:ashuai.work/api/stream....

问题分析

我们看一下控制台,响应头的Content-Type,如下图:

具体原因------Content-Type: text/plain;没有charset=utf-8

  • 在这个请求的响应头中,Content-Type: text/plain;
  • 缺少charset=utf-8,缺少了编码字符集,导致浏览器渲染乱码
  • 要是HTTP 响应头中只指定 Content-Type: text/plain
  • 而没有明确设置字符集(如 charset=utf-8
  • 因为浏览器需要知道如何解码接收到的文本数据,若无指定字符集,浏览器会尝试自动猜测字符编码
  • 而且,不同浏览器的默认编码可能不同,比如有的默认 ISO-8859-1,有的可能尝试 UTF-8
  • 最终导致可能出现乱码情况
  • 好吧...
  • 那为何没有charset=utf-8
  • 那是因为上传txt文件的时候
  • 就没有charset=utf-8
  • 这是前端的锅咯...
js 复制代码
route.post('/upload', upload.single('file'), async (req, res) => {
    try {
        if (!req.file) {
            return res.status(400).json({ error: '没有文件被上传' });
        }

        console.log('req.file--->', req.file)
        
        ......

    } catch (error) {
        console.error('上传错误:', error);
        res.status(500).json({ error: '文件上传失败' });
    }
});
  • 上述是一个后端的上传接口
  • 在接口中打印前端传递过来的file文件信息如下:
js 复制代码
req.file---> {
  fieldname: 'file',
  originalname: 'txt文本.txt',
  encoding: '7bit',
  mimetype: 'text/plain',
  buffer: <Buffer e7 99 bd e6 97 a5 e4 be 9d e5 b1 b1 e5 b0 bd 0d 0a e9 bb 84 e6 b2 b3 e5 85 a5 e6 b5 b7 e6 b5 81 0d 0a e6 ac b2 e7 a9 b7 e5 8d 83 e9 87 8c e7 9b ae 0d ... 16 more bytes>,
  size: 66
}
  • 在这里我们就可以看到,mimetype: 'text/plain', 这里,就已经少了字符集utf-8
  • 同时,我们再看一下,上传接口的载荷如下图:

也就是说,最初前端上传txt文本的时候,就没有指定utf-8,导致后端接收文件的时候,也没有编码,自然后续都没有utf-8,浏览器解析也就有可能出问题了...

解决方案

知道问题原因,我们就可以制定解决方案了

方案一 前端上传的时候,针对于txt文本类型的,要额外指定utf-8字符集编码

如下代码:

js 复制代码
const formData = new FormData();
formData.append('file', file);

// 使用 fetch 或 axios 上传
fetch('/upload', {
  method: 'POST',
  body: formData,
  headers: {
    'Content-Type': 'text/plain; charset=utf-8', // 指定字符集utf-8
  }
});

方案二 后端接口指定utf-8

  • 假设这个文件相关,都是存储在minio上,我们可以在存txt文件的时候
  • 指定charset=utf-8
js 复制代码
// 文件上传接口
route.post('/upload', upload.single('file'), async (req, res) => {

    console.log('req.file--->', req.file)

    const fileName = req.file.originalname

    const metaData = {
        'Content-Type': 'Content-Type': 'text/plain; charset=utf-8',
        'Content-Disposition': 'inline'
    };

    await minioClient.putObject(bucketName, fileName, req.file.buffer, metaData);

    res.json({
        success: true,
        fileName: fileName,
        message: '文件上传成功'
    });
});

如果是简单的接口,可以直接设置响应头,如下:

js 复制代码
// 动态路由传参方式,通过params获取动态参数
route.get('/stream.txt', (req, res) => {

  // 存储一份txt文件的路径
  let txtUrl = './assets/stream.txt'
  const Myfilesize = fs.statSync(txtUrl).size

  // 此接口允许跨域
  res.header('Access-Control-Allow-Origin', '*');

  res.writeHead(200, { 'Content-Type': 'text/plain' }); // 纯文本,不设置编码,容易乱码
  // res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); // 纯文本,设置编码utf-8,解决乱码问题

  //创建可读流 引入文件模块 const fs = require("fs")
  let readStream = fs.createReadStream(txtUrl)

  // 将读取的结果以管道pipe流的方式返回给前端
  readStream.pipe(res);
})

方案三 nginx处理,指定charset=utf-8

假设,我是通过nginx反向代理的文件服务,指定文本字符集集合

js 复制代码
location /file/ {
    proxy_pass http://file-server.com/;
    
    proxy_hide_header Content-Type;
    add_header Content-Type "text/plain; charset=utf-8" always;
}

js 复制代码
# 特殊处理 .txt 文件 重写Content-Type 响应头
location ~* ^/file/(.+\.txt)$ {
    # 去除前缀 /file/ 后转发
    proxy_pass http://file-server.com/$1;

    proxy_hide_header Content-Type;
    add_header Content-Type "text/plain; charset=utf-8" always;
}

方案四 不使用iframe,换成直接发请求,搭配pre标签

效果图

代码

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.9.0/axios.js"></script>
    <style>
        iframe {
            width: 360px;
            height: 200px;
        }

        pre {
            white-space: pre-wrap;
            text-align: left;
        }
    </style>
</head>

<body>
    <!-- iframe乱码 -->
    <iframe src="https://ashuai.work/api/stream.txt" frameborder="0"></iframe>

    <!-- 直接请求文本内容,放置在pre标签里 -->
    <pre></pre>
    <script>
        let pre = document.querySelector('pre')
        axios.get('https://ashuai.work/api/stream.txt').then((res) => {
            console.log('res', res.data)
            pre.innerHTML = res.data
        })
    </script>
</body>

</html>

具体选择哪种解决方案,大家根据自己实际情况操作即可

知识点回顾,常见的Content-Type

上述的案例,是针对于Content-Type: text/plain; 添加 charset=utf-8 字符集,在此,我们回顾一下,常见的内容类型有哪些

常见的 Content-Type(内容类型)用于标识 HTTP 请求或响应中传输的数据格式。不同的 Content-Type 对应不同的数据编码方式,用来正确解析数据,很重要!


1. 文本类型(Text Types)

Content-Type 用途 示例
text/plain 纯文本(默认无格式) .txt 文件
text/html HTML 文档 网页(<html>...</html>
text/css CSS 样式表 .css 文件
text/csv CSV 表格数据 .csv 文件
text/javascript JavaScript 代码(旧标准,推荐 application/javascript .js 文件
text/xml XML 数据 .xml 文件
text/markdown Markdown 文档 .md 文件

如:

http 复制代码
Content-Type: text/plain; charset=utf-8 

一般建议文本类型最好指定一下字符集为utf-8,以防出现乱码的情况


2. 应用类型(Application Types)

Content-Type 用途 示例
application/json JSON 数据(API 常用) {"name": "John"}
application/xml XML 数据(比 text/xml 更严格) <name>John</name>
application/javascript JavaScript 代码 .js 文件
application/pdf PDF 文档 .pdf 文件
application/zip ZIP 压缩文件 .zip 文件
application/octet-stream 二进制流(默认下载类型) 任意二进制文件
application/x-www-form-urlencoded 表单 URL 编码(默认表单提交) name=John&age=20
application/xml-dtd XML DTD 定义 .dtd 文件
application/ld+json JSON-LD(结构化数据) SEO 数据

示例

http 复制代码
Content-Type: application/json; charset=utf-8
Content-Type: application/x-www-form-urlencoded

3. 多媒体类型(Multimedia Types)

Content-Type 用途 示例
image/jpeg JPEG 图片 .jpg, .jpeg
image/png PNG 图片 .png
image/gif GIF 动图 .gif
image/webp WebP 图片 .webp
image/svg+xml SVG 矢量图 .svg
audio/mpeg MP3 音频 .mp3
audio/wav WAV 音频 .wav
video/mp4 MP4 视频 .mp4
video/webm WebM 视频 .webm

示例

http 复制代码
Content-Type: image/jpeg
Content-Type: video/mp4

4. 表单数据(Form Data)

Content-Type 用途 示例
multipart/form-data 文件上传(含二进制数据) <form enctype="multipart/form-data">
application/x-www-form-urlencoded 普通表单提交(键值对) <form> 默认方式

示例

http 复制代码
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123

5. 其他常见类型

Content-Type 用途 示例
font/woff WOFF 字体 .woff
font/woff2 WOFF2 字体 .woff2
application/vnd.ms-excel Excel 旧格式 .xls
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet Excel (XLSX) .xlsx
application/msword Word 旧格式 .doc
application/vnd.openxmlformats-officedocument.wordprocessingml.document Word (DOCX) .docx

总结

类别 常见 Content-Type
文本 text/plain, text/html, text/css, text/csv
JSON/XML application/json, application/xml
表单 multipart/form-data, application/x-www-form-urlencoded
图片 image/jpeg, image/png, image/svg+xml
视频/音频 video/mp4, audio/mpeg
文件下载 application/octet-stream

浏览器会默认MIME 类型嗅探

  • 当服务器没有明确指定 Content-Type 或者设置不完整(例如缺少字符编码信息)时,浏览器通常会尝试进行 MIME 类型嗅探(MIME Type Sniffing)。
  • 它通过检查内容的实际数据来猜测正确的 MIME 类型和字符编码,以便正确地显示内容。
  • 不过,可能不太够用,所以大家最好,还是指定content-type
  • 所以:正确设置 Content-Type 可以避免乱码、解析错误等问题,特别是在文件上传、接口交互时非常重要!
相关推荐
小前端大牛马1 小时前
react中hook和高阶组件的选型
前端·javascript·vue.js
刺客-Andy1 小时前
React第六十二节 Router中 createStaticRouter 的使用详解
前端·javascript·react.js
秋田君2 小时前
深入理解JavaScript设计模式之策略模式
javascript·设计模式·策略模式
chxii2 小时前
1.13使用 Node.js 操作 SQLite
数据库·sqlite·node.js
菜鸡爱上编程3 小时前
React16,17,18,19更新对比
前端·javascript·reactjs·react
我命由我123454 小时前
VSCode - VSCode 转换英文字母的大小写
开发语言·javascript·ide·vscode·编辑器·html·软件工具
陈龙龙的陈龙龙4 小时前
uniapp 金额处理组件
前端·javascript·uni-app
我命由我123454 小时前
VSCode - VSCode 让未被编辑的标签页不被自动关闭
前端·javascript·ide·vscode·编辑器·html·js
layman05285 小时前
openeuler 虚拟机:Nginx 日志分析脚本
前端·javascript·nginx
烛阴5 小时前
快速上手Axios:前端开发者必备技能
前端·javascript