tomcat上传文件大小限制10M springboot上传文件大小设置

序这两天在另一个社区看到一个关于Tomcat的问题,挺有意思的。只是之前没想过这个问题,今天就用Tomcat机制来说说这个“为什么”。本文对HTTP协议中文件上传标准和Tomcat机制的分析比较基础,不必要的大佬可以跳到文末。HTTP协议中的文件上传众所周知,HTTP是一种文本协议。文...

这两天在另一个社区看到一个关于Tomcat的问题,挺有意思的。只是之前没想过这个问题,今天就用Tomcat机制来说说这个“为什么”。

本文对HTTP协议中文件上传标准和Tomcat机制的分析比较基础,不必要的大佬可以跳到文末。

HTTP协议中的文件上传

众所周知,HTTP是一种文本协议。文本协议如何传输文件?

直接传送...是的,就这么简单。协议只是从应用层的角度来看。到了传输层,所有的数据都是字节,没什么区别,不需要额外的编码或者解码。

多部分/表单数据模式

HTTP协议指定了一种基于表单的文件上传方法。定义一个具有multipart/form-data值的类型格式的ENCTYPE属性,然后添加一个

<FORM ENCTYPE="multipart/form-data" ACTION="_URL_" METHOD=POST> File to process: <INPUT NAME="userfile1" TYPE="file"> <INPUT TYPE="submit" VALUE="Send File"> </FORM>

这种multipart/form-data类型的表单与默认的x-***-form-urlencoded略有不同。虽然两种形式都可以上传多个字段,但前者可以上传文件,后者只能传输文本。

现在我们来看看这种表单文件上传方式的协议。下图显示了一个简单的多部分/表单数据类型请求消息:

从上图可以看出,HTTP头部分没有什么变化,只是在Content-Type上增加了一个boundary标签,但是净荷部分完全不同。

多部分/表单数据中边界的作用是分隔表单的多个字段。在有效载荷部分,第一行和第二行有一个边界,每个字段(部分/项目)之间也有一个边界。

服务器读取时,只需要从Content-Type中获取边界,然后通过边界拆分payload部分,就可以得到所有的字段。

在每个字段的消息中,有一个Content-Disposition字段作为该字段的报头部分。其中记录了当前的字段名称(name)。如果是一个文件,将会有一个filename属性,一个Content-Type将会附加到下一行来标识文件类型。

虽然x-***-form-urlencoded和multipart表单都可以传输字段,但multipart不仅可以传输文本字段,还可以传输文件。而且这种多部分文件传输方式也是“标配”,可以被各种服务器支持,直接读取文件。

X-***-form-urlencoded只能传输基本的文本数据,但是如果你强制文件为文本,没有人可以阻止你使用这种类型的传输。但作为文本传输,后端必须按字符串解析,byte->;str的编码开销是完全不必要的,可能会导致编码错误...

在x-***-form-urlencoded消息中,没有边界,多个字段会通过&符号拼接,对key/value应用urlencode编码

x-***-form-urlencoded虽然增加了一步编码过程,但是并没有给每个字段增加一个头,没有边界。与多部分模式相比,消息量要小得多。

除了这种multipart,还有一种直接上传文件的形式,但不常用。

二进制有效载荷模式

除了multipart/form-data,还有一种上传二进制负载的方法。这个二进制有效载荷是我自己的名字...因为在HTTP协议中找不到这种方法的描述(如果大boss评论区有贴链接的话),但是很多HTTP客户端都支持。

例如,邮递员:

比如OkHttp:

OkHttpClient client = new OkHttpClient().newBuilder() .build();MediaType mediaType = MediaType.parse("i***ge/png");RequestBody body = RequestBody.create(mediaType, "<file contents here>");Request request = new Request.Builder() .url("localhost:8098/upload") .method("POST", body) .addHeader("Content-Type", "i***ge/png") .build();Response response = client.newCall(request).execute();

这个方法很简单,就是整个payload部分用来存储文件数据。如下图所示,整个有效负载部分是文件内容:

