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.