views:

5424

answers:

6

Hi everyone,

I am writing a simple application that let user upload images. After the upload the user can tag them or remove them.

Nothing fancy nothing special.

So I figured out how to upload the files and save them once the files are uploaded. I am keeping tracking of a global path where images are kept. In the database i keep the meta data about the images like file name, tags, etc.

I am using Java/JSP (Specifically Stripes framework but my problem is generic).

My question is where do i keep these image files once they are uploaded?

Right now i have two web applications deploy on tomcat. One the main web application and other one is the where i upload the images.

But this does not work as i can not see the uploaded images in the main application until i redeploy/restart tomcat.

It seem like tomcat does not pick newly uploaded images automatically.

Do anyone has any solution?

Please this is a simple project do not ask me to store them in database or use Apache for images. Just too complicated for this small project.

Thanks,

TRF

+2  A: 

Definitely don't store the images in the database, but you will want to store the image path in the database. This will allow you to store the image just about anywhere.

Since you are using two tomcat applications, your best bet may be to store the images outside of either app and stream the image back to the user instead of letting tomcat manage the files. Otherwise, I would ask why you are trying to do this with two web apps.

Chris Johnston
A: 

I was using two web applications to avoid over writing the uploaded images in case I redeploy a new main application war file.

But as you mention there is no other option but to stream them through a Servlet or something I guess I can keep them outside tomcat directory.

I wanted to avoid writing this Streaming Servlet. Just too small project to deal with all the mess (like proper content type, 404, etc.) while writing the streaming servlet.

TRF
+1  A: 

However, storing uploaded images inside the web-app directory is not a wise thing to do, and you know it.

By the way, you might want to look this stackoverflow thread, lately discussed where to store the images. It might not solve your issue, surely will give you more confidence on what you are doing.

Adeel Ansari
A: 
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Image streaming Servlet.
 */
public class ImageDisplayServlet extends HttpServlet {
    private static final long serialVersionUID = 1L;

    /**
     * @see HttpServlet#HttpServlet()
     */
    public ImageDisplayServlet() {
        super();
    }

    /**
     * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
     */
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     String relativePath = trimToEmpty(request.getPathInfo());

