通过HTTP PUT请求上传文件

5

有没有人知道像Apache Commons FileUpload这样的产品或库,可以处理PUT文件上传?

任何友好的建议或指导都将不胜感激!

完整故事:

我们正在为我们的Java Web应用程序实现文件上传rest(like)服务,但似乎没有任何“简单”的解决方案来处理通过HTTP PUT方法上传的文件。

我们希望找到一个类似于Apache Commons FileUpload项目的库,但不仅仅处理“HTML中基于表单的文件上传”和/或“multipart/form-data”。

我们真的很喜欢FileUpload存储文件的能力,在被要求时移动这些文件,然后在不再使用它们时清理临时文件。我们还喜欢Spring会自动将MultipartFile列表绑定到我们的命令对象,并且当它进入我们的其他基于HTML表单的文件上传控制器时,它就可以使用了。

完整堆栈背景:

  • Spring MVC(3.2.3.RELEASE)
  • Tomcat 7
  • 我们正在尝试遵循分层架构(UI、服务/业务逻辑、持久性)

谢谢您的时间!


以下URL是一个示例,展示了从请求的InputStream上传文件的能力。代码可以完成工作,但它并不完全符合生产质量要求。

https://boplicity.nl/confluence/display/spring/Using+HTTP+PUT+and+Spring+MVC+to+upload+files


我们正在使用以下curl命令来测试我们的Web服务:
curl -v -k -X PUT --data-binary @"c:/java/files/tempfilename.txt" https://localhost:8443/api/file/tempfilename.txt
随后给出了以下精美的Curl示例:
curl -v -X PUT -T "myfile" http://localhost:8080/mytargetfilename

找到了原始代码: https://boplicity.nl/confluence/display/spring/Using+HTTP+PUT+and+Spring+MVC+to+upload+files - hooknc
你不应该在某个地方使用路径变量fileName吗? - xwoker
@xwoker,是的,我们应该在某个地方使用fileName,但这并不是我发帖的重点。我会尝试更新我的问题,并用更好的代码示例来解释我们试图实现的简单性。 - hooknc
2个回答

10

如果要使用HTTP PUT方法进行文件上传请求,只需覆盖自定义的MultipartResolver类中的isMultipart()方法即可让Spring正确响应。

这非常简单且无痛苦。

import org.apache.commons.fileupload.FileUploadBase;
import org.apache.commons.fileupload.servlet.ServletRequestContext;

import javax.servlet.http.HttpServletRequest;

public class PostAndPutCommonsMultipartResolver extends CommonsMultipartResolver {

    private static final String POST_METHOD = "POST";
    private static final String PUT_METHOD = "PUT";

    @Override
    public boolean isMultipart(HttpServletRequest request) {

        boolean isMultipartRequest = false;

        if (request != null) {

            if (POST_METHOD.equalsIgnoreCase(request.getMethod()) || PUT_METHOD.equalsIgnoreCase(request.getMethod())) {

                isMultipartRequest = FileUploadBase.isMultipartContent(new ServletRequestContext(request));
            }
        }

        return isMultipartRequest;
    }
}

重要的是,默认的 MultipartResolver 被扩展,以便 isMultipart() 方法将对 POST 或 PUT 请求返回 true。
通常有两个默认的 MultipartResolver 实现: CommonsMultipartResolver (与 Apache Commons FileUpload 一起使用) 和 StandardServletMultipartResolver (与 Servlet 3.0+ Part API 一起使用)。
由于我们使用了 Apache Commons FileUpload,所以我们扩展了 CommonsMultipartResolver 类。

MultipartResolver的Javadoc页面上有文档说明如何为您的应用程序正确定义自定义MultipartResolver(已强调):

Spring DispatcherServlets没有默认的解析器实现,因为应用程序可能选择自己解析其多部分请求。要定义一个实现,请在DispatcherServlet的应用程序上下文中创建一个ID为“multipartResolver”的bean。这样的解析器将应用于由该DispatcherServlet处理的所有请求。

对于使用xml配置的应用程序,它看起来接近以下内容:

<bean id="multipartResolver" class="<package>.<name>.PostAndPutCommonsMultipartResolver"/>

对于一个配置了注解的应用程序,它看起来会接近以下内容:
@Bean(name = "multipartResolver")
public CommonsMultipartResolver createMultipartResolver() {
    return new PostAndPutCommonsMultipartResolver();
}

有关MultipartResolver注释配置的更多信息


我遇到了同样的问题,我尝试了你的代码,但是我得到了“Bad Request”的错误。我可以看到当我使用Postman进行PUT操作时,isMultipart方法被执行并返回true。但是无论如何,请求从未到达控制器,返回http 400。有什么想法吗? - Keetah
很抱歉您在解决方案上遇到了困难。我建议您创建一个新的Stack Overflow问题,并在新评论中链接它。确保包含所有相关的代码和堆栈跟踪信息。 - hooknc

1
我不知道有任何满足您要求的库。但是,如果您不介意编写一些代码,我认为创建类似的东西的好方法是编写自己的代码。
public class FileMessageConverter extends AbstractHttpMessageConverter<File> 

将请求主体转换为tmp目录中的文件:
@Override
protected File readInternal(Class<? extends File> clazz, HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException {

    InputStream inputStream = inputMessage.getBody();
    File tmpFile = File.createTempFile("upload","tmp");
    if (inputStream != null) {
        FileOutputStream outputStream = new FileOutputStream(tmpFile);

        byte[] buffer = new byte[1024];
        int bytesRead;

        while ((bytesRead = inputStream.read(buffer)) > 0) {
            outputStream.write(buffer, 0, bytesRead);
        }

        outputStream.flush();

        outputStream.close();
    }
    return tmpFile;
}

在控制器中,您需要定义方法如下:
@RequestMapping(value="/{fileName}", method = RequestMethod.PUT)
public ResponseEntity uploadFile(@PathVariable(value="fileName") String fileName, @RequestBody File tmpFile) throws IOException {

    // .. process tmpFile, e.g. tmpFile.renameTo(new File(fileName);
    return new ResponseEntity<String>(HttpStatus.CREATED);
}

不要忘记注册您的FileMessageConverter,例如:
@Configuration
@EnableWebMvc
@ComponentScan(basePackages =  {"my.controller.package"})
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new FileMessageConverter());
    }
}

用于调用上传的curl命令:

curl -v -X PUT -T "myfile" http://localhost:8080/mytargetfilename

不错。我还没有想到使用MessageConverter,这是个好主意!我知道实际上在野外拥有一个库来完成这项工作的可能性非常小,我们已经决定要编写一些代码。但问题是,在这种情况下,你如何清理临时文件呢? - hooknc
我认为你无法保证早期清理。但是你可能想看看Apache Axis如何实现他们的TempFileManager。或者甚至使用它来代替File.createTempFile https://svn.apache.org/repos/asf/axis/axis2/java/core/trunk/modules/kernel/src/org/apache/axis2/deployment/util/TempFileManager.java - xwoker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接