Struts is a MVC web framework. It allows you to specify the paths through a web application in an XML configuration file, and provides some nice tag libraries for JSP manipulation. The views for the states are usually JSPs, and the controller is a servlet provided for you.

This standard setup is fine, most of the time. But I’ve run into situations, as have others (1,2), where you need Struts to output a binary file. In my case, I’m sending a dynamic image to a non browser client, and I basically need to write a ByteArrayOutputStream to the response output stream.

Now, you’d think I’d be able to do this with a JSP. After all, a JSP is just a servlet turned inside out, right? Well, according to the specification, you’d be right. From page 42 of JSP 1.2 spec:

—————-
The JSP container should not invoke response.getWriter() until the time when the first portion of the content is to be sent to the client. This enables a number of uses of JSP, including using JSP as a language to glue actions that deliver binary content, or reliably forwarding to a servlet, or change dynamically the content type of the response before generating content. See Chapter JSP.3.
—————-

But, according to Tomcat 4.1.24 on the Linux platform, you’d be wrong. When calling ‘java.io.OutputStream rs = response.getOutputStream();’ in my JSP, I get this code generated:

—————

....snip...
      application = pageContext.getServletContext();
      config = pageContext.getServletConfig();
      session = pageContext.getSession();
      out = pageContext.getOut();
      _jspx_out = out;
java.io.OutputStream rs = response.getOutputStream();
....snip...

—————

The JSP is taking my stream before my code has a chance. Therefore, I get an “getOutputStream() has already been called for this response” error. The weird bit is that this doesn’t seem to happen on Tomcat 4.1.24 on Windows (same version of struts).

So, what do you do? You write a servlet instead. That way you have utter control over the output stream:

—————

import javax.servlet.http.*;
import javax.servlet.*;
import java.io.*;

public class BinaryStreamServlet extends HttpServlet {

   public void service(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
      String contentType =
(String)req.getAttribute("contentType");
      if (contentType == null || "".equals(contentType)) {
         contentType = "image/png"; // default
      }
      res.reset();
      res.setContentType(contentType);
      OutputStream sos = res.getOutputStream();
      ByteArrayOutputStream baos = (ByteArrayOutputStream)req.getAttribute("baos");
      baos.writeTo(sos);
   }
}

—————

I set up my action classes to cache the ByteArrayOutputStream in the request, with the name “baos.” I added these lines to my web.xml:

—————

<servlet>
      <servlet-name>binaryFileServlet</servlet-name>
       <servlet-class>BinaryStreamServlet</servlet-class>
  </servlet>
....snip...
  <servlet-mapping>
    <servlet-name>binaryFileServlet</servlet-name>
    <url-pattern>/binaryFile</url-pattern>
  </servlet-mapping>

—————

and this to my struts-config.xml for any actions that needed to be able to send binary data:

—————

<forward name="success"         path="/binaryFile"/>

—————

Works like a charm. Leave the JSPs to the character data, and use this simple servlet for binary delivery.

