java.lang.IllegalStateException: getReader() has already been called for this request - java

I want to add logging to my Servlet, so I've created Filter which should display request and go to the Servlet. But unfortunately I've encoutered exception:
java.lang.IllegalStateException: getReader() has already been called for this request
at org.apache.catalina.connector.Request.getInputStream(Request.java:948)
at org.apache.catalina.connector.RequestFacade.getInputStream(RequestFacade.java:338)
at com.noelios.restlet.ext.servlet.ServletCall.getRequestEntityStream(ServletCall.java:190)
So to fix this problem I've found solution with Wrapper, but it doesn't work. What else can I use/change in code? Any ideas?
[MyHttpServletRequestWrapper]
public class MyHttpServletRequestWrapper extends HttpServletRequestWrapper
{
public MyHttpServletRequestWrapper(HttpServletRequest request)
{
super(request);
}
private String getBodyAsString()
{
StringBuffer buff = new StringBuffer();
buff.append(" BODY_DATA START [ ");
char[] charArr = new char[getContentLength()];
try
{
BufferedReader reader = new BufferedReader(getReader());
reader.read(charArr, 0, charArr.length);
reader.close();
}
catch (IOException e)
{
e.printStackTrace();
}
buff.append(charArr);
buff.append(" ] BODY_DATA END ");
return buff.toString();
}
public String toString()
{
return getBodyAsString();
}
}
[MyFilter]
public class MyFilterimplements Filter
{
#Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
final HttpServletRequest httpServletRequest = (HttpServletRequest) request;
final HttpServletResponse httpServletResponse = (HttpServletResponse) response;
final HttpServletRequestWrapper requestWrapper = new MyHttpServletRequestWrapper(httpServletRequest);
final String requestBody = requestWrapper.toString();
chain.doFilter(request, response);
}
}

Looks like the restlet framework has called getRequestEntityStream() on the Request object which in turn calls getInputStream(), so calling getReader() on the request throws IllegalStateException. The Servlet API documentation for getReader() and getInputStream() says:
public java.io.BufferedReader getReader()
...
...
Throws:
java.lang.IllegalStateException - if getInputStream() method has been called on this request
public ServletInputStream getInputStream()
...
...
Throws:
java.lang.IllegalStateException - if the getReader() method has already been called for this request
From the documentation it seems that we cannot call both getReader() and getInputStream() on the Request object. I suggest you use getInputStream() rather than getReader() in your wrapper.

Use ContentCachingRequestWrapper class. Wrap HttpServletRequest in thi will resolve issue
Sample : if you want to convert your "HttpServletRequest servletRequest" you can do some thing like
import org.springframework.web.util.ContentCachingRequestWrapper;
ContentCachingRequestWrapper request = new ContentCachingRequestWrapper(servletRequest);
Hope it helps!!!

