Can spring boot controller receive plain/text?

回眸只為那壹抹淺笑 提交于 2021-02-09 11:09:59

问题


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).


回答1:


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.




回答2:


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



回答3:


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


来源:https://stackoverflow.com/questions/57970995/can-spring-boot-controller-receive-plain-text

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!