【Spring Cloud总结】27.通Zuul上传文件的机制探讨

接上篇《26.Zuul的各种姿势》  Spring Cloud版本为Finchley.SR2版

上一篇我们简单介绍Zuul的一些其它特性,如Head的过滤、有关路由的一些管理服务(Routes、Filters),以及本地转发。本篇我们来探讨一下使用Zuul上传文件,以及有关Zuul的Filter过滤器禁用的相关知识。
本部分官方文档:https://cloud.spring.io/spring-cloud-static/Finchley.SR4/single/spring-cloud.html#_zuul_http_client
注:好像Finchley.SR2的文档已经挂了,最新的是Finchley.SR4的文档。

目录

一、Zuul上传文件的端倪

二、试一试就知道

三、Zuul的大文件上传

四、总结


一、Zuul上传文件的端倪

正常情况下,我们使用普通的响应层(如SpringMVC)框架,是可以上传任意大小的文件的。一般我们对文件大小的控制,例如SpringMVC,是通过其“multipartResolver”这个Bean的设置,指定其“MaxUploadSize”参数的具体数字,来控制上传文件的具体大小的。
我们使用Zuul作为服务网关的情况下,也是可以上传文件的。但是使用Zuul上传文件的时候,根据Spring Cloud官方文档的介绍,Zuul在做文件上传的时候,只支持小文件的上传(1M以内),大文件(10M以上)上传会则报错,这是Zuul对文件上传的默认的设置。
我们下面来编写一个上传文件的实例,来测试一下Zuul对文件上传有何种限制,是不是像上面说的那样,然后对Zuul为什么会有这种限制,其原理是什么进行一个深入探讨。

二、试一试就知道

首先我们创建一个Maven工程,名为“microserver-file-upload”:


然后在POM文件中引入Spring Cloud的父工程和“eureka”的依赖,因为需要Web页面来上传文件,所以也引入了Web的Strater依赖:

<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>
  <artifactId>microserver-file-upload</artifactId>
  
  <parent>
    <groupId>com.microserver.cloud</groupId>
    <artifactId>microserver-spring-cloud</artifactId>
    <version>0.0.1-SNAPSHOT</version>
  </parent>
 
  <dependencies>
        <dependency>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
        </dependency>
   </dependencies>
  
</project>

注意,这里为了便于版本统一管理,该工程的parent父工程和我们User、Movie工程一样,均依赖于microserver-spring-cloud工程(此父工程统一引入了spring-cloud-dependencies的Finchley.SR2版,以及spring-boot-starter-parent-2.0.6.RELEASE的parent,这个在前面的章节已经讲过)。

然后编写启动类MicroserverFileUploadApplication.java:

package com.microserver.cloud;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;

@SpringBootApplication
public class MicroserverFileUploadApplication {
  public static void main(String[] args) {
    SpringApplication.run(MicroserverFileUploadApplication.class, args);
  }
}

这里注意:Finchley.RELEASE版本的SpringCloud不需要@EnableEurekaClient注解就可以启用注册功能。在SpringBoot的自动配置类里启用了注册功能,只要引了eureka-client的依赖就会进行注册。然后咱们编写一个简单的上传文件的服务(代码参考自教学视频):

package com.microserver.cloud.controller;

import java.io.File;
import java.io.IOException;

import org.springframework.stereotype.Controller;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

@Controller
public class FileUploadController {
  /**
   * 上传文件
   * ps.该示例比较简单,没有做IO异常、文件大小、文件非空等处理
   * @param file 待上传的文件
   * @return 文件在服务器上的绝对路径
   * @throws IOException IO异常
   */
  @RequestMapping(value = "/upload", method = RequestMethod.POST)
  public @ResponseBody String handleFileUpload(@RequestParam(value = "file", required = true) MultipartFile file) throws IOException {
    byte[] bytes = file.getBytes();
    File fileToSave = new File(file.getOriginalFilename());
    FileCopyUtils.copy(bytes, fileToSave);
    return fileToSave.getAbsolutePath();
  }
}

注:上面是一段已经被各大博客参考烂了的上传代码,这里再厚脸皮的用一用o(╯□╰)o。
这里仅仅从POST请求体中获取Multi类型的多媒体流(这里即文件流),然后获取其比特流,然后将数据流通过Fiile工具类,默认写入当前工程的根目录下(文件名同上传文件)。

然后我们在src/main/resource下创建一个静态页面index.html:

该文件是上传文件的前置页面:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form method="POST" enctype="multipart/form-data" action="/upload">
        File to upload: 
        <input type="file" name="file">
        <input type="submit" value="Upload">
    </form>
</body>
</html>

下面我们src/main/resource下新建application.yml主配置文件,添加以下配置:
 

server:
  port: 8050
eureka:
  client:
    serviceUrl:
      defaultZone: http://user:password123@eureka1:8761/eureka
  instance:
    prefer-ip-address: true
spring:
  application:
    name: microservice-file-upload
  servlet:
    multipart:
      max-file-size: 500Mb      # Max file size,默认1M
      max-request-size: 1000Mb   # Max request size,默认10M

这里我们首先配置了该应用的端口为8050,然后将该应用注册到了erueka,添加了eureka的serviceUrl;最后指定了该应用的服务名,以及上传文件的最大和最小限制(Spring Boot2.0以上的配置方式)。这里上传文件的大小是以Servlet的multipart设置为主,我们在pom文件上使用Ctrl+鼠标左键,可以进入源码:


这里我们可以看到max-file-size的默认值为1M,而max-request-size的默认值为10M。那么我们在配置文件中设置了最大文件请求大小为500Mb,在使用Zuul的情况下,能上传成功吗?我们接下来试试。

