Read HttpServletRequest object from Spring Boot controller method
Asked Answered
B

2

6

Below is my controller method definition

@Autowired
private HttpServletRequest request;

@PostMapping(path = "/abc")
public String createAbc(@RequestBody HttpServletRequest request)
        throws IOException {

    logger.info("Request body: "+request.getInputStream());

    return "abc";

}

All i want to do is print contents to request body. But when i make a POST request i see below error:

Type definition error: [simple type, class javax.servlet.http.HttpServletRequest]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of javax.servlet.http.HttpServletRequest (no Creators, like default construct, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information\n at [Source: (PushbackInputStream); line: 1, column: 2]",

I'm using Spring boot 2.x version.Any idea what's wrong in my code?

Baize answered 12/9, 2019 at 0:56 Comment(1)
You already have autowired your request. Where did you get @RequestBody HttpServletRequest request from ?Erlene
P
18

First, remove the @Autowired field. It's wrong and you're not using it anyway.

Now you have two choices:

  1. Let Spring process the request body for you, by using the @RequestBody annotation:

    @PostMapping(path = "/abc")
    public String createAbc(@RequestBody String requestBody) throws IOException {
        logger.info("Request body: " + requestBody);
        return "abc";
    }
    
  2. Process it yourself, i.e. don't use the @RequestBody annotation:

    @PostMapping(path = "/abc")
    public String createAbc(HttpServletRequest request) throws IOException {
        StringBuilder builder = new StringBuilder();
        try (BufferedReader in = request.getReader()) {
            char[] buf = new char[4096];
            for (int len; (len = in.read(buf)) > 0; )
                builder.append(buf, 0, len);
        }
        String requestBody = builder.toString();
        logger.info("Request body: " + requestBody);
        return "abc";
    }
    

Don't know why you'd use option 2, but you can if you want.

Prisilla answered 12/9, 2019 at 1:34 Comment(7)
remove the @Autowired field. It's wrong - can you please explain why it is wrong ? Seen in #3321174Erlene
@ScaryWombat Well, true, it is wrong for a singleton bean. It is not wrong in that link you provided, since it explicitly says that it is a request-scoped bean. Since this question didn't specify that, the default type of bean is singleton, so it would be wrong. Besides, why create a request-scoped bean, when a singleton bean will suffice? Anyway, it is surely wrong to have HttpServletRequest as both an autowired field and as a parameter.Prisilla
the default type of bean is singleton, so it would be wrong - ThanksErlene
@ScaryWombat Yeah, you have to add another annotation to change the scope, e.g. @Scope(WebApplicationContext.SCOPE_REQUEST). Without a @Scope annotation, the bean is singleton.Prisilla
@Prisilla one of the reasons you'd like to go for option 2 is when you don't receive the post as plain text but something like a gzip, that is sent by the client. You of course would not use the string builder to unzip the data but something like GZIPInputStream and dump it to a normal String, but that's one of the reasons why you would need to have access to HttpServletRequest. So, thanks for the answer mate, very helpful!Tall
@4n0y If the content is gzipped, the request should have a header saying so, either Content-Encoding: gzip or Transfer-Encoding: gzip, and the unzipping should be applied by the framework before the data is received by the handler. With Spring, that does require a Filter, though. As such, you still wouldn't need to use option 2, which is a good thing, because repeating the unzipping and unmarshaling logic in every handler that needs to support gzip'd content would entirely fail the DRY principle.Prisilla
@Prisilla Of course it should, but sometimes it doesn't, depending on where the request comes from and then you may want to handle the request differently. But regardless, the unzipping is not provided by the framework, and as you pointed out, you need to have a filter. Having access to HttpServletRequest in the controller, especially for a 2 additional lines of code to check for header and unzip is much more practical than providing a full-blown filter just for that - that's imho of course :-)Tall
R
1

if you want to take it out easily:

private ObjectMapper objectMapper;

byte[] bytes = request.getInputStream().readAllBytes();

User u = objectMapper.readValue(bytes, User.class);
Risibility answered 23/1, 2022 at 14:52 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.