As far as I can tell servlets are fundamentally broken in this regard. You can try and work around this problem as outlined here but that causes other mysterious problems when other things try and work with it.
Effectively he suggests cloning the request, reading the body and then in the the cloned class overriding the getReader and getInputStream methods to return the stuff already retrieved.
The code I ended up with was this:
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.*;
//this class stops reading the request payload twice causing an exception
public class WrappedRequest extends HttpServletRequestWrapper
{
private String _body;
private HttpServletRequest _request;
public WrappedRequest(HttpServletRequest request) throws IOException
{
super(request);
_request = request;
_body = "";
try (BufferedReader bufferedReader = request.getReader())
{
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 BufferedReader getReader() throws IOException
{
return new BufferedReader(new InputStreamReader(this.getInputStream()));
}
}
Anyway this appeared to be working fine until we realised that uploading a file from the browser wasn't working. I bisected through the changes and discovered this was the culprit.
Some people in the comments in that article say you need to override methods to do with parameters but don't explain how to do this.
As a result I checked to see if there was any difference in the two requests. However after cloning the request it had identical sets of parameters (both original request + cloned had none) aswell as an identical set of headers.
However in some manner the request was being effected and screwing up the understanding of the request further down the line - in my case causing a bizaare error in a library (extdirectspring) where something was trying to read the contents as Json. Taking out the code that read the body in the filter made it work again.
My calling code looked like this:
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
{
HttpServletRequest properRequest = ((HttpServletRequest)request);
String pathInfo = properRequest.getPathInfo();
String target = "";
if(pathInfo == null)
pathInfo = "";
if(pathInfo.equals("/router"))
{
//note this is because servlet requests hate you!
//if you read their contents more than once then they throw an exception so we need to do some madness
//to make this not the case
WrappedRequest wrappedRequest = new WrappedRequest(properRequest);
target = ParseExtDirectTargetFrom(wrappedRequest);
request = wrappedRequest;
}
boolean callingSpecialResetMethod = pathInfo.equals("/resetErrorState") || target.equals("resetErrorState");
if(_errorHandler.IsRejectingRequests() && !callingSpecialResetMethod)
return;
try {
filterChain.doFilter(request, response);
}
catch (Exception exception) {
((HttpServletResponse) response).sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "ERROR");
_errorHandler.NotifyOf(exception);
}
}
I've ommitted the contents of ParseExtDirectTargetFrom but it calls getReader().
In my case the filter was working for all other requests but the strange behaviour in this case made me realise something wasn't quite right and what I was trying to do (implement sensible exception handling behaviour for tests) wasn't worth potentially breaking random future requests (as I couldn't figure out what had caused the request to become broken).
Also it's worth noting that the broken code is unavoidable - I assumed it might be something from spring but ServletRequest goes all the way up - thats all you get even if you were making a servlet from scratch by subclassing HttpServlet
My recommendation would be this - don't read the request body in a filter. You'll be opening up a can of worms that will cause strange problems later on.

The main problem is that you can't read the input both as binary stream and character stream, not even if the one is called in a filter and the other in the servlet.

Well, maybe this is something quite obvious, but I want to share with you this code that work OK for me. In a Spring boot project with JWT, for request of client, was necesary save all requests with their responses in a database table, and the same time authorize the access to consume the resources. Off Course i use getReader() for get request body, but i was obtain java.lang.IllegalStateException...
#Slf4j
#Component
#RequiredArgsConstructor
public class CustomAuthorizationFilter extends OncePerRequestFilter {
private final AuthorizationService authorizationService;
private String requestBody;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
HttpRequestDto requestDto = new HttpRequestDto();
try {
if (RequestMethod.POST.name().equalsIgnoreCase(request.getMethod()) && requestBody != null) { //This line and validation is useful for me [requestBody != null]
requestBody = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
}
//Do all JWT control
requestDto.setRequestURI(request.getRequestURI());
requestDto.setMethod(request.getMethod());
requestDto.setBody(requestBody);
}catch (IOException ie) {
responseError(_3001, response, ie);
} finally {
try {
ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
filterChain.doFilter(request, responseWrapper);
saveResponse(responseWrapper, requestDto);
} catch (ServletException | IOException se) {
responseError(_3002, response, se);
}
}
}
private void saveResponse(ContentCachingResponseWrapper responseWrapper, HttpRequestDto requestDto) {
try {
HttpResponseDto responseDto = new HttpResponseDto();
responseDto.setStatus(responseWrapper.getStatus());
byte[] responseArray = responseWrapper.getContentAsByteArray();
String responseBody = new String(responseArray, responseWrapper.getCharacterEncoding());
responseDto.setBody(responseBody);
responseWrapper.copyBodyToResponse();
authorizationService.seveInDatabase(requestDto, responseDto);
} catch (Exception e) {
log.error("Error ServletException | IOException in CustomAuthorizationFilter.saveResponse", e);
}
}
private void responseError(LogCode code, HttpServletResponse response, Exception e) {
try {
Map<String, Object> error = new HashMap<>();
error.put("log", LogUtil.getLog(code));
error.put("message", e.getMessage());
response.setContentType(APPLICATION_JSON_VALUE);
new ObjectMapper().writeValue(response.getOutputStream(), error);
e.printStackTrace();
} catch (IOException ie) {
log.error("Error IOException in HttpLoggingFilter.responseError:", ie);
}
}
public String getRequestBody() {
return requestBody;
}
public void setRequestBody(String requestBody) {
this.requestBody = requestBody;
}
}
So my solution was use getter and setter methods of de local attribute requestBody, for validate if this is or not null and does not call again getReader() method because save in memory when set value. This worked perfect for me. Regards.

Related

Sling Filter for Wrapping a Request Body

Use Case:
We are developing an AEM Closed User Group site where users will need to submit forms which trigger workflows. Since the users are authenticated, part of the workflow payload needs to include the user who initiated the form.
I'm considering using AEM Forms for this, which saves to nodes under /content/usergenerated/content/forms/af/my-site but the user is not mentioned in the payload (only the service user). In this case, there are two service users: workflow-service running the workflow, and fd-service which handled the form processing and initial saving. E.G. the following code called from the workflow step reports 'fd-service'
workItem.getWorkflowData().getMetaDataMap().get("userId", String.class);
To work around this constraint,
Workflow initiated from publish AEM instance: All workflow instances are created using a service user when adaptive forms, interactive communications, or letters are submitted from AEM publish instance. In these cases, the user name of the logged-in user is not captured in the workflow instance data.
I am adding a filter servlet to intercept the initial form submission before the AEM Forms servlet using a request wrapper to modify the request body adding the original userID.
In terms of forms, workflows and launchers.. This is basically the setup I have
https://helpx.adobe.com/aem-forms/6/aem-workflows-submit-process-form.html
I have reviewed the following resources:
How to change servlet request body in java filter?
https://coderanch.com/t/364591/java/read-request-body-filter
https://gitter.im/Adobe-Consulting-Services/acs-aem-commons?at=5b2d59885862c35f47bf3c71
https://helpx.adobe.com/experience-manager/6-4/forms/using/forms-workflow-osgi-handling-user-data.html
Here is the code for my wrapper
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.wrappers.SlingHttpServletRequestWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.ServletInputStream;
import java.io.*;
public class FormSubmitRequestWrapper extends SlingHttpServletRequestWrapper {
String requestPayload;
private static final Logger log = LoggerFactory.getLogger(FormSubmitRequestWrapper.class);
public FormSubmitRequestWrapper(SlingHttpServletRequest slingRequest) {
super(slingRequest);
// read the original payload into the requestPayload variable
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
// read the payload into the StringBuilder
InputStream inputStream = slingRequest.getInputStream();
if (inputStream != null) {
bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[128];
int bytesRead = -1;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, 0, bytesRead);
}
} else {
// make an empty string since there is no payload
stringBuilder.append("");
}
} catch (IOException ex) {
log.error("Error reading the request payload", ex);
} finally {
if (bufferedReader != null) {
try {
bufferedReader.close();
} catch (IOException iox) {
log.error("Error closing bufferedReader", iox);
}
}
}
requestPayload = stringBuilder.toString();
}
/**
* Override of the getInputStream() method which returns an InputStream that reads from the
* stored requestPayload string instead of from the request's actual InputStream.
*/
#Override
public ServletInputStream getInputStream ()
throws IOException {
final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(requestPayload.getBytes());
ServletInputStream inputStream = new ServletInputStream() {
public int read ()
throws IOException {
return byteArrayInputStream.read();
}
};
return inputStream;
}
}
Here is my filter
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.engine.EngineConstants;
import org.osgi.framework.Constants;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.jcr.Session;
import javax.servlet.*;
import java.io.IOException;
#Component(service = Filter.class,
immediate = true,
property = {
Constants.SERVICE_DESCRIPTION + "=Add the CUG userID to any UGC posts",
EngineConstants.SLING_FILTER_SCOPE + "=" + EngineConstants.FILTER_SCOPE_REQUEST,
Constants.SERVICE_RANKING + ":Integer=3000",
EngineConstants.SLING_FILTER_PATTERN + "=/content/forms/af/my-site.*"
})
public class DecorateUserGeneratedFilter implements Filter {
private static final Logger log = LoggerFactory.getLogger(DecorateUserGeneratedFilter.class);
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse ) response;
final SlingHttpServletRequest slingRequest= (SlingHttpServletRequest) request;
FormSubmitRequestWrapper wrappedRequest = new FormSubmitRequestWrapper(slingRequest);
log.info("starting ConfirmAlumniStatus workflow");
log.info(getCurrentUserId(slingRequest));
chain.doFilter(wrappedRequest, slingResponse);
}
#Override
public void destroy() {
}
public String getCurrentUserId(SlingHttpServletRequest request) {
ResourceResolver resolver = request.getResourceResolver();
Session session = resolver.adaptTo(Session.class);
String userId = session.getUserID();
return userId;
}
}
When POST submissions get processed by this filter, I'm getting the error below stating the request body has already been read. So it seems the filter ranking might not be high enough.
25.06.2018 13:11:13.200 ERROR [0:0:0:0:0:0:0:1 [1529946669719] POST /content/forms/af/my-site/request-access/jcr:content/guideContainer.af.internalsubmit.jsp
HTTP/1.1] org.apache.sling.engine.impl.SlingRequestProcessorImpl
service: Uncaught Throwable java.lang.IllegalStateException: Request
Data has already been read at
org.apache.sling.engine.impl.request.RequestData.getInputStream(RequestData.java:669)
at
org.apache.sling.engine.impl.SlingHttpServletRequestImpl.getInputStream(SlingHttpServletRequestImpl.java:292)
at
javax.servlet.ServletRequestWrapper.getInputStream(ServletRequestWrapper.java:136)
at
my.site.servlets.FormSubmitRequestWrapper.(FormSubmitRequestWrapper.java:26)
at
my.site.servlets.DecorateUserGeneratedFilter.doFilter(DecorateUserGeneratedFilter.java:75)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:73)
at
com.cognifide.cq.includefilter.DynamicIncludeFilter.doFilter(DynamicIncludeFilter.java:82)
at
org.apache.sling.engine.impl.filter.AbstractSlingFilterChain.doFilter(AbstractSlingFilterChain.java:68)
at
org.apache.sling.engine.impl.debug.RequestProgressTrackerLogFilter.doFilter(RequestProgressTrackerLogFilter.java:10
I don't think the service ranking is working. When I view
http://localhost:4502/system/console/status-slingfilter
my filter is listed as shown. Judging from the other filters listed, I think the leftmost number is the filter ranking. For some reason my filter is ranked 0 even though I set is as service.ranking=700
0 : class my.site.servlets.DecorateUserGeneratedFilter (id:
8402, property: service.ranking=700); called: 0; time: 0ms; time/call:
-1µs
Update: I was able to fix the filter rank, making it 700 still gave the IllegalStateException. Making it 3000 made that problem go away. But when request.getInputStream() is called from my wrapper. It returns null.
What you are trying to do might be the easy route, but might not be future-proof for new AEM releases.
You need total control of how your workflow is triggered!:
Your forms should have a field that contains the workflow path (and maybe other information needed for that workflow)
Create a custom servlet that your forms will post to.
In that servlet process all user posted values (from the form). But especially get a hold of the intended workflow path and trigger it using the workflow API.
This way you don't have to mess with launchers and the workflows are triggered by your users using their user id.
Hope this helps.
Right idea, wrong location.
The short answer is that when you implement the SlingHttpServletRequestWrapper it provides a default handling of method calls to the original SlingHttpServletRequest if you're adding a parameter on the fly what you want to do is to make sure that the methods that are interacting with the parameters are overridden so that you can be sure yours is added. So on initialization, call the original parameter map, copy those items in a new map which includes your own values.
Then over ride any methods that would request those values
getParameter(String)
getParameterMap()
getParameterNames()
getParameterValues(String)
Don't touch the InputStream, that's already been processed to obtain any parameters that are being passed in.
Additionally, that is one of two ways you can handle this type of use case, the other option is to use the SlingPOSTProcessors as documented
https://sling.apache.org/documentation/bundles/manipulating-content-the-slingpostservlet-servlets-post.html
which allows you to detect what is being written to the repository and modify the data to include, like your case, an additional field.
if you are looking for code example :
#SlingFilter(order = 1)
public class MyFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
return;
}
#Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
ServletRequest request = servletRequest;
if (servletRequest instanceof SlingHttpServletRequest) {
final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) servletRequest;
request = new SlingHttpServletRequestWrapper(slingRequest) {
String userId = getCurrentUserId(slingRequest);
};
}
filterChain.doFilter(request, servletResponse);
}
#Override
public void destroy() {
return;
}

