package org.nutz.mvc.upload; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.Reader; import java.lang.reflect.Type; import java.util.List; import java.util.Map; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.nutz.filepool.NutFilePool; import org.nutz.lang.Lang; import org.nutz.log.Log; import org.nutz.log.Logs; import org.nutz.mvc.Mvcs; import org.nutz.mvc.adaptor.PairAdaptor; import org.nutz.mvc.adaptor.ParamInjector; import org.nutz.mvc.annotation.Param; import org.nutz.mvc.upload.injector.FileInjector; import org.nutz.mvc.upload.injector.FileMetaInjector; import org.nutz.mvc.upload.injector.InputStreamInjector; import org.nutz.mvc.upload.injector.MapListInjector; import org.nutz.mvc.upload.injector.MapSelfInjector; import org.nutz.mvc.upload.injector.ReaderInjector; import org.nutz.mvc.upload.injector.TempFileInjector; /** * 本适配器专门处理 HTTP 文件上传(1.b.44及之后的版本,兼容Html5的流式上传)。 它支持多文件,多参数上传。具体的做法是将 HTTP * 上传的所有内容 包括文件以及名值对都预先缓存下来。其中,文件缓存在磁盘上,名值对缓存在内存中。 * <p> * 因此,本适配器构造的时候,需要四个参数: * <ol> * <li>临时文件存放的目录 * <li>数据缓冲区大小,建议设置为8192 * <li>HTTP 请求的编码方式。 * <li>临时文件的最大数量 * </ol> * 本适配器提供了四个构造函数,最简单的一个只有一个参数,需要你提供一个临时文件目录,缓冲区大小默认为8192, 临时文件数目默认的为 "2000",HTTP * 请求的编码方式为 "UTF-8", * <p> * 为了能让入口函数了解 HTTP 请求的更多信息,本适配器入口函数声明更多的参数类型: * <ul> * <li>java.io.File : 指向已上传至临时目录的文件对象 * <li>org.nutz.mvc.upload.FieldMeta : 描述了一个上传参数的更多属性 * <li>org.nutz.mvc.upload.TempFile : 组合了 File 和 FieldMeta * </ul> * 当然,这三种参数,都是需要你在入口函数的参数列表里声明 '@Param' 注解,用来告诉本适配器,你的参数 具体取自请求中的哪一个参数。 * * <p/> * <b>Html5流式上传(实验性)的注意事项: * 参数名默认是filedata,除非req.getHeader("Content-Disposition")中有描述另外的name</b> * * @author zozoh(zozohtnt@gmail.com) * @author wendal(wendal1985@gmail.com) * * @see org.nutz.mvc.annotation.Param */ public class UploadAdaptor extends PairAdaptor { private static final Log log = Logs.get(); private UploadingContext context; public UploadAdaptor() throws IOException { context = new UploadingContext(File.createTempFile("nutz", null).getParent()); } public UploadAdaptor(UploadingContext context) { this.context = context; } public UploadAdaptor(String path) { context = new UploadingContext(path); } public UploadAdaptor(String path, int buffer) { this(path); context.setBufferSize(buffer); } public UploadAdaptor(String path, int buffer, String charset) { this(path); context.setBufferSize(buffer); context.setCharset(charset); } public UploadAdaptor(String path, int buffer, String charset, int poolSize) { context = new UploadingContext(new NutFilePool(path, poolSize)); context.setBufferSize(buffer); context.setCharset(charset); } public UploadAdaptor(String path, int buffer, String charset, int poolSize, int maxFileSize) { context = new UploadingContext(new NutFilePool(path, poolSize)); context.setBufferSize(buffer); context.setCharset(charset); context.setMaxFileSize(maxFileSize); } public UploadingContext getContext() { return context; } @Override public Object[] adapt(ServletContext sc, HttpServletRequest req, HttpServletResponse resp, String[] pathArgs) { //临时 if (!Mvcs.getActionContext().getMethod().toGenericString().equals(method.toGenericString())) { throw new IllegalArgumentException(String.format("Method miss match: expect %s but %s. using Ioc? set singleton=false, pls", method, Mvcs.getActionContext().getMethod())); } return super.adapt(sc, req, resp, pathArgs); } protected ParamInjector evalInjectorBy(Type type, Param param) { // TODO 这里的实现感觉很丑, 感觉可以直接用type进行验证与传递 // TODO 这里将Type的影响局限在了 github issue #30 中提到的局部范围 Class<?> clazz = Lang.getTypeClass(type); if (clazz == null) { if (log.isWarnEnabled()) log.warnf("!!Fail to get Type Class : type=%s , param=%s", type, param); return null; } // Map if (Map.class.isAssignableFrom(clazz)) return new MapSelfInjector(); if (null == param) return super.evalInjectorBy(type, null); String paramName = param.value(); // File if (File.class.isAssignableFrom(clazz)) return new FileInjector(paramName); // FileMeta if (FieldMeta.class.isAssignableFrom(clazz)) return new FileMetaInjector(paramName); // TempFile if (TempFile.class.isAssignableFrom(clazz)) return new TempFileInjector(paramName); // InputStream if (InputStream.class.isAssignableFrom(clazz)) return new InputStreamInjector(paramName); // Reader if (Reader.class.isAssignableFrom(clazz)) return new ReaderInjector(paramName); // List if (List.class.isAssignableFrom(clazz)) return new MapListInjector(paramName); // Other return super.evalInjectorBy(type, param); } public Map<String, Object> getReferObject(ServletContext sc, HttpServletRequest request, HttpServletResponse response, String[] pathArgs) { try { if (!"POST".equals(request.getMethod()) && !"PUT".equals(request.getMethod())) { String str = "Not POST or PUT, Wrong HTTP method! --> " + request.getMethod(); throw Lang.makeThrow(IllegalArgumentException.class, str); } // 看看是不是传统的上传 String contentType = request.getContentType(); if (contentType == null) { throw Lang.makeThrow(IllegalArgumentException.class, "Content-Type is NULL!!"); } if (contentType.contains("multipart/form-data")) { // 普通表单上传 if (log.isDebugEnabled()) log.debug("Select Html4 Form upload parser --> " + request.getRequestURI()); Uploading ing = new FastUploading(); return ing.parse(request, context); } if (contentType.contains("application/octet-stream")) { // Html5 // 流式上传 if (log.isDebugEnabled()) log.debug("Select Html5 Stream upload parser --> " + request.getRequestURI()); Uploading ing = new Html5Uploading(); return ing.parse(request, context); } // 100%是没写enctype='multipart/form-data' if (contentType.contains("application/x-www-form-urlencoded")) { log.warn("Using form upload ? You forgot this --> enctype='multipart/form-data' ?"); } throw Lang.makeThrow(IllegalArgumentException.class, "Unknow Content-Type : " + contentType); } catch (UploadException e) { throw Lang.wrapThrow(e); } finally { Uploads.removeInfo(request); } } }