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

582 阅读7分钟

问题描述

  • 前段时间,笔者接手了一个老项目
  • 其中用户会上传一些资源,比如图片、视频啥的,其中还有txt文本文档
  • 用户提出,需要再加上一个txt文本预览的功能
  • 笔者看了一下代码,原本的图片、视频预览功能,是使用iframe实现的
  • 只需要把后端返回的文本资源的url丢到iframe的src里面,就能够自动给渲染出来
  • 结果,笔者这样操作以后,直接乱码了,如下图:

1.png

  • 但是这个txt下载下来,打开是没有问题的,如下图

2.png

  • 这里,为了方便大家看效果,笔者用自己的服务器提供了一个乱码的txt文本连接
  • 乱码文本链接,点击看效果:ashuai.work/api/stream.…

问题分析

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

3.png

具体原因——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
  • 这是前端的锅咯???
  • 我们继续往下看就知道了
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文件信息如下:
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
  • 同时,我们再看一下,上传接口的载荷如下图:
4.png

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

解决方案

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

不可行方案——前端上传的时候,针对于txt文本类型的,指定utf-8字符集编码

下方代码不可以想当然:

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
  }
});

原因:

使用 FormData 构造请求体并发送 POST 请求时,浏览器会自动识别这是一个 multipart/form-data 类型的请求,并添加正确的 Content-Type 头,比如:Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryxxxxxxxx,这个 boundary 是一段特殊的字符串,用来分隔表单中的多个字段(包括文件、文本等),它必须由浏览器自动生成。所以,这种情况,前端没法操作添加内容类型,指定字符集。

所以,不是前端的锅哦

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

  • 假设这个文件相关,都是存储在minio上,我们可以在存txt文件的时候
  • 指定charset=utf-8
// 文件上传接口
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: '文件上传成功'
    });
});

方案二 简单的接口,可以直接设置响应头

// 动态路由传参方式,通过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反向代理的文件服务,指定文本字符集集合

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

# 特殊处理 .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标签

效果图

5.png

代码

<!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/htmlHTML 文档网页(<html>...</html>
text/cssCSS 样式表.css 文件
text/csvCSV 表格数据.csv 文件
text/javascriptJavaScript 代码(旧标准,推荐 application/javascript.js 文件
text/xmlXML 数据.xml 文件
text/markdownMarkdown 文档.md 文件

如:

Content-Type: text/plain; charset=utf-8 

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


2. 应用类型(Application Types)

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

示例

Content-Type: application/json; charset=utf-8
Content-Type: application/x-www-form-urlencoded

3. 多媒体类型(Multimedia Types)

Content-Type用途示例
image/jpegJPEG 图片.jpg, .jpeg
image/pngPNG 图片.png
image/gifGIF 动图.gif
image/webpWebP 图片.webp
image/svg+xmlSVG 矢量图.svg
audio/mpegMP3 音频.mp3
audio/wavWAV 音频.wav
video/mp4MP4 视频.mp4
video/webmWebM 视频.webm

示例

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> 默认方式

示例

Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryABC123

5. 其他常见类型

Content-Type用途示例
font/woffWOFF 字体.woff
font/woff2WOFF2 字体.woff2
application/vnd.ms-excelExcel 旧格式.xls
application/vnd.openxmlformats-officedocument.spreadsheetml.sheetExcel (XLSX).xlsx
application/mswordWord 旧格式.doc
application/vnd.openxmlformats-officedocument.wordprocessingml.documentWord (DOCX).docx

总结

类别常见 Content-Type
文本text/plain, text/html, text/css, text/csv
JSON/XMLapplication/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 可以避免乱码、解析错误等问题,特别是在文件上传、接口交互时非常重要!