org.redkale.net.http.HttpServlet Maven / Gradle / Ivy
/*
* 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.io.IOException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import org.redkale.asm.*;
import static org.redkale.asm.ClassWriter.COMPUTE_FRAMES;
import static org.redkale.asm.Opcodes.*;
import org.redkale.net.*;
import org.redkale.service.RetResult;
import org.redkale.util.*;
/**
* HTTP版的Servlet, 执行顺序 execute --> preExecute --> authenticate --> HttpMapping对应的方法
*
*
* 详情见: https://redkale.org
*
* @author zhangjx
*/
public class HttpServlet extends Servlet {
public static final int RET_SERVER_ERROR = 1200_0001;
public static final int RET_METHOD_ERROR = 1200_0002;
String _prefix = ""; //当前HttpServlet的path前缀
HashMap _tmpentrys; //Rest生成时赋值, 字段名Rest有用到
private Map.Entry[] mappings; //字段名Rest有用到
//这里不能直接使用HttpServlet,会造成死循环初始化HttpServlet
private final Servlet authSuccessServlet = new Servlet() {
@Override
public void execute(HttpRequest request, HttpResponse response) throws IOException {
InnerActionEntry entry = (InnerActionEntry) request.attachment;
if (entry.cacheseconds > 0) {//有缓存设置
CacheEntry ce = entry.cache.get(request.getRequestURI());
if (ce != null && ce.time + entry.cacheseconds > System.currentTimeMillis()) { //缓存有效
response.setStatus(ce.status);
response.setContentType(ce.contentType);
response.finish(ce.getBuffers());
return;
}
response.setBufferHandler(entry.cacheHandler);
}
entry.servlet.execute(request, response);
}
};
//preExecute运行完后执行的Servlet
private final Servlet preSuccessServlet = new Servlet() {
@Override
public void execute(HttpRequest request, HttpResponse response) throws IOException {
for (Map.Entry en : mappings) {
if (request.getRequestURI().startsWith(en.getKey())) {
InnerActionEntry entry = en.getValue();
if (!entry.checkMethod(request.getMethod())) {
response.finishJson(new RetResult(RET_METHOD_ERROR, "Method(" + request.getMethod() + ") Error"));
return;
}
request.attachment = entry;
request.moduleid = entry.moduleid;
request.actionid = entry.actionid;
if (entry.auth) {
response.thenEvent(authSuccessServlet);
authenticate(request, response);
} else {
authSuccessServlet.execute(request, response);
}
return;
}
}
throw new IOException(this.getClass().getName() + " not found method for URI(" + request.getRequestURI() + ")");
}
};
@SuppressWarnings("unchecked")
void preInit(HttpContext context, AnyValue config) {
if (this.mappings != null) return; //无需重复preInit
String path = _prefix == null ? "" : _prefix;
WebServlet ws = this.getClass().getAnnotation(WebServlet.class);
if (ws != null && !ws.repair()) path = "";
HashMap map = this._tmpentrys != null ? this._tmpentrys : loadActionEntry();
this.mappings = new Map.Entry[map.size()];
int i = -1;
for (Map.Entry en : map.entrySet()) {
mappings[++i] = new AbstractMap.SimpleEntry<>(path + en.getKey(), en.getValue());
}
//必须要倒排序, /query /query1 /query12 确保含子集的优先匹配 /query12 /query1 /query
Arrays.sort(mappings, (o1, o2) -> o2.getKey().compareTo(o1.getKey()));
}
void postDestroy(HttpContext context, AnyValue config) {
}
/**
*
* 预执行方法,在execute方法之前运行,设置当前用户信息,或者加入常规统计和基础检测,例如 :
*
* @Override
* public void preExecute(final HttpRequest request, final HttpResponse response) throws IOException {
* //设置当前用户信息
* final String sessionid = request.getSessionid(false);
* if (sessionid != null) request.setCurrentUser(userService.current(sessionid));
*
* if (finer) response.recycleListener((req, resp) -> { //记录处理时间比较长的请求
* long e = System.currentTimeMillis() - ((HttpRequest) req).getCreatetime();
* if (e > 200) logger.finer("http-execute-cost-time: " + e + " ms. request = " + req);
* });
* response.nextEvent();
* }
*
*
*
* @param request HttpRequest
* @param response HttpResponse
*
* @throws IOException IOException
*/
protected void preExecute(HttpRequest request, HttpResponse response) throws IOException {
response.nextEvent();
}
/**
*
* 用户登录或权限验证, 注解为@HttpMapping.auth == true 的方法会执行authenticate方法, 若验证成功则必须调用response.nextEvent();进行下一步操作, 例如:
*
* @Override
* public void authenticate(HttpRequest request, HttpResponse response) throws IOException {
* UserInfo info = request.currentUser();
* if (info == null) {
* response.finishJson(RET_UNLOGIN);
* return;
* } else if (!info.checkAuth(request.getModuleid(), request.getActionid())) {
* response.finishJson(RET_AUTHILLEGAL);
* return;
* }
* response.nextEvent();
* }
*
*
*
*
* @param request HttpRequest
* @param response HttpResponse
*
* @throws IOException IOException
*/
protected void authenticate(HttpRequest request, HttpResponse response) throws IOException {
response.nextEvent();
}
@Override
public void execute(HttpRequest request, HttpResponse response) throws IOException {
response.thenEvent(preSuccessServlet);
preExecute(request, response);
}
private HashMap loadActionEntry() {
WebServlet module = this.getClass().getAnnotation(WebServlet.class);
final int serviceid = module == null ? 0 : module.moduleid();
final HashMap map = new HashMap<>();
HashMap nameset = new HashMap<>();
final Class selfClz = this.getClass();
Class clz = this.getClass();
do {
if (java.lang.reflect.Modifier.isAbstract(clz.getModifiers())) break;
for (final Method method : clz.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 HttpMapping mapping = method.getAnnotation(HttpMapping.class);
if (mapping == null) continue;
final boolean inherited = mapping.inherited();
if (!inherited && selfClz != clz) continue; //忽略不被继承的方法
final int actionid = mapping.actionid();
final String name = mapping.url().trim();
final String[] methods = mapping.methods();
if (nameset.containsKey(name)) {
if (nameset.get(name) != clz) continue;
throw new RuntimeException(this.getClass().getSimpleName() + " have two same " + HttpMapping.class.getSimpleName() + "(" + name + ")");
}
nameset.put(name, clz);
map.put(name, new InnerActionEntry(serviceid, actionid, name, methods, method, createActionServlet(method)));
}
} while ((clz = clz.getSuperclass()) != HttpServlet.class);
return map;
}
protected static final class InnerActionEntry {
InnerActionEntry(int moduleid, int actionid, String name, String[] methods, Method method, HttpServlet servlet) {
this(moduleid, actionid, name, methods, method, auth(method), cacheseconds(method), servlet);
}
//供Rest类使用,参数不能随便更改
public InnerActionEntry(int moduleid, int actionid, String name, String[] methods, Method method, boolean auth, int cacheseconds, HttpServlet servlet) {
this.moduleid = moduleid;
this.actionid = actionid;
this.name = name;
this.methods = methods;
this.method = method; //rest构建会为null
this.servlet = servlet;
this.auth = auth;
this.cacheseconds = cacheseconds;
this.cache = cacheseconds > 0 ? new ConcurrentHashMap<>() : null;
this.cacheHandler = cacheseconds > 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;
}
private static boolean auth(Method method) {
HttpMapping mapping = method.getAnnotation(HttpMapping.class);
return mapping == null || mapping.auth();
}
private static int cacheseconds(Method method) {
HttpMapping mapping = method.getAnnotation(HttpMapping.class);
return mapping == null ? 0 : mapping.cacheseconds();
}
boolean isNeedCheck() {
return this.moduleid != 0 || this.actionid != 0;
}
boolean checkMethod(final String reqMethod) {
if (methods.length == 0) return true;
for (String m : methods) {
if (reqMethod.equalsIgnoreCase(m)) return true;
}
return false;
}
final BiFunction cacheHandler;
final ConcurrentHashMap cache;
final int cacheseconds;
final boolean auth;
final int moduleid;
final int actionid;
final String name;
final String[] methods;
final Method method;
final HttpServlet servlet;
}
private HttpServlet createActionServlet(final Method method) {
//------------------------------------------------------------------------------
final String supDynName = HttpServlet.class.getName().replace('.', '/');
final String interName = this.getClass().getName().replace('.', '/');
final String interDesc = org.redkale.asm.Type.getDescriptor(this.getClass());
final String requestSupDesc = org.redkale.asm.Type.getDescriptor(Request.class);
final String responseSupDesc = org.redkale.asm.Type.getDescriptor(Response.class);
final String requestDesc = org.redkale.asm.Type.getDescriptor(HttpRequest.class);
final String responseDesc = org.redkale.asm.Type.getDescriptor(HttpResponse.class);
String newDynName = interName + "_Dyn_" + method.getName();
int i = 0;
for (;;) {
try {
Thread.currentThread().getContextClassLoader().loadClass(newDynName.replace('/', '.'));
newDynName += "_" + (++i);
} catch (Throwable 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, "", "()V", null, null));
//mv.setDebug(true);
mv.visitVarInsn(ALOAD, 0);
mv.visitMethodInsn(INVOKESPECIAL, supDynName, "", "()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.getDeclaredConstructor().newInstance();
instance.getClass().getField(factfield).set(instance, this);
return instance;
} catch (Exception ex) {
throw new RuntimeException(ex);
}
}
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;
}
}
}