20 thoughts on “Sending Binary Data Via Struts

  1. Edson Watanabe says:

    Thank you for the solution for my problem. I have to do a generic download for Struts and tried the way of using getOutputStream(). It apparently worked, but generated 200 lines of stack traces for each buffer I sent to the browser. So I will try your servlet solution.

  2. Dan Moore says:

    Hey Edson,

    Glad I could help. I wish there was an easier way.

    Dan

  3. Justin says:

    Thanks, *a lot*. I was completely stuck till I found this page mentioning that TC4 on Linux works differently.

    J.

  4. Paul Richards says:

    It’s nice to know that my code is infact compliant and TC4 is just being a bit retarded.

    Maybe this will convince my smalltime ISP to upgrade.. 🙂

  5. kuzya says:

    THIS IS NOT A SOLUTION

    going thru servlet solves problem but it by-passes the whole struts framework. The question is how do we get it working thru struts?

  6. Dan says:

    Kuzya,

    I agree that this is not the optimal solution. I won’t say it bypasses struts entirely, since you’re still using the struts-config.xml to map the destination. It does miss giving you the internationalization and automagic error handling of struts. But, if it’s binary data, I can’t see what features of struts you’re needing.

  7. Gang says:

    I had the same situation that the pdf always came back as TEXT/HTML. After number of tests, I got it work with the following. The key is to flush() before you write to it. Maybe this is just a bug of the current struts version (1.1):

    public ActionForward execute(…) {

    response.setContentType(“application/pdf”);
    response.setHeader(“Content-disposition”, sbContentDispValue.toString());
    response.setContentLength(baosPDF.size());
    ServletOutputStream sos;
    sos = response.getOutputStream();
    sos.flush(); // this line made a big difference.
    baosPDF.writeTo(sos);
    sos.flush();
    sos.close();

    return null;
    }

  8. Gang says:

    I took the sos.fluch() off (the one before writeTo()) and tried again. Well, I could not reproduce the issue anymore. I got PDF generated directly from the Action class. It worked just like it was supposed to.

    I guess the issue I had before was just some kind of random problem on my computer. So, I’d like to recall the previous message. Please disregard it.

    Thanks,

  9. Martin Reurings says:

    Everything in this article rings true to the issue I had just solved and the info in here was quite helpfull in finding out what was wrong. However, there was a simple solution to fix this using jsp, even in tomcat. I’m not sure if it applies to the exact same versions, but, as the mentioned article comes up in google quite high in the ranking and is used in many resolutions of the mentioned exception I thought I’d like you know what I came to find.
    As most of us will have to import a few packages in the jsp page one may assume the first line of the jsp page is taken up by a page-directive, as was the case with me. Then after that follows a scriptlet section. Now the only problem is, most of us generally place a carriage return between the closing greater than ‘>’ character and the starting lesser than ‘<‘ character of the page-directive and the scriptlet section. This will cause tomcat to open the JspWriter. Making sure no characters existed outside of my directives and scriptlet-tags solved the problem without the need for writing a seperate servlet.

  10. Patrik Thornblad says:

    I would like to add a comment to your article “Sending Binary Data Via Struts”. I’m having the same problem as you have (had) with
    generating binary content from JSP in Struts. We’re using IBM WebSphere and are about to upgrade from WebSphere4 to WebSphere6.

    Everything worked fine in WAS4, but not now in WAS6. The binary data is generated and displayed on screen, but it is followed by ugly
    stack traces on the server console: “OutputStream already obtained”.

    But by inspecting the stack trace, I realised that the exception was *not* thrown when my code “response.getOutputstream” was executed.
    Instead, the exception was thrown after all the code in the JSP had finished executing, which explains why I first got the file and then
    was followed by the ugly stack trace.

    I also found the cause. WAS has a feature called “keepgenerated” which, if set to true, keeps the java code that is generated from the
    JSP. By inspecting the generated java code I discovered that WAS6 causes a \r\n to be written to the implicit variable “out” (of type
    JSPWriter):

    <jsp:useBean
    id=”actionForm”
    scope=”request”
    type=”mypackage.MyActionForm”>
    </jsp:useBean>

    The exception was thrown when WAS tried to flush “out” (containing the \r\n), *after* the binary data had been sent to the client.

    It didn’t help to write like this either:

    &lt%@ page

    %><jsp:useBean

    </jsp:useBean><% /** Java code **/ %>

    The solution was to replace the useBean tag with java code:

    <%@ page

    %><%

    mypackage.MyActionForm actionForm = null;
    actionForm = (mypackage.MyActionForm) request.getAttribute(“actionForm”);

    /** Java code **/ %>

  11. Patrik Thornblad says:

    I was actually wrong in my conclusion the useBean tag was resplonsible for the newline character. In fact, the JSP contained
    a newline character between the opening and closing useBean tag. By removing this, it worked fine with the useBean tag!

    Non-working jsp jsp:

    <%@ page

    %><jsp:useBean
    id=”actionForm”
    scope=”request”
    type=”mypackage.MyActionForm”>
    </jsp:useBean><% /** Java code **/ %>

    Working jsp:

    <%@ page

    %><jsp:useBean
    id=”actionForm”
    scope=”request”
    type=”mypackage.MyActionForm”
    ></jsp:useBean><% /** Java code **/ %>

  12. Sangeetha Sundaram says:

    Hi All,
    I too had a similiar problem when I try to open a file using struts action. I was getting response is already in use.

    I did the file reading and wrote it to the Outputstream and then retun null instead of any action. This solved my issue and everything is fine.

    It is just that we open a ouput stream in the action and when we forward to any jsp, the container tries to open an output stream to write the JSP contents.

  13. Arivazhagan kannan says:

    Use this code for sending binary data via STRUTS/Servlet/Jsp:

    File pdfFile = new File(pdfFileName);
    response.setContentType(BIRTSchedulerConstants.CONTENT_TYPE);
    response.setContentLength((int) pdfFile.length());

    response.setHeader(“Content-Disposition”, “attachment; filename=\”” + pdfFile.getName() + “\””);

    response.setHeader(BIRTSchedulerConstants.CACHE_CONTROL, BIRTSchedulerConstants.NO_CACHE);
    response.flushBuffer();
    OutputStream outStream = response.getOutputStream();
    byte[] buf = new byte[(int) pdfFile.length()];
    FileInputStream inStream = new FileInputStream(pdfFile);
    int sizeRead = 0;
    while ((sizeRead = inStream.read(buf, 0, buf.length)) > 0) {
    outStream.write(buf, 0, sizeRead);
    }
    inStream.close();
    outStream.flush();

    Note: In struts action, use the mapping.findforward(null). becoz flush() method will submit the data to response stream. so if u redirect to another page then u will get response.getOutputStream() been already called error.

  14. Almond Saria says:

    thank you Arivazhagan kannan!!! works great for me!

  15. Ashok says:

    In struts 2.0 you can return Action.NONE in execute method and dont specify anything in struts.xml action specification. Use the output stream as like in struts. 🙂

  16. karthik says:

    Hi,

    In struts 1, is there any way to show errors or messages after file output.

    I need to export a file of ms-excel type and display a message using saveErrors(req,err) or saveMessages(req,msg) methods.

    But after file export. i got a exception as,

    SEVERE: Servlet.service() for servlet ActionServlet threw exception
    java.lang.IllegalStateException: getOutputStream() has already been called for this response

    and the page is not forwarding. even the data in the forms are not reset. (i called actionForm.reset() method.)

    any clues.
    thanks.

  17. moore says:

    Hi Karthik,

    This is going to be an issue with sending any file over HTTP, because you can’t send both text (a message) and a file for display in a browser at the same time.

    You could try sending a http redirect header along with the file, but I’m not sure exactly how to do this.

  18. Johnny Hastings says:

    Ashok, yer awesome. That solved my problem after a ton of investigation!

    “In struts 2.0 you can return Action.NONE in execute method and dont specify anything in struts.xml action specification. Use the output stream as like in struts. “

Comments are closed.


© Moore Consulting, 2003-2019