| This Chapter | |
| - | Chapter 17: File Upload and File Download |
| - | File Upload |
| - | File Download |
| - | Summary |
Downloading files is a day-to-day activity for an Internet surfer. Writing a Web application that allows only authorized users to download certain files is a totally different story. This is because you cannot simply publish the URL of the file you want your users to download.
For security reasons, normally you use the operating system or Web container authentication system. This authentication mechanism lets you password-protect files so that file downloading is allowed only after the user has entered the correct user name and password. However, if you have more than one user, the password must be shared, greatly reducing the effectiveness of password-protecting a resource. The more people know the password, the less secure it is. Furthermore, if many users know the password, it is almost impossible to record who downloads what.
In other applications, you may want to dynamically send a file when the name or location of the file is not known at design time. For instance, in a product search form, you display the products found as the result of the search. Each product has a thumbnail image. Since you do not know at design time which product will be searched for, you do not know which image files to send to the browser.
In another scenario, you have a large and expensive image that should only be displayed in your Web pages. How do you prevent other Web sites from cross referencing it? If only you could check the referer header of each request for this image before allowing the image to be downloaded.
Programmable file download can help solve all the problems detailed above. In short, programmable file download lets you selectively send a file to the browser.
As with file upload, to protect the files to be sent to the user, they must reside outside the application directory or under WEB-INF or in an external storage such as a database.
To send a file to the browser, do the following.
For instance, this code sends the specified file to the browser.
FileInputStream fis = new FileInputStream(file); BufferedInputStream bis = new BufferedInputStream(fis); byte[] bytes = new byte[bis.available()]; response.setContentType(contentType); OutputStream os = response.getOutputStream(); bis.read(bytes); os.write(bytes);
First, you read the file as a FileInputStream and load the content to a byte array. Then, you obtain the HttpServletResponse object’s OutputStream and call its write method, passing the byte array.
Warning
Do not send any characters other than the actual file content. This could happen without your realizing it. For example, if you need to write some page directives in a JSP, you might write the directives in the normal way, such as this:
<%@ page import="java.io.FileInputStream"%> <jsp:useBean id="DBBeanId" scope="page" class="MyBean" />Withour your realizing it, the carriage return at the end of the first page directive will be sent to the browser. To prevent extra characters from being sent, write those directives like the following:
<%@ page import="java.io.FileInputStream" %><jsp:useBean id="DBBeanId" scope="page" class="MyBean" />It looks unusual, but helps.
The app17b application demonstrates how to send a file to the browser. The application has a search form for searching products and each product has an associated image.
The displaySearchProductForm.jsp page is used to search for products and display the search results. It has been designed very simple, as shown in Figure 17.2.
Figure 17.2: The search product form.
You can request this page by using the following URL:
http://localhost:8080/app17b/searchProduct.do
When the form is submitted, the SearchProductAction object’s execute method is invoked. The SearchProductAction class is given in Listing 17.4
Listing 17.4: The SearchProductAction class
package app17b.action;
import app17b.to.ProductTO;
import java.util.ArrayList;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
public class SearchProductAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) {
ArrayList productList = new ArrayList();
productList.add(new ProductTO(1,"Television"));
productList.add(new ProductTO(2,"Computer"));
productList.add(new ProductTO(3,"VCR"));
request.setAttribute("productList", productList);
return (mapping.getInputForward());
}
}
The execute method always ‘finds’ three products, each of which is represented by a ProductTO object. Now, checks the displaySearchProductForm.jsp page in Listing 17.5, which displays the search form as well as the search result:
Listing 17.5: The displaySearchProductForm.jsp page
<%@ taglib uri="/tags/struts-html" prefix="html" %>
<%@ taglib uri="http://java.sun.com/jstl/core" prefix="c" %>
<html>
<body>
<b>Search Product</b>
<html:form action="/searchProduct">
Click this button to search products. <br/><br/>
<html:submit>Search</html:submit></td>
<c:if test="${productList!=null}">
<hr/>
<b>Search Results</b>
<br/><br/>
<table border="1">
<tr>
<td width="100"><b>Name</b></td>
<td width="100"><b>Picture</b></td>
</tr>
<c:forEach var="product" items="${productList}">
<tr>
<td><c:out value="${product.name}"/></td>
<td>
<html:img action="/getImage" width="100" height="50"
paramName="product" paramId="productId" paramProperty="id"/>
</td>
</tr>
</c:forEach>
</c:if>
</html:form>
</body>
</html>
The forEach tag is used to iterate the search result. For each product found, the name and thumbnail of the product is displayed. Pay special attention to the img tag in the JSP.
<html:img action="/getImage" width="100" height="50" paramName="product" paramId="productId"paramProperty="id"/>
This img tag will be rendered as
<img src="/app17b/getImage.do?productId=2" height="50" width="100">
The src attribute has the value of the path to the getImage action followed by the request parameter productId and a value. When a browser encounters an img tag when parsing an HTML page, it will request the image by using the URL in the src attribute. For static image, this is normally the image file and location, such as in the following img element.
<img src="/images/logo.gif" height="50" width="100">
In the img element rendered by the img tag in the JSP in Listing 17.5, the browser tries to retrieve the image by sending a request to the /app17b/getImage.do, and as a result the getImage Struts action will be invoked. Since the search product action finds three products, there will be three img elements, resulting in the getImage action to be invoked three times, each with a different product id.
Let’s now see how the GetImageAction class (in Listing 17.6) responds to user requests.
Listing 17.6: The GetImageAction class
package app17b.action;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.File;
import java.io.FileInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.struts.action.Action;
import org.apache.struts.action.ActionForm;
import org.apache.struts.action.ActionForward;
import org.apache.struts.action.ActionMapping;
public class GetImageAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form, HttpServletRequest request,
HttpServletResponse response) {
String contentType = "image/gif";
String id = request.getParameter("productId");
String path = request.getSession().getServletContext().
getRealPath("images");
// The images can be a jpg or gif,
// retrieve default image if no file found
File file = new File(path, id + ".GIF");
if (!file.exists()) {
file = new File(path, id + ".JPG");
contentType = "image/jpeg";
}
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
byte[] bytes = new byte[bis.available()];
response.setContentType(contentType);
OutputStream os = response.getOutputStream();
bis.read(bytes);
os.write(bytes);
}
catch (IOException ex) {
System.out.println (ex.toString());
}
}
return null;
}
}
The execute method starts by reading the productId parameter, to find out which image to retrieve. All image files are stored in the images directory under the application directory. An image file is either a JPG or GIF file. The name of the file is the same as the product identifier.
The execute method will first try to retrieve a GIF file. If the GIF file is not found, it will try to read a JPG file.
// The images can be a jpg or gif,
// retrieve default image if no file found
File file = new File(path, id + ".GIF");
if (!file.exists()) {
file = new File(path, id + ".JPG");
contentType = "image/jpeg";
}
If a gif file for a product is found, the content type will be image/gif. Otherwise, it will be image/jpg. If an image file for a product exists, the execute method will read the file and send it as an output stream:
if (file.exists()) {
try {
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
byte[] bytes = new byte[bis.available()];
response.setContentType(contentType);
OutputStream os = response.getOutputStream();
bis.read(bytes);
os.write(bytes);
}
catch (IOException ex) {
System.out.println (ex.toString());
}
Note that, unlike other action classes, the GetImageAction class’s execute method returns null. This means control is not forwarded to anywhere. Instead, an image file is sent to the browser in the OutputStream of the HttpServletResponse object.
Figure 17.3 displays what your browser will look like upon submitting the search product form.
Figure 17.3: The images sent from the GetImageAction object.