这里我们别忘记在Zuul中为microservice-file-upload工程设置网关的路由(因为我们原来配置了ignored-services: '*',会忽略file服务的网关):

zuul:
  ignored-services: '*'
  routes:
    microserver-provider-user: /user/**
    microservice-file-upload: /file-upload/**

这里我们设置microservice-file-upload服务的网关路由为“/file-upload/**”。

我们分别启动microservice-file-upload工程、之前写好的Zuul服务microserver-getaway-zuul和microserver-discovery-eureka注册中心:

然后在eureka Server主页上看到我们的文件上传服务、Zuul服务已经启动起来:

我们访问“http://localhost:8050/index.html”路径,可以进行文件上传的测试:

首先我们选择一个文件大小有248KB左右的文件,发现可以上传成功,并返回了上传成功的路径:


然后我们换一个252MB大小的文件,发现上传也没有问题:


到工程的根目录下查看,文件也成功上传了:

但是当我们通过zuul的网关去上传文件,路径应为“http://localhost:8040/file-upload/upload”,我们这里把原来html中的服务名更改为这个路径:

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
    <form method="POST" enctype="multipart/form-data" action="http://localhost:8040/file-upload/upload">
        File to upload: 
        <input type="file" name="file">
        <input type="submit" value="Upload">
    </form>
</body>
</html>

然后重新访问上传文件的路径,删除之前上传到工程根目录下的两个文件,重新上传。这时我们会发现上传248KB左右的文件依然成功,但是上传252MB大小的文件报错了:

这里报的什么错呢?很简单,“the request was rejected”就是请求被拒绝了,Zuul也给出了原因,就是“because its size (264656928) exceeds the configured maximum (10485760)”,即请求上传内容的大小(252MB)超出了最大上传大小(10M)。对于我们上传文件的微服务,我们尚且为Servlet设置了max-file-size以及max-request-size,而在我们使用的Zuul上,人家也是需要设置类似max-file-size以及max-request-size的,所以这里我们给Zuul也设置一下max-file-size以及max-request-size(Zuul工程的application.yml):

spring:
  application:
    name: microserver-getaway-zuul
  servlet:
    multipart:
      max-file-size: 500Mb      # Max file size,默认1M
      max-request-size: 1000Mb   # Max request size,默认10M

然后重启Zuul,重新做刚才的操作,虽然文件上传成功了,但是发现没有返回路径,并且控制台还报错了:

我们都已经设置了max-file-size以及max-request-size,为什么还会报错?这是因为Zuul的上传机制问题,我们下面就来仔细探讨一下为什么会报这个错,以及怎么给Zuul进行设置,才能使我们能正常的上传大文件。

三、Zuul的大文件上传

通过上面的报错“microservice-file-upload timed-out and no fallback available.”我们可以了解到,主要原因还是因为请求处理超时,并且没有响应。那么为什么为超时和没有响应呢?
而该超时的异常为com.netflix.hystrix.exception.HystrixRuntimeException,可以注意到异常是hystrix抛出的,说明hystrix对于情求的反馈时间是有要求的,而我们在上传大文件时,会因为上传时间过长,而导致hystrix认为该请求无响应,最终给客户端反馈了超时并没有响应的异常,我们只需在Zuul的配置文件中,提升hystrix的超时时间即可:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

我们重启Zuul,删除上传好的文件,再试一遍,发现不再报错:

有同学问了,我在Zuul上还需要再配置max-file-size以及max-request-size,岂不是很麻烦,为什么不能直接使用上传服务的设置呢?这里我们要认真探讨一下:
对于Zuul而言,其实它的本质就是一个Servlet,默认集成了SpringMVC,当我们使用Zuul上传文件时,实际上的上传还是经过了SpringMVC的DispatcherServlet,而使用SpringMVC的DispatcherServlet上传文件,会默认进行“multipart processing”,该处理是用来上传时检查文件大小之类的,我们即使在真正的上传微服务中配置了大小限制,也没有用,因为过不了这第一关,所以这就是为什么我们在Zuul上又设置了max-file-size以及max-request-size才成功的
如果你不想将上传交给SpringMVC处理,需要真实的调用自己的上传服务处理,此时你就需要使用Zuul提供Servlet路径绕过SpringMVC,这个Servlet的默认路径为:/zuul/*。当你提供的zuul.routes.customers=/customers/**,那么你访问"/zuul/customers/*"会直接访问对应微服务的接口。
这样就不需要在Zuul客户端再设置一次max-file-size以及max-request-size了。(经过测试确实是这样,大家将Html中action改为http://localhost:8040/zuul/file-upload/upload,然后把max-file-size以及max-request-size去掉试一下就知道了)

四、总结

对于小文件(1M以内上传),无须任何处理,即可正常上传。
对于大文件(10M以上)上传,需要为上传路径添加/zuul前缀。也可使用zuul.servlet-path自定义前缀。
假设zuul.routes.microservice-file-upload=/microservice-file-upload/**
如果http://{HOST}:{PORT}/upload是微服务microservice-file-upload的上传路径,则可使用Zuul的/zuul/microservice-file-upload/upload路径上传大文件。
如果Zuul使用了Ribbon做负载均衡,那么对于超大的文件(例如500M),需要提高超时设置,例如:

hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 60000
ribbon:
  ConnectTimeout: 3000
  ReadTimeout: 60000

参考:《51CTO学院Spring Cloud高级视频》
https://blog.csdn.net/chengqiuming/article/details/80805667
https://blog.csdn.net/qq_26440803/article/details/82917766

转载请注明出处:https://blog.csdn.net/acmman/article/details/103759368

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

光仔December

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值