5

I am trying to process a POST request with body of plain text (utf-8) but it seems that spring does not like the plain text nature of the call. Could it be that it is not supported - or otherwise, am I coding it wrong?

@RestController
@RequestMapping(path = "/abc", method = RequestMethod.POST)
public class NlpController {
    @PostMapping(path= "/def", consumes = "text/plain; charset: utf-8", produces = MediaType.APPLICATION_JSON_VALUE)
    public ResponseEntity<Object> doSomething(@RequestBody String bodyText)
    {
        ...
        return ResponseEntity.ok().body(responseObject);
    }
}

Respond is:

Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded' not supported]

I tested with curl command:

curl -s -X POST -H 'Content-Type: text/plain; charset: utf-8' --data-binary @text.txt localhost:8080/abc/def

The text.txt contains plain text (UTF-8 in Hebrew).

rubmz
  • 1,947
  • 5
  • 27
  • 49
  • 1
    Please add the error you get. For starters you could remove the `consumes` part. – M. Deinum Sep 17 '19 at 09:13
  • Added it to the question. – rubmz Sep 17 '19 at 09:14
  • 3
    Well you aren't sending plain text, but a form. That is what the exception is telling you. – M. Deinum Sep 17 '19 at 09:14
  • Well, I am using curl and I am quite sure that the incoming request is not a form. It is a plain text - in Hebrew. This should be okay with the UTF-8 charset... But could it be that the REST framework does not ACCEPT anything else but form queries (JSON/XML)? – rubmz Sep 17 '19 at 09:17
  • Please include the `curl` command you use (and triggers the exception) – Tasos P. Sep 17 '19 at 09:18
  • 2
    The error clearly states you are sending a form. So the request you send has the wrong content type. – M. Deinum Sep 17 '19 at 09:19
  • `@PostMapping(path= "/def", consumes = MediaType.TEXT_PLAIN, produces = MediaType.APPLICATION_JSON_VALUE)` – Jude Niroshan Sep 17 '19 at 09:20
  • with "consumes = MediaType.TEXT_PLAIN_VALUE" I get: Resolved [org.springframework.web.HttpMediaTypeNotSupportedException: Content type 'application/x-www-form-urlencoded' not supported] – rubmz Sep 17 '19 at 09:23
  • 1
    You can verify with `curl -v ...` that `curl -d` will "...will cause curl to pass the data to the server using the content-type application/x-www-form-urlencoded" as explained [here](https://linux.die.net/man/1/curl) – Tasos P. Sep 17 '19 at 09:26
  • I am NOT trying to set the content type to aplication/x-www-form-urlencoded. I want the server to receive PLAIN TEXT. If it is impossible, that would be an answer, and I will use JSON, although it is less preferable by me, as it requires extra characters. – rubmz Sep 17 '19 at 09:31
  • 3
    CURL is sending the wrong content type and hence it won't be parsed on the server. I thas nothing to do with spring not supporting it is is in HOW you are (knowingly or not) are sending the request. CUrl will always use `application/x-www-form-urlencoded` when you use `--data-binary` according to the curl documentation. – M. Deinum Sep 17 '19 at 09:32
  • You are right .... You deserve Pizza!!!!!! And beer :) – rubmz Sep 17 '19 at 09:38

3 Answers3

2

I would like to throw some light on the other part of the question of whether spring supports text/plain?

According to spring docs: what is "consumes" or Consumable Media Types in the @RequestMapping annotation

Definition :

Consumable Media Types

"You can narrow the primary mapping by specifying a list of consumable media types. The request will be matched only if the Content-Type request header matches the specified media type. For example:"

@Controller
@RequestMapping(value = "/pets", method = RequestMethod.POST, consumes="application/json")
public void addPet(@RequestBody Pet pet, Model model) {
    // implementation omitted
}

Consumable media type expressions can also be negated as in !text/plain to match to all requests other than those with Content-Type of text/plain.

