I have implemented servlet which behaves not stable, sometimes it mixes header in content and writing same twice.
and sometimes it is returning file which contains response header mixed by content like this:
Server: Apache-Coyote/1.1
: W/"43-1353687036000"
DatCCoonntenntt--DDiissppoosittiioonn: : atatatacehnmte;n tf;i lfenlaemnea=m20=12201112211127325421_4W1_Wirnkgi_nSgc_Seern.xnlsx
sx
Content-Typ-eT: ype: applaipcatciaoti/on/toctestt-rstare
am
ConCtoententy-pTeype: appalicatcion/oon/octet-setarm
m
CCoonntent-Lnegtht h: 4199
Date: te: FriF,r i2,3 2No vNo2v0 120162: 215:25 :G4M2T
....
File content bytes ...
And again same header and content
UPDATE
*This situation happens on Tomcat7*
I have tested also on Tomcat6 and Jetty, in both cases there is no injection of HTTP-Header to response content but HTTP-Header is wrong and returns wrong file name, the file content is correct file. I have noticed that wrong return from servlet happens when
returns transfer-encoding is chunked.
When I am removing header stuff, and second part of bytes, it is valid file.
Is it possible that is synchronization issue ?
UPDATE
Here is full source of servlet :
public class ExcelDownloadServlet extends HttpServlet
{
private static final long serialVersionUID = 1L;
private static final Logger LOG = Logger
.getLogger (ExcelDownloadServlet.class);
#Override
protected void doGet (HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException
{
try
{
TransactionId transactionId = getTransactionId (request);
String fileName =
request.getParameter (GlobalConstants.EXCEL_FILE);
ExcelDownloadType downloadType =
ExcelDownloadType
.valueOf (request
.getParameter (GlobalConstants.EXCEL_DOWNLOAD_TYPE));
ActionContextFactory actionContextFactory =
ApplicationContext.getContext ()
.getActionContextFactory ();
//suppress warning. HttpServletRequest.getLocales does not support generics
#SuppressWarnings("unchecked")
ActionContext actionContext =
actionContextFactory.create (request.getSession ()
.getId (), Collections.<Locale> list (request
.getLocales ()));
GetExcelDataResponse dataResponse =
new GetExcelData (transactionId, fileName, downloadType)
.execute (actionContext);
writeToResponse (response, dataResponse.getFileName (),
dataResponse.getData ());
}
catch (InvalidSessionException e)
{
LOG.error ("Invalid session in Excel download", e);
throw new ServletException (e);
}
catch (ActionException e)
{
LOG.error ("Could not download into excel.", e);
throw new ServletException (e);
}
}
protected TransactionId getTransactionId (HttpServletRequest request)
{
return RequestParameterDeserializer.<TransactionId> deserialize (
request, GlobalConstants.TRANSACTION_ID);
}
protected void writeToResponse (HttpServletResponse response,
String rawFileName, byte[] data) throws IOException
{
ServletOutputStream sout = null;
try
{
response.setContentType ("application/octet-stream");
response.setContentLength (data.length);
// removing blanks from the file name, since FF cuts file names
// otherwise.
String fileNameWithTime = rawFileName.replaceAll (" ", "_");
response.setHeader ("Content-Disposition", "attachment; filename="
+ fileNameWithTime);
sout = response.getOutputStream ();
sout.write (data, 0, data.length);
}
finally
{
if (sout != null)
{
sout.close ();
}
}
}
UPDATE
*The call comes from GWT application when is generating the URL of servlet with required parameters and sets in IFrame, then servlet calls and file is downloading. Are there any suggestions ?*
I had a similar issue a long time ago.
It turned out that closing the ServletOutputStream triggered an unexpected behaviour on the request flow.
Servlets are not supposed to close the container provided OutputStream.
Another issue could be manually setting the content length, it is responsibility of the container producing the correct value.
To summarize, try removing out.close() and response.setContentLength()
Related
Good evening, i want to know how to clear the data written to a PrintWriter, i.e. is it possible to remove the data from a PrintWriter after printing?
here in this servlet i print some text to the response and at the line denoted by # i want to remove all the previously printed data and print new stuff:
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
String uName = request.getParameter("uName");
String uPassword = request.getParameter("uPassword");
if (uName .equals("Islam")) {
out.println("Valid-Name");
if (uPassword !=null) {
if (uPassword .equals("Islam")) {
// # clear the writer from any printed data here
out.println("Valid-password");
} else {
out.println("");
out.println("InValid-password");
}
}
} else {
out.println("InValid-Name");
}
}
Note: i tried out.flush() but the old printed text remains
Create an in-memory PrintWriter using a StringWriter. You can get the underlying buffer from the StringWriter and clear it if you need to.
StringWriter sr = new StringWriter();
PrintWriter w = new PrintWriter(sr);
w.print("Some stuff");
// Flush writer to ensure that it's not buffering anything
w.flush();
// clear stringwriter
sr.getBuffer().setLength(0);
w.print("New stuff");
// write to Servlet out
w.flush();
response.getWriter().print(sr.toString());
HttpServlteResponse.resetBuffer() will clear the buffered content. But yes, if the response is already flushed to the client it will throw IllegalStateException. Because it is illegal to clear after partial response is sent to the client.
resetBuffer........
void resetBuffer()
Clears the content of the underlying buffer in the response without clearing headers or status code. If the response has been committed, this method throws an IllegalStateException.
References:
Cause of Servlet's 'Response Already Committed'
You can't do that with the original PrintWriter you get from the response, as that's backed by the actual OutputStream corresponding to the client connection. What you write there goes right to the browser via the wire (after some buffering), so you can't "take it back".
What you can do is write your message in some StringBuilder and once you know it's good to go, write it to the PrintWriter.
If you want this logic to be applied in multiple places (transparently), you can consider writing a filter that wraps the original response in an HttpServletResponseWrapper which returns a "fake" OutputStream or PrintWriter and performs this check prior to actually sending it over the wire.
public class CensorshipFilter implements Filter {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
CensorshipResponseWrapper wrapper = new CensorshipResponseWrapper(httpServletResponse);
chain.doFilter(request, wrapper);
String output = wrapper.sw.toString();
if ( output.contains("Some forbidden pattern") ) { // your check goes here
// throw exception or whatever
} else { // write the whole thing
httpServletResponse.getWriter().write(output);
}
}
#Override
public void destroy() {
}
static class CensorshipResponseWrapper extends HttpServletResponseWrapper {
private final StringWriter sw = new StringWriter();
public CensorshipResponseWrapper(HttpServletResponse response) {
super(response);
}
#Override
public ServletOutputStream getOutputStream() throws IOException {
// you may also fake the output stream, if some of your servlets use this method
return super.getOutputStream();
}
#Override
public PrintWriter getWriter() throws IOException {
return new PrintWriter(sw);
}
}
}
What ended up working for me was to change the logic of how I was outputting my data.
This is the data structure I was outputting that stored the results of a search using the text from a html form as input.
private final TreeMap<String, ArrayList<SearchResult>> searchResults;
So I was iterating over the contents of this data structure and printing it out to html.
public void writeSearchResultsToHtml(PrintWriter writer)
{
try
{
JSONTreeWriter. writeSearchResultsToHtml(searchResults, writer);
} catch (ArithmeticException | IllegalArgumentException | IOException | NoSuchElementException e)
{
System.err.println("Unable to write the search results builder to JSON to the file html.");
}
// clear results for next search otherwise
// the next search will contain the previous
// results, store them in history.
searchResults.clear();
}
Clearing the data structure worked great given my servlet setup.
Here was my main serverlet loop logic:
public void startServer()
{
// seed the database for testing
crawler.startCrawl("http://cs.usfca.edu/~cs212/birds/birds.html");
index.toJSON("index.json");
// type of handler that supports sessions
ServletContextHandler servletContext = null;
// turn on sessions and set context
servletContext = new ServletContextHandler(ServletContextHandler.SESSIONS);
servletContext.setContextPath("/");
servletContext.addServlet(ViewServlet.class, "/");
// default handler for favicon.ico requests
DefaultHandler defaultHandler = new DefaultHandler();
defaultHandler.setServeIcon(true);
ContextHandler defaultContext = new ContextHandler("/favicon.ico");
defaultContext.setHandler(defaultHandler);
// setup handler order
HandlerList handlers = new HandlerList();
handlers.setHandlers(new Handler[]{defaultContext, servletContext});
openWebBrowser();
// setup jetty server
Server server = new Server(portNumber);
server.setHandler(handlers);
try
{
server.start();
server.join();
} catch (Exception e)
{
e.printStackTrace();
}
}
I need to log the full http request and response in a JAX-WS WebService call. For the request I need the request headers and the body and for the response, response headers and body.
After some researching, I've found that I can get this information with the property:
-Dcom.sun.xml.ws.transport.http.client.HttpTransportPipe.dump=true
and show the information that I need but it dumps it to the console and I need to store it in the database with an internal request id.
I've tried to implement a handler:
public class LoggingHandler implements SOAPHandler<SOAPMessageContext> {
#Override
public boolean handleMessage(SOAPMessageContext context) {
Boolean outbound = (Boolean) context.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
if (outbound) {
System.out.println("SOAP outbound!!!!!");
Map<String, List<String>> responseHeaders = (Map<String, List<String>>) context
.get(SOAPMessageContext.HTTP_RESPONSE_HEADERS);
try {
String headers = getHeaders(responseHeaders);
System.out.println(headers);
String body = getBody(context.getMessage());
System.out.println(body);
} catch (Exception ex) {
// TODO: What do I have to do in this case?
}
} else {
System.out.println("SOAP inbound!!!!!");
Map<String, List<String>> requestHeaders = (Map<String, List<String>>) context
.get(SOAPMessageContext.HTTP_REQUEST_HEADERS);
try {
String headers = getHeaders(requestHeaders);
System.out.println(headers);
String body = getBody(context.getMessage());
System.out.println(body);
} catch (Exception ex) {
// TODO: What do I have to do in this case?
}
}
return true;
}
private String getBody(SOAPMessage message) throws SOAPException, IOException {
OutputStream stream = new ByteArrayOutputStream();
message.writeTo(stream);
return stream.toString();
}
public String getFullHttpRequest(HttpServletRequest request) throws IOException {
InputStream in = request.getInputStream();
String encoding = request.getCharacterEncoding();
encoding = encoding == null ? "UTF-8" : encoding;
String body = IOUtils.toString(in, encoding);
return body;
}
private String getHeaders(Map<String, List<String>> headers) throws IOException {
StringBuffer result = new StringBuffer();
if (headers != null) {
for (Entry<String, List<String>> header : headers.entrySet()) {
if (header.getValue().isEmpty()) {
// I don't think this is legal, but let's just dump it,
// as the point of the dump is to uncover problems.
result.append(header.getValue());
} else {
for (String value : header.getValue()) {
result.append(header.getKey() + ": " + value);
}
}
result.append("\n");
}
}
return result.toString();
}
}
but in this case, I can get the http request headers and body but in the response, I only get the body, http response headers are always empty.
Any idea on how to archieve this? The objective is to be able to store the full http request and response in a database.
Thanks!!
You could also try
-Dcom.sun.xml.ws.transport.http.HttpAdapter.dump=true
I'm assuming you're providing your web service from within a Java EE application server of some sort (and not from a standalone client). You cannot have access to Java EE infrastructure like HttpServletRequest and HttpServletResponse outside of the context of a web/Java EE container.
You could try to get your hands on the actual servlet response object (within a web context) with
HttpServletResponse response = (HttpServletResponse) messageContext.get(SOAPMessageContext.SERVLET_RESPONSE); //messageContext is the SOAPMessageContext
List<String> responseHeaderNames = (List<String>)response.getHeaderNames();
for(String headerName : responseHeaderNames){
//Do whatever you want with it.
}
I seriously doubt that you'll be able to get your hands on the full response headers within a handler though. Your question really intrigued me and I've spent quite some time researching that part. In all the code samples I've seen, Not even the example on the metro site attempt to implement this functionality and I think the reason is simple. As at the point where a handler is invoked, the container may not have enough definitive information to stamp an http header on the outbound message. You might be able to add stuff but that's doubtful as well.
When I try using standard servlet approach, in my browser the popup window shows up asking me whether to open .xls file or save it.
I tried the exactly same code via JAX-RS and the browser popup won't show up somehow. Has anyone encounter this?
JAX-RS way that won't display popup:
#Path("excellaTest")
public class ExcellaTestResource {
#Context
private UriInfo context;
#Context
private HttpServletResponse response;
#Context
private HttpServletRequest request;
public ExcellaTestResource() {
}
#Path("horizontalProcess")
#GET
//#Produces("application/vnd.ms-excel")
#Produces("application/vnd.ms-excel")
public void getProcessHorizontally() {
try {
URL templateFileUrl = this.getClass().getResource("myExcelTemplate.xls");
String templateFilePath = URLDecoder.decode(templateFileUrl.getPath(), "UTF-8");
String outputFileDir = "MasatoExcelHorizontalOutput";
ReportProcessor reportProcessor = new ReportProcessor();
ReportBook outputBook = new ReportBook(templateFilePath, outputFileDir, ExcelExporter.FORMAT_TYPE);
ReportSheet outputSheet = new ReportSheet("myExcelSheet");
outputBook.addReportSheet(outputSheet);
reportProcessor.addReportBookExporter(new OutputStreamExporter(response));
reportProcessor.process(outputBook);
System.out.println("done!!");
}
catch(Exception e) {
System.out.println(e);
}
return;
}
}//end class
class OutputStreamExporter extends ReportBookExporter {
private HttpServletResponse response;
public OutputStreamExporter(HttpServletResponse response) {
this.response = response;
}
//ReportProcessor output()
//This method is call when ReportProcessor process() is invoked.
//The Workbook from POI API can be used to write to stream
#Override
public void output(Workbook book, BookData bookdata, ConvertConfiguration configuration) throws ExportException {
//TODO write to stream
try {
response.setContentType("application/vnd.ms-excel");
response.setHeader("Content-Disposition", "attachment; filename=masatoExample.xls");
book.write(response.getOutputStream());
response.getOutputStream().close();
System.out.println("booya!!");
}
catch(Exception e) {
System.out.println(e);
}
}
}//end class
What JAX-RS framework are you using?
My guess is that your code doesn't work, because you are returning void. The framework you are using probably recognizes void as HTTP 204 No Content. This causes browser to skip the actual response body and to ignore conntent-disposition header.
As I already wrote you in a parallel thread: try to returning Response object. You can put either OutputStream or byte[] as entity as set the content-disposition header.
I have never used class level injection for a jax-rs service. I suggest one of 2 solutions.
1) Try injecting the request and response as method arguments.
2) Output your file to a byte array output stream and return a byte array from your method instead of void.
I have the following code...
#Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.doGet(req, resp);
final int idValue = Integer.parseInt(req.getParameter("id"));
final ProjectRunEntity projectRunEntity = projectDataService.findProjectRunEntity(idValue);
try {
final byte[] documentAsBytes = wordFileGenerationService.getDocumentAsBytes(projectRunEntity);
resp.setContentType("application/msword");
resp.setHeader("Content-Disposition", "inline; filename=example.doc;");
final ServletOutputStream out = resp.getOutputStream();
out.write(documentAsBytes);
out.flush();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
Which gets some bytes which happen to be a word document and write them to the servlet response. For some reason I get the following message in my browser when I hit the url...
"HTTP Status 405 - HTTP method GET is
not supported by this URL"
I am on Tomcat 6. Any ideas? I know that nothing is breaking in my debugger, the bytes are being written to the outputstream of the response.
I guess the error is thrown by the default doGet implementation (when you call super.doGet(req, resp)).
That status is set in super.doGet(...). Please remove that call.
the do{Http-Method} methods are meant to be overridden. And their default implementation is "not supported". No need to call the super.do{http-Method}
I just had to remove this line...
super.doGet(req, resp);
However, I want to compress my responses with GZIP wheren possible. I tried using the Compression filter code available for free download in the headfirst site. It works great for html, images, css and javascript.
I post the filter next. It checks if GZIP is an accepted encoding and it adds gzip as Content-Encoding. See: wrappedResp.setHeader("Content-Encoding", "gzip");
public class CompressionFilter implements Filter {
private ServletContext ctx;
private FilterConfig cfg;
/**
* The init method saves the config object and a quick reference to the
* servlet context object (for logging purposes).
*/
public void init(FilterConfig cfg)
throws ServletException {
this.cfg = cfg;
ctx = cfg.getServletContext();
//ctx.log(cfg.getFilterName() + " initialized.");
}
/**
* The heart of this filter wraps the response object with a Decorator
* that wraps the output stream with a compression I/O stream.
* Compression of the output stream is only performed if and only if
* the client includes an Accept-Encoding header (specifically, for gzip).
*/
public void doFilter(ServletRequest req,
ServletResponse resp,
FilterChain fc)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
// Dose the client accept GZIP compression?
String valid_encodings = request.getHeader("Accept-Encoding");
if ( (valid_encodings != null) && (valid_encodings.indexOf("gzip") > -1) ) {
// Then wrap the response object with a compression wrapper
// We'll look at this class in a minute.
CompressionResponseWrapper wrappedResp = new CompressionResponseWrapper(response);
// Declare that the response content is being GZIP encoded.
wrappedResp.setHeader("Content-Encoding", "gzip");
// Chain to the next component (thus processing the request)
fc.doFilter(request, wrappedResp);
// A GZIP compression stream must be "finished" which also
// flushes the GZIP stream buffer which sends all of its
// data to the original response stream.
GZIPOutputStream gzos = wrappedResp.getGZIPOutputStream();
gzos.finish();
// The container handles the rest of the work.
//ctx.log(cfg.getFilterName() + ": finished the request.");
} else {
fc.doFilter(request, response);
//ctx.log(cfg.getFilterName() + ": no encoding performed.");
}
}
public void destroy() {
// nulling out my instance variables
cfg = null;
ctx = null;
}
}
I was using the next code to send JSON responses in Struts web application.
public ActionForward get(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response) {
JSONObject json = // Do some logic here
RequestUtils.populateWithJSON(response, json);
return null;
}
public static void populateWithJSON(HttpServletResponse response,JSONObject json) {
if(json!=null) {
response.setContentType("text/x-json;charset=UTF-8");
response.setHeader("Cache-Control", "no-cache");
try {
response.getWriter().write(json.toString());
} catch (IOException e) {
throw new ApplicationException("IOException in populateWithJSON", e);
}
}
}
It works fine without compression but if I compress JSON responses, I can not see my JSON objects anymore. I handle JSON Ajax calls with JQuery with code snippets as follows:
$.post(url,parameters, function(json) {
// Do some DOM manipulation with the data contained in the JSON Object
}, "json");
If I see the response with Firebug it is empty.
Should I refractor my compression filter to skip compression in JSON responses? or there is a workaround to this?
For me, it looks like JQuery does not recognize the response as JSON because I am adding the Gzip compression.
If I see the response with Firebug it
is empty.
There's your clue - it's not a JQuery problem, it's server-side. (I'm afraid I can't help you with that, other than to suggest you stop looking at the client-side)
There's no problem gzipping ajax responses - if you can't see the response in Firebug, then JQuery can't see it either.
you have to add one more header "content-encoding: gzip" if you are compressing it.
Have you tried with an explicit java-based client to ensure it's a problem with jQuery or browser? If java client fails, something is wrong with server response.
But I am guessing that whereas browser can deal with uncompression with direct requests, this is perhaps not applied to Ajax calls.
It's an interesting question, I hope we'll get a more definitive answer. :)