How to customize Jackson JSON serialization based on request parameter in Spring MVC - json

I am working on a project and we want to pass down custom ContextAttributes to the Jackson ObjectMapper#writer() method.
Basically I am imagining some kind of global piece of code that sits between the controllers and serialization. It should look at the HttpServletRequest, get a parameter and then hook into the serialization.
Writing a custom HttpMessageConverter doesn't seem to be enough because it does not have access to the request.

You can access Request this way
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
if (ra instanceof ServletRequestAttributes) {
HttpServletRequest request = ((ServletRequestAttributes)ra).getRequest();
}
Or you can add a Filter and store Request in a ThreadLocal storage and acccess from your custom HttpMessageConverter.

You can create a filter and apply that for all urls and implement the logic in the filter. The filter has access to request object
public class FilterName extends GenericFilterBean {
#Override
public void doFilter(ServletRequest req, ServletResponse res,
FilterChain chain) {
//TODP
}
}
and add this to the security xml

Related

How to include JSON response body in Spring Boot Actuator's Trace?

Spring Boot Actuator's Trace does a good job of capturing input/output HTTP params, headers, users, etc. I'd like to expand it to also capture the body of the HTTP response, that way I can have a full view of what is coming in and going out of the the web layer. Looking at the TraceProperties, doesn't look like there is a way to configure response body capturing. Is there a "safe" way to capture the response body without messing up whatever character stream it is sending back?
Recently, I wrote a blog post about customization of Spring Boot Actuator's trace endpoint and while playing with Actuator, I was kinda surprised that response body isn't one of the supported properties to trace.
I thought I may need this feature and came up with a quick solution thanks to Logback's TeeFilter.
To duplicate output stream of the response, I copied and used TeeHttpServletResponse and TeeServletOutputStream without too much examination.
Then, just like I explained in the blog post, extended WebRequestTraceFilter like:
#Component
public class RequestTraceFilter extends WebRequestTraceFilter {
RequestTraceFilter(TraceRepository repository, TraceProperties properties) {
super(repository, properties);
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
TeeHttpServletResponse teeResponse = new TeeHttpServletResponse(response);
filterChain.doFilter(request, teeResponse);
teeResponse.finish();
request.setAttribute("responseBody", teeResponse.getOutputBuffer());
super.doFilterInternal(request, teeResponse, filterChain);
}
#Override
protected Map<String, Object> getTrace(HttpServletRequest request) {
Map<String, Object> trace = super.getTrace(request);
byte[] outputBuffer = (byte[]) request.getAttribute("responseBody");
if (outputBuffer != null) {
trace.put("responseBody", new String(outputBuffer));
}
return trace;
}
}
Now, you can see responseBody in the JSON trace endpoint serves.
From one of the spring maintainers:
Tracing the request and response body has never been supported out of the box. Support for tracing parameters was dropped as, when the request is POSTed form data, it requires reading the entire request body.
https://github.com/spring-projects/spring-boot/issues/12953
With a webflux reactive stack, it is possible to capture http request and response body using spring-cloud-gateway and inject them into actuator httptrace by defining a custom HttpTraceWebFilter.
See full associated code at https://gist.github.com/gberche-orange/06c26477a313df9d19d20a4e115f079f
This requires quite a bit of duplication, hopefully springboot team will help reduce this duplication, see related https://github.com/spring-projects/spring-boot/issues/23907

How to force using particular MediaType in RestEasy client

I have a shared API and services are annotated
#Produces({"application/json","application/x-jackson-smile"})
#Consumes({"application/json","application/x-jackson-smile"})
public class AServiceClass {
So default is JSON - this will be preferred when using browser AJAX calls.
However I have a RestEasy client which I create using
ProxyFactory.create(AServiceClass.class, url)
And I want this client to use SMILE for both inbound and aoutbound communication. Of course it picks first item from #Consumes and tries marshalling to JSON.
I'm using RestEasy 2.3.5
How to force the client to use SMILE marshalling?
How to force the client to accept SMILE instead of JSON?
Actually it turns out that you ( I mean I :-) ) can't do this.
Checking MediaTypeHelper.getConsumes() shows that always first annotation value is picked to determine marshalling media type.
return MediaType.valueOf(consume.value()[0]);
The same happens when it comes to figuring out accept header. The code uses MediaTypeHelper.getProduces()
It can be done by specifying the value for the header Accept
Response response = client.target(host + "/yourpath").request()
.header(HttpHeaders.ACCEPT, "application/x-jackson-smile")
.get();
You can also achieve this with a ClientRequestFilter, which is more usful if you are using proxies of your JAX-RS annotated classes. For example
public class AcceptedHeaderFilter implements ClientRequestFilter
{
private final MediaType acceptedType;
public AcceptedHeaderFilter(final MediaType acceptedType)
{
super();
this.acceptedType = acceptedType;
}
#Override
public void filter(final ClientRequestContext requestContext) throws IOException
{
requestContext.getHeaders().get(HttpHeaders.ACCEPT).clear();
requestContext.getHeaders().get(HttpHeaders.ACCEPT).add(acceptedType.toString());
}
}
If you are using Resteasy, you can register on your ResteasyWebTarget
MediaType contentType = MediaType.APPLICATION_XML_TYPE;
final ResteasyWebTarget target = buildTarget();
target.getResteasyClient().register(new AcceptedHeaderFilter(contentType));
CXF or Jersey will let you register the filter, but will require you to do it in a slightly different way.

XML/JSON POST with RequestBody in Spring REST Controller