虽然这个方法很简单,但是客户端实现也很简单,但是...服务器没有良好的支持。例如,在Tomcat中,这个二进制文件不被视为一个文件,而是一个普通的消息。

Tomcat处理机制分析

当Tomcat处理文本消息时,它将首先读取前面的头部分,并解析Content-Length以划分消息边界。它不是一次读取剩余的有效负载部分,而是包装一个InputStream并在内部调用Socket read来读取RCV_BUF的数据(当完整消息的大小大于Read BUF的大小时)

在对HttpServletRequest调用
GetParameter/GetInputStream等涉及部分读取净荷的操作时,会读取InputStream内部的Socket RCV_BUF来读取净荷的数据。

此方法

不会一次读取所有数据并将其临时存储在内存中,而是包装了一种读取RCV _缓冲区

的InputStream内部方式。它的特点是不存储数据,只是包装数据,应用层对ServletRequest#inputStream的读操作会转发到Socket RCV_BUF。

但是,如果应用层完全读取ServletRequest#inputStream,然后转换字符串并存储在内存中,则与Tomcat无关。

Tomcat对多部分请求有一个特殊的处理机制。由于multipart设计用于传输文件,Tomcat在处理这种类型的请求时添加了临时文件的概念。

解析消息时,它将多部分数据写入磁盘

如下图所示,Tomcat将每个字段包装成一个DiskFileItem & # 8211
org . Apache . Tomcat . util . http . file upload . disk . DiskFileItem(这个diskfileitem不区分文件和文本数据)。DiskFileItem分为头部分和内容部分。一部分内容存储在内存中,其余存储在磁盘中,磁盘由一个sizeThreshold划分;

但是,这个值默认为0

,这意味着默认情况下,所有内容都将存储到磁盘。

由于它存储在磁盘上,所以在读取时也必须从磁盘上读取...效率自然就低了。所以如果只是短信,就不要用multipart type来传输。这种类型将被转移到磁盘。

还有一个冷知识。当Tomcat处理多部分消息时,如果一个字段不是文件,它会将这个字段的键/值添加到parameterMap中,这意味着这些非文件字段可以通过
request.getParameter/getParameterMap.获得

//org.apache.catalina.connector.Request#parsePartsif (part.getSubmittedFileName() == null) { String name = part.getName(); String value = null; try { value = part.getString(charset.name()); } catch (UnsupportedEncodingException uee) { // Not possible } ...... parameters.addParameter(name, value);}

要知道这个getParameter只能获取表单参数(FormParam)和查询参数(QueryString),但是multipart也是表单,获取参数似乎也没什么问题...

简单总结一下。

Tomcat处理不同类型的请求:

如果参数是 GET queryString方式(url上拼参数),那么所有参数都在报文头中,会一次性全部读取至内存如果是 POST 类型的报文,Tomcat 只会对读取 Header 部分,Payload 部分不会主动读取,而是将 Socket 包装成一个 InputStream 供应用层 readx-***-form-urlencoded 这种类型的报文,虽然不会主动读取,但很多 Web 框架(比如 SpringMVC)会调用 getParameter,还是会出发 InputStream 的read,对 RCV_BUF 进行读取上面提到的 binary payload也是一样,Tomcat 并不会主动发起 read 操作,需要应用层调用 ServletRequest#InputStream 进行 read操作读取 RCV_BUF 的数据multipart 类型的报文,一样不会主动读取,调用HttpServletRequest#getParts 才会触发解析/读取;同样的,很多 Web 框架会调用 getParts,所以会触发解析

为什么要先写临时文件?就不能把InputStream打包交给应用层读取吗?

如果应用层没有(及时)读取RCV_BUF,那么当接收到的数据充满RCV_BUF时,就不会返回ACK。客户端的数据也会存储在SND_BUF中,所以它无法继续发送数据。当SND_BUF被应用层占满时,这个连接被阻塞。

