This commit is contained in:
@@ -5,362 +5,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.redkale.net.http;
|
package org.redkale.net.http;
|
||||||
|
|
||||||
import org.redkale.net.Response;
|
|
||||||
import org.redkale.net.Request;
|
|
||||||
import org.redkale.util.AnyValue;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.annotation.*;
|
|
||||||
import static java.lang.annotation.ElementType.*;
|
|
||||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.nio.*;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.concurrent.*;
|
|
||||||
import java.util.logging.*;
|
|
||||||
import jdk.internal.org.objectweb.asm.*;
|
|
||||||
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
|
|
||||||
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
|
||||||
import org.redkale.service.RetResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* <p>
|
* <p>
|
||||||
* 详情见: http://redkale.org
|
* 详情见: http://redkale.org
|
||||||
*
|
*
|
||||||
|
* @deprecated 使用 org.redkale.net.http.HttpBaseServlet 代替
|
||||||
|
*
|
||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
*/
|
*/
|
||||||
public abstract class BasedHttpServlet extends HttpServlet {
|
public abstract class BasedHttpServlet extends HttpBaseServlet {
|
||||||
|
|
||||||
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
|
||||||
|
|
||||||
public static final int RET_SERVER_ERROR = 1800_0001;
|
|
||||||
|
|
||||||
public static final int RET_METHOD_ERROR = 1800_0002;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配合 BasedHttpServlet 使用。
|
|
||||||
* 当标记为 @AuthIgnore 的方法在执行execute之前不会调用authenticate 方法。
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 详情见: http://redkale.org
|
|
||||||
*
|
|
||||||
* @author zhangjx
|
|
||||||
*/
|
|
||||||
@Inherited
|
|
||||||
@Documented
|
|
||||||
@Target({METHOD, TYPE})
|
|
||||||
@Retention(RUNTIME)
|
|
||||||
protected @interface AuthIgnore {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配合 BasedHttpServlet 使用。
|
|
||||||
* 用于对@WebServlet对应的url进行细分。 其url必须是包含WebServlet中定义的前缀, 且不能是正则表达式
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 详情见: http://redkale.org
|
|
||||||
*
|
|
||||||
* @author zhangjx
|
|
||||||
*/
|
|
||||||
@Target({ElementType.METHOD})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
protected @interface WebAction {
|
|
||||||
|
|
||||||
int actionid() default 0;
|
|
||||||
|
|
||||||
String url();
|
|
||||||
|
|
||||||
String[] methods() default {};//允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配合 BasedHttpServlet 使用。
|
|
||||||
* 当标记为 @HttpCacheable 的方法使用response.finish的参数将被缓存一定时间(默认值timeout=15秒)。
|
|
||||||
* 通常情况下 @HttpCacheable 需要与 @AuthIgnore 一起使用,因为没有标记@AuthIgnore的方法一般输出的结果与当前用户信息有关。
|
|
||||||
*
|
|
||||||
* <p>
|
|
||||||
* 详情见: http://redkale.org
|
|
||||||
*
|
|
||||||
* @author zhangjx
|
|
||||||
*/
|
|
||||||
@Target({ElementType.METHOD})
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
@Documented
|
|
||||||
protected @interface HttpCacheable {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 超时的秒数
|
|
||||||
*
|
|
||||||
* @return 超时秒数
|
|
||||||
*/
|
|
||||||
int timeout() default 15;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map.Entry<String, Entry>[] actions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 异常输出
|
|
||||||
*
|
|
||||||
* @param req HTTP请求对象
|
|
||||||
* @param resp HTTP响应对象
|
|
||||||
* @param exp 异常
|
|
||||||
*/
|
|
||||||
protected void sendExceptionResult(HttpRequest req, HttpResponse resp, Throwable exp) {
|
|
||||||
logger.log(Level.SEVERE, "request = " + req, exp);
|
|
||||||
resp.finishJson(new RetResult(RET_SERVER_ERROR, "Server Error"));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean preExecute(HttpRequest request, HttpResponse response) throws IOException {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void execute(HttpRequest request, HttpResponse response) throws IOException {
|
|
||||||
if (!preExecute(request, response)) return;
|
|
||||||
for (Map.Entry<String, Entry> en : actions) {
|
|
||||||
if (request.getRequestURI().startsWith(en.getKey())) {
|
|
||||||
Entry entry = en.getValue();
|
|
||||||
if (!entry.checkMethod(request.getMethod())) {
|
|
||||||
response.finishJson(new RetResult(RET_METHOD_ERROR, "Method(" + request.getMethod() + ") Error"));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (entry.ignore || authenticate(entry.moduleid, entry.actionid, request, response)) {
|
|
||||||
if (entry.cachetimeout > 0) {//有缓存设置
|
|
||||||
CacheEntry ce = entry.cache.get(request.getRequestURI());
|
|
||||||
if (ce != null && ce.time + entry.cachetimeout > System.currentTimeMillis()) { //缓存有效
|
|
||||||
response.setStatus(ce.status);
|
|
||||||
response.setContentType(ce.contentType);
|
|
||||||
response.finish(ce.getBuffers());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
response.setBufferHandler(entry.cacheHandler);
|
|
||||||
}
|
|
||||||
entry.servlet.execute(request, response);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new IOException(this.getClass().getName() + " not found method for URI(" + request.getRequestURI() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void preInit(HttpContext context, AnyValue config) {
|
|
||||||
String path = _prefix == null ? "" : _prefix;
|
|
||||||
WebServlet ws = this.getClass().getAnnotation(WebServlet.class);
|
|
||||||
if (ws != null && !ws.repair()) path = "";
|
|
||||||
HashMap<String, Entry> map = load();
|
|
||||||
this.actions = new Map.Entry[map.size()];
|
|
||||||
int i = -1;
|
|
||||||
for (Map.Entry<String, Entry> en : map.entrySet()) {
|
|
||||||
actions[++i] = new AbstractMap.SimpleEntry<>(path + en.getKey(), en.getValue());
|
|
||||||
}
|
|
||||||
//必须要倒排序, /query /query1 /query12 确保含子集的优先匹配 /query12 /query1 /query
|
|
||||||
Arrays.sort(actions, (o1, o2) -> o2.getKey().compareTo(o1.getKey()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public final void postDestroy(HttpContext context, AnyValue config) {
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract boolean authenticate(int module, int actionid, HttpRequest request, HttpResponse response) throws IOException;
|
|
||||||
|
|
||||||
private HashMap<String, Entry> load() {
|
|
||||||
final boolean typeIgnore = this.getClass().getAnnotation(AuthIgnore.class) != null;
|
|
||||||
WebServlet module = this.getClass().getAnnotation(WebServlet.class);
|
|
||||||
final int serviceid = module == null ? 0 : module.moduleid();
|
|
||||||
final HashMap<String, Entry> map = new HashMap<>();
|
|
||||||
Set<String> nameset = new HashSet<>();
|
|
||||||
for (final Method method : this.getClass().getMethods()) {
|
|
||||||
//-----------------------------------------------
|
|
||||||
String methodname = method.getName();
|
|
||||||
if ("service".equals(methodname) || "preExecute".equals(methodname) || "execute".equals(methodname) || "authenticate".equals(methodname)) continue;
|
|
||||||
//-----------------------------------------------
|
|
||||||
Class[] paramTypes = method.getParameterTypes();
|
|
||||||
if (paramTypes.length != 2 || paramTypes[0] != HttpRequest.class
|
|
||||||
|| paramTypes[1] != HttpResponse.class) continue;
|
|
||||||
//-----------------------------------------------
|
|
||||||
Class[] exps = method.getExceptionTypes();
|
|
||||||
if (exps.length > 0 && (exps.length != 1 || exps[0] != IOException.class)) continue;
|
|
||||||
//-----------------------------------------------
|
|
||||||
|
|
||||||
final WebAction action = method.getAnnotation(WebAction.class);
|
|
||||||
if (action == null) continue;
|
|
||||||
final int actionid = action.actionid();
|
|
||||||
final String name = action.url().trim();
|
|
||||||
|
|
||||||
if (nameset.contains(name)) throw new RuntimeException(this.getClass().getSimpleName() + " has two same " + WebAction.class.getSimpleName() + "(" + name + ")");
|
|
||||||
//屏蔽以下代码,允许相互包含
|
|
||||||
// for (String n : nameset) {
|
|
||||||
// if (n.contains(name) || name.contains(n)) {
|
|
||||||
// throw new RuntimeException(this.getClass().getSimpleName() + " has two sub-contains " + WebAction.class.getSimpleName() + "(" + name + ", " + n + ")");
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
nameset.add(name);
|
|
||||||
map.put(name, new Entry(typeIgnore, serviceid, actionid, name, action.methods(), method, createHttpServlet(method)));
|
|
||||||
}
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpServlet createHttpServlet(final Method method) {
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
final String supDynName = HttpServlet.class.getName().replace('.', '/');
|
|
||||||
final String interName = this.getClass().getName().replace('.', '/');
|
|
||||||
final String interDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(this.getClass());
|
|
||||||
final String requestSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Request.class);
|
|
||||||
final String responseSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Response.class);
|
|
||||||
final String requestDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpRequest.class);
|
|
||||||
final String responseDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpResponse.class);
|
|
||||||
String newDynName = interName + "_Dyn_" + method.getName();
|
|
||||||
int i = 0;
|
|
||||||
for (;;) {
|
|
||||||
try {
|
|
||||||
Class.forName(newDynName.replace('/', '.'));
|
|
||||||
newDynName += "_" + (++i);
|
|
||||||
} catch (Exception ex) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
|
|
||||||
FieldVisitor fv;
|
|
||||||
MethodVisitor mv;
|
|
||||||
AnnotationVisitor av0;
|
|
||||||
final String factfield = "_factServlet";
|
|
||||||
cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null);
|
|
||||||
{
|
|
||||||
fv = cw.visitField(ACC_PUBLIC, factfield, interDesc, null, null);
|
|
||||||
fv.visitEnd();
|
|
||||||
}
|
|
||||||
{ //构造函数
|
|
||||||
mv = (cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null));
|
|
||||||
//mv.setDebug(true);
|
|
||||||
mv.visitVarInsn(ALOAD, 0);
|
|
||||||
mv.visitMethodInsn(INVOKESPECIAL, supDynName, "<init>", "()V", false);
|
|
||||||
mv.visitInsn(RETURN);
|
|
||||||
mv.visitMaxs(1, 1);
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
mv = (cw.visitMethod(ACC_PUBLIC, "execute", "(" + requestDesc + responseDesc + ")V", null, new String[]{"java/io/IOException"}));
|
|
||||||
mv.visitVarInsn(ALOAD, 0);
|
|
||||||
mv.visitFieldInsn(GETFIELD, newDynName, factfield, interDesc);
|
|
||||||
mv.visitVarInsn(ALOAD, 1);
|
|
||||||
mv.visitVarInsn(ALOAD, 2);
|
|
||||||
mv.visitMethodInsn(INVOKEVIRTUAL, interName, method.getName(), "(" + requestDesc + responseDesc + ")V", false);
|
|
||||||
mv.visitInsn(RETURN);
|
|
||||||
mv.visitMaxs(3, 3);
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
{
|
|
||||||
mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "execute", "(" + requestSupDesc + responseSupDesc + ")V", null, new String[]{"java/io/IOException"});
|
|
||||||
mv.visitVarInsn(ALOAD, 0);
|
|
||||||
mv.visitVarInsn(ALOAD, 1);
|
|
||||||
mv.visitTypeInsn(CHECKCAST, HttpRequest.class.getName().replace('.', '/'));
|
|
||||||
mv.visitVarInsn(ALOAD, 2);
|
|
||||||
mv.visitTypeInsn(CHECKCAST, HttpResponse.class.getName().replace('.', '/'));
|
|
||||||
mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "execute", "(" + requestDesc + responseDesc + ")V", false);
|
|
||||||
mv.visitInsn(RETURN);
|
|
||||||
mv.visitMaxs(3, 3);
|
|
||||||
mv.visitEnd();
|
|
||||||
}
|
|
||||||
cw.visitEnd();
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
byte[] bytes = cw.toByteArray();
|
|
||||||
Class<?> newClazz = new ClassLoader(this.getClass().getClassLoader()) {
|
|
||||||
public final Class<?> loadClass(String name, byte[] b) {
|
|
||||||
return defineClass(name, b, 0, b.length);
|
|
||||||
}
|
|
||||||
}.loadClass(newDynName.replace('/', '.'), bytes);
|
|
||||||
try {
|
|
||||||
HttpServlet instance = (HttpServlet) newClazz.newInstance();
|
|
||||||
instance.getClass().getField(factfield).set(instance, this);
|
|
||||||
return instance;
|
|
||||||
} catch (Exception ex) {
|
|
||||||
throw new RuntimeException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class Entry {
|
|
||||||
|
|
||||||
public Entry(boolean typeIgnore, int moduleid, int actionid, String name, String[] methods, Method method, HttpServlet servlet) {
|
|
||||||
this.moduleid = moduleid;
|
|
||||||
this.actionid = actionid;
|
|
||||||
this.name = name;
|
|
||||||
this.methods = methods;
|
|
||||||
this.method = method;
|
|
||||||
this.servlet = servlet;
|
|
||||||
this.ignore = typeIgnore || method.getAnnotation(AuthIgnore.class) != null;
|
|
||||||
HttpCacheable hc = method.getAnnotation(HttpCacheable.class);
|
|
||||||
this.cachetimeout = hc == null ? 0 : hc.timeout() * 1000;
|
|
||||||
this.cache = cachetimeout > 0 ? new ConcurrentHashMap() : null;
|
|
||||||
this.cacheHandler = cachetimeout > 0 ? (HttpResponse response, ByteBuffer[] buffers) -> {
|
|
||||||
int status = response.getStatus();
|
|
||||||
if (status != 200) return null;
|
|
||||||
CacheEntry ce = new CacheEntry(response.getStatus(), response.getContentType(), buffers);
|
|
||||||
cache.put(response.getRequest().getRequestURI(), ce);
|
|
||||||
return ce.getBuffers();
|
|
||||||
} : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isNeedCheck() {
|
|
||||||
return this.moduleid != 0 || this.actionid != 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean checkMethod(final String reqMethod) {
|
|
||||||
if (methods.length == 0) return true;
|
|
||||||
for (String m : methods) {
|
|
||||||
if (reqMethod.equalsIgnoreCase(m)) return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final HttpResponse.BufferHandler cacheHandler;
|
|
||||||
|
|
||||||
public final ConcurrentHashMap<String, CacheEntry> cache;
|
|
||||||
|
|
||||||
public final int cachetimeout;
|
|
||||||
|
|
||||||
public final boolean ignore;
|
|
||||||
|
|
||||||
public final int moduleid;
|
|
||||||
|
|
||||||
public final int actionid;
|
|
||||||
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
public final String[] methods;
|
|
||||||
|
|
||||||
public final Method method;
|
|
||||||
|
|
||||||
public final HttpServlet servlet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final class CacheEntry {
|
|
||||||
|
|
||||||
public final long time = System.currentTimeMillis();
|
|
||||||
|
|
||||||
private final ByteBuffer[] buffers;
|
|
||||||
|
|
||||||
private final int status;
|
|
||||||
|
|
||||||
private final String contentType;
|
|
||||||
|
|
||||||
public CacheEntry(int status, String contentType, ByteBuffer[] bufs) {
|
|
||||||
this.status = status;
|
|
||||||
this.contentType = contentType;
|
|
||||||
final ByteBuffer[] newBuffers = new ByteBuffer[bufs.length];
|
|
||||||
for (int i = 0; i < newBuffers.length; i++) {
|
|
||||||
newBuffers[i] = bufs[i].duplicate().asReadOnlyBuffer();
|
|
||||||
}
|
|
||||||
this.buffers = newBuffers;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ByteBuffer[] getBuffers() {
|
|
||||||
final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length];
|
|
||||||
for (int i = 0; i < newBuffers.length; i++) {
|
|
||||||
newBuffers[i] = buffers[i].duplicate();
|
|
||||||
}
|
|
||||||
return newBuffers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
366
src/org/redkale/net/http/HttpBaseServlet.java
Normal file
366
src/org/redkale/net/http/HttpBaseServlet.java
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.redkale.net.Response;
|
||||||
|
import org.redkale.net.Request;
|
||||||
|
import org.redkale.util.AnyValue;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.annotation.*;
|
||||||
|
import static java.lang.annotation.ElementType.*;
|
||||||
|
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.nio.*;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
import java.util.logging.*;
|
||||||
|
import jdk.internal.org.objectweb.asm.*;
|
||||||
|
import static jdk.internal.org.objectweb.asm.ClassWriter.COMPUTE_FRAMES;
|
||||||
|
import static jdk.internal.org.objectweb.asm.Opcodes.*;
|
||||||
|
import org.redkale.service.RetResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 详情见: http://redkale.org
|
||||||
|
*
|
||||||
|
* @author zhangjx
|
||||||
|
*/
|
||||||
|
public abstract class HttpBaseServlet extends HttpServlet {
|
||||||
|
|
||||||
|
protected final Logger logger = Logger.getLogger(this.getClass().getSimpleName());
|
||||||
|
|
||||||
|
public static final int RET_SERVER_ERROR = 1800_0001;
|
||||||
|
|
||||||
|
public static final int RET_METHOD_ERROR = 1800_0002;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配合 HttpBaseServlet 使用。
|
||||||
|
* 当标记为 @AuthIgnore 的方法在执行execute之前不会调用authenticate 方法。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 详情见: http://redkale.org
|
||||||
|
*
|
||||||
|
* @author zhangjx
|
||||||
|
*/
|
||||||
|
@Inherited
|
||||||
|
@Documented
|
||||||
|
@Target({METHOD, TYPE})
|
||||||
|
@Retention(RUNTIME)
|
||||||
|
protected @interface AuthIgnore {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配合 HttpBaseServlet 使用。
|
||||||
|
* 用于对@WebServlet对应的url进行细分。 其url必须是包含WebServlet中定义的前缀, 且不能是正则表达式
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 详情见: http://redkale.org
|
||||||
|
*
|
||||||
|
* @author zhangjx
|
||||||
|
*/
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
protected @interface WebAction {
|
||||||
|
|
||||||
|
int actionid() default 0;
|
||||||
|
|
||||||
|
String url();
|
||||||
|
|
||||||
|
String[] methods() default {};//允许方法(不区分大小写),如:GET/POST/PUT,为空表示允许所有方法
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配合 HttpBaseServlet 使用。
|
||||||
|
* 当标记为 @HttpCacheable 的方法使用response.finish的参数将被缓存一定时间(默认值timeout=15秒)。
|
||||||
|
* 通常情况下 @HttpCacheable 需要与 @AuthIgnore 一起使用,因为没有标记@AuthIgnore的方法一般输出的结果与当前用户信息有关。
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* 详情见: http://redkale.org
|
||||||
|
*
|
||||||
|
* @author zhangjx
|
||||||
|
*/
|
||||||
|
@Target({ElementType.METHOD})
|
||||||
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
|
@Documented
|
||||||
|
protected @interface HttpCacheable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 超时的秒数
|
||||||
|
*
|
||||||
|
* @return 超时秒数
|
||||||
|
*/
|
||||||
|
int timeout() default 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map.Entry<String, Entry>[] actions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异常输出
|
||||||
|
*
|
||||||
|
* @param req HTTP请求对象
|
||||||
|
* @param resp HTTP响应对象
|
||||||
|
* @param exp 异常
|
||||||
|
*/
|
||||||
|
protected void sendExceptionResult(HttpRequest req, HttpResponse resp, Throwable exp) {
|
||||||
|
logger.log(Level.SEVERE, "request = " + req, exp);
|
||||||
|
resp.finishJson(new RetResult(RET_SERVER_ERROR, "Server Error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean preExecute(HttpRequest request, HttpResponse response) throws IOException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void execute(HttpRequest request, HttpResponse response) throws IOException {
|
||||||
|
if (!preExecute(request, response)) return;
|
||||||
|
for (Map.Entry<String, Entry> en : actions) {
|
||||||
|
if (request.getRequestURI().startsWith(en.getKey())) {
|
||||||
|
Entry entry = en.getValue();
|
||||||
|
if (!entry.checkMethod(request.getMethod())) {
|
||||||
|
response.finishJson(new RetResult(RET_METHOD_ERROR, "Method(" + request.getMethod() + ") Error"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entry.ignore || authenticate(entry.moduleid, entry.actionid, request, response)) {
|
||||||
|
if (entry.cachetimeout > 0) {//有缓存设置
|
||||||
|
CacheEntry ce = entry.cache.get(request.getRequestURI());
|
||||||
|
if (ce != null && ce.time + entry.cachetimeout > System.currentTimeMillis()) { //缓存有效
|
||||||
|
response.setStatus(ce.status);
|
||||||
|
response.setContentType(ce.contentType);
|
||||||
|
response.finish(ce.getBuffers());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
response.setBufferHandler(entry.cacheHandler);
|
||||||
|
}
|
||||||
|
entry.servlet.execute(request, response);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new IOException(this.getClass().getName() + " not found method for URI(" + request.getRequestURI() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void preInit(HttpContext context, AnyValue config) {
|
||||||
|
String path = _prefix == null ? "" : _prefix;
|
||||||
|
WebServlet ws = this.getClass().getAnnotation(WebServlet.class);
|
||||||
|
if (ws != null && !ws.repair()) path = "";
|
||||||
|
HashMap<String, Entry> map = load();
|
||||||
|
this.actions = new Map.Entry[map.size()];
|
||||||
|
int i = -1;
|
||||||
|
for (Map.Entry<String, Entry> en : map.entrySet()) {
|
||||||
|
actions[++i] = new AbstractMap.SimpleEntry<>(path + en.getKey(), en.getValue());
|
||||||
|
}
|
||||||
|
//必须要倒排序, /query /query1 /query12 确保含子集的优先匹配 /query12 /query1 /query
|
||||||
|
Arrays.sort(actions, (o1, o2) -> o2.getKey().compareTo(o1.getKey()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void postDestroy(HttpContext context, AnyValue config) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract boolean authenticate(int module, int actionid, HttpRequest request, HttpResponse response) throws IOException;
|
||||||
|
|
||||||
|
private HashMap<String, Entry> load() {
|
||||||
|
final boolean typeIgnore = this.getClass().getAnnotation(AuthIgnore.class) != null;
|
||||||
|
WebServlet module = this.getClass().getAnnotation(WebServlet.class);
|
||||||
|
final int serviceid = module == null ? 0 : module.moduleid();
|
||||||
|
final HashMap<String, Entry> map = new HashMap<>();
|
||||||
|
Set<String> nameset = new HashSet<>();
|
||||||
|
for (final Method method : this.getClass().getMethods()) {
|
||||||
|
//-----------------------------------------------
|
||||||
|
String methodname = method.getName();
|
||||||
|
if ("service".equals(methodname) || "preExecute".equals(methodname) || "execute".equals(methodname) || "authenticate".equals(methodname)) continue;
|
||||||
|
//-----------------------------------------------
|
||||||
|
Class[] paramTypes = method.getParameterTypes();
|
||||||
|
if (paramTypes.length != 2 || paramTypes[0] != HttpRequest.class
|
||||||
|
|| paramTypes[1] != HttpResponse.class) continue;
|
||||||
|
//-----------------------------------------------
|
||||||
|
Class[] exps = method.getExceptionTypes();
|
||||||
|
if (exps.length > 0 && (exps.length != 1 || exps[0] != IOException.class)) continue;
|
||||||
|
//-----------------------------------------------
|
||||||
|
|
||||||
|
final WebAction action = method.getAnnotation(WebAction.class);
|
||||||
|
if (action == null) continue;
|
||||||
|
final int actionid = action.actionid();
|
||||||
|
final String name = action.url().trim();
|
||||||
|
|
||||||
|
if (nameset.contains(name)) throw new RuntimeException(this.getClass().getSimpleName() + " has two same " + WebAction.class.getSimpleName() + "(" + name + ")");
|
||||||
|
//屏蔽以下代码,允许相互包含
|
||||||
|
// for (String n : nameset) {
|
||||||
|
// if (n.contains(name) || name.contains(n)) {
|
||||||
|
// throw new RuntimeException(this.getClass().getSimpleName() + " has two sub-contains " + WebAction.class.getSimpleName() + "(" + name + ", " + n + ")");
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
nameset.add(name);
|
||||||
|
map.put(name, new Entry(typeIgnore, serviceid, actionid, name, action.methods(), method, createHttpServlet(method)));
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private HttpServlet createHttpServlet(final Method method) {
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
final String supDynName = HttpServlet.class.getName().replace('.', '/');
|
||||||
|
final String interName = this.getClass().getName().replace('.', '/');
|
||||||
|
final String interDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(this.getClass());
|
||||||
|
final String requestSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Request.class);
|
||||||
|
final String responseSupDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(Response.class);
|
||||||
|
final String requestDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpRequest.class);
|
||||||
|
final String responseDesc = jdk.internal.org.objectweb.asm.Type.getDescriptor(HttpResponse.class);
|
||||||
|
String newDynName = interName + "_Dyn_" + method.getName();
|
||||||
|
int i = 0;
|
||||||
|
for (;;) {
|
||||||
|
try {
|
||||||
|
Class.forName(newDynName.replace('/', '.'));
|
||||||
|
newDynName += "_" + (++i);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
ClassWriter cw = new ClassWriter(COMPUTE_FRAMES);
|
||||||
|
FieldVisitor fv;
|
||||||
|
MethodVisitor mv;
|
||||||
|
AnnotationVisitor av0;
|
||||||
|
final String factfield = "_factServlet";
|
||||||
|
cw.visit(V1_8, ACC_PUBLIC + ACC_FINAL + ACC_SUPER, newDynName, null, supDynName, null);
|
||||||
|
{
|
||||||
|
fv = cw.visitField(ACC_PUBLIC, factfield, interDesc, null, null);
|
||||||
|
fv.visitEnd();
|
||||||
|
}
|
||||||
|
{ //构造函数
|
||||||
|
mv = (cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null));
|
||||||
|
//mv.setDebug(true);
|
||||||
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
|
mv.visitMethodInsn(INVOKESPECIAL, supDynName, "<init>", "()V", false);
|
||||||
|
mv.visitInsn(RETURN);
|
||||||
|
mv.visitMaxs(1, 1);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
mv = (cw.visitMethod(ACC_PUBLIC, "execute", "(" + requestDesc + responseDesc + ")V", null, new String[]{"java/io/IOException"}));
|
||||||
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
|
mv.visitFieldInsn(GETFIELD, newDynName, factfield, interDesc);
|
||||||
|
mv.visitVarInsn(ALOAD, 1);
|
||||||
|
mv.visitVarInsn(ALOAD, 2);
|
||||||
|
mv.visitMethodInsn(INVOKEVIRTUAL, interName, method.getName(), "(" + requestDesc + responseDesc + ")V", false);
|
||||||
|
mv.visitInsn(RETURN);
|
||||||
|
mv.visitMaxs(3, 3);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
mv = cw.visitMethod(ACC_PUBLIC + ACC_BRIDGE + ACC_SYNTHETIC, "execute", "(" + requestSupDesc + responseSupDesc + ")V", null, new String[]{"java/io/IOException"});
|
||||||
|
mv.visitVarInsn(ALOAD, 0);
|
||||||
|
mv.visitVarInsn(ALOAD, 1);
|
||||||
|
mv.visitTypeInsn(CHECKCAST, HttpRequest.class.getName().replace('.', '/'));
|
||||||
|
mv.visitVarInsn(ALOAD, 2);
|
||||||
|
mv.visitTypeInsn(CHECKCAST, HttpResponse.class.getName().replace('.', '/'));
|
||||||
|
mv.visitMethodInsn(INVOKEVIRTUAL, newDynName, "execute", "(" + requestDesc + responseDesc + ")V", false);
|
||||||
|
mv.visitInsn(RETURN);
|
||||||
|
mv.visitMaxs(3, 3);
|
||||||
|
mv.visitEnd();
|
||||||
|
}
|
||||||
|
cw.visitEnd();
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
byte[] bytes = cw.toByteArray();
|
||||||
|
Class<?> newClazz = new ClassLoader(this.getClass().getClassLoader()) {
|
||||||
|
public final Class<?> loadClass(String name, byte[] b) {
|
||||||
|
return defineClass(name, b, 0, b.length);
|
||||||
|
}
|
||||||
|
}.loadClass(newDynName.replace('/', '.'), bytes);
|
||||||
|
try {
|
||||||
|
HttpServlet instance = (HttpServlet) newClazz.newInstance();
|
||||||
|
instance.getClass().getField(factfield).set(instance, this);
|
||||||
|
return instance;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class Entry {
|
||||||
|
|
||||||
|
public Entry(boolean typeIgnore, int moduleid, int actionid, String name, String[] methods, Method method, HttpServlet servlet) {
|
||||||
|
this.moduleid = moduleid;
|
||||||
|
this.actionid = actionid;
|
||||||
|
this.name = name;
|
||||||
|
this.methods = methods;
|
||||||
|
this.method = method;
|
||||||
|
this.servlet = servlet;
|
||||||
|
this.ignore = typeIgnore || method.getAnnotation(AuthIgnore.class) != null;
|
||||||
|
HttpCacheable hc = method.getAnnotation(HttpCacheable.class);
|
||||||
|
this.cachetimeout = hc == null ? 0 : hc.timeout() * 1000;
|
||||||
|
this.cache = cachetimeout > 0 ? new ConcurrentHashMap() : null;
|
||||||
|
this.cacheHandler = cachetimeout > 0 ? (HttpResponse response, ByteBuffer[] buffers) -> {
|
||||||
|
int status = response.getStatus();
|
||||||
|
if (status != 200) return null;
|
||||||
|
CacheEntry ce = new CacheEntry(response.getStatus(), response.getContentType(), buffers);
|
||||||
|
cache.put(response.getRequest().getRequestURI(), ce);
|
||||||
|
return ce.getBuffers();
|
||||||
|
} : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isNeedCheck() {
|
||||||
|
return this.moduleid != 0 || this.actionid != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean checkMethod(final String reqMethod) {
|
||||||
|
if (methods.length == 0) return true;
|
||||||
|
for (String m : methods) {
|
||||||
|
if (reqMethod.equalsIgnoreCase(m)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final HttpResponse.BufferHandler cacheHandler;
|
||||||
|
|
||||||
|
public final ConcurrentHashMap<String, CacheEntry> cache;
|
||||||
|
|
||||||
|
public final int cachetimeout;
|
||||||
|
|
||||||
|
public final boolean ignore;
|
||||||
|
|
||||||
|
public final int moduleid;
|
||||||
|
|
||||||
|
public final int actionid;
|
||||||
|
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
public final String[] methods;
|
||||||
|
|
||||||
|
public final Method method;
|
||||||
|
|
||||||
|
public final HttpServlet servlet;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class CacheEntry {
|
||||||
|
|
||||||
|
public final long time = System.currentTimeMillis();
|
||||||
|
|
||||||
|
private final ByteBuffer[] buffers;
|
||||||
|
|
||||||
|
private final int status;
|
||||||
|
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
public CacheEntry(int status, String contentType, ByteBuffer[] bufs) {
|
||||||
|
this.status = status;
|
||||||
|
this.contentType = contentType;
|
||||||
|
final ByteBuffer[] newBuffers = new ByteBuffer[bufs.length];
|
||||||
|
for (int i = 0; i < newBuffers.length; i++) {
|
||||||
|
newBuffers[i] = bufs[i].duplicate().asReadOnlyBuffer();
|
||||||
|
}
|
||||||
|
this.buffers = newBuffers;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ByteBuffer[] getBuffers() {
|
||||||
|
final ByteBuffer[] newBuffers = new ByteBuffer[buffers.length];
|
||||||
|
for (int i = 0; i < newBuffers.length; i++) {
|
||||||
|
newBuffers[i] = buffers[i].duplicate();
|
||||||
|
}
|
||||||
|
return newBuffers;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -36,8 +36,8 @@ public final class HttpPrepareServlet extends PrepareServlet<String, HttpContext
|
|||||||
this.servlets.forEach(s -> {
|
this.servlets.forEach(s -> {
|
||||||
if (s instanceof WebSocketServlet) {
|
if (s instanceof WebSocketServlet) {
|
||||||
((WebSocketServlet) s).preInit(context, getServletConf(s));
|
((WebSocketServlet) s).preInit(context, getServletConf(s));
|
||||||
} else if (s instanceof BasedHttpServlet) {
|
} else if (s instanceof HttpBaseServlet) {
|
||||||
((BasedHttpServlet) s).preInit(context, getServletConf(s));
|
((HttpBaseServlet) s).preInit(context, getServletConf(s));
|
||||||
}
|
}
|
||||||
s.init(context, getServletConf(s));
|
s.init(context, getServletConf(s));
|
||||||
});
|
});
|
||||||
@@ -165,8 +165,8 @@ public final class HttpPrepareServlet extends PrepareServlet<String, HttpContext
|
|||||||
s.destroy(context, getServletConf(s));
|
s.destroy(context, getServletConf(s));
|
||||||
if (s instanceof WebSocketServlet) {
|
if (s instanceof WebSocketServlet) {
|
||||||
((WebSocketServlet) s).postDestroy(context, getServletConf(s));
|
((WebSocketServlet) s).postDestroy(context, getServletConf(s));
|
||||||
} else if (s instanceof BasedHttpServlet) {
|
} else if (s instanceof HttpBaseServlet) {
|
||||||
((BasedHttpServlet) s).postDestroy(context, getServletConf(s));
|
((HttpBaseServlet) s).postDestroy(context, getServletConf(s));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.allMapStrings.clear();
|
this.allMapStrings.clear();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import java.io.IOException;
|
|||||||
* @author zhangjx
|
* @author zhangjx
|
||||||
* @param <T>
|
* @param <T>
|
||||||
*/
|
*/
|
||||||
public abstract class RestHttpServlet<T> extends BasedHttpServlet {
|
public abstract class RestHttpServlet<T> extends HttpBaseServlet {
|
||||||
|
|
||||||
protected abstract T currentUser(HttpRequest req) throws IOException;
|
protected abstract T currentUser(HttpRequest req) throws IOException;
|
||||||
|
|
||||||
|
|||||||
@@ -29,6 +29,6 @@ public @interface RestService {
|
|||||||
|
|
||||||
boolean repair() default true; //同@WebServlet的repair属性
|
boolean repair() default true; //同@WebServlet的repair属性
|
||||||
|
|
||||||
int module() default 0; //模块ID值,鉴权时用到, 对应@WebServlet.ignore
|
int module() default 0; //模块ID值,鉴权时用到, 对应@WebServlet.module
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,8 +49,8 @@ public final class RestServletBuilder {
|
|||||||
final String webServletDesc = Type.getDescriptor(WebServlet.class);
|
final String webServletDesc = Type.getDescriptor(WebServlet.class);
|
||||||
final String httpRequestDesc = Type.getDescriptor(HttpRequest.class);
|
final String httpRequestDesc = Type.getDescriptor(HttpRequest.class);
|
||||||
final String httpResponseDesc = Type.getDescriptor(HttpResponse.class);
|
final String httpResponseDesc = Type.getDescriptor(HttpResponse.class);
|
||||||
final String authDesc = Type.getDescriptor(BasedHttpServlet.AuthIgnore.class);
|
final String authDesc = Type.getDescriptor(HttpBaseServlet.AuthIgnore.class);
|
||||||
final String actionDesc = Type.getDescriptor(BasedHttpServlet.WebAction.class);
|
final String actionDesc = Type.getDescriptor(HttpBaseServlet.WebAction.class);
|
||||||
final String serviceTypeString = serviceType.getName().replace('.', '/');
|
final String serviceTypeString = serviceType.getName().replace('.', '/');
|
||||||
final Class userType = getSuperUserType(baseServletClass);
|
final Class userType = getSuperUserType(baseServletClass);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user