Servlet3.0.1与dojo实现图片AJAX上传与下载

jopen 10年前

在servlet2.5中,并没有处理文件上传二进制流的API,一般都是借用Apache Commons下的fileUploader。在servlet3.0以后,文件上传的request处理已经被纳入servlet jsr. 各大主流web容器也都支持了新的jcr。而servlet3.0.1则更进一步支持文件上传,tomcat8完全实现了servlet3.0.1. 这篇文章将使用servlet3.0.1做后端,dojo做前端,制作一个图片AJAX上传于下载网站。

开发环境

tomcat8, eclipse+maven,dojo1.9

Maven项目

首先需要生成一个webapp的maven项目。本次开发用到了tomcat的servlet api, jsp api以及jstl,因此需要在pom.xml中声明所需的依赖。为了不超过字数上限,我只列出依赖的ID。

    tomcat-servlet-api      tomcat-jsp-api      jstl

我们将有两个servlet和一个JSP

  1. index.jsp, 负责界面展示。对应的URL为http://localhost:8080/images 

  2. UploaderServlet, 负责处理dojo发送的AJAX图片上传请求,并返回JSON格式的消息。对应的URL为http://localhost:8080/images/ajaxUploader 

  3. ImagesServlet, 负责处理对上传后的图片访问请求。对应的URL为http://localhost:8080/images/*.png|jpg|gif?download=true|false

后端开发

web.xml

Servlet3可以使用annotation config,也可以使用传统的web.xml。我们还是用web.xml.

<?xml version="1.0" encoding="UTF-8"?>  <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee                       http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">    <display-name>Archetype Created Web Application</display-name>    <context-param>      <!--  定义上传图片最终存放的文件夹。 -->      <param-name>shareFolder</param-name>      <param-value>d:/share/data</param-value>    </context-param>        <servlet>      <servlet-name>imageUploader</servlet-name>      <servlet-class>servlets.UploaderServlet</servlet-class>      <multipart-config>        <!--临时存放上传文件的地方-->        <location>d:/share/tmp</location>        <!--允许上传的文件最大值,约10MB。默认值为 -1,表示没有限制。-->        <max-file-size>10240000</max-file-size>        <!--针对该 multipart/form-data 请求的最大数量,默认值为 -1,表示没有限制。-->        <max-request-size>20480000</max-request-size>        <!--当数据量大于约10MB时,内容将被写入tmp目录下的文件。-->        <file-size-threshold>10240000</file-size-threshold>        </multipart-config>    </servlet>    <servlet-mapping>      <servlet-name>imageUploader</servlet-name>      <url-pattern>/ajaxUploader</url-pattern>    </servlet-mapping>        <servlet>      <servlet-name>imageAccess</servlet-name>      <servlet-class>servlets.ImagesServlet</servlet-class>    </servlet>    <servlet-mapping>      <servlet-name>imageAccess</servlet-name>      <url-pattern>*.png</url-pattern>      <url-pattern>*.jpg</url-pattern>      <url-pattern>*.gif</url-pattern>    </servlet-mapping>  </web-app>

从上可见,所有的图片访问均由ImagesServlet处理,文件上传由UploaderServlet处理。UploaderServlet下面配置了文件上传的限制,请看注释。另外我还声明了一个context-param,作用请看注释。

UploaderServlet

使用request.getPart()方法,可以得到文件上传part的对象。将上传的流保存在指定的目录文件中。

特别注意的是,此servlet响应的将是dojo.io.iframe发送来的AJAX请求,因此,返回JSON格式的响应,将是最好的选择。

响应dojo.io.iframe的JSON格式,必须包含在下面的结构中。这是dojo.io.iframe所能解析的结构,也是必须遵循的规则

<html>    <body>      JSON_DATA    </body>  </html>

UploadServlet的部分代码如下:

public class UploaderServlet extends HttpServlet {      @Override      protected void doPost(HttpServletRequest req, HttpServletResponse resp)              throws ServletException, IOException {          try {              Part image = req.getPart("image");              //servlet3.0.1新加入的方法,在3中,我们只能通过header来自己解析filename.              String imageName = image.getSubmittedFileName();              String shareFolder = req.getServletContext().getInitParameter("shareFolder");              File imageFile = new File(shareFolder, imageName);              FileOutputStream fos=null;              InputStream is =null;              try {                  fos = new FileOutputStream(imageFile);                  is = image.getInputStream();                  byte[] bytes = new byte[4096];                  int bytesReaded = -1;                  while ((bytesReaded=is.read(bytes))!=-1) {                      fos.write(bytes, 0, bytesReaded);                  }                  fos.flush();              } finally {                  //关闭所有的流。              }              sendCreated(resp, imageName);          } catch (Exception e) {              e.printStackTrace();              sendError(resp);              return;          }      }             private void sendError(HttpServletResponse resp) {          resp.setStatus(200);          resp.setContentType("text/html;charset=UTF-8");          StringBuilder body = new StringBuilder()              .append("<html>                                                 ")              .append("  <boby>                                               ")              .append("    <textarea>                                         ")              .append("       {status:500, message:'Can't load your image.'}  ")              .append("    </textarea>                                        ")              .append("  </body>                                              ")              .append("</html>                                                ");          resp.setContentLength(body.length());          PrintWriter out;          try {              out = resp.getWriter();              out.print(body.toString());              out.flush();              out.close();          } catch (IOException e) {              e.printStackTrace();          }      }            private void sendCreated(HttpServletResponse resp, String image) {          resp.setStatus(200);          resp.setContentType("text/html;charset=UTF-8");          StringBuilder body = new StringBuilder()              .append("<html>                                                 ")              .append("  <boby>                                               ")              .append("    <textarea>                                         ")              .append(String.format("{status:201, image:'%s', message:'File created.'}", image))              .append("    </textarea>                                        ")              .append("  </body>                                              ")              .append("</html>                                                ");          resp.setContentLength(body.length());          PrintWriter out;          try {              out = resp.getWriter();              out.print(body.toString());              out.flush();              out.close();          } catch (IOException e) {              e.printStackTrace();          }      }  }

ImagesServlet

处理图片访问请求的servlet。只需要将存放的文件以字节流的形式输出到response中即可。在输出流之前,我们还要检查If-Modified-Since和If-None-Match两个header。如果图片没有修改过,则返回304. 如果修改过,则输出流,并设置新的Last-Modified和ETag两个header。

public class ImagesServlet extends HttpServlet {      @Override      public void doGet(HttpServletRequest request, HttpServletResponse response) {          String path = request.getRequestURI();          String imageName = path.substring(path.lastIndexOf('/')+1);          File image = new File(request.getServletContext().getInitParameter("shareFolder"), imageName);                    if (!image.exists()) {              response.setStatus(404);              return;          }                    long lastModified = image.lastModified();          long ifModifiedSince = request.getDateHeader("If-Modified-Since");          if ((lastModified / 1000 * 1000) <= ifModifiedSince) {              response.setStatus(304);              return;          }                    response.setStatus(200);          String mimeType = request.getServletContext().getMimeType(imageName);          if(mimeType==null) {mimeType = "application/octet-stream";}          response.addHeader("content-type", mimeType);          response.setContentLength((int)image.length());          response.addDateHeader("Last-Modified", lastModified);                    String isDownload = request.getParameter("download");          if (isDownload!=null&&"true".equals(isDownload)) {              //force download              String headerKey = "Content-Disposition";              String headerValue = String.format("attachment; filename=\"%s\"", imageName);              response.setHeader(headerKey, headerValue);          }          FileInputStream in=null;          ServletOutputStream out=null;          try {              in = new FileInputStream(image);              out = response.getOutputStream();              byte[] bytes = new byte[4096];              int bytesReaded = -1;              while ((bytesReaded = in.read(bytes))!=-1) {                  out.write(bytes, 0, bytesReaded);              }              out.flush();          } catch (Exception e) {              e.printStackTrace();              response.setStatus(500);          } finally {              //关闭所有的流。          }      }  }

前端开发

前端开发重要是对index.jsp的内容展示方面进行开发。作为网站的welcome页面,也是唯一的页面,它将承载所有的用户交互。在此页面上只包含两个功能:

  1. 图片上传Form。

  2. 已经上传的图片的展现。

index.jsp

使用dojo.io.iframe进行无刷新的图片上传。图片上传成功以后,会接受服务器返回的JSON对象,其中含有图片的信息。然后使用JS将图片插入到所有图片的前面。

当刷新本页面时,JSP会扫面图片文件夹。列出所有的图片,并将preview显示在页面上。我们可以点击download以下载图片,也可以点击view,使得浏览器直接打开图片。

以下是部分代码。

<script src="//ajax.googleapis.com/ajax/libs/dojo/1.9.2/dojo/dojo.js"></script>  <script type="text/javascript">    require(["dojo/io/iframe", "dojo/dom-construct","dojo/on","dojo/dom","dojo/query","dojo/string", "dojo/domReady!"],         function(ioIframe,domConst,on,dom,query,string){      var upload=function() {        ioIframe.send({          url:'${pageContext.request.contextPath }/ajaxUploader',           form:'uploader',          handleAs:'json',          load: function(response, ioArgs){            if (response.status == 201) {              var nodeHtml = <!--创建新image的preview的html-->              var node = domConst.toDom(nodeHtml);              domConst.place(node, 'icons', 'first');            } else {              alert(response.message);            }                return response;            },                // Callback on errors:            error: function(response, ioArgs){              alert(response);                return response;            }        });      };            on(dom.byId("uploadButton"), "click", upload);    });  </script>    <div>        <form enctype="multipart/form-data" id="uploader" method="post">          <div><label>Please select a image:</label><input type="file" name="image"/></div>          <div><input type="button" value="Upload" id="uploadButton"></div>        </form>      <div id="icons">        <%          String shareFolder = request.getServletContext().getInitParameter("shareFolder");         File dir = new File(shareFolder);         String[] list = dir.list();         pageContext.setAttribute("list", list);        %>        <c:forEach var="image" items="${list }">          <!--创建每个image的preview-->        </c:forEach>      </div>    </div>

最终的UI效果

Servlet3.0.1与dojo实现图片AJAX上传与下载

源代码下载地址

http://pan.baidu.com/s/1dDpDNhR 

来自:http://my.oschina.net/xpbug/blog/208432