由于multipart一般用于传输文件,所以文件大小通常远大于Socket Buffer的容量。因此,为了不阻塞TCP连接,Tomcat会一次性读取整个有效载荷部分,然后将其中的所有部分存储到磁盘(头在内存,内容在磁盘)。

应用层只需要从Tomcat提供的DiskFileItem中读取部分数据,这样看起来RCV_BUF中的数据是可以及时消耗的,虽然是转移到第一层。

从效率上看,转移+存储磁盘的操作肯定比不转移慢很多,但是RCV_BUF可以及时消耗,保证TCP连接不被阻塞。

如果多个请求在HTTP2复用下使用同一个TCP连接,如果RCV_BUF没有被及时消耗,所有的逻辑HTTP连接都会被阻塞。

那为什么其他类型的消息不使用临时存储磁盘呢?

因为消息小,普通的请求消息都不会太大,常见的也就几到几十k,而且对于纯文本消息,阅读操作一定要及时,一次全部读完。与多部分消息不同,它是文本和文件的混合方式,也可能是多文件。

比如服务器收到文件后,需要将文件转移到一些云厂商的对象存储服务中。那么有两种方法来传输文件:

接收到完整文件数据,存储至内存中,然后调用对象存储的SDK用流的方式,一边 read ServletRequest#InputStream,一边 write 到 SDK 的 OutputStream 中

1.虽然及时读取了RCV_BUF,但是占用内存太大,容易爆,非常不合理。2.虽然占用的内存很小(最多只有一个读缓冲区的大小),但是RCV_BUF因为是边读边写,两边都是联网的,所以无法及时消耗。

此外,不仅是Tomcat,Jetty也是这样处理multipart的。虽然其他Web服务器没有看过,但是我想他们都会这样处理。

本文来自吃鸡只用平底锅投稿,不代表舒华文档立场,如若转载,请注明出处:https://www.chinashuhua.cn/24/598396.html

打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
() 0
上一篇 06-19
下一篇 06-19

相关推荐

  • 微信摇一摇传图功能 微信朋友圈上传原图的步骤

    详细解释一下拥有超级震撼大屏效果的微信的首发仪式是如何被撼动的。启动仪式是许多活动的重要组成部分。一些传统的启动仪式可能主要由参与启动仪式的嘉宾来完成,对于现场的普通大众来说没有太大的参与感。但是云交互平台的微信可以通过摇一摇启动仪式完美解决这个问题。微信

    2023-07-24 18:52:01
    846 0
  • 山姆亲友卡要上传照片吗

    1.山姆会员是一个受欢迎的会员超市。只有会员自己可以去购物。一般来说,不能去购物的Sam会员卡分为主卡和副卡。子卡和主卡一样,是Sam子卡,需要注册姓名,上传照片。需要登记姓名和照片吗?山姆会员卡的子卡;可以,但是只有会员卡才能买东西。穿着它到处逛逛也是可以的。可

    2023-07-20 00:26:01
    421 0
  • 135编辑器可上传多大图片详情

    可以用小蚂蚁编辑器,最多可以上传8M高清大图。你好,这个你肯定是要把这图片自己调整下尺寸 或者是把这个图片压缩下,把这个尽量的压缩文件到5M以下的 否则你这个是上传不上去的啦也不能用的,可以使用美图秀秀或者是魔术师这样的软件 来把这个压缩或者是像素调低一点。图片

    2023-07-13 03:23:01
    346 0
  • spring文件上传怎么实现 springmvc文件上传教程

    SpringMVC实现文件上传文件上传知识复习文件上传的必要先决条件导入通过文件上传的jar包以传统方式上传文件跨服务器上传文件可能的问题文件上传知识复习文件上传的必要先决条件1.表单form的enctype值必须为:multipart/form-data(默认值为:application/x-***-form-urlencoded)

    2023-07-10 13:35:01
    1039 0

评论列表

联系我们

在线咨询: QQ交谈

邮件:admin@qq.com

工作时间:周一至周五,9:30-18:30,节假日休息

关注微信