【问题标题】:How do i upload/stream large images using Spring 3.2 spring-mvc in a restful way我如何以一种宁静的方式使用 Spring 3.2 spring-mvc 上传/流式传输大图像
【发布时间】:2013-01-14 23:00:37
【问题描述】:

我尝试将大图像上传/流式传输到 REST 控制器,该控制器获取文件并将其存储到数据库中。

@Controller
@RequestMapping("/api/member/picture")
public class MemberPictureResourceController {

  @RequestMapping(value = "", method = RequestMethod.POST)
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void addMemberPictureResource(@RequestBody InputStream image) {
    // Process and Store image in database
  }
}

这是我试图实现的一个无效示例(当然,或者我猜 InputStream 不工作)。我想通过@RequestBody 流式传输/读取图像。

我到处搜索,但找不到一个很好的例子来实现这一点。大多数人似乎只问如何通过表单上传图像,但不使用 REST/RestTemplate 来做到这一点。有没有人可以帮助我解决这个问题?

感谢您对正确方向的任何提示。

亲切的问候, 克里斯

解决方案

在下面,我尝试在 Dirk 和 Gigadot 的输入之后发布对我有用的解决方案。目前我认为这两种解决方案都值得一看。首先,我尝试在 Dirk 的帮助下发布一个工作示例,然后我将尝试在 Gigadot 的帮助下创建一个。我将 Dirks 的答案标记为正确答案,因为我一直在明确询问如何通过 @RequestBody 上传文件。但我也很想测试 Gigadot 的解决方案,因为它可能更容易使用且更常见。

在以下示例中,我将文件存储在 MongoDB GridFS 中。

解决方案 1 - Dirks 推荐后的示例

控制器(注释中带有用于测试的 curl 命令):

/**
*
* @author charms
* curl -v -H "Content-Type:image/jpeg" -X PUT --data-binary @star.jpg http://localhost:8080/api/cardprovider/logo/12345
*/
@Controller
@RequestMapping("/api/cardprovider/logo/{cardprovider_id}")
public class CardproviderLogoResourceController {

  @Resource(name = "cardproviderLogoService")
  private CardproviderLogoService cardproviderLogoService;

  @RequestMapping(value = "", method = RequestMethod.PUT)
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void addCardproviderLogo(@PathVariable("cardprovider_id") String cardprovider_id,
      HttpEntity<byte[]> requestEntity) {
    byte[] payload = requestEntity.getBody();
    InputStream logo = new ByteArrayInputStream(payload);
    HttpHeaders headers = requestEntity.getHeaders();

    BasicDBObject metadata = new BasicDBObject();
    metadata.put("cardproviderId", cardprovider_id);
    metadata.put("contentType", headers.getContentType().toString());
    metadata.put("dirShortcut", "cardproviderLogo");
    metadata.put("filePath", "/resources/images/cardproviders/logos/"); 

    cardproviderLogoService.create1(logo, metadata);
  }
}

服务(未完成但作为测试工作):

@Service
public class CardproviderLogoService {

  @Autowired
  GridFsOperations gridOperation;

  public Boolean create1(InputStream content, BasicDBObject metadata) {
    Boolean save_state = false;
    try {
      gridOperation.store(content, "demo.jpg", metadata);
      save_state = true;
    } catch (Exception ex) {
      Logger.getLogger(CardproviderLogoService.class.getName())
          .log(Level.SEVERE, "Storage of Logo failed!", ex);
    }
    return save_state;    
  }
}

解决方案 2 - Gigadots 推荐后的示例

这在 Spring 手册中有描述: http://static.springsource.org/spring/docs/3.2.1.RELEASE/spring-framework-reference/html/mvc.html#mvc-multipart

这很简单,默认情况下还包含所有信息。我想我至少会为二进制上传选择这个解决方案。

感谢大家发帖和回答。非常感谢。

【问题讨论】:

  • post 方法应该使用多部分形式
  • 感谢 gigadot,但我将使用 resttemplate 将文件解析到控制器,我想将它放在 requestbody 中。我不知道使用多部分是否真的符合休息标准。是吗?
  • 感谢 gigadot。我会研究这个解决方案。你的意思是我应该使用具有流功能的 apache-commons-fileupload 来上传文件而不是 REST?我可以使用 RestTemplate 来解析文件吗?
  • 我已将其发布为答案。我希望它可能会有所帮助。如果没有,您可以在 Google 上搜索“multipart spring mvc”。那里有很多示例,但如果您使用的是新版本的 spring,您可能想尝试最近的示例。

标签: spring rest spring-mvc


【解决方案1】:

如果您想将请求正文作为 InputStream 处理,您可以直接从 Request 中获取。

@Controller
@RequestMapping("/api/member/picture")
public class MemberPictureResourceController {

    @RequestMapping(value = "", method = RequestMethod.POST)
    @ResponseStatus(HttpStatus.NO_CONTENT)
    public void addMemberPictureResource(HttpServletRequest request) {
        try {
            InputStream is = request.getInputStream();
            // Process and Store image in database
        } catch (IOException e) {
            // handle exception
        }
    }
}