     // Make sure no one try to screw with us. 
     // This is important as user can literally access any file if we are not careful
     if(isXSSAttack(relativePath) == false) {
      String pathToFile = this.getServletContext().getRealPath(request.getPathInfo());
      File file = new File(pathToFile);

      System.out.println("Looking for file " + file.getAbsolutePath());

      // show a 404 page
      if(!file.exists() || !file.isFile()) {
       httpError(404, response);
      } else {
       try {
        streamImageFile(file, response);
       } catch(Exception e) {
        // Tell the user there was some internal server error.\
        // 500 - Internal server error.
        httpError(500, response);
        e.printStackTrace();
       }
      }
     } else {
      // what to do if i think it is a XSS attack ?!?
     }
    }

    private void streamImageFile(File file, HttpServletResponse response) {
     // find the right MIME type and set it as content type
     response.setContentType(getContentType(file));
     BufferedInputStream bis = null;
     BufferedOutputStream bos = null;
     try {
      response.setContentLength((int) file.length());

      // Use Buffered Stream for reading/writing.
      bis = new BufferedInputStream(new FileInputStream(file));
      bos = new BufferedOutputStream(response.getOutputStream());

      byte[] buff = new byte[(int) file.length()];
      int bytesRead;

      // Simple read/write loop.
      while (-1 != (bytesRead = bis.read(buff, 0, buff.length))) {
       bos.write(buff, 0, bytesRead);
      }
     } catch (Exception e) {

      throw new RuntimeException(e);
     } finally {
      if (bis != null) {
       try {
        bis.close();
       } catch (IOException e) {
        e.printStackTrace();
        // To late to do anything about it now, we may have already sent some data to user.
       }
      }
      if (bos != null) {
       try {
        bos.close();
       } catch (IOException e) {
        e.printStackTrace();
        // To late to do anything about it now, we may have already sent some data to user.
       }
      }
     } 
    }

    private String getContentType(File file) {
     if(file.getName().length() > 0) {
      String[] parts = file.getName().split("\\.");
      if(parts.length > 0) {
       // only last part interests me
       String extention = parts[parts.length - 1];
       if(extention.equalsIgnoreCase("jpg")) {
        return "image/jpg";
       } else if(extention.equalsIgnoreCase("gif")) {
        return "image/gif"; 
       } else if(extention.equalsIgnoreCase("png")) {
        return "image/png";
       }
      }
     }
     throw new RuntimeException("Can not find content type for the file " +  file.getAbsolutePath());
    }

    private String trimToEmpty(String pathInfo) {
     if(pathInfo == null) {
      return "";
     } else {
      return pathInfo.trim();
     }
    }

    private void httpError(int statusCode, HttpServletResponse response) {
     try {
      response.setStatus(statusCode);
      response.setContentType("text/html");
      PrintWriter writer = response.getWriter();
      writer.append("<html><body><h1>Error Code: " + statusCode + "</h1><body></html>");
      writer.flush();
     } catch (IOException e) {
      e.printStackTrace();
     }
    }

    private boolean isXSSAttack(String path) {
     boolean xss = false;
     // Split on the bases of know file separator
     String[] parts = path.split("/|\\\\");

     // Now verify that no part contains anything harmful
     for(String part : parts) {
      // No double dots .. 
      // No colons :
      // No semicolons ;
      if(part.trim().contains("..") || part.trim().contains(":") || part.trim().contains(";")) {
       // Fire in the hole!
       xss = true;
       break;
      }
     }
     return xss;
    }

    /**
     * @see HttpServlet#doPost(Ht/promotions/some.jpgtpServletRequest request, HttpServletResponse response)
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
     doGet(request, response);
    }
}

Ok Here is a Servlet that I quickly wrote that can stream images:

Here is the List of limitations and know issues:

  • May have XSS vulnerability use with care
  • Not production ready use as reference
  • Images need to in the web application directory. Can be easily change but I too lazy (it is not worth it the project is too small)
  • Only stream jpg,gif or png files.

Usage:

Let say you deploy this web application called images as separate application.

http://www.example.com/images/promotions/promo.jpg

means there should be a directory in "promotions" with image "promo.jpg" with in this images web application.

PS: Do not ask why I am doing this Servlet Container only solution that sucks big time.

TRF
A: 
  <servlet>
    <description></description>
    <display-name>ImageDisplayServlet</display-name>
    <servlet-name>ImageDisplayServlet</servlet-name>
    <servlet-class>com.example.images.ImageDisplayServlet</servlet-class>
  </servlet>
  <servlet-mapping>
    <servlet-name>ImageDisplayServlet</servlet-name>
    <url-pattern>/*</url-pattern>
  </servlet-mapping>

Oh ya configure your Servlet like above for best results :P

TRF
+2  A: 

I've solved this in different ways.

First, the non-portable way, is that Glassfish (and I do believe Tomcat as well) allows you to map an external directory in to the webapps hierarchy. This works really well and does exactly what you want. It lets you store your images in an external directory away from your webapp, yet still serve them up.

However, this technique is not portable.

The way to I've done it portably is by creating a filter.

You place the filter someplace obvious, say "/images".

What the filter does is this:

  • it checks for the image (or anything, it works with any static resource) in a special directory within the webapp. For our example we'll use the url "/webapp/images".

  • if the file DOES NOT exist, we copy the file from your external location in to the appropriate spot within the webapp. So, let's say the reqyest url is "/images/banner.gif". And that your files are stored on disk at "/home/app/images". So, our source file is "/home/app/images/banner.gif". We then copy it to where we want it in the webapp tree. We use "ServletContext.getRealPath" for this. So, the destination will be "ServletContext.get RealPath("/webapp/images/banner.gif"). Just copy the source to the destination.

  • if the file already existed, or now exists, simply forward to the actual image at /webapp/images/banner.gif.

Effectively you end up having a file cache within your webapps deployment tree. The down side is that it's a cache, so it needs to be maintained (i.e. you should check if the original is newer than your cache, make sure you delete if the source is deleted, etc.). Also, it duplicates your resources, so your images will consume, eventually, twice as much disk space. Finally, there's the initial copy cost at start up.

However, it DOES work, and it prevents you from having to serve up static resources using your own code. (Which is the 3rd solution, map a filter/servlet to intercept the URLs and simply stream it your self.)

I would look at the construct within Tomcat (assuming it exists) to do the mapping for you. I know it exists in Glassfish. (Google alternatedocroot for Glassfish to see how it works.)

Will Hartung