24

I try to upload/stream a large image to a REST controller that takes the file and stores it in to a database.

@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
  }
}

This is a non-working example of what I'm trying to achieve (of course, or I guess so InputStream is not working). I want to stream/read the image over the @RequestBody.

I have searched everywhere but can't find a good example how to achieve this. Most people seem to ask only how to upload images over forms but don't use REST/RestTemplate to do it. Is there anyone that can help me with this?

I'm thankful for any hint in to the right direction.

Kind regards, Chris

Solutions

Below here I try to post the solutions that worked for me after the Input from Dirk and Gigadot. At the moment I think both solutions are worth while looking at. At first I try to post a working example with the help I got from Dirk and then I'll try to create one with the help from Gigadot. I will mark Dirks answer as the correct one as I have been asking explicitly how to upload the file over the @RequestBody. But I'm also curious to test the solution from Gigadot as it is maybe easier and more common to use.

In the below examples I store the files in MongoDB GridFS.

Solution 1 - Example after Dirks recommendation

Controller (with curl command for testing in the comment):

/**
*
* @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 (unfinished but working as a test):

@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;    
  }
}

Solution 2 - Example after Gigadots recommendation

This is described in the Spring manual: http://static.springsource.org/spring/docs/3.2.1.RELEASE/spring-framework-reference/html/mvc.html#mvc-multipart

This is quite easy and also contains all information by default. I think I'll go for this solution at least for the binary uploads.

Thanks everyone for posting and for your answers. It's much appreciated.

Christopher Armstrong
  • 3,477
  • 6
  • 34
  • 40
  • 2
    you should use multipart form for the post method – gigadot Jan 30 '13 at 23:28
  • 1
    thanks gigadot but i will use resttemplate to parse the file to the controller and I would like to have it in the requestbody. i don't know if using multipart is really rest conform. is it? – Christopher Armstrong Feb 01 '13 at 23:47
  • Thanks gigadot. I will look in to this solution. Do you mean that I should use the apache-commons-fileupload with streaming capability to upload files instead of REST? Can I use RestTemplate to parse the file? – Christopher Armstrong Feb 02 '13 at 01:10
  • i have posted it as an answer. i hope it might help. if not you may google for "multipart spring mvc". there are quite a lot of examples out there but you may want to try the recent examples if you are using a new version of spring. – gigadot Feb 02 '13 at 02:57

4 Answers4

13

As it looks as if you are using spring you could use HttpEntity ( http://static.springsource.org/spring/docs/3.1.x/javadoc-api/org/springframework/http/HttpEntity.html ).

Using it, you get something like this (look at the 'payload' thing):

@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.");
    }
}

We're using PUT here because it's a RESTfull "put an image to a product". 'spn' is the products number, the imagename is created by fileService.store(). Of course you could also POST the image to create the image resource.

Dirk Lachowski
  • 3,121
  • 4
  • 40
  • 66
  • 1
    Hi Dirk, thanks for your example. I have tested it and it is working. In addition to your example as I'm storing the file in gridfs i had to add following line to convert from a byte array to an input stream: InputStream is = new ByteArrayInputStream(payload);. Also I still have to look at the headers to check if the Content-type is present or how to provide it during upload. I have used following curl command to test if it works: curl -v -X PUT -d "file=@star.jpg" http://localhost:8080/product/1234/image – Christopher Armstrong Feb 02 '13 at 01:31
  • Hi Christopher, we are checking the headers in the metaDataFactory service (the 'newSpnInstance()' call) so there's no explicit handling in this sample. If any errors are found, an IllegalArgEx is thrown and handled by the 'badRequest()' call (last return in sample). – Dirk Lachowski Feb 02 '13 at 09:37
  • You need to consider if you need the uploaded file in memory ( HttpEntity requestEntity as parameter). If not use use Spring multipart which stores the uploaded file to a file where you could work using File handle to do controller work. – Stackee007 Nov 15 '17 at 14:55
  • @Stackee007 That's a point regarding possible memory pressure but it's out of scope of OP's question. In a real world implementation the `FileService` will probably be another restful service so one will stream from this controller to the receiver by connecting io streams or something like that. After all, it's only a simplified 'how to' sample. – Dirk Lachowski Nov 15 '17 at 17:40
6

When you send a POST request, there are two type of encoding you can use to send the form/parameters/files to the server, i.e. application/x-www-form-urlencoded and multipart/form-data. Here is for more reading.

Using application/x-www-form-urlencoded is not a good idea if you have a large content in your POST request because it usually crashes the web browser (from my experience). Thus, multipart/form-data is recommended.

Spring can handle multipart/form-data content automatically if you add a multipart resolver to your dispatcher. Spring's implementation for handling this is done using apache-commons-fileupload so you will need to add this library to your project.

Now for the main answer of how to actually do it is already been blogged here http://viralpatel.net/blogs/spring-mvc-multiple-file-upload-example/

I hope that might help you to find a solution. You may want to read up about what REST is. It sounds like you are a bit confused. Basically, almost all http requests are RESTful even if the urls are ugly.

gigadot
  • 8,879
  • 7
  • 35
  • 51
  • Hi gigadot. Thank you for your write up. I have learnt the basics of REST and have used Jersey JAX-RS in the past successfully uploading several gigs of data. I didn't have to use multipart/form-data. But I'm a newbie with REST and Spring and the conepts behind it. Also I have not considered that multipart/form-data is treated as rest too as it is just another media type. You are right from that point of view. Also I'm thankful for your tip regarding your experience with application/x-www-form-urlencoded. I'll defenatly take that in to consideration. – Christopher Armstrong Feb 02 '13 at 13:23
4

I have done this recently, see the details here.

On the browser we can make use of the file API to load a file, then encode its contents using Base64 encoding, and finally assign the encoded contents to a javascript object before posting it.

On the server, we have a Spring Controller which will handle the request. As part of the json unmarshalling that converts the the request body to a java object, the base64-encoded value for the image bytes will be converted to a standard Java byte[], and stored as a LOB in the database.

To retrieve the image, another Spring Controller method can provide the image by streaming the bytes directly.

The blog post I linked to above presumes you want to use the image as part of another object, but the principle should be the same if you want to only work with the image directly. Let me know if anything needs clarification.

Jason
  • 7,356
  • 4
  • 41
  • 48
  • Hi Jay, thanks for posting this example. Can you tell me why the image has to be base64 encoded? Basically I would assume that Spring with it's MessageConverters should be able to receive the binary data of the image without encoding it. Or is my assumption wrong? My client is a spring mvc application using jsp. I guess I would have to write the same base64 method in java then. – Christopher Armstrong Feb 02 '13 at 00:20
  • HTTP is a text based protocol (http://stackoverflow.com/questions/393407/why-http-protocol-is-designed-in-plain-text-way) so we ultimately have to send text over an http connection. Base64 encoding is just a standard way of sending binary data over a text based protocol, thats why the image was base64 encoded. Spring's message converters will receive the encoded data and decode it to server side binary data (a byte[]) when the object comes in to your controller method. You should not have to write any base64 method in java. – Jason Feb 03 '13 at 21:07
  • Hi Jay, thanks for your answer. I understand it now. Thanks for your help. – Christopher Armstrong Feb 04 '13 at 19:38
0

If you want do deal with the request body as an InputStream you can get it directly from the 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
        }
    }
}
Marcos Pirmez
  • 256
  • 4
  • 5