How spring does the media type matching internally?

Spring Request Driven Design is centred around a servlet called the dispatcher Servlet which uses special beans to process requests one of the bean is RequestMappingHandlerMapping which checks for the media type using getMatchingCondition method of ConsumesRequestCondition class as shown below.

    @Override
    public ConsumesRequestCondition getMatchingCondition(ServerWebExchange exchange) {
        if (CorsUtils.isPreFlightRequest(exchange.getRequest())) {
            return PRE_FLIGHT_MATCH;
        }
        if (isEmpty()) {
            return this;
        }
        Set<ConsumeMediaTypeExpression> result = new LinkedHashSet<>(expressions);
        result.removeIf(expression -> !expression.match(exchange));
        return (!result.isEmpty() ? new ConsumesRequestCondition(result) : null);
    }

the get matching condition class uses static inner class ConsumesMediaType Expression which actually makes the check

     @Override
     protected boolean matchMediaType(ServerWebExchange exchange) throws UnsupportedMediaTypeStatusException {
    try {
        MediaType contentType = exchange.getRequest().getHeaders().getContentType();
        contentType = (contentType != null ? contentType : MediaType.APPLICATION_OCTET_STREAM);
        return getMediaType().includes(contentType);
    }
    catch (InvalidMediaTypeException ex) {
        throw new UnsupportedMediaTypeStatusException("Can't parse Content-Type [" +
                exchange.getRequest().getHeaders().getFirst("Content-Type") +
                "]: " + ex.getMessage());
    }}

This method returns false once the media type does not match and getMatchingCondition returns null which results in handleNoMatch method of RequestMappingInfoHandlerMapping being called and using PartialMatchHelper class we check for the what type of mismatch it has as shown below and spring throws HttpMediaTypeNotSupportedException error once its see consumes mismatch

if (helper.hasConsumesMismatch()) {
        Set<MediaType> mediaTypes = helper.getConsumableMediaTypes();
        MediaType contentType = null;
        if (StringUtils.hasLength(request.getContentType())) {
            try {
                contentType = MediaType.parseMediaType(request.getContentType());
            }
            catch (InvalidMediaTypeException ex) {
                throw new HttpMediaTypeNotSupportedException(ex.getMessage());
            }
        }
        throw new HttpMediaTypeNotSupportedException(contentType, new ArrayList<>(mediaTypes));
    }

Spring supports all media types as per the IANA https://www.iana.org/assignments/media-types/media-types.xhtml the problem lies only with the curl command as quoted by others.

Richard Elite
  • 720
  • 5
  • 15
  • cool answer! thanks. I think by now I understand that spring was not the one to blame here... – rubmz Sep 17 '19 at 16:50
0

@rumbz Please refer to the below link it might solve your issue

Content type 'application/x-www-form-urlencoded;charset=UTF-8' not supported for @RequestBody MultiValueMap

1 Using annotation

@RequestMapping(value = "/some-path", produces = 
org.springframework.http.MediaType.TEXT_PLAIN)    
public String plainTextAnnotation() {    
    return "<response body>";    
}

where replace /some-path with whatever you'd like to use.

2 Setting content type in the response entity's HTTP headers:

public String plainTextResponseEntity() {    
     HttpHeaders httpHeaders = new HttpHeaders();    
     
     httpHeaders.setContentType(org.springframework.http.MediaType.TEXT_PLAIN);    
     return new ResponseEntity("<response body>", httpHeaders, HttpStatus.OK);    
}  
Community
  • 1
  • 1
0

Per @m-deinum 's comment: The problem is not in the spring framework - but in the fact that curl adds "application/x-www-form-urlencoded" to the request ...

And just to make this question complete:

curl -s -X POST -H "Content-Type:" -H "Content-Type: text/plain; charset: utf-8" --data-binary @file.txt localhost:8080/abc/def
rubmz
  • 1,947
  • 5
  • 27
  • 49