【讨论】:

    【解决方案2】:

    当您发送 POST 请求时,您可以使用两种编码类型将表单/参数/文件发送到服务器,即application/x-www-form-urlencodedmultipart/form-data。这里有更多reading

    如果您的 POST 请求中有大量内容,则使用 application/x-www-form-urlencoded 不是一个好主意,因为它通常会使 Web 浏览器崩溃(根据我的经验)。因此, 推荐multipart/form-data

    如果您向调度程序添加多部分解析器,Spring 可以自动处理 multipart/form-data 内容。 Spring 处理此问题的实现是使用 apache-commons-fileupload 完成的,因此您需要将此库添加到您的项目中。

    现在关于如何实际操作的主要答案已在此处发布http://viralpatel.net/blogs/spring-mvc-multiple-file-upload-example/

    我希望这可以帮助您找到解决方案。您可能想了解什么是 REST。听起来你有点困惑。基本上,即使 url 很丑,几乎所有的 http 请求都是 RESTful 的。

    【讨论】:

    • 嗨 gigadot。谢谢你的来信。我已经了解了 REST 的基础知识,并且过去曾使用 Jersey JAX-RS 成功上传了几场数据。我不必使用 multipart/form-data。但我是 REST 和 Spring 以及它背后的概念的新手。此外,我还没有认为 multipart/form-data 也被视为休息,因为它只是另一种媒体类型。从这个角度来看,你是对的。另外,我很感谢您提供有关 application/x-www-form-urlencoded 体验的提示。我肯定会考虑到这一点。
    【解决方案3】:

    看起来好像您使用的是 spring,您可以使用 HttpEntity (http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/http/HttpEntity.html)。

    使用它,你会得到这样的东西(看看“有效负载”的东西):

    @Controller
    public class ImageServerEndpoint extends AbstractEndpoint {
    
    @Autowired private ImageMetadataFactory metaDataFactory;
    @Autowired private FileService fileService;
    
    @RequestMapping(value="/product/{spn}/image", method=RequestMethod.PUT) 
    public ModelAndView handleImageUpload(
            @PathVariable("spn") String spn,
            HttpEntity<byte[]> requestEntity, 
            HttpServletResponse response) throws IOException {
        byte[] payload = requestEntity.getBody();
        HttpHeaders headers = requestEntity.getHeaders();
    
        try {
            ProductImageMetadata metaData = metaDataFactory.newSpnInstance(spn, headers);
            fileService.store(metaData, payload);
            response.setStatus(HttpStatus.NO_CONTENT.value());
            return null;
        } catch (IOException ex) {
            return internalServerError(response);
        } catch (IllegalArgumentException ex) {
            return badRequest(response, "Content-Type missing or unknown.");
        }
    }
    

    我们在这里使用 PUT 是因为它是一个 RESTfull “将图像放入产品”。 'spn' 是产品编号,图像名称由 fileService.store() 创建。当然你也可以发布图片来创建图片资源。

    【讨论】:

    • 嗨,Dirk,感谢您的示例。我已经测试过它并且它正在工作。除了您将文件存储在 gridfs 中的示例之外,我还必须添加以下行以将字节数组转换为输入流: InputStream is = new ByteArrayInputStream(payload);。此外,我仍然需要查看标题以检查 Content-type 是否存在或如何在上传期间提供它。我使用了以下 curl 命令来测试它是否有效: curl -v -X PUT -d "file=@star.jpg" localhost:8080/product/1234/image
    • 嗨,Christopher,我们正在检查 metaDataFactory 服务中的标头(“newSpnInstance()”调用),因此此示例中没有显式处理。如果发现任何错误,则会抛出一个 IllegalArgEx,并由“badRequest()”调用处理(示例中的最后一个返回)。
    • 需要考虑内存中是否需要上传的文件(HttpEntity requestEntity作为参数)。如果不使用 Spring multipart,它将上传的文件存储到一个文件中,您可以在其中使用文件句柄进行控制器工作。
    • @Stackee007 这是关于可能的内存压力的一点,但它超出了 OP 的问题范围。在现实世界的实现中,FileService 可能是另一种宁静的服务,因此人们将通过连接 io 流或类似的东西从该控制器流式传输到接收器。毕竟,这只是一个简化的“操作方法”示例。
    【解决方案4】:

    我最近做了这个,见details here

    在浏览器上,我们可以利用文件API来加载文件,然后使用Base64编码对其内容进行编码,最后在发布之前将编码的内容分配给一个javascript对象。

    在服务器上,我们有一个 Spring 控制器来处理请求。作为将请求正文转换为 java 对象的 json 解组的一部分,图像字节的 base64 编码值将被转换为标准 Java byte[],并作为 LOB 存储在数据库中。

    要检索图像,另一个 Spring Controller 方法可以通过直接流式传输字节来提供图像。

    我在上面链接到的博客文章假设您想将图像用作另一个对象的一部分,但如果您只想直接使用图像,原理应该是相同的。如果有任何需要澄清的地方,请告诉我。

    【讨论】:

    • 嗨,Jay,感谢您发布此示例。你能告诉我为什么图像必须是base64编码的吗?基本上我会假设带有 MessageConverters 的 Spring 应该能够接收图像的二进制数据而不对其进行编码。还是我的假设是错误的?我的客户是一个使用jsp的spring mvc应用程序。我想我必须在 java 中编写相同的 base64 方法。
    • HTTP 是基于文本的协议 (stackoverflow.com/questions/393407/…),因此我们最终必须通过 http 连接发送文本。 Base64 编码只是通过基于文本的协议发送二进制数据的标准方式,这就是图像采用 base64 编码的原因。当对象进入您的控制器方法时,Spring 的消息转换器将接收编码数据并将其解码为服务器端二进制数据(一个字节 [])。您不必在 java 中编写任何 base64 方法。
    • 您好,Jay,感谢您的回答。我现在明白了。感谢您的帮助。
    猜你喜欢
    • 2017-06-24
    • 1970-01-01
    • 2021-11-12
    • 1970-01-01
    • 1970-01-01
    • 2020-10-14
    • 1970-01-01
    • 2019-07-01
    • 1970-01-01
    相关资源
    最近更新 更多