Log JSON responses from JAX-RS web services - java

I have some JAX-RS web services that takes JSON input from clients, and return JSON outputs to clients. I need to log both the input and output JSON messages. I know how to do so for inputs, as shown in code below. This code will grab the exact JSON input that is coming in. How do I do the same for the outputs?
public class LogRequestFilter implements ContainerRequestFilter {
#Override
public void filter(ContainerRequestContext requestContext) throws IOException {
InputStream inputStream = requestContext.getEntityStream();
// Read the JSON request input from the input stream.
}
}

Use ContainerResponseFilter to get the exchanged Entity. Add the filter to the provider list of the JAX-RS server
public class LogResponseFilter implements ContainerResponseFilter {
public void filter(ContainerRequestContext inContext, ContainerResponseContext outContext) throws IOException{
Object entity = outContext.getEntity();
//log entity.toSgring()
//You can use the output stream to write a custom message
//OutputStream outputStream = outContext.getEntityStream();
}
}
To just log the payload, looking at this link https://stackoverflow.com/a/25337892/6371459 I see
The OutputStream is empty at the time the Filter is called because the JAX-RS runtime has not written to it. After your Filter the runtime will choose the correct MessageBodyWriter which will serialize the entity to the OutputStream
So it is needed to add a WriterInterceptor to be execuded after MessageBodyWriters write to desired content-type.
In the previous link you have the full code.

Related

Why do I get Error method has more than one entity. You must use only one entity parameter

I am developing a servlet for JAVA EE and keep getting this error "Error Viewerpage.index method has more than one entity. You must use only one entity parameter."
#ApplicationPath("REST2")
#Path("/viewer")
public class Viewerpage extends Application {
private GlobalConfiguration globalConfiguration;
private ViewerService viewerService;
#GET
#Path(value = "/viewer")
public Response index(String filename, String page, HttpServletResponse response) throws IOException {
// set headers before we write to response body
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.TEXT_HTML);
// render a page of a file based on a parameters from request
renderPage(filename, response.getOutputStream());
// complete response
response.flushBuffer();
String value = "redirect:index";
return Response.status(Response.Status.OK).entity(value).build();
}
private void renderPage(String filename, OutputStream outputStream) {
String filepath = "storage/" + filename;
// render first page
MemoryPageStreamFactory pageStreamFactory = new MemoryPageStreamFactory(outputStream);
HtmlViewOptions viewOptions = HtmlViewOptions.forEmbeddedResources(pageStreamFactory);
Viewer viewer = new Viewer(filepath);
viewer.view(viewOptions);
viewer.close();
}
}
Any ideas what cause this error?
When you declare a resource method, you can only have one parameter that is the request entity. The parameter without any annotations is considered the entity body. All other parameters must have some kind of annotation that specifies what it is and what should be injected. If they are query parameters, use #QueryParam. If it is a path parameter, use #PathParam. If it some other non-Param injectable (that is supported) e.g. HttpServletRequest, then use #Context. Other supported "Param" injectable types are #HeaderParam, #FormParam, #CookeParam, #MatrixParam, etc.
Think of the HTTP response that gets streamed to the client. You are sending it with
response.setStatus(HttpServletResponse.SC_OK);
response.setContentType(MediaType.TEXT_HTML);
renderPage(filename, response.getOutputStream());
response.flushBuffer();
But then, afterwards (when the response stream at most should be closed), you try to do something that looks like building a second response:
Response.status(Response.Status.OK).entity(value).build();
As every response can have only one set of header and body you cannot go back setting headers or sending a second response entity. That is what the error is about.

Passing parameters between JAX-RS WriterInterceptor and ReaderInterceptor

