4

I am using Spring mvc 4.3.x, java 8, Tomcat 7

CODE:

@Controller
public class StreamRecordsController {

    @RequestMapping(value = "/streamrecords", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE, 
            produces = "application/octet-stream")
    @ResponseBody
    public ResponseEntity<StreamingResponseBody> export() throws FileNotFoundException {
        File file = new File("C:\\Users\\Ankur\\sample.pdf");
        StreamingResponseBody responseBody = outputStream -> {
            Files.copy(file.toPath(), outputStream);
        };
        return ResponseEntity.ok()
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=generic_file_name.pdf")
                .contentType(MediaType.APPLICATION_OCTET_STREAM)
                .body(responseBody);
    }
}

EXCEPTION:

enter image description here

POSTMAN SNAPSHOT

enter image description here QUESTION:

What am I missing here ?

Ankur Soni
  • 5,725
  • 5
  • 50
  • 81

5 Answers5

1

Seems like you are missing 2 things:

First of all, your code is returning MediaType.APPLICATION_OCTET_STREAM as content type. It would be nice, if you will tell spring that export() method is producing that type. You can do this with produces attribute of @RequestMapping.

Second thing is that you browser is not asking for APPLICATION_OCTET_STREAM - you can see that by Accept header value. APPLICATION_OCTET_STREAM maps to application/octet-stream - your request from browser will need to include it within Accept header value, so Spring will be able to recognize, which method should be called in your controller.

Edit: after you will fix it, take a look on @RestController annotation that can be used instead of @Controller - you will not have to add @ResponseBody annotation, as it will be included by default. Look also to @GetMapping that is an overlay for @RequestMapping annotation for HTTP GET methods.

Pawel P
  • 131
  • 5
1

406 Not Acceptable

The resource identified by the request is only capable of generating response entities which have content characteristics not acceptable according to the accept headers sent in the request.

Try use the Produces annotation in your controller method:

@Produces({MediaType.APPLICATION_JSON})
Adi Ohana
  • 927
  • 2
  • 13
  • 18
1

What is happening to you might be a Spring MVC bug. With browsers & it also varies among browsers - Accept header in request is mandatory with correct values. You might get a different behavior in different browser and your code might work in swagger UI.

As per your screenshot, your Accept header doesn't match with response type , it should be */* as your code is special in the sense that you don't take any input which is a rare scenario in REST world.

So I suggest to add consumes in your mapping like below & see if it works,

@RequestMapping(value = "/streamrecords", method = RequestMethod.GET, consumes = MediaType.ALL_VALUE)

and make sure that Accept header in request carries value - */*.

Sabir Khan
  • 9,826
  • 7
  • 45
  • 98
  • Thank you Sabir, Will update you shortly on this.... Let me check doing above changes. – Ankur Soni May 06 '19 at 14:11
  • Still the output is same, Please find the above updated code. – Ankur Soni May 06 '19 at 14:26
  • What is your Rest client ? Some JavaScript / Angular etc code or anything else ? Try setting `Accept` header value to `*/*` in your client as it has to be set in request header. – Sabir Khan May 07 '19 at 11:10
  • My Rest Client is postman and web browser. If I remember, I did try `*/*`.... things did not work out – Ankur Soni May 07 '19 at 11:18
  • Swagger is firing this - `curl -X GET "http://localhost:7001/streamrecords" -H "accept: */*"` & its working . Does your browser showed Accept header value to be `*/*` after your code changes or value remained same as you shown in initial screenshot ? – Sabir Khan May 07 '19 at 11:21
  • Swagger gives me option to download the file & same is the case in Postman if I set `Accept=*/*` i.e. Http Status is 200 from both clients. If Accept header value remains same even after you explicitly set that header value, you need to post that code & postman screen shots. – Sabir Khan May 07 '19 at 11:47
  • Did you use Spring Boot? If yes then I am not using Spring Boot here. It is pure Spring MVC 4.x – Ankur Soni May 07 '19 at 14:55
  • Yes, I am on Spring Boot version - `1.5.2.RELEASE`. – Sabir Khan May 08 '19 at 06:55
  • Thanks for the reply, can you tell me how can we achieve this without using Spring Boot. – Ankur Soni May 08 '19 at 07:11
  • In that case, it might be the case of missing / incompatible json libraries - that is just the starting point. I suggest to upload your full code somewhere & provide link. – Sabir Khan May 08 '19 at 12:14
1

This error says that client (eg browser) expects some sort of content X (via Accept header eg. application/json) to be sent by server, but server does not provide such content on given endpoint (eg produces only XML).

In your case, you are "accepting" multiple formats,but none of them is application/octet-stream and that is what you have declared that server will return in @RequestMapping(produces = "application/octet-stream")annotation. Include that in your Accept header browser side.

Antoniossss
  • 31,590
  • 6
  • 57
  • 99
1

Finally I was able to sort this out which is now helping me stream huge data from backend to Frontend using Spring 4.3.x as mentioned in my post. Below are the points to guide you to execute the program successfully.

Below procedure is so effective that you can even paginate huge data at the back end (can be hibernate, Mongo-java-driver, cassandra java driver, etc)and keep on streaming the data unless your db operation is complete. In some domains like Manufacturing, Insurance, Logistics etc, you need such utility where end user expects quiet huge data from server in the form of CSV, JSON etc to analyse the raw data.

  1. Add one more annotation @EnableWebMvc above your controller class.

  2. When you add above annotation, the code will break at runtime, you can see in catalina.log, this error : java.lang.NoClassDefFoundError: com/fasterxml/jackson/core/util/DefaultIndenter

  3. To fix this you will need to add below jar dependency in your pom.xml

    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.9.8</version>
    </dependency>
    
  4. Now, add <async-supported>true</async-supported> in web.xml under <servlet> tag as below example,

    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    

Below is the code for both FILE Streaming Download and Data Stream support.

DATA STREAM CODE:

package com.emg.server.controller.rest;

import java.io.IOException;
import java.io.OutputStream;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

@Controller
@EnableWebMvc
public class StreamRecordsController {

    @RequestMapping(value = "/streamrecords")
    @ResponseBody
    public StreamingResponseBody export() {
        return new StreamingResponseBody() {
            @Override
            public void writeTo (OutputStream out) throws IOException {
                for (int i = 0; i < 1000; i++) {
                    out.write((Integer.toString(i) + " - ")
                                        .getBytes());
                    out.flush();
                    try {
                        Thread.sleep(5);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
    }
}

FILE STREAM CODE:

package com.emg.server.controller.rest;

import java.io.File;
import java.nio.file.Files;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

@Controller
@EnableWebMvc
public class StreamRecordsController {

    @RequestMapping(value = "/streamrecords", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
    @ResponseBody
    public ResponseEntity<StreamingResponseBody> export() {
         File file = new File("C:\\Users\\Ankur\\sample.pdf");
            StreamingResponseBody responseBody = outputStream -> {
                Files.copy(file.toPath(), outputStream);
            };
            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=generic_file_name.pdf")
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .body(responseBody);
        }
}

OUTPUT for both the streaming style

enter image description here

NOTE: I execute both programs individually, deployed and tested it separately.

Ankur Soni
  • 5,725
  • 5
  • 50
  • 81