Why does the following example swallow my HTTP-Header for "Content-Encoding" in the request. I am writing an application where I need to decode a custom encoding format. However, I can never get hold of the "Content-Encoding" header from the request. Neither in the actual resource nor in an ReaderInterceptor. In the response, this encoding header is not swallowed.
This behavior can be easily observed in the following (runnable) example:
public class Demo extends JerseyTest {
#Override
protected Application configure() {
enable(TestProperties.DUMP_ENTITY);
enable(TestProperties.LOG_TRAFFIC);
return new ResourceConfig(MyResource.class, MyInterceptor.class);
}
public static final String PATH = "path";
public static final String ENCODING = "my-encoding";
public static final String CUSTOM_HEADER = "X-Content-Encoding";
public static final String QUESTION = "question", ANSWER = "answer";
#Path(PATH)
public static class MyResource {
#POST
public Response handle(String value, #Context HttpHeaders httpHeaders) {
assertEquals(ENCODING, httpHeaders.getHeaderString(CUSTOM_HEADER));
// Here, the "Content-Encoding" header mysteriously disappeared.
assertEquals(ENCODING, httpHeaders.getHeaderString(HttpHeaders.CONTENT_ENCODING));
return Response
.ok(ANSWER)
.header(CUSTOM_HEADER, ENCODING)
.header(HttpHeaders.CONTENT_ENCODING, ENCODING)
.build();
}
}
public static class MyInterceptor implements ReaderInterceptor, WriterInterceptor {
#Override
public Object aroundReadFrom(ReaderInterceptorContext context)
throws IOException, WebApplicationException {
assertEquals(ENCODING, context.getHeaders().getFirst(CUSTOM_HEADER));
// Here, the "Content-Encoding" header mysteriously disappeared.
assertEquals(ENCODING, context.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING));
return context.proceed();
}
#Override
public void aroundWriteTo(WriterInterceptorContext context)
throws IOException, WebApplicationException {
assertEquals(ENCODING, context.getHeaders().getFirst(CUSTOM_HEADER));
// Here, the "Content-Encoding" header can be found.
assertEquals(ENCODING, context.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING));
context.proceed();
}
}
#Test
public void test() throws Exception {
Response response = target(PATH)
.request()
.header(CUSTOM_HEADER, ENCODING)
.header(HttpHeaders.CONTENT_ENCODING, ENCODING)
.post(Entity.text(QUESTION));
assertEquals(200, response.getStatus());
assertEquals(ENCODING, response.getHeaders().getFirst(CUSTOM_HEADER));
// Here, the "Content-Encoding" header can be found.
assertEquals(ENCODING, response.getHeaders().getFirst(HttpHeaders.CONTENT_ENCODING));
}
}
Is there some magic happening behind the curtains where Jersey tries to fix up my content encoding? (What it cannot since its a closed-source encoding which I must as a matter of fact resolve by querying another application on another server in the network.) I cannot even discover the "Content-Encoding" header in the request dump which is why I suspect Jersey to not send the header at all.
I could of course use some "X-Content-Encoding" header, this works as demonstrated in the example. But this solution is just dumb. I already searched the various CommonProperties, ServerProperties, ClientProperties constant pools but I did not find a configuration option.
The problem you see is because you are effectively overwriting the Content-Encoding header with the
.post(Entity.text(QUESTION));
call. The Entity.text(...) method produces entity with the content data Variant fields set to:
media type = "text/plain";
content language = null;
content encoding = null;
These null values of content encoding and language in turn erase any previously set Content-Encoding or Content-Language headers. To fix this, you need to specify the content encoding as part of your entity:
Response response = target(PATH)
.request()
.post(Entity.entity(QUESTION,
new Variant(MediaType.TEXT_PLAIN_TYPE, (String) null, "my-encoding")));
(I agree this behavior is somewhat confusing as it is not obvious. Perhaps we should fix Jersey to not override headers with null variant field values if set...)
Related
I have a RestController that multiple partners use to send XML requests. However this is a legacy system that it was passed on to me and the original implementation was done in a very loose way in PHP.
This has allowed to clients, that now they refuse to change, to send different content-types (application/xml, text/xml, application/x-www-form-urlencoded) and it has left me with the need to support many MediaTypes to avoid returning 415 MediaType Not Supported Errors.
I have used the following code in a configuration class to allow many media types.
#Bean
public MarshallingHttpMessageConverter marshallingMessageConverter() {
MarshallingHttpMessageConverter converter = new MarshallingHttpMessageConverter();
converter.setMarshaller(jaxbMarshaller());
converter.setUnmarshaller(jaxbMarshaller());
converter.setSupportedMediaTypes(Arrays.asList(MediaType.APPLICATION_OCTET_STREAM, MediaType.APPLICATION_XML,
MediaType.TEXT_XML, MediaType.TEXT_PLAIN, MediaType.APPLICATION_FORM_URLENCODED, MediaType.ALL));
return converter;
}
#Bean
public Jaxb2Marshaller jaxbMarshaller() {
Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
marshaller.setClassesToBeBound(CouponIssuedStatusDTO.class, CouponIssuedFailedDTO.class,
CouponIssuedSuccessDTO.class, RedemptionSuccessResultDTO.class, RedemptionResultHeaderDTO.class,
RedemptionFailResultDTO.class, RedemptionResultBodyDTO.class, RedemptionDTO.class, Param.class,
ChannelDTO.class, RedeemRequest.class);
Map<String, Object> props = new HashMap<>();
props.put(javax.xml.bind.Marshaller.JAXB_FORMATTED_OUTPUT, true);
marshaller.setMarshallerProperties(props);
return marshaller;
}
The controller method is this:
#PostMapping(value = "/request", produces = { "application/xml;charset=UTF-8" }, consumes = MediaType.ALL_VALUE)
public ResponseEntity<RedemptionResultDTO> request(
#RequestHeader(name = "Content-Type", required = false) String contentType,
#RequestBody String redeemRequest) {
return requestCustom(contentType, redeemRequest);
}
This endpoint is hit by all clients. It is only one last client giving me trouble. They are sending content-type = application/x-www-form-urlencoded; charset=65001 (UTF-8)": 65001 (UTF-8)
Due to the way the charset is sent, Spring Boot refuses to return anything but 415. Not even MediaType.ALL seems to have any effect.
Is there a way to make Spring allow this to reach me ignoring the content-type? Creating a filter and changing the content type was not feasible since the HttpServletRequest is not allowing to mutate the content-type. I am out of ideas but I really think there has to be a way to allow custom content-types.
UPDATE
If I remove the #RequestBody then I don't get the error 415 but I have no way to get the request body since the HttpServletRequest reaches the Controller action empty.
You best case is to remove the consumes argument from the RequestMapping constructor. The moment you have it added, spring will try to parse it into known type MediaType.parseMediaType(request.getContentType()) & which tries to create a new MimeType(type, subtype, parameters) and thus throws exception due to invalid charset format being passed.
However, if you remove the consumes, and you wanna validate/restrict the incoming Content-Type to certain type, you can inject HttpServletRequest in your method as parameter, and then check the value of request.getHeader(HttpHeaders.CONTENT_TYPE).
You also have to remove the #RequestBody annotation so Spring doesn't attempt to parse the content-type in attempt to unmarshall the body. If you directly attempt to read the request.getInputStream() or request.getReader() here, you will see null as the stream has already been read by Spring. So to get access to input content, use spring's ContentCachingRequestWrapper inject using Filter and then you can later repeatedly read the content as it's cached & not reading from original stream.
I am including some code snippet here for reference, however to see executable example, you can refer my github repo. Its a spring-boot project with maven, once you launch it, you can send your post request to http://localhost:3007/badmedia & it will reflect you back in response request content-type & body. Hope this helps.
#RestController
public class BadMediaController {
#PostMapping("/badmedia")
#ResponseBody
public Object reflect(HttpServletRequest request) throws IOException {
ObjectMapper mapper = new ObjectMapper();
JsonNode rootNode = mapper.createObjectNode();
((ObjectNode) rootNode).put("contentType", request.getHeader(HttpHeaders.CONTENT_TYPE));
String body = new String(((ContentCachingRequestWrapper) request).getContentAsByteArray(), StandardCharsets.UTF_8);
body = URLDecoder.decode(body, StandardCharsets.UTF_8.name());
((ObjectNode) rootNode).put("body", body);
return mapper.writerWithDefaultPrettyPrinter().writeValueAsString(rootNode);
}
}
#Component
public class CacheRequestFilter extends GenericFilterBean {
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest cachedRequest
= new ContentCachingRequestWrapper((HttpServletRequest) servletRequest);
//invoke caching
cachedRequest.getParameterMap();
chain.doFilter(cachedRequest, servletResponse);
}
}
I'm using Spring to create an API, but I'm having some trouble introducing custom error reporting on (a part of) the validation of the request body.
When parsing/validation errors occur, I want to give a custom response back to the user.
This works well for fields annotated with #Valid along with validators like #javax.validation.constraints.NotNull by using a custom ResponseEntityExceptionHandler annotated with #ControllerAdvice.
It does not work however if an Exception is thrown while parsing the request body (before the validations even run). In that case I get an html error page with status 500 (Server Error)
How can I make sure the exceptions during parsing lead to the same kind of response as the (custom) one I return for validation failures?
My endpoint's code looks like this:
#RequestMapping(value= "/endpoint"
produces = { "application/json" },
consumes = { "application/json" },
method = RequestMethod.POST)
default ResponseEntity<Object> postSomething(#Valid #RequestBody MyRequestBody requestData){
// ...
}
MyRequestBody class looks like this:
#Validated
public class MyRequestData {
#JsonProperty("stringValue")
private String stringValue = null;
#NotNull
#Valid
public String getStringValue() {
return stringValue;
}
// ...
public enum EnumValueEnum {
VALUE_1("value 1"),
VALUE_1("value 2");
private String value;
EnumValueEnum(String value) {
this.value = value;
}
#Override
#JsonValue
public String toString() {
return String.valueOf(value);
}
#JsonCreator
public static EnumValueEnum fromValue(String text) {
if(text == null){
return null;
}
for (EnumValueEnum b : EnumValueEnum.values()){
if (String.valueOf(b.value).equals(text)) {
return b;
}
}
throw new HttpMessageNotReadableException("EnumValueEnum \"" + text + "\" does not exist");
}
}
#JsonProperty("enumValue")
private EnumValueEnum enumValue = null;
}
The custom validation error handling (and reporting) looks like this:
#Order(Ordered.HIGHEST_PRECEDENCE)
#ControllerAdvice
public class MyValidationHandler extends ResponseEntityExceptionHandler {
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse(ex.getBindingResult().getFieldErrors()));
}
#Override
protected ResponseEntity<Object> handleHttpMessageNotReadable(HttpMessageNotReadableException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) ex.getCause()));
}
}
In this code, if a user sends a request with an enum value that doesn't exist, an HttpMessageNotReadableException is thrown. I would like to catch that somewhere and replace it with a custom response that is consistent with the other exception handling I do. Where/How can I do that?
I found a solution to my own problem.
You can actually use Spring MVC's normal exception handling:
Annotating a method with #ExceptionHandler will make Spring try to use it for exception handling for the exception type specified (in the annotation's value field or the method's argument). This method can be placed in the controller or even in the ResponseEntityExceptionHandler I use for the other validation response handling.
#ExceptionHandler
public ResponseEntity handle(HttpMessageConversionException e){
// return status(BAD_REQUEST).body(new ValidationResponse((JsonMappingException) e.getCause()));
}
Mind which type of exception you handle:
The catch here was that the exception thrown while parsing is wrapped in (some subtype of) a JsonMappingException which in turn is wrapped again in a HttpMessageConversionException.
e instanceof HttpMessageConversionException
e.getCause() instanceof JsonMappingException
e.getCause().getCause() // == your original exception
The #ExceptionHandler should therefor accept HttpMessageConversionException instead of the originally thrown exception (which in my case was HttpMessageNotReadableException)
It will not work if you write an #ExceptionHandler that only accepts your original Exception!
To replace a legacy system and not breaking the interface, I'm looking for a way to implement the following scenario:
If a REST client hasn't set a specific HTTP header (applicationId) but sends it as a query-paramter (aka RequestParameter), this value should be taken as a method parameter in a Spring Boot RestController.
The current method looks like this:
#RequestMapping(value = "/something", method = RequestMethod.GET)
public void doSomething(#RequestHeader("applicationId") String applicationId) { }
I think there could be two possible ways:
Annotate the method somehow to map a query-parameter OR a header to a method parameter
Write an Interceptor which reads all query-parameters of a request and set non-existing headers with their values. This way, the method wouldn't have to be touched at all.
In both ways I'm not sure how to implement them (don't know if 1. is even possible). I tried with an own HandlerInterceptor which reads query-params in preHandle (successfully) but isn't able to set headers in the request before it is forwarded to the RestController.
Write a Filter that wraps the incoming request using a HttpServletRequestWrapper. This wrapper should override the getHeader method.
public ParameterToHeaderWrappingRequestFilter extends OncePerRequestFilter {
protected void doFilterInternal(
HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
filterChain.doFilter(new ParameterToHeaderWrappingRequest(request), response, filterChain);
}
}
Register this filter as a #Bean in your Spring Boot application and it will be applied automatically.
public class ParameterToHeaderWrappingRequest extends HttpServletRequestWrapper {
public String getHeader(String name) {
String header = super.getHeader(name);
if (header == null) {
header = getParameter(name);
}
return header;
}
}
Something like that should do the trick. Depending on your needs you might want/need to override some additional header based methods and you probably want to limit the number of headers to override with parameters.
The rest of your code can now be written as is.
I'm building a small application to serve as a client for some third party library here at work. The API states that a Webhookis needed to respond some asynchronous events, but all their methods have the very same signature, apart from a changing _method field between the calls. For example, I have a _method = ping, media, etc.
I'd like to have separate methods on my controller to respond for each one of these methods. If the app allowed me to specify different URLs for each method it would be easy to use Spring MVC's #RequestMapping for each one of them. But I have to specify a single endpoint to receive all calls.
Is there a way (for example using Spring's HttpMessageConverter or something like that) to map different controller methods based on what the Request Body is? I've already tried with #RequestBody, #RequestParam but didn't seem to find anything.
I really, really didn't want to use a bunch of case, switch methods on a front controller to dispatch actions based on my _method field that comes with my POST data, so I happen to believe someone had this problem before and solved it intelligently.
Thanks a lot!
Edit 1: Providing source code
#Controller
#RequestMapping("/webhooks")
public class WebhookController {
#RequestMapping(method = RequestMethod.POST, params = {"_method=ping"})
#ResponseBody
public String ping(){
return "pong";
}
#RequestMapping(method = RequestMethod.POST, params = {"_method=media"})
#ResponseBody
public String media(){
return "media";
}
}
This is the answer:
{
"timestamp": 1440875190389,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.UnsatisfiedServletRequestParameterException",
"message": "Parameter conditions \"_method=ping\" not met for actual request parameters: ",
"path": "/webhooks"
}
Right, I got it working. The answer is a bit tricky so I wanted to register it here should anyone have such problem.
#Neil McGuigan pointed me on the right direction on his comment but I didn't pay attention at first. The main culprit here is a very, very, very bad API design on our remote application's side.
_method is a field used to specify non-standard HTTP verbs such as PUT, PATCH, DELETE, TRACE and so on. This field is filtered by HiddenHttpMethodFilter and the HttpServletRequest is wrapped with this 'new' method. You can see at the file's source how it works.
As I wanted this _method field to get thru the filter without modifying the whole request (and causing the errors because there's no such verb as pingor message on `RequestMethod) I firstly had to deactivate the filter. This could be done by two ways:
I could stop Spring Boot from automagically configuring Spring MVC, skipping WebMvcAutoConfiguration from being loaded when the ApplicationContext was loaded. As you can imagine this is a BIG, BIG, BIIIIG NO because, well, things could happen.
I could use a FilterRegistrationBean to disable the bad filter. Pretty simple and straightforward, this was the method I chose to use:
#Bean
public FilterRegistrationBean registration(HiddenHttpMethodFilter filter) {
FilterRegistrationBean registration = new FilterRegistrationBean(filter);
registration.setEnabled(false);
return registration;
}
Last but not least, I decided to give HiddenHttpMethodFilter a little extension to somehow improve how the requests were getting thru. The Java EE Spec is pretty clear on the Servlet Spec Commandments where it states:
Thou should not alter your request on your side. You must respect the sender (something like that)
Though I agree with this, for the sake of my mental stability I decided to alter it anyway. To achieve this, we can use a simple HttpServletRequestWrapper, override the chosen methods and filter the original request with the wrapped part. I ended up doing something like this:
public class WhatoolsHiddenHttpMethodFilter extends OrderedHiddenHttpMethodFilter {
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String paramValue = request.getParameter(OrderedHiddenHttpMethodFilter.DEFAULT_METHOD_PARAM);
if("POST".equals(request.getMethod()) && StringUtils.hasLength(paramValue)) {
String method = paramValue.toUpperCase(Locale.ENGLISH);
List<String> whatoolsMethods = Arrays.asList("ping", "message", "carbon", "media", "media_carbon", "ack");
if(whatoolsMethods.contains(paramValue)){
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, "POST", paramValue);
filterChain.doFilter(wrapper, response);
} else {
WhatoolsHiddenHttpMethodFilter.HttpMethodRequestWrapper wrapper = new WhatoolsHiddenHttpMethodFilter
.HttpMethodRequestWrapper(request, method, null);
filterChain.doFilter(wrapper, response);
}
} else {
filterChain.doFilter(request, response);
}
}
private static class HttpMethodRequestWrapper extends HttpServletRequestWrapper {
private final String method;
private final String whatoolsMethod;
public HttpMethodRequestWrapper(HttpServletRequest request, String method, String whatoolsMethod) {
super(request);
this.method = method;
this.whatoolsMethod = whatoolsMethod;
}
#Override
public String getMethod() {
return this.method;
}
#Override
public String getHeader(String name) {
if("x-whatools-method".equals(name)){
return this.whatoolsMethod;
}
return super.getHeader(name);
}
#Override
public Enumeration<String> getHeaderNames() {
List<String> names = Collections.list(super.getHeaderNames());
if(this.whatoolsMethod != null){
names.add("x-whatools-method");
}
return Collections.enumeration(names);
}
}
}
So, what this does is to wrap the request with a new x-whatools-method header when the header is in my whatoolsMethods list. With this, I can easily use #RequestMapping's headers property and map the requests to the correct controller methdods.
Back to the initial question, I'm almost sure (well, 99,95% should be completely sure but let's not risk it) the params property on #RequestMapping works only for request parameters on GET URIs, e.g http://foo.bar/?baz=42. It won't work filtering parameters sent on the request's body.
Thanks Neil for your guidance, even if small! I hope this helps someone.
You can use params in a request mapping:
#RequestMapping(value="/foo", params={"_method=ping"})
Assuming these are post parameters that is
params DOES work for POST, I promise you
Here's my controller:
#Controller
#RequestMapping("/test1")
public class ParamTestController {
#RequestMapping(method = RequestMethod.POST)
#ResponseBody String getA(){
return "A";
}
#RequestMapping(method = RequestMethod.POST, params = {"b"})
#ResponseBody String getB(){
return "B";
}
}
Here's my test:
I have a REST endpoint implemented with Spring MVC #RestController. Sometime, depends on input parameters in my controller I need to send http redirect on client.
Is it possible with Spring MVC #RestController and if so, could you please show an example ?
Add an HttpServletResponse parameter to your Handler Method then call response.sendRedirect("some-url");
Something like:
#RestController
public class FooController {
#RequestMapping("/foo")
void handleFoo(HttpServletResponse response) throws IOException {
response.sendRedirect("some-url");
}
}
To avoid any direct dependency on HttpServletRequest or HttpServletResponse I suggest a "pure Spring" implementation returning a ResponseEntity like this:
HttpHeaders headers = new HttpHeaders();
headers.setLocation(URI.create(newUrl));
return new ResponseEntity<>(headers, HttpStatus.MOVED_PERMANENTLY);
If your method always returns a redirect, use ResponseEntity<Void>, otherwise whatever is returned normally as generic type.
Came across this question and was surprised that no-one mentioned RedirectView. I have just tested it, and you can solve this in a clean 100% spring way with:
#RestController
public class FooController {
#RequestMapping("/foo")
public RedirectView handleFoo() {
return new RedirectView("some-url");
}
}
redirect means http code 302, which means Found in springMVC.
Here is an util method, which could be placed in some kind of BaseController:
protected ResponseEntity found(HttpServletResponse response, String url) throws IOException { // 302, found, redirect,
response.sendRedirect(url);
return null;
}
But sometimes might want to return http code 301 instead, which means moved permanently.
In that case, here is the util method:
protected ResponseEntity movedPermanently(HttpServletResponse response, String url) { // 301, moved permanently,
return ResponseEntity.status(HttpStatus.MOVED_PERMANENTLY).header(HttpHeaders.LOCATION, url).build();
}
As the redirections are usually needed in a not-straightforward path, I think throwing an exception and handling it later is my favourite solution.
Using a ControllerAdvice
#ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
#ExceptionHandler(value = {
NotLoggedInException.class
})
protected ResponseEntity<Object> handleNotLoggedIn(
final NotLoggedInException ex, final WebRequest request
) {
final String bodyOfResponse = ex.getMessage();
final HttpHeaders headers = new HttpHeaders();
headers.add("Location", ex.getRedirectUri());
return handleExceptionInternal(
ex, bodyOfResponse,
headers, HttpStatus.FOUND, request
);
}
}
The exception class in my case:
#Getter
public class NotLoggedInException extends RuntimeException {
private static final long serialVersionUID = -4900004519786666447L;
String redirectUri;
public NotLoggedInException(final String message, final String uri) {
super(message);
redirectUri = uri;
}
}
And I trigger it like this:
if (null == remoteUser)
throw new NotLoggedInException("please log in", LOGIN_URL);
if you #RestController returns an String you can use something like this
return "redirect:/other/controller/";
and this kind of redirect is only for GET request, if you want to use other type of request use HttpServletResponse