From ef28e32e04d4b842edecb54572b604cd99f00602 Mon Sep 17 00:00:00 2001 From: Redkale <22250530@qq.com> Date: Sun, 14 May 2017 15:52:15 +0800 Subject: [PATCH] --- src/org/redkale/convert/ConvertFactory.java | 2 + .../redkale/convert/ext/FileSimpledCoder.java | 41 +++++++ src/org/redkale/net/http/MultiContext.java | 95 +++++++++++++++- src/org/redkale/net/http/Rest.java | 105 +++++++++++++++++- src/org/redkale/net/http/RestUploadFile.java | 48 ++++++++ test/org/redkale/test/rest/HelloEntity.java | 11 ++ 6 files changed, 295 insertions(+), 7 deletions(-) create mode 100644 src/org/redkale/convert/ext/FileSimpledCoder.java create mode 100644 src/org/redkale/net/http/RestUploadFile.java diff --git a/src/org/redkale/convert/ConvertFactory.java b/src/org/redkale/convert/ConvertFactory.java index df24ec232..4200f86d6 100644 --- a/src/org/redkale/convert/ConvertFactory.java +++ b/src/org/redkale/convert/ConvertFactory.java @@ -5,6 +5,7 @@ */ package org.redkale.convert; +import java.io.File; import java.lang.reflect.*; import java.math.BigInteger; import java.net.*; @@ -91,6 +92,7 @@ public abstract class ConvertFactory { this.register(Class.class, TypeSimpledCoder.instance); this.register(InetSocketAddress.class, InetSocketAddressSimpledCoder.instance); this.register(Pattern.class, PatternSimpledCoder.instance); + this.register(File.class, FileSimpledCoder.instance); this.register(CompletionHandler.class, CompletionHandlerSimpledCoder.instance); this.register(AsyncHandler.class, AsyncHandlerSimpledCoder.instance); this.register(URL.class, URLSimpledCoder.instance); diff --git a/src/org/redkale/convert/ext/FileSimpledCoder.java b/src/org/redkale/convert/ext/FileSimpledCoder.java new file mode 100644 index 000000000..a08b39e81 --- /dev/null +++ b/src/org/redkale/convert/ext/FileSimpledCoder.java @@ -0,0 +1,41 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.convert.ext; + +import java.io.File; +import org.redkale.convert.*; + +/** + * 文件 的SimpledCoder实现 + * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + * @param Reader输入的子类型 + * @param Writer输出的子类型 + */ +public class FileSimpledCoder extends SimpledCoder { + + public static final PatternSimpledCoder instance = new PatternSimpledCoder(); + + @Override + public void convertTo(W out, File value) { + if (value == null) { + out.writeNull(); + } else { + out.writeString(value.getPath()); + } + } + + @Override + public File convertFrom(R in) { + String value = in.readString(); + if (value == null) return null; + return new File(value); + } + +} diff --git a/src/org/redkale/net/http/MultiContext.java b/src/org/redkale/net/http/MultiContext.java index 8cf6e0524..47cb80183 100644 --- a/src/org/redkale/net/http/MultiContext.java +++ b/src/org/redkale/net/http/MultiContext.java @@ -87,10 +87,103 @@ public final class MultiContext { return this.boundary != null; } + //或被 REST 用到 + /** + * 获取第一个文件的二进制 + * + * @param max 可接收的文件大小最大值 + * @param filenameReg 可接收的文件名正则表达式 + * @param contentTypeReg 可接收的ContentType正则表达式 + * + * @return 二进制文件 + * @throws IOException + */ + public byte[] partsFirstBytes(final long max, final String filenameReg, final String contentTypeReg) throws IOException { + if (!isMultipart()) return null; + for (MultiPart part : parts()) { + if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) return null; + if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) return null; + return part.getContentBytes(max < 1 ? Long.MAX_VALUE : max); + } + return null; + } + + //或被 REST 用到 + /** + * 获取第一个文件 + * + * @param home 进程目录 + * @param max 可接收的文件大小最大值 + * @param filenameReg 可接收的文件名正则表达式 + * @param contentTypeReg 可接收的ContentType正则表达式 + * + * @return 文件 + * @throws IOException + */ + public File partsFirstFile(final File home, final long max, final String filenameReg, final String contentTypeReg) throws IOException { + if (!isMultipart()) return null; + for (MultiPart part : parts()) { + if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) return null; + if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) return null; + String name = part.getFilename(); + int pos = name.lastIndexOf('.'); + if (pos > 0) { + int pos2 = name.lastIndexOf('.', pos - 1); + if (pos2 >= 0) pos = pos2; + } + File file = new File(home, "tmp/redkale_" + System.nanoTime() + (pos > 0 ? name.substring(pos) : name)); + file.getParentFile().mkdirs(); + boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file); + if (!rs) { + file.delete(); + return null; + } + return file; + } + return null; + } + + //或被 REST 用到 + /** + * 获取所有文件 + * + * @param home 进程目录 + * @param max 可接收的文件大小最大值 + * @param filenameReg 可接收的文件名正则表达式 + * @param contentTypeReg 可接收的ContentType正则表达式 + * + * @return 文件列表 + * @throws IOException + */ + public File[] partsFiles(final File home, final long max, final String filenameReg, final String contentTypeReg) throws IOException { + if (!isMultipart()) return null; + List files = null; + for (MultiPart part : parts()) { + if (filenameReg != null && !filenameReg.isEmpty() && !part.getFilename().matches(filenameReg)) continue; + if (contentTypeReg != null && !contentTypeReg.isEmpty() && !part.getContentType().matches(contentTypeReg)) continue; + String name = part.getFilename(); + int pos = name.lastIndexOf('.'); + if (pos > 0) { + int pos2 = name.lastIndexOf('.', pos - 1); + if (pos2 >= 0) pos = pos2; + } + File file = new File(home, "tmp/redkale_" + System.nanoTime() + (pos > 0 ? name.substring(pos) : name)); + file.getParentFile().mkdirs(); + boolean rs = part.save(max < 1 ? Long.MAX_VALUE : max, file); + if (!rs) { + file.delete(); + continue; + } + if (files == null) files = new ArrayList<>(); + files.add(file); + } + return files == null ? null : files.toArray(new File[files.size()]); + } + /** * 获取上传文件信息列表 * - * @return Iterable + * @return Iterable * @throws IOException IOException */ public Iterable parts() throws IOException { diff --git a/src/org/redkale/net/http/Rest.java b/src/org/redkale/net/http/Rest.java index 093ff286b..1cb2db579 100644 --- a/src/org/redkale/net/http/Rest.java +++ b/src/org/redkale/net/http/Rest.java @@ -189,6 +189,13 @@ public final class Rest { av0.visitEnd(); fv.visitEnd(); } + { //注入 @Resource(name = "APP_HOME") private File _redkale_home; + fv = cw.visitField(ACC_PRIVATE, "_redkale_home", Type.getDescriptor(File.class), null, null); + av0 = fv.visitAnnotation("Ljavax/annotation/Resource;", true); + av0.visit("name", "APP_HOME"); + av0.visitEnd(); + fv.visitEnd(); + } { //_servicemap字段 Map fv = cw.visitField(ACC_PRIVATE, REST_SERVICEMAP_FIELD_NAME, "Ljava/util/Map;", "Ljava/util/Map;", null); fv.visitEnd(); @@ -254,6 +261,7 @@ public final class Rest { final Map bodyTypes = new HashMap<>(); for (final MappingEntry entry : entrys) { + boolean hasupload = false; final Method method = entry.mappingMethod; final Class returnType = method.getReturnType(); final java.lang.reflect.Type returnGenericType = method.getGenericReturnType(); @@ -340,6 +348,17 @@ public final class Rest { if (annaddr != null) throw new RuntimeException("@RestBody and @RestAddress cannot on the same Parameter in " + method); if (ptype.isPrimitive()) throw new RuntimeException("@RestBody cannot on primitive type Parameter in " + method); } + RestUploadFile annfile = param.getAnnotation(RestUploadFile.class); + if (annfile != null) { + if (hasupload) throw new RuntimeException("@RestUploadFile repeat in " + method); + hasupload = true; + if (annhead != null) throw new RuntimeException("@RestUploadFile and @RestHeader cannot on the same Parameter in " + method); + if (anncookie != null) throw new RuntimeException("@RestUploadFile and @RestCookie cannot on the same Parameter in " + method); + if (annsid != null) throw new RuntimeException("@RestUploadFile and @RestSessionid cannot on the same Parameter in " + method); + if (annaddr != null) throw new RuntimeException("@RestUploadFile and @RestAddress cannot on the same Parameter in " + method); + if (annbody != null) throw new RuntimeException("@RestUploadFile and @RestBody cannot on the same Parameter in " + method); + if (ptype != byte[].class && ptype != File.class && ptype != File[].class) throw new RuntimeException("@RestUploadFile must on byte[] or File or File[] Parameter in " + method); + } RestParam annpara = param.getAnnotation(RestParam.class); if (annpara != null) radix = annpara.radix(); @@ -357,11 +376,11 @@ public final class Rest { n = ("bean" + i); } } - if (annhead == null && anncookie == null && annaddr == null && annbody == null + if (annhead == null && anncookie == null && annaddr == null && annbody == null && annfile == null && (entry.name.startsWith("find") || entry.name.startsWith("delete")) && params.length == 1) { if (ptype.isPrimitive() || ptype == String.class) n = "#"; } - paramlist.add(new Object[]{param, n, ptype, radix, comment, required, annpara, annsid, annaddr, annhead, anncookie, annbody, param.getParameterizedType()}); + paramlist.add(new Object[]{param, n, ptype, radix, comment, required, annpara, annsid, annaddr, annhead, anncookie, annbody, annfile, param.getParameterizedType()}); } Map mappingMap = new LinkedHashMap<>(); @@ -405,7 +424,7 @@ public final class Rest { av0 = mv.visitAnnotation(webparamsDesc, true); AnnotationVisitor av1 = av0.visitArray("value"); //设置 WebParam - for (Object[] ps : paramlist) { //{param, n, ptype, radix, comment, required, annpara, annsid, annaddr, annhead, anncookie, annbody, pgentype} + for (Object[] ps : paramlist) { //{param, n, ptype, radix, comment, required, annpara, annsid, annaddr, annhead, anncookie, annbody, annfile, pgentype} final boolean ishead = ((RestHeader) ps[9]) != null; //是否取getHeader 而不是 getParameter final boolean iscookie = ((RestCookie) ps[10]) != null; //是否取getCookie @@ -439,7 +458,8 @@ public final class Rest { RestHeader annhead = (RestHeader) ps[9]; RestCookie anncookie = (RestCookie) ps[10]; RestBody annbody = (RestBody) ps[11]; - java.lang.reflect.Type pgentype = (java.lang.reflect.Type) ps[12]; + RestUploadFile annfile = (RestUploadFile) ps[12]; + java.lang.reflect.Type pgentype = (java.lang.reflect.Type) ps[13]; final boolean ishead = annhead != null; //是否取getHeader 而不是 getParameter final boolean iscookie = anncookie != null; //是否取getCookie @@ -495,6 +515,39 @@ public final class Rest { mv.visitVarInsn(ASTORE, maxLocals); varInsns.add(new int[]{ALOAD, maxLocals}); } + } else if (annfile != null) { //MultiContext.partsFirstBytes / HttpRequest.partsFirstFile / HttpRequest.partsFiles + if (ptype == byte[].class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, reqInternalName, "getMultiContext", "()Lorg/redkale/net/http/MultiContext;", false); + mv.visitLdcInsn(annfile.maxLength()); + mv.visitLdcInsn(annfile.fileNameReg()); + mv.visitLdcInsn(annfile.contentTypeReg()); + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/MultiContext", "partsFirstBytes", "(JLjava/lang/String;Ljava/lang/String;)[B", false); + mv.visitVarInsn(ASTORE, maxLocals); + varInsns.add(new int[]{ALOAD, maxLocals}); + } else if (ptype == File.class) { + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, reqInternalName, "getMultiContext", "()Lorg/redkale/net/http/MultiContext;", false); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_redkale_home", "Ljava/io/File;"); + mv.visitLdcInsn(annfile.maxLength()); + mv.visitLdcInsn(annfile.fileNameReg()); + mv.visitLdcInsn(annfile.contentTypeReg()); + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/MultiContext", "partsFirstFile", "(Ljava/io/File;JLjava/lang/String;Ljava/lang/String;)Ljava/io/File;", false); + mv.visitVarInsn(ASTORE, maxLocals); + varInsns.add(new int[]{ALOAD, maxLocals}); + } else if (ptype == File[].class) { //File[] + mv.visitVarInsn(ALOAD, 1); + mv.visitMethodInsn(INVOKEVIRTUAL, reqInternalName, "getMultiContext", "()Lorg/redkale/net/http/MultiContext;", false); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_redkale_home", "Ljava/io/File;"); + mv.visitLdcInsn(annfile.maxLength()); + mv.visitLdcInsn(annfile.fileNameReg()); + mv.visitLdcInsn(annfile.contentTypeReg()); + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/MultiContext", "partsFiles", "(Ljava/io/File;JLjava/lang/String;Ljava/lang/String;)[Ljava/io/File;", false); + mv.visitVarInsn(ASTORE, maxLocals); + varInsns.add(new int[]{ALOAD, maxLocals}); + } } else if ("#".equals(pname)) { //从request.getRequstURI 中取参数 if (ptype == boolean.class) { mv.visitVarInsn(ALOAD, 1); @@ -772,12 +825,20 @@ public final class Rest { RestSessionid rs = field.getAnnotation(RestSessionid.class); RestAddress ra = field.getAnnotation(RestAddress.class); RestBody rb = field.getAnnotation(RestBody.class); - if (rh == null && rc == null && ra == null && rb == null && rs == null) continue; + RestUploadFile ru = field.getAnnotation(RestUploadFile.class); + if (rh == null && rc == null && ra == null && rb == null && rs == null && ru == null) continue; if (rh != null && field.getType() != String.class) throw new RuntimeException("@RestHeader must on String Field in " + field); if (rc != null && field.getType() != String.class) throw new RuntimeException("@RestCookie must on String Field in " + field); if (rs != null && field.getType() != String.class) throw new RuntimeException("@RestSessionid must on String Field in " + field); if (ra != null && field.getType() != String.class) throw new RuntimeException("@RestAddress must on String Field in " + field); if (rb != null && field.getType().isPrimitive()) throw new RuntimeException("@RestBody must on cannot on primitive type Field in " + field); + if (ru != null && field.getType() != byte[].class && field.getType() != File.class && field.getType() != File[].class) { + throw new RuntimeException("@RestUploadFile must on byte[] or File or File[] Field in " + field); + } + if (ru != null) { + if (hasupload) throw new RuntimeException("@RestUploadFile repeat on Field(" + field + ") in " + method); + hasupload = true; + } org.redkale.util.Attribute attr = org.redkale.util.Attribute.create(loop, field); String attrFieldName; String restname = ""; @@ -802,11 +863,20 @@ public final class Rest { } else if (rb != null && field.getType() != String.class && field.getType() != byte[].class) { attrFieldName = "_redkale_attr_bodyjson_" + restAttributes.size(); //restname = ""; + } else if (ru != null && field.getType() == byte[].class) { + attrFieldName = "_redkale_attr_uploadbytes_" + restAttributes.size(); + //restname = ""; + } else if (ru != null && field.getType() == File.class) { + attrFieldName = "_redkale_attr_uploadfile_" + restAttributes.size(); + //restname = ""; + } else if (ru != null && field.getType() == File[].class) { + attrFieldName = "_redkale_attr_uploadfiles_" + restAttributes.size(); + //restname = ""; } else { continue; } restAttributes.put(attrFieldName, attr); - attrParaNames.put(attrFieldName, new Object[]{restname, field.getType(), field.getGenericType()}); + attrParaNames.put(attrFieldName, new Object[]{restname, field.getType(), field.getGenericType(), ru}); fields.add(field.getName()); } } while ((loop = loop.getSuperclass()) != Object.class); @@ -816,6 +886,7 @@ public final class Rest { Label lif = new Label(); mv.visitJumpInsn(IFNULL, lif); //if(bean != null) { for (Map.Entry en : attrParaNames.entrySet()) { + RestUploadFile ru = (RestUploadFile) en.getValue()[3]; mv.visitVarInsn(ALOAD, 0); mv.visitFieldInsn(GETFIELD, newDynName, en.getKey(), attrDesc); mv.visitVarInsn(ALOAD, maxLocals); @@ -844,6 +915,28 @@ public final class Rest { mv.visitFieldInsn(GETFIELD, newDynName, typefieldname, "Ljava/lang/reflect/Type;"); mv.visitMethodInsn(INVOKEVIRTUAL, reqInternalName, "getBodyJson", "(Ljava/lang/reflect/Type;)Ljava/lang/Object;", false); mv.visitTypeInsn(CHECKCAST, Type.getInternalName((Class) en.getValue()[1])); + } else if (en.getKey().contains("_uploadbytes_")) { + mv.visitMethodInsn(INVOKEVIRTUAL, reqInternalName, "getMultiContext", "()Lorg/redkale/net/http/MultiContext;", false); + mv.visitLdcInsn(ru.maxLength()); + mv.visitLdcInsn(ru.fileNameReg()); + mv.visitLdcInsn(ru.contentTypeReg()); + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/MultiContext", "partsFirstBytes", "(JLjava/lang/String;Ljava/lang/String;)[B", false); + } else if (en.getKey().contains("_uploadfile_")) { + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/HttpRequest", "getMultiContext", "()Lorg/redkale/net/http/MultiContext;", false); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_redkale_home", "Ljava/io/File;"); + mv.visitLdcInsn(ru.maxLength()); + mv.visitLdcInsn(ru.fileNameReg()); + mv.visitLdcInsn(ru.contentTypeReg()); + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/MultiContext", "partsFirstFile", "(Ljava/io/File;JLjava/lang/String;Ljava/lang/String;)Ljava/io/File;", false); + } else if (en.getKey().contains("_uploadfiles_")) { + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/HttpRequest", "getMultiContext", "()Lorg/redkale/net/http/MultiContext;", false); + mv.visitVarInsn(ALOAD, 0); + mv.visitFieldInsn(GETFIELD, newDynName, "_redkale_home", "Ljava/io/File;"); + mv.visitLdcInsn(ru.maxLength()); + mv.visitLdcInsn(ru.fileNameReg()); + mv.visitLdcInsn(ru.contentTypeReg()); + mv.visitMethodInsn(INVOKEVIRTUAL, "org/redkale/net/http/MultiContext", "partsFiles", "(Ljava/io/File;JLjava/lang/String;Ljava/lang/String;)[Ljava/io/File;", false); } mv.visitMethodInsn(INVOKEINTERFACE, attrInternalName, "set", "(Ljava/lang/Object;Ljava/lang/Object;)V", true); } diff --git a/src/org/redkale/net/http/RestUploadFile.java b/src/org/redkale/net/http/RestUploadFile.java new file mode 100644 index 000000000..388bc33ee --- /dev/null +++ b/src/org/redkale/net/http/RestUploadFile.java @@ -0,0 +1,48 @@ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package org.redkale.net.http; + +import java.lang.annotation.*; +import static java.lang.annotation.ElementType.*; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +/** + * + * 依附在RestService类的方法的参数上, 用于接收上传文件
+ * 只能标记在byte[]/File/File[] 类型的参数上
+ * + *

+ * 详情见: https://redkale.org + * + * @author zhangjx + */ +@Inherited +@Documented +@Target({PARAMETER, FIELD}) +@Retention(RUNTIME) +public @interface RestUploadFile { + + /** + * 可接收的文件大小最大值, 小于1表示无大小限制 + * + * @return int + */ + long maxLength() default 0; + + /** + * 可接收的文件名正则表达式, 为空表示接收任何文件
+ * + * @return String + */ + String fileNameReg() default ""; + + /** + * 可接收的ContentType正则表达式, 为空表示接收任何文件类型
+ * + * @return String + */ + String contentTypeReg() default ""; +} diff --git a/test/org/redkale/test/rest/HelloEntity.java b/test/org/redkale/test/rest/HelloEntity.java index f2dd74022..01d73e804 100644 --- a/test/org/redkale/test/rest/HelloEntity.java +++ b/test/org/redkale/test/rest/HelloEntity.java @@ -29,6 +29,9 @@ public class HelloEntity { @RestBody private byte[] bodys; + @RestUploadFile + private byte[] uploads; + @RestBody private Map bodymap; @@ -123,6 +126,14 @@ public class HelloEntity { this.bodymap = bodymap; } + public byte[] getUploads() { + return uploads; + } + + public void setUploads(byte[] uploads) { + this.uploads = uploads; + } + @Override public String toString() { return JsonFactory.root().getConvert().convertTo(this);