Save request and response with session id for each request in a text file, REST API Spring Boot

Need help in saving request and response of a REST API in below way,
Session ID:
Request:
{
{
request header
}
{
request body
}
}
Response:
{
{
response header
}
{
response body
}
}
This shouldn't depend on the logging level or any other logging related concepts.
Checked many similar questions but no answers for them,
Can any one help me in this please, thank you.
Spring Boot - How to log all requests and responses with exceptions in single place?
You could use the HandlerInterceptorAdapter, and write the informations you need on your file :
Spring provides a mechanism for configuring user-defined interceptors
to perform actions before and after web requests.
Among the Spring request interceptors, one of the noteworthy
interfaces is HandlerInterceptor, which can be used to log the
incoming request by implementing the following methods:
preHandle() – this method is executed before the actual controller
service method afterCompletion() – this method is executed after the
controller is ready to send the response Furthermore, Spring provides
the default implementation of HandlerInterceptor interface in the form
of HandlerInterceptorAdaptor class which can be extended by the user.
Let’s create our own interceptor – by extending
HandlerInterceptorAdaptor as:
#Component public class TaxiFareRequestInterceptor extends
HandlerInterceptorAdapter {
#Override
public boolean preHandle(
HttpServletRequest request,
HttpServletResponse response,
Object handler) {
return true;
}
#Override
public void afterCompletion(
HttpServletRequest request,
HttpServletResponse response,
Object handler,
Exception ex) {
//
} }
http://www.baeldung.com/spring-http-logging
http://www.baeldung.com/spring-mvc-handlerinterceptor
I found answer from Gist https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba
Logging both request and response. Made some minor changes based on my requirement to write into a file instead of logging using java 7 feature.
Path path = Paths.get("home/midoriya/sample.txt");
String strValue = "Whatever the values want to write in file";
Path path = Paths.get(fileName);
byte[] bytes = strValue.getBytes();
Files.write(path, bytes);
or
FileWriter fw = null;
BufferedWriter writer = null;
// File logFile = null;
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
try {
LocalDate localDate = LocalDate.now();
File logFile = new File("/home/ramesh/logReqRes"+localDate.getDayOfMonth()+localDate.getMonth()+".txt");
boolean flag = logFile.createNewFile();
System.out.println("flag :" + flag);
if( flag || logFile.length() >= (1024*1024*1024))
fw = new FileWriter(logFile, false);
else
fw = new FileWriter(logFile, true);
writer = new BufferedWriter(fw);
if (isAsyncDispatch(request)) {
filterChain.doFilter(request, response);
} else {
doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
}
} catch (IOException io) {
io.printStackTrace();
}
catch (Exception ex) {
ex.printStackTrace();
}
finally {
try {
if (writer != null)
writer.close();
if (fw != null)
fw.close();
} catch (IOException ex) {
ex.printStackTrace();
}
}

