【问题标题】:Spring Boot Multipart File Upload Size InconsistencySpring Boot 多部分文件上传大小不一致
【发布时间】:2019-11-10 01:51:11
【问题描述】:

我有一个端点,可以将图像文件上传到服务器,然后上传到 S3。

我在localhost上运行,MultipartFile字节大小正确,上传成功。

但是,当我将它部署到我的 EC2 实例时,上传的文件大小不正确。

控制器代码

@PostMapping("/{id}/photos")
fun addPhotos(@PathVariable("id") id: Long,
              @RequestParam("file") file: MultipartFile,
              jwt: AuthenticationJsonWebToken) = ApiResponse.success(propertyBLL.addPhotos(id, file, jwt))

PropertyBLL.addPhotos 方法中,打印file.size 会导致大小错误。

实际文件大小为 649305 字节,但是当上传到我的产品服务器时,它读取为 1189763 字节。

  • 我的生产服务器是一个 AWS EC2 实例,位于 Https 后面。
  • Spring 应用程序 yml 文件是相同的。我覆盖的唯一配置是文件最大大小属性。
  • 我正在使用 PostMan 发布请求。我将正文作为表单数据传递,键名为“文件”。
  • 同样,它在本地运行时完美运行。

我进行了另一项测试,将上传的文件写入服务器,以便进行比较。

在十六进制编辑器中上传文件的前 n 个字节:

EFBFBD50 4E470D0A 1A0A0000 000D4948 44520000 03000000 02400802 000000EF BFBDCC96 01000000 0467414D 410000EF BFBDEFBF BD0BEFBF BD610500 00002063 48524D00 007A2600

原始文件的前 n 个字节:

89504E47 0D0A1A0A 0000000D 49484452 00000300 00000240 08020000 00B5CC96 01000000 0467414D 410000B1 8F0BFC61 05000000 20634852 4D00007A 26000080 840000FA 00000080

它们似乎都包含文本“PNG”,并且还具有结尾的 EXtdate:modify/create 标记。

Per Request,addPhoto的核心内容:

val metadata = ObjectMetadata()
metadata.contentLength = file.size
metadata.contentType = "image/png"
LOGGER.info("Uploading image of size {} bytes, name: {}", file.size, file.originalFilename)
val request = PutObjectRequest(awsProperties.cdnS3Bucket, imageName, file.inputStream, metadata)
awsSdk.putObject(request)

这在我在本地运行 Web 服务器时有效。 imageName 只是一个自定义构建的名称。还有其他代码涉及休眠模型,但不相关。

更新