I am creating a RESTful website with Spring 3.0. I am using ContentNegotiatingViewResolver as well as HTTP Message Convertors (like MappingJacksonHttpMessageConverter for JSON, MarshallingHttpMessageConverter for XML, etc.). I am able to get the XML content successfully, if I use the .xml suffix in the last of url and same in case of JSON with .json suffix in URL.
Getting XML/JSON contents from controller doesn't produce any problem for me. But, how can I POST the XML/JSON with request body in same Controller method?
For e.g.
#RequestMapping(method=RequestMethod.POST, value="/addEmployee")
public ModelAndView addEmployee(#RequestBody Employee e) {
employeeDao.add(e);
return new ModelAndView(XML_VIEW_NAME, "object", e);
}
You should consider not using a View for returning JSON (or XML), but use the #ResponseBody annotation. If the employee is what should be returned, Spring and the MappingJacksonHttpMessageConverter will automatic translate your Employee Object to JSON if you use a method definition and implementation like this (note, not tested):
#RequestMapping(method=RequestMethod.POST, value="/addEmployee")
#ResponseBody
public Employee addEmployee(#RequestBody Employee e) {
Employee created = employeeDao.add(e);
return created;
}

ASP.NET MVC: Specify value provider on a per-action or per-route basis?

I'm trying to set up an action in ASP.NET MVC 3 to handle the payload of a mercurial webhook request - in this case, generated by Kiln.
The payload is JSON, but unfortunately it is sent as a URL encoded form value with the content type application/x-www-form-urlencoded, because apparently using application/json and sending it unencoded with no parameter name would make it too easy and um... standard.
This means that I can't just use the new JsonValueProviderFactory because it only picks up requests using the standard application/json content type. And of course I can't just kludge the factory to also pick up application/x-www-form-urlencoded requests too, because I need those requests to use the form data value provider everywhere else in my app that's actually receiving form data and not JSON.
So, is there a way I can specify that a ValueProvider or ValueProviderFactory should only be used for a specific action or route?
If you create a specific controller to handle these webhook requests, you can assign your unique ValueProvider when you instantiate your controller.
public class KilnController : Controller
{
public KilnController()
{
this.ValueProvider = MyCustomValueProvider;
}
...
}
This should fulfill your need for a custom ValueProvider for these requests.
Turns out that IValueProvider was not the particular bit of extensibility I was looking for - I just needed to use a quick IModelBinder implementation I found courtesy of James Hughes. It needed a little tweaking to cover pulling something out of the form:
public class JsonFormModelBinder : IModelBinder
{
#region [ ModelBinder Members ]
Object IModelBinder.BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
HttpRequestBase request = controllerContext.HttpContext.Request;
var jsonStringData = request.Form[bindingContext.ModelName];
if (jsonStringData != null) return JsonConvert.DeserializeObject(jsonStringData, bindingContext.ModelType);
else return null;
}
#endregion
}
And the usage:
[HttpPost]
public ActionResult WebHook([ModelBinder(typeof(JsonFormModelBinder))] WebHookMessage payload)
{
return Content("OK");
}

Using Spring's #RequestBody and reading HttpServletRequest.getInputStream() afterwards

I'm mapping my request's JSON POST data into an object using Spring's #RequestBody annotation and MappingJacksonHttpMessageConverter. However after that I'd like to read the data in String form to do some additional authentication. But when the marshalling has happened, the InputStream in HttpServletRequest is empty. Once I remove the #RequestBody parameter from the method the reading of POST data into a String works as expected.
Do I have to compromise by giving up the #RequestBody and doing the binding somehow manually or is there a more elegant solution?
So, basically you need to compute a hash of the request body. The elegant way to do it is to apply a decorator to the InputStream.
For example, inside a handler method (in this case you can't use #RequestBody and need to create HttpMessageConverter manually):
#RequestMapping(...)
public void handle(HttpServletRequest request) throws IOException {
final HashingInputStreamDecorator d =
new HashingInputStreamDecorator(request.getInputStream(), secretKey);
HttpServletRequest wrapper = new HttpServletRequestWrapper(request) {
#Override
public ServletInputStream getInputStream() throws IOException {
return d;
}
};
HttpMessageConverter conv = ...;
Foo requestBody = (Foo) conv.read(Foo.class, new ServletServerHttpRequest(wrapper));
String hash = d.getHash();
...
}
where hash is computed incrementally in overriden read methods of HashingInputStreamDecorator.
You can also use #RequestBody if you create a Filter to apply the decorator. In this case decorator can pass the computed hash to the handler method as a request attribute. However, you need to map this filter carefully to apply it only to the requests to specific handler method.
In your urlMapping bean you can declare list of additional interceptors:
<bean id="urlMapping" class="org.springframework.web.servlet.mvc.annotation.DefaultAnnotationHandlerMapping">
<property name="interceptors">
<list>
<bean class="org.foo.MyAuthInterceptor"/>
</list>
</property>
</bean>
Those interceptors have access to HttpServletRequest, though if you read from the stream the chances are that parameter mapper won't be able to read it.
public class AuthInterceptor extends HandlerInterceptorAdapter {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
...
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView mav) {
...
}
}
If I understand this correctly, one common way used with JAX-RS (which is somewhat similar to Spring MVC with respect to binding requests) is to first "bind" into some intermediate raw type (usually byte[], but String also works), and manually bind from that to object, using underlying data binder (Jackson). I often do this to be able to fully customize error handling of data binding.