Get opened input stream from rest template for large file processing

I am looking for a way to get opened input stream from rest template - I was trying to used ResponseExtractor, but the stream is getting closed before returning, as written here:
https://jira.spring.io/browse/SPR-7357
"Note that you cannot simply return the InputStream from the extractor, because by the time the execute method returns, the underlying connection and stream are already closed"
I hope that there is a way and I will not have to write to my output stream directly in the rest template.
I didn't find a way to do it, the stream is always getting closed. As a workaround I created the following code:
public interface ResourceReader {
void read(InputStream content);
}
with the following implementation:
public class StreamResourceReader implements ResourceReader {
private HttpServletResponse response;
public StreamResourceReader(HttpServletResponse response) {
this.response = response;
}
#Override
public void read(InputStream content) {
try {
IOUtils.copy(content, response.getOutputStream());
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
then in controller:
#RequestMapping(value = "document/{objectId}")
public void getDocumentContent(#PathVariable String objectId, HttpServletResponse response) {
ResourceReader reader = new StreamResourceReader(response);
service.readDocumentContent(objectId, reader);
}
call to rest template:
restTemplate.execute(uri, HttpMethod.GET, null,
new StreamResponseExtractor(reader));
and the string response extractor:
#Override
public ResponseEntity extractData(ClientHttpResponse response) throws IOException {
reader.read(response.getBody());
return null;
}
and it works like a charm! :)

I can't get cross domain ajax working with Weblogic/Spring 3

I am having trouble trying to get cross-domain ajax request working and despite the many solutions I have found on Stack Overflow I am unable to get it working.
$.ajax({
url : 'http://SERVER:PORT/CONTEXT/RESOURCE.html?someParameter=1234',
dataType : 'json',
success: function(xhr) {
alert('ok '+JSON.stringify(xhr));
},
error : function(xhr) {
alert('error '+JSON.stringify(xhr));
}
});
Doing just a standard $.ajax call with datatype "json" the server responds with a blank response and statusText "error", like so:
error {"readyState":0,"responseText":"","status":0,"statusText":"error"}
So I tried simply changing datatype to "jsonp" as suggested in other threads but this time it still goes to error condition with the following response:
error {"readyState":4,"status":200,"statusText":"success"}
and an error message of "parsererror"
Yet no data.
What gives?
Do I need to do something special on the server side because it is Spring MVC in Weblogic?
EDIT:
jQuery version 1.9.1
Spring-3 MVC
EDIT2: Oh yes I also tried $.getJSON but this command seems to do nothing - when I run the code replacing $.ajax with $.getJSON nothing happens. No response and I dont see any error occurring in console and no Network request seen going to the URL. I did also change the syntax in a 2nd try where I called it like $.getJSON(url, callback); but that did not change anything
EDIT3: I should also mention when I run the original code using "json" datatype and look in Firebug's Response tab, it is empty. But when I run the second code using "jsonp" I do see the JSON text in the Response tab. So it is strange why it still throws an error.
OK, upon more research I finally found the cause - yes I did need to do something on the Server side to support jsonp. I ended up writing a servlet filter which wraps the returning json string in the appropriate callback.
Learn something new everyday!
public class JsonPCallbackFilter extends OncePerRequestFilter {
Logger logger = Logger.getLogger(JsonPCallbackFilter.class);
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
//logger.debug("Filter: "+request.getRequestURI());
#SuppressWarnings("unchecked")
Map<String, String[]> parms = request.getParameterMap();
if(parms.containsKey("callback")) {
logger.debug("Wrapping response with JSONP callback '" + parms.get("callback")[0] + "'");
OutputStream out = response.getOutputStream();
ByteResponseWrapper wrapper = new ByteResponseWrapper(response);
chain.doFilter(request, wrapper);
StringBuffer sb = new StringBuffer();
sb.append(parms.get("callback")[0] + "(");
sb.append(new String(wrapper.getBytes()));
sb.append(new String(");"));
out.write(sb.toString().getBytes());
wrapper.setContentType("text/javascript;charset=UTF-8");
response.setContentLength(sb.length());
out.close();
} else {
chain.doFilter(request, response);
}
}
}
static class ByteOutputStream extends ServletOutputStream {
private ByteArrayOutputStream bos = new ByteArrayOutputStream();
#Override
public void write(int b) throws IOException {
bos.write(b);
}
public byte[] getBytes() {
return bos.toByteArray();
}
}
static class ByteResponseWrapper extends HttpServletResponseWrapper {
private PrintWriter writer;
private ByteOutputStream output;
public byte[] getBytes() {
writer.flush();
return output.getBytes();
}
public ByteResponseWrapper(HttpServletResponse response) {
super(response);
output = new ByteOutputStream();
writer = new PrintWriter(output);
}
#Override
public PrintWriter getWriter() {
return writer;
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
return output;
}
}
<filter>
<filter-name>jsonpFilter</filter-name>
<filter-class>com.blahblah.JsonPCallbackFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>jsonpFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

Unhandled JSP exception in Filter

I have a filter configured for a JSP. In the filter I am using HTTPResponseWrapper for modifying the response to the user. My code in doFilter method of Filter is:
CharArrayWrapper responseWrapper = new CharArrayWrapper(
(HttpServletResponse) response);
chain.doFilter(request, responseWrapper);
String responseString = responseWrapper.toString();
System.out.println("responseString ********************* "
+ responseString);
PrintWriter out = response.getWriter();
out.write(responseString);
The CharArrayWrapper class is:
public class CharArrayWrapper extends HttpServletResponseWrapper {
private CharArrayWriter charWriter;
public CharArrayWrapper(HttpServletResponse response) {
super(response);
charWriter = new CharArrayWriter();
}
public PrintWriter getWriter() {
return (new PrintWriter(charWriter));
}
public String toString() {
return (charWriter.toString());
}
public char[] toCharArray() {
return (charWriter.toCharArray());
}
}
If the JSP for which the above filter is configured executes fine, there is no problem in the filter too and I can see the sysout on the console.
On the other hand, when there is some unhandled exception raised in the JSP scriptlet code (its an old application) and I invoke chain.doFilter(request, responseWrapper);, I get a NullPointerException. Is there any way by which I can know in my filter that there was an unhandled exception raised in the JSP
You can simply wrap the code in a try catch block like this:
CharArrayWrapper responseWrapper = new CharArrayWrapper((HttpServletResponse) response);
try {
chain.doFilter(request, responseWrapper);
String responseString = responseWrapper.toString();
System.out.println("responseString ********************* " + responseString);
PrintWriter out = response.getWriter();
out.write(responseString);
} catch (Exception e) {
// do whatever is necessary (logging etc.)
}
There are some other things to consider:
getWriter always creates a new PrintWriter, this may lead to some side effects as other components might call getWriter as well. I'd create an instance variable.
the character array approach has one disadvantage, you 'cache' the whole response instead of streaming it back to the client, given large JSPs this might have impact on your JVM heap.

Categories