简介
本周,我的团队和我遇到了一个问题,这是我在大学时第一次遇到的。自那以后,我完全忘记了它,直到今年十月的星期三:如何通过HTTP传输一个非常大的文件。
需求和设计
我们的客户用云端的CRM替换了原来的CRM,并要求我们将其与整个软件映射集成。
其中一个集成流程是从本地存储库中发布文档到CRM,并将其与存储的客户帐户相关联;文件大小没有上限,我们假定1 GB是中等值。
所有CRM集成都是基于REST的,没有共享文件夹,没有分段数据库,只允许使用REST API OAUTH1进行安全保护。
下面是我为你勾画的简化的架构模型图。
图1 — 解决方案架构
我们的应用程序与应用程序映射的其他部分一样,运行在本地环境中,而CRM则托管在云租户上。
暴露的API接受一个_Multipart_主体,其中包含两个部分:一个包含元数据的JSON文档,例如文件名、客户帐户ID等,另一个是文件的二进制内容。
标准解决方案
该应用程序由两部分组成:第一部分是文件轮询器,每当在分段文件夹中看到新文件时,就创建一个线程,将其与客户端帐户相关联并发送到CRM。
如果你有兴趣创建一个文件轮询器,我向你提供一个链接:Apache Camel轮询消费者,它是一个很好的解决方案,可以轻松地实现。
在这里,我想谈谈我们如何将文件发送到CRM。
让我们开始编码;这里是我们_pom.xml_的一个摘录:
<dependency>
<groupId>org.apache.httpcomponents</groupId> <artifactId>httpclient</artifactId>
</dependency><dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency><dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
</dependency>
这里是我们标准解决方案的代码
RestTemplate remoteService = new RestTemplate();//HTTP has two parts: Header and Body//Here is the header:HttpHeader header = new HttpHeader();//Here is the bodyMultiValueMap<String,Object> bodyMap = new MultiValueMap<String,Object>();
bodyMap.add(“customer_file”,new FileSystemResource(fileName));
bodyMap.add(“customer_name”, customerJSON);HttpEntity<MultiValueMap> request = new HttpEntity(bodyMap, header);
ResponseEntity<String> restResponse = remoteService .exchange(remoteServiceURL, HttpMethod.POST, request, String);
customerJson_变量是一个_javax.json.JsonObject;以这种方式,多部分请求会自动选择正确的内容类型,而使用_org.springframework.core.io.FileSystemResource_实例时也期望出现相同的行为。
我们进行了以下测试:
- 发送一个小文件,以查找一些格式不正确的请求
- 发送一个巨大的文件,以证明我们的应用程序的稳健性
对于测试1,我们没有遇到任何重要问题,只是一些缺少标题值、格式不正确的URL输入等等。对于测试2,我们等了几分钟,然后出现了所有Java开发人员的噩梦。
java.lang.OutOfMemoryError: Java heap space
这个问题不仅仅是因为我们在开发环境中运行了代码,而且因为应用程序试图将整个文件内容加载到RAM中,使其比J. Wellington Wimpy还要耗费更多的内存。
从简单的应用程序内存占用来看,这是显而易见的。
总之,从架构的角度来看,这不是一个好的解决方案,因为:
- 我们无法假设传入文件的最大大小
- 我们无法按顺序处理文件
我们需要改进它。
分块解决方案
我们需要训练我们的代码,不要将整个文件内容加载到内存中,而是使用HTTP1.1支持的功能,即直到我在大学里的那个年代才出现的分块传输编码。
该功能告诉服务器,传入请求由多个HTTP消息组成,并且需要接收所有消息才能开始处理。
从客户端的角度来看,优点在于你只在你正在传输的那一部分中加载内存。
如果你想了解更多关于HTTP如何实现分块传输编码的内容,请参考这些WIKI、W3C。
我们通过适当配置RestTemplate来改进我们的类:
RestTemplate remoteService = new RestTemplate();SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();requestFactory.setBufferRequestBody(false); remoteService.setRequestFactory(requestFactory);
我们重复了测试2,这次一切都很顺利。
我们观察到完整传输1.5 GB文件时的内存占用少于300 MB!成功了!
结论和问候
在本文中,我描述了我们发现的传输大型文件的解决方案;你可以使用不同的库找到多个解决方案。
我想补充一点,这个功能只有在HTTP1.1中才有,而HTTP 2不再支持分块传输编码;我认为你需要寻找某种流式API。
在这里,我们选择使用众所周知的RestTemplate类,而不是较新的WebClient:我无法告诉你是否可以适应它。
译自:https://medium.com/swlh/transfer-large-files-using-a-rest-api-a0aa96983ebb
评论(0)