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();
}
Related
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.
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;
}
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.
I have a scenario where I want to store session information across multiple sessions in Application # 2. We have two applications deployed on a tomcat server. Our use case is as follows:
A. Web Application # 1 makes a HTTP Post request to Application # 2 using a HTTP Rest Client. POST request contains a JSON http request body encapsulating the data to be send to Application # 2. The code block is as follows:
RestTemplate template = new RestTemplate();
final SearchCustomer customer = new SearchCustomer();
restTemplate.execute(
SEND_CUSTOMER_PROFILE, HttpMethod.POST,
new SearchRequestCallback(searchCustomer), null);
The request callback function is
static class SearchRequestCallback implements RequestCallback {
/**
* Write a JSON response to the request body.
*/
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptableMediaTypes = new LinkedList<>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptableMediaTypes);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
request.getBody().write(
new Gson().toJson(this.searchCustomer).getBytes(StandardCharsets.UTF_8.displayName()));
}
}
The second application has a Spring controller with the following set up
#Controller
public class SearchCustomerController {
/**
* Builds customer profile knowledge graph.
*
* <p>This is invoked as an synchronous request.
*/
#RequestMapping(value="/searchProfilePayload.go", method=RequestMethod.POST)
#ResponseStatus(HttpStatus.OK)
public void constructSearchCustomerProfileKnowledgeGraph(
#RequestBody final SearchCustomer customer, HttpServletRequest request) {
UserContext userContext =
(UserContext) request.getSession().getAttribute("userContext");
if (userContext == null) {
// Perform heavy operation to fetch user session.
userContext = UserContextHelper.getUserContext(request);
request.getSession("userContext", userContext)
}
userContext.setCustomerProfile(customer);
}
}
When I make a call to another URI within the application # 2 say via browser, I want it done in such as way that the session attributes are retained when making this call. Is there a way to do that?
I know about URL rewriting that stores JSESSIONIDin the cookie, but I don't think how you can set the value when making a rest call, and using the same JESSIONID to maintain session attributes.
Is there a better way to do this? These have no answers. I have looked at these links, but none seem to answer my question.
HTTP and Sessions
comparison of ways to maintain state
jraahhali is spot on.
Set the cookie header with the value of JSESSIONID=${sessionId} or use it directly in the url as per the URL rewriting link.
First step is to retrieve the JSESSIONID from the initial response (this will depend on how you decide to set the session id - URL or Cookies, lets assume by cookie for now)
#Override
public void doWithRequest(ClientHttpRequest request) throws IOException {
HttpHeaders httpHeaders = request.getHeaders();
List<MediaType> acceptableMediaTypes = new LinkedList<>();
acceptableMediaTypes.add(MediaType.APPLICATION_JSON);
httpHeaders.setAccept(acceptableMediaTypes);
httpHeaders.setContentType(MediaType.APPLICATION_JSON);
request.getBody().write(
new Gson().toJson(this.searchCustomer).getBytes(StandardCharsets.UTF_8.displayName()));
ClientHttpResponse response = request.execute();
String sessionId = response.getHeaders().get(HttpHeaders.SET_COOKIE).split(":")[1].trim(); // I didnt test this, will prolly get a NPE :P
this.sessionId = sessionId;
}
Then in subsequent requests (ie from the app #1 or a browser or whatever)
if (this.sessionId != null && !this.sessionId.equals(""))
httpHeaders.set(HttpHeaders.COOKIE, "JSESSIONID=" + this.sessionId);
// ...
request.execute();
Note if you really want to use a browser as the other client then I would use the URL rewriting method for ease of use ...
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.