FCKEditor上传文件与Struts2的完美结合
FCKEditor是2.6版本,Struts为2.1版本。
FCKEditor与Java的连接上传封装在ConnectorServlet里,用的是commons-fileupload组件解析request。
在此不细表此基础知识,不懂请Google一把FCKEditor的Java上传配置,再看看ConnectorServlet的源码。
问题是:博客使用了Struts2框架,Struts2的Filter会过滤到上传文件的request,封装为Struts2自己的MultiPartRequestWrapper,这个时候再到FCKEditor里的ConnectorServlet中用commons-fileupload组件解析request,就无法获取到正确的文件流,所以上传失败。
之所以原来是没问题的,是因为当时web.xml配置的Struts2只过滤后缀为action的请求,对于上传文件的请求不做处理。而后来更新了URLRewrite,更改Struts2的Filter配置为过滤所有的请求,上传文件自然也就被过滤到了。
翻查了一下网络上的解决方案,都是更改Struts2的web.xml配置,只过滤后缀为action的请求或者其他struts2需要处理的请求。
这个解决方案在本博客基本行不通,因为使用URLRewrite并且伪装url的原因,绝大部分url都需要匹配Struts2的过滤器,不可能一个一个去配,web.xml中的Filter貌似又没有类似exclude的配置方式,这可头疼了。
除此之外,解决这个冲突不外乎两个方案,一是改造FCKEditor的ConnectorServlet,二是改造Struts2的Filter。
改造Struts2的Filter意味着所有上传文件的request都不再封装了,这恐怕不合适,万一以后我其他地方要用到呢?
那就只有改造FCKEditor的ConnectorServlet了,这还是有两条路,第一条路是彻底改变FCKEditor的Java上传方式,抛弃原来的ConnectorServlet,改为Struts2中的Action,第二条路是对ConnectorServlet进行有限改造,让它在解析request时能够解析Struts2封装的MultiPartRequestWrapper。
其实第一条路更好些,这样FCKEditor的文件上传就在Struts2的Action管理体系下,方便进行Struts2拦截器等一系列配置,同时也就更方便地解决了安全问题,不用对ConnectorServlet做安全性处理比如专门加个Filter或者在ConnectorServlet中做一些侵入性的判断。至于这么做是否麻烦,我还没有尝试,因为目前我走的是第二条路。但以后一定会这么做,大家可以等待本文的续篇了,。
第二条路,Struts2本身的文件上传默认使用的就是commons-fileupload组件,理论上是行得通,只是要仔细研究一下ConnectorServlet使用commons-fileupload组件解析request的方式。
当真正开始去看的时候,我才发现这TMD其实不那么简单,虽然最终写出来确实没几行代码,但要把Struts2中封装request、对上传请求拦截和commons-fileupload解析request的那一堆源码看懂了分析明白了并且让两者能对应得上才是要命的功夫……
更要命的是Struts2封装的MultiPartRequestWrapper对象只提供了一些直接面对文件信息的public方法,并没有把被封装的原始request开放出来,否则就简单了,我直接获取原始request传递给commons-fileupload就哦了。难道要让我用反射不成?
又看源码,发现ConnectorServlet使用commons-fileupload解析request生成了一个FileItem对象用于存储文件信息,OK,这可能就是突破口。我能不能先获取MultiPartRequestWrapper对象里封装的文件信息,然后绕过原有的解析,仿照commons-fileupload的源码重新封装生成一个FileItem对象去使用呢?
按照这个思路,参照源码,一边写一边测一边想一边改,好几个参数绕来绕去,有时就是靠猜,终于调试成功,于是形成了以下的代码。因为FCKEditor上传文件是ConnectorServlet中的doPost方法,所以我将改造之后的doPost方法完整贴出来,按照注释应该可以看懂:
public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {logger.debug("Entering Connector#doPost");response.setCharacterEncoding("UTF-8");response.setContentType("text/html; charset=UTF-8");response.setHeader("Cache-Control", "no-cache");PrintWriter out = response.getWriter();String commandStr = request.getParameter("Command");String typeStr = request.getParameter("Type");String currentFolderStr = request.getParameter("CurrentFolder");logger.debug("Parameter Command: {}", commandStr);logger.debug("Parameter Type: {}", typeStr);logger.debug("Parameter CurrentFolder: {}", currentFolderStr);UploadResponse ur;// if this is a QuickUpload request, 'commandStr' and 'currentFolderStr'// are emptyif (Utils.isEmpty(commandStr) && Utils.isEmpty(currentFolderStr)) {commandStr = "QuickUpload";currentFolderStr = "/";}if (!RequestCycleHandler.isEnabledForFileUpload(request))ur = new UploadResponse(UploadResponse.SC_SECURITY_ERROR, null, null, Messages.NOT_AUTHORIZED_FOR_UPLOAD);else if (!CommandHandler.isValidForPost(commandStr))ur = new UploadResponse(UploadResponse.SC_ERROR, null, null, Messages.INVALID_COMMAND);else if (typeStr != null && !ResourceTypeHandler.isValid(typeStr))ur = new UploadResponse(UploadResponse.SC_ERROR, null, null, Messages.INVALID_TYPE);else if (!UtilsFile.isValidPath(currentFolderStr))ur = UploadResponse.UR_INVALID_CURRENT_FOLDER;else {ResourceTypeHandler resourceType = ResourceTypeHandler.getDefaultResourceType(typeStr);String typePath = UtilsFile.constructServerSidePath(request, resourceType);String typeDirPath = getServletContext().getRealPath(typePath);File typeDir = new File(typeDirPath);UtilsFile.checkDirAndCreate(typeDir);File currentDir = new File(typeDir, currentFolderStr);if (!currentDir.exists())ur = UploadResponse.UR_INVALID_CURRENT_FOLDER;else {String newFilename = null;FileItemFactory factory = new DiskFileItemFactory();ServletFileUpload upload = new ServletFileUpload(factory);upload.setHeaderEncoding("UTF-8");//解决上传中文文件名问题try {//---------------FCKEditor上传文件与Struts2结合改造-----开始----------------------------FileItem uplFile = null;//基本思路:如果request为Struts2封装的MultiPartRequestWrapper,则自行解析该request,重新构造FileItem,将Struts2上传的临时文件传递给FileItemif(request instanceof MultiPartRequestWrapper){//转为Struts2封装的MultiPartRequestWrapperMultiPartRequestWrapper mr = (MultiPartRequestWrapper)request;//通过MultiPartRequestWrapper的方法取得相应的属性Enumeration<String> e = mr.getFileParameterNames();String fieldName = "";//只获取第一个文件if(e.hasMoreElements()){fieldName = e.nextElement();}//获取Struts2封装好的临时文件信息File file = mr.getFiles(fieldName)[0];String fileName = mr.getFileNames(fieldName)[0];String contentType = mr.getContentTypes(fieldName)[0];//使用fileupload组件API重新构造一个FileItemDiskFileItemFactory fac = new DiskFileItemFactory(); fac.setSizeThreshold(0); fac.setRepository(file.getParentFile()); uplFile = fac.createItem(fieldName, contentType, false, fileName); //将Struts2封装好的临时文件传递给这个FileItem Streams.copy(new FileInputStream(file), uplFile.getOutputStream(), true);}else{//如果不是Struts2封装的MultiPartRequestWrapper,按原有执行//-----------以下两行基本是原有代码---------------List<FileItem> items = upload.parseRequest(request);uplFile = items.get(0);}//---------------FCKEditor上传文件与Struts2结合改造-----结束----------------------------String rawName = UtilsFile.sanitizeFileName(uplFile.getName());String filename = FilenameUtils.getName(rawName);String baseName = FilenameUtils.removeExtension(filename);String extension = FilenameUtils.getExtension(filename);if (!ExtensionsHandler.isAllowed(resourceType, extension))ur = new UploadResponse(UploadResponse.SC_INVALID_EXTENSION);else {// construct an unique file nameFile pathToSave = new File(currentDir, filename);int counter = 1;while (pathToSave.exists()) {newFilename = baseName.concat("(").concat(String.valueOf(counter)) .concat(")").concat(".").concat(extension);pathToSave = new File(currentDir, newFilename);counter++;}if (Utils.isEmpty(newFilename))ur = new UploadResponse(UploadResponse.SC_OK, UtilsResponse .constructResponseUrl(request, resourceType, currentFolderStr, true, ConnectorHandler.isFullUrl()).concat(filename));elseur = new UploadResponse(UploadResponse.SC_RENAMED, UtilsResponse.constructResponseUrl(request, resourceType, currentFolderStr, true, ConnectorHandler.isFullUrl()) .concat(newFilename), newFilename);// secure image checkif (resourceType.equals(ResourceTypeHandler.IMAGE) && ConnectorHandler.isSecureImageUploads()) {if (UtilsFile.isImage(uplFile.getInputStream()))uplFile.write(pathToSave);else {uplFile.delete();ur = new UploadResponse(UploadResponse.SC_INVALID_EXTENSION);}} elseuplFile.write(pathToSave);}} catch (Exception e) {ur = new UploadResponse(UploadResponse.SC_SECURITY_ERROR);}}}out.print(ur);out.flush();out.close();logger.debug("Exiting Connector#doPost");}