I'm using JAX-RS and within the WriterInterceptor, I need to access some information contained in the original request.
As an example, consider the below request body.
{
"ClientId": "MY_CLIENT_ID",
"UserId": "MY_USER_ID",
"AccountId": "MY_ACCOUNT_ID",
"Scope" : "MY_SCOPES",
}
Within my WriteInterceptor, I need to read Client ID and User ID from the request and add those values to the response.
I am currently working on a ReadInterceptor implementation for this. I initially assumed there is a way to put parameters to ReaderInterceptorContext and then read it somehow from the WriterInterceptorContext. But It seems there is no way to do that. ( Please correct me if I'm wrong).
So, now I'm trying to use a concurrent hashmap to store these parameters in the ReaderInterceptor and retrieve it in the WriteInterceptor. I need a unique key to create the correlation between request and response. Is it ok to use the thread ID for this?
Please point me if there is a better approach to resolve this problem
I resolved this problem by adding a container response filter which can add a header to the response. Read interceptor reads required parameters from the request and set those as context properties.
#Override
public Object aroundReadFrom(ReaderInterceptorContext readerInterceptorContext)
throws IOException, WebApplicationException {
InputStream is = readerInterceptorContext.getInputStream();
String requestBody = new Scanner(is, StandardCharsets.UTF_8.name()).useDelimiter("\\A").next();
JSONObject request = new JSONObject(requestBody);
//Adding the stream back to the context object
readerInterceptorContext.setInputStream(new ByteArrayInputStream(requestBody.getBytes()));
//Adding properties to read in filter
readerInterceptorContext.setProperty("ClientId", request.get("ClientId"));
readerInterceptorContext.setProperty("UserId","UserId"));
return readerInterceptorContext.proceed();
}
These properties are then read inside the container response filter and added as a response header.
#Override
public void filter(ContainerRequestContext containerReqContext, ContainerResponseContext containerResponseContext) {
//Adding temporary headers to read in WriterInterceptor
containerResponseContext.getHeaders().add(
"ClientId", containerReqContext.getProperty("ClientId"));
containerResponseContext.getHeaders().add(
"UserId", containerReqContext.getProperty("UserId"));
}
Existing writer interceptor read these headers, add those to JWT and then remove as header values. I did a POC for this and it is working as expected
#Override
public void aroundWriteTo(WriterInterceptorContext writerInterceptorContext) throws IOException {
OutputStream outputStream = writerInterceptorContext.getOutputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
writerInterceptorContext.setOutputStream(baos);
String clientId = writerInterceptorContext.getHeaders().getFirst("ClientId").toString();
String user = writerInterceptorContext.getHeaders().getFirst("UserId").toString();
}

Spring Security: deserialize request body twice (oauth2 processing)