这似乎与 Https/api 代理有关。当我点击 EC2 节点的 http url 时,它工作正常。但是,当我通过代理 EC2 节点的 api 代理 (https://api.thedomain.com) 时,它会失败。我会沿着这条路继续走下去。

【问题讨论】:

  • 您是否对文件进行 gzip 压缩?
  • 你能检查一次this。日志过滤器正在更改请求,从而导致文件大小不同。
  • 能否也提供PropertyBLL.addPhotos()方法?
  • @MaruthiAdithya 添加。我还在测试期间执行了 Files.copy(from, to) 以对二进制文件执行完整性测试。 (结果是关于十六进制转储的 cmets)
  • @KennyCason 看起来多部分文件上传在 AWS API 网关上存在问题。 AWS 开发人员页面中有一个线程完全讨论了您的问题。请参阅 thisthis。发现很多类似问题的链接都集中在将二进制媒体类型更改为使用multipart/form-data

标签: amazon-web-services spring-boot file-upload aws-api-gateway


【解决方案1】:

经过更多调试后,我发现当我直接 POST 到 EC2 实例时,一切都按预期工作。我们的主要和公共 api url 通过 Amazon 的 API Gateway 服务发出代理请求。出于某种原因,该服务将数据转换为 Base64,而不仅仅是传递原始二进制数据。

我找到了更新 API 网关以传递二进制数据的文档:here

我正在使用 multipart/form-data 的 Content-Type 值。不要忘记在启用二进制支持的 API 设置中添加它。

我不必编辑标题选项,另外我使用了默认的“方法请求传递”模板。

最后,不要忘记部署您的 api 更改...

它现在按预期工作。

【讨论】:

    【解决方案2】:

    抱歉,许多 cmets 没有意义。 file.size 将返回上传文件的大小(以字节为单位),而不是请求的大小(是的,由于不同的过滤器可能会通过附加信息和大小增加来增强)。 Spring 不能只是神奇地使 PNG 文件的大小翻倍(在您的情况下,在您发送的任何内容之上添加几乎另外约 600kb 的信息)。虽然我想相信你知道你在做什么并且你给我们的数字确实是正确的,但对我来说,所有的证据都指向人为错误......请双重、三重、四重检查您确实在所有情况下都上传了相同的文件。

    您最初是如何达到 649305 字节的?谁给你这个号码的?是您的代码还是您实际上查看了磁盘上的文件并查看它有多大?在这种情况下,压缩讨论有意义的唯一方法是,在本地运行时,如果 649305 字节是文件的已压缩大小(磁盘上的实际大小为 1189763 字节),并且确实,在部署到 AWS 时压缩未打开某些原因并且您收到完整的未压缩文件(我们甚至不知道您是如何部署它的......它真的和本地一样吗?在这两种情况下您都运行独立的 .jar 吗?您是否将 .war 部署到 AWS也许相反?在这两种情况下,您是否真的在相同的容器和容器版本中运行应用程序,或者您可能在本地运行 Tomcat 并在 AWS 上运行 Jetty?等等等等)。您确定您的 Postman 请求没有搞砸,并且您发送的内容不是意外(或超出您的想象)吗?

    编辑:

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.sandbox</groupId>
        <artifactId>spring-boot-file-upload</artifactId>
        <version>1.0-SNAPSHOT</version>
    
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.1.6.RELEASE</version>
        </parent>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
            </dependency>
        </dependencies>
    
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    
    </project>
    
    package com.sandbox;
    
    import static org.springframework.http.ResponseEntity.ok;
    
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.http.ResponseEntity;
    import org.springframework.web.bind.annotation.PostMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;
    import org.springframework.web.multipart.MultipartFile;
    
    @SpringBootApplication
    public class Application {
    
        public static void main(final String[] arguments) {
            SpringApplication.run(Application.class,
                                  arguments);
        }
    
        @RestController
        class ImageRestController {
    
            @PostMapping(path = "/images")
            ResponseEntity<String> upload(@RequestParam(name = "file") final MultipartFile image) {
                return ok("{\"response\": \"Uploaded image having a size of '" + image.getSize() + "' byte(s).\"}");
            }
        }
    }
    

    这个例子是用 Java 编写的,因为它组合起来更快(环境是一个简单的 Java 环境,部署了独立的 .jar - 没有额外的配置或任何东西,除了服务器端口在 5000 上)。无论哪种方式,您都可以通过向http://test123456.us-east-1.elasticbeanstalk.com/images 发送POST 请求来亲自​​尝试

    这是我的 Postman 请求和使用您提供的图像的响应:

    在我的 AWS EB 实例上似乎一切正常,所有数字都按预期加起来。如果您说您的设置就像听起来一样简单,那么不幸的是,我和您一样感到困惑。我只能假设您到目前为止共享的内容更多(但是,我怀疑这个问题与 Spring Boot 相关......那么它更有可能与您的 AWS 配置/设置有关)。

    【讨论】:

    • > 首先,我不确定 cmets 的哪一部分“没有意义”,我重新阅读了它们,它们对我来说似乎很清楚。 > “file.size 将返回上传文件的大小(以字节为单位),而不是请求的大小”。我知道,这就是我要说的。这正是我如此惊讶的原因。鉴于上传的文件相同,这两种情况下的 file.size 应该相同。我无法弄清楚是什么导致了这种差异。唯一改变的变量是在 ec2 实例上运行本地 vs 的 Web 服务器。春天,或者流动的东西,神奇地翻了一番。因此,我的精神错乱。
    • 我通过直接在磁盘上查看文件大小来获得它们。 > 634K 6 月 20 日 17:05 fayetteville_property.png,当我在 localhost 运行的网络服务器上上传时,file.size(字节)完美匹配。当我使用完全相同的邮递员请求上传到产品实例(运行相同的代码/配置)时,它实际上翻了一番,达到 1.1MB
    • 正在运行的 jar 是一个独立的 jar,它们共享相同的 application.yml,我确实有一个 application-dev.yml,但唯一的区别是数据库配置。我也刚刚在本地使用了相同的 application.yml。它们甚至被打包在 jar 中,而不是外部配置(新项目),所以我知道我没有搞砸环境。
    • 我没有部署战争。这是一个胖罐子。但是,我在 intellij 本地运行,这是另一个区别......让我确认 jar 的打包方式没有问题。
    • 我在本地运行了在 EC2 服务器上运行的同一个 jar。该请求按预期工作并上传到 s3。这是最终上传文件的网址cdn.joinarrived.com/properties/…
    【解决方案3】:

    CloudFormation Templatesn-p 实现Kenny Cason的解决方案:

      MyApi:
        Type: AWS::Serverless::Api
        Properties:
          BinaryMediaTypes:
            - "multipart/form-data"
    

    【讨论】:

      猜你喜欢
      • 2019-07-24
      • 1970-01-01
      • 2014-10-31
      • 2014-05-01
      • 1970-01-01
      • 2022-10-22
      • 1970-01-01
      • 2021-05-29
      • 2019-04-10
      相关资源
      最近更新 更多