This question is a result of some work I'm doing with the Spring Security Oauth2 library. I've set up an oauth2 authorization server and an oauth2 resource server, the latter of which is meant to authorize based on access tokens.
The problem is that normally access tokens are passed in a header, but the big client we're setting this up for wants to pass the access token in a JSON request body. There's an interface you can use to set up custom access token extraction, but it looks like this:
public interface TokenExtractor {
/**
* Extract a token value from an incoming request without authentication.
*
* #param request the current ServletRequest
* #return an authentication token whose principal is an access token (or null if there is none)
*/
Authentication extract(HttpServletRequest request);
}
So, as best I can tell, all I have access to is the raw HTTPServletRequest, from which I need to deserialize the request and extract the access token.
Further complicating things, though, is the fact that the request body also contains other parameters needed for processing, so I want to deserialize it to a DTO class that I pass into my controller, something like so:
#RequestMapping("/oauth/someresource")
#Transactional
public Map<String, String> resource(#AuthenticationPrincipal UserDetails userDetails,
#RequestBody ClientRequestDto clientRequestDto) {
// Do some processing based on the request dto
}
I tried manually deserializing the request in the token extractor, but then I get an error "java.lang.IllegalStateException: getReader() has already been called for this request".
I was brainstorming a few possible solutions that I could research, and so far I've come up with:
find a way to reset the input stream
deserialize the object in the Token Extractor, attach it to the raw request object, and just access the raw request object in my controller instead of using #RequestBody
like 2, but find a way to add a custom deserializer that fetches the object attached to the raw request instead of processing the request's input stream.
Anyways, those are just some thoughts, if anyone has any ideas in terms of an elegant way of solving this, I'd greatly appreciate it.
EDIT: I did find this question which is similar: Spring reading request body twice, and the last answer did have one possible solution (creating a decorator request class that allows multiple input stream reads and creating a filter early on in the filter chain that wraps the HttpServletRequest). It seems workable, but a little heavy duty, so I'll leave this up to see if anyone has any other ideas as well.
So I ended up finding yet another question that addressed this issue that I didn't see before posting (How can I read request body multiple times in Spring 'HandlerMethodArgumentResolver'?). That one also suggested creating a decorator around the HttpServletRequest, so I adapted the info from http://www.myjavarecipes.com/how-to-read-post-request-data-twice-in-spring/, adding a protection against large requests.
Here's what I came up with, in case anyone has any feedback:
public class MultiReadHttpServletRequest extends HttpServletRequestWrapper {
// We include a max byte size to protect against malicious requests, since this all has to be read into memory
public static final Integer MAX_BYTE_SIZE = 1_048_576; // 1 MB
private String _body;
public MultiReadHttpServletRequest(HttpServletRequest request) throws IOException {
super(request);
_body = "";
InputStream bounded = new BoundedInputStream(request.getInputStream(), MAX_BYTE_SIZE);
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(bounded));
String line;
while ((line = bufferedReader.readLine()) != null){
_body += line;
}
}
#Override
public ServletInputStream getInputStream() throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(_body.getBytes());
return new ServletInputStream() {
public int read() throws IOException {
return byteArrayInputStream.read();
}
#Override
public boolean isFinished() {
return byteArrayInputStream.available() == 0;
}
#Override
public boolean isReady() {
return true;
}
#Override
public void setReadListener(ReadListener readListener) {
}
};
}
#Override
public BufferedReader getReader() throws IOException {
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
I used the following configuration:
#Bean
FilterRegistrationBean multiReadFilter() {
FilterRegistrationBean registrationBean = new FilterRegistrationBean();
MultiReadRequestFilter multiReadRequestFilter = new MultiReadRequestFilter();
registrationBean.setFilter(multiReadRequestFilter);
registrationBean.setOrder(SecurityProperties.DEFAULT_FILTER_ORDER - 2);
registrationBean.setUrlPatterns(Sets.newHashSet("/path/here"));
return registrationBean;
}

Log Jersey entity as JSON as response

I'm a Jersey newbie and I need to log the JSON response. I wish to take the entity and convert it to JSON exactly as done by the Jersey framework does (same mapper, etc.). Is there a way to extract its mapper (and call, for example, its writeValueAsString)?
You don't specify which package you use for producing the JSON response (neither you explain much about your jersey server), but I will assume you use Jackson.
You have some tracing in Jersey, take a look here, but as fas as I know it does not do what you need.
But first of all you should implement a LoggingFilter,
public class YourLoggingFilter implements ContainerResponseFilter {
#Override
public void filter(final ContainerRequestContext requestContext, final ContainerResponseContext responseContext)
throws IOException {
...
}
}
And I assume you extend the ResourceConfig class to add resources, you need to add the new filter here:
public class YourApplication extends ResourceConfig {
public YourApplication() {
super();
register(YourAuthenticationRequestFilter.class, Priorities.AUTHENTICATION);
register(YourExceptionMapper.class);
register(YourLoggingFilter.class);
packages("...");
}
}
And finally, a simple way to log your response inside YourLoggingFilter would be (here I assume Jackson, but this is just an example, I don't know what logger you are using, but don't open a file every time you do a request!!!)
Object obj = responseContext.getEntity();
ObjectMapper om = new ObjectMapper();
File file = new File("...");
try {
OutputStream out = new FileOutputStream(file);
om.writeValue(out, obj);
} catch (IOException e) {
// this could fail but you don't want your request to fail
e.printStackTrace();
}
Hope it helps.

Changing content type in jax-rs REST service

Forgive me, but I may not be familiar with all the lingo necessary to ask this question properly.
I'm working on a fairly simple REST web service in Java using the org.apache.cxf.jaxrs.ext implementation of jax-rs. The method header is like this:
#GET
#Path("json/{fullAlias}")
#Produces({"application/json"})
public String json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req)
where MessageContext is org.apache.cxf.jaxrs.ext.MessageContext.
There are two things I'm trying to accomplish that I can't seem to figure out:
Change the content-type if certain conditions are met (e.g. for an error)
Change the status code of the response
I've tried using changing the response by accessing it through the MessageContext:
HttpServletResponse response = req.getHttpServletResponse();
response.setContentType("text/plain")
response.setStatus("HttpServletResponse.SC_BAD_REQUEST);
But these changes have no bearing on the response sent; with or without the #Produces annotation, setting the content type inside the method doesn't affect the actual content type (With the annotation, it of course returns "application/json", without it defaults to "text/html").
I am returning a simple String as the body. I've entertained trying to return a javax.ws.rs.core.Response object to do what I want, but I don't know much about it.
How would I change the content type and/or the status codes from inside this method?
One approach is to throw a WebApplicationException, as described by Pace, which will work if you are looking to specifically handle an error condition. If you are looking to be able to change your content at any time for any reason, then you will want to take a look at returning a Response as the result of your service method rather than a String. Returning a Response gives you the greatest amount of control over how your service responds to the client request (it does require more code than returning a simple string).
Here is an example of how you would can make use of the Response object:
#GET
#Path("json/{fullAlias}")
public Response json(#PathParam("fullAlias") String fullAlias, #Context MessageContext req) {
...
if (success) {
ResponseBuilder rBuild = Response.ok(responseData, MediaType.APPLICATION_JSON);
return rBuild.build();
}
else {
ResponseBuilder rBuild = Response.status(Response.Status.BAD_REQUEST);
return rBuild.type(MediaType.TEXT_PLAIN)
.entity("error message")
.build();
}
}
I'm not sure if it's the best approach but I've done the following to solve your question #1.
public WebApplicationException createStatusException(String statusMessage) {
ResponseBuilder rb = Response.noContent();
rb = rb.type(MediaType.TEXT_PLAIN);
rb = rb.status(Status.BAD_REQUEST);
rb = rb.entity(statusMessage);
return new WebApplicationException(rb.build());
}
EDIT: I then threw the resulting WebApplicationException.
You can write your own Response Filter to change the content-type header.
#Provider
public class MimeAddingFilter implements ContainerResponseFilter {
#Override
public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext)
throws IOException {
responseContext.getHeaders().add("Content-Type", "image/png");
}
}
This filter will add the "image/png" content-type header. You can also change or remove headers in JAX-RS response filters.

Categories

Resources