diff --git a/.gitignore b/.gitignore index c456c4a..ef3fb04 100644 --- a/.gitignore +++ b/.gitignore @@ -11,7 +11,7 @@ .sts4-cache ### IntelliJ IDEA ### -.idea +.idea/ *.iws *.iml *.ipr diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 44895f1..2799bc2 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -9,6 +9,8 @@ - + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 4b661a5..d30d09e 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,5 @@ - - + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 0bae62b..965a79c 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,25 +1,86 @@ - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + - - + - + - - + + + + + + - - + + + @@ -75,7 +141,14 @@ - + + + + - - 1578489428831 + + 1578882265176 @@ -115,15 +188,8 @@ - - - - - file://$PROJECT_DIR$/src/main/java/cn/codeyourlife/server/ServerHandler.java - 46 - - - + + + \ No newline at end of file diff --git a/README.md b/README.md index 0b264f2..bd25eea 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,13 @@ 项目中涉及的框架相关知识并不多,主要涉及了许多 Java 基础的知识,如:Java 程序编译和运行的过程、Java 类加载机制、Java 类文件结构、Java 反射等。除此之外,还涉及到了一个简单的并发问题:如何将一个非线程安全的类变为一个线程安全的类。因此,本项目较为适合在比较注重基础的面试中介绍给面试官,可以引出一些 Java 虚拟机,Java 并发相关的问题,较能体现应聘者对于 Java 的一些原理性的知识的掌握程度。在本篇文章中,我们尽可能的将用到的知识简单讲解一下或者给出讲解的链接,以方便大家阅读。 + +## TODO +1. 基于注解实现类似spring的controller的注解; +1. 类似SpringBoot的Application启动接口。 +1. 系统运行日志 +1. 完善页面 + #### 运行效果 ![项目展示](./doc/pic/项目展示_update_20190430.gif) diff --git a/WebIDE.iml b/WebIDE.iml index 78b2cc5..0ad3e3b 100644 --- a/WebIDE.iml +++ b/WebIDE.iml @@ -1,2 +1,23 @@ - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 7c8e7ff..a736335 100644 --- a/pom.xml +++ b/pom.xml @@ -7,6 +7,23 @@ cn.codeyourlife WebIDE 1.0-SNAPSHOT + + + + org.apache.maven.plugins + maven-compiler-plugin + + 8 + 8 + + + + + + UTF-8 + UTF-8 + 1.8 + io.netty @@ -18,6 +35,33 @@ fastjson 1.2.62 + + org.ow2.asm + asm + 6.1 + + + + + + ch.qos.logback + logback-classic + 1.2.3 + + + org.slf4j + slf4j-api + 1.7.25 + + + + + junit + junit + 4.12 + test + + \ No newline at end of file diff --git a/src/main/java/cn/codeyourlife/JDEApplication.java b/src/main/java/cn/codeyourlife/JDEApplication.java new file mode 100644 index 0000000..4dd71fb --- /dev/null +++ b/src/main/java/cn/codeyourlife/JDEApplication.java @@ -0,0 +1,40 @@ +package cn.codeyourlife; + +import cn.codeyourlife.controller.ExceptionController; +import cn.codeyourlife.interceptor.CorsInterceptor; +import cn.codeyourlife.server.WebServer; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public class JDEApplication { + public static void main(String[] args) { + // 忽略指定url + WebServer.getIgnoreUrls().add("/favicon.ico"); + + // 全局异常处理 + WebServer.setExceptionHandler(new ExceptionController()); + + // 设置监听端口号 + WebServer server = new WebServer(2006); + + // 设置Http最大内容长度(默认 为10M) + server.setMaxContentLength(1024 * 1024 * 50); + + // 设置Controller所在包 + server.setControllerBasePackage("cn.codeyourlife.controller"); + + // 添加拦截器,按照添加的顺序执行。 + // 跨域拦截器 + server.addInterceptor(new CorsInterceptor(), "/不用拦截的url"); + + try { + server.start(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } +} diff --git a/src/main/java/cn/codeyourlife/WebIdeApplication.java b/src/main/java/cn/codeyourlife/WebIdeApplication.java deleted file mode 100644 index e4b5593..0000000 --- a/src/main/java/cn/codeyourlife/WebIdeApplication.java +++ /dev/null @@ -1,61 +0,0 @@ -package cn.codeyourlife; - -import cn.codeyourlife.server.ServerInitializer; -import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; -import io.netty.channel.nio.NioEventLoopGroup; -import io.netty.channel.socket.nio.NioServerSocketChannel; -import io.netty.handler.logging.LogLevel; -import io.netty.handler.logging.LoggingHandler; -import io.netty.handler.ssl.SslContext; -import io.netty.handler.ssl.SslContextBuilder; -import io.netty.handler.ssl.util.SelfSignedCertificate; - - -/** - * Author: wbq813@foxmail.com - * Copyright: http://codeyourlife.cn - * Platform: Win10 Jdk8 - * Date: 2020/1/8 - */ -public final class WebIdeApplication { - - /*是否使用https协议*/ - static final boolean SSL = System.getProperty("ssl") != null; - static final int PORT = Integer.parseInt(System.getProperty("port", SSL ? "8443" : "6789")); - - public static void main(String[] args) throws Exception { - // Configure SSL. - final SslContext sslCtx; - if (SSL) { - SelfSignedCertificate ssc = new SelfSignedCertificate(); - sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); - } else { - sslCtx = null; - } - - // Configure the server. - EventLoopGroup bossGroup = new NioEventLoopGroup(1); - EventLoopGroup workerGroup = new NioEventLoopGroup(); - try { - ServerBootstrap b = new ServerBootstrap(); - b.option(ChannelOption.SO_BACKLOG, 1024); - b.group(bossGroup, workerGroup) - .channel(NioServerSocketChannel.class) - .handler(new LoggingHandler(LogLevel.INFO)) - .childHandler(new ServerInitializer(sslCtx)); - - Channel ch = b.bind(PORT).sync().channel(); - - System.err.println("Open your web browser and navigate to " + - (SSL ? "https" : "http") + "://127.0.0.1:" + PORT + '/'); - - ch.closeFuture().sync(); - } finally { - bossGroup.shutdownGracefully(); - workerGroup.shutdownGracefully(); - } - } -} \ No newline at end of file diff --git a/src/main/java/cn/codeyourlife/compile/StringSourceCompiler.java b/src/main/java/cn/codeyourlife/compile/StringSourceCompiler.java index 3da948d..ada68e0 100644 --- a/src/main/java/cn/codeyourlife/compile/StringSourceCompiler.java +++ b/src/main/java/cn/codeyourlife/compile/StringSourceCompiler.java @@ -112,4 +112,4 @@ public byte[] getCompiledBytes() { return outputStream.toByteArray(); } } -} +} \ No newline at end of file diff --git a/src/main/java/cn/codeyourlife/controller/ExceptionController.java b/src/main/java/cn/codeyourlife/controller/ExceptionController.java new file mode 100644 index 0000000..4b8efca --- /dev/null +++ b/src/main/java/cn/codeyourlife/controller/ExceptionController.java @@ -0,0 +1,30 @@ +package cn.codeyourlife.controller; + +import cn.codeyourlife.server.HttpContextHolder; +import cn.codeyourlife.server.HttpResponse; +import cn.codeyourlife.server.HttpStatus; +import cn.codeyourlife.server.controller.ExceptionHandler; +import cn.codeyourlife.server.exception.ResourceNotFoundException; + +public class ExceptionController implements ExceptionHandler { + + /** + * 处理异常 + * @param e + */ + @Override + public void doHandle(Exception e) { + HttpStatus status = HttpStatus.INTERNAL_SERVER_ERROR; + if(e instanceof ResourceNotFoundException) { + status = HttpStatus.NOT_FOUND; + } + String errorMessage = e.getCause() == null ? "" : e.getCause().getMessage(); + if(errorMessage == null) { + errorMessage = e.getMessage(); + } + HttpResponse response = HttpContextHolder.getResponse(); + response.write(status, errorMessage); + response.closeChannel(); + } + +} diff --git a/src/main/java/cn/codeyourlife/controller/RunCodeController.java b/src/main/java/cn/codeyourlife/controller/RunCodeController.java index c06e79b..5663431 100644 --- a/src/main/java/cn/codeyourlife/controller/RunCodeController.java +++ b/src/main/java/cn/codeyourlife/controller/RunCodeController.java @@ -1,11 +1,20 @@ package cn.codeyourlife.controller; +import cn.codeyourlife.server.ResponseEntity; +import cn.codeyourlife.server.annotation.GetMapping; +import cn.codeyourlife.server.annotation.JsonResponse; +import cn.codeyourlife.server.annotation.RequestMapping; +import cn.codeyourlife.server.annotation.RestController; + /** * Author: wbq813@foxmail.com * Copyright: http://codeyourlife.cn * Platform: Win10 Jdk8 * Date: 2020/1/8 */ + +@RestController +@RequestMapping("/ide") public class RunCodeController { private static final String defaultSource = "public class Run {\n" + " public static void main(String[] args) {\n" @@ -13,6 +22,11 @@ public class RunCodeController { + " }\n" + "}"; + @GetMapping("") + public ResponseEntity hello() { + return ResponseEntity.ok().build("hello world"); + } + // public String entry(Model model) { // model.addAttribute("lastSource", defaultSource); // return "ide"; diff --git a/src/main/java/cn/codeyourlife/execute/ByteUtils.java b/src/main/java/cn/codeyourlife/execute/ByteUtils.java index c9dc80b..3d9585c 100644 --- a/src/main/java/cn/codeyourlife/execute/ByteUtils.java +++ b/src/main/java/cn/codeyourlife/execute/ByteUtils.java @@ -37,4 +37,4 @@ public static byte[] byteReplace(byte[] oldBytes, int offset, int len, byte[] re oldBytes.length - offset - len); return newBytes; } -} +} \ No newline at end of file diff --git a/src/main/java/cn/codeyourlife/interceptor/CorsInterceptor.java b/src/main/java/cn/codeyourlife/interceptor/CorsInterceptor.java new file mode 100644 index 0000000..8b21226 --- /dev/null +++ b/src/main/java/cn/codeyourlife/interceptor/CorsInterceptor.java @@ -0,0 +1,30 @@ +package cn.codeyourlife.interceptor; + +import cn.codeyourlife.server.HttpResponse; +import cn.codeyourlife.server.interceptor.Interceptor; +import io.netty.handler.codec.http.FullHttpRequest; + +/** + * 跨域拦截器 + * @author Leo + */ +public final class CorsInterceptor implements Interceptor { + + @Override + public boolean preHandle(FullHttpRequest request, HttpResponse response) throws Exception { + response.getHeaders().put("Access-Control-Allow-Origin", "*"); + response.getHeaders().put("Access-Control-Allow-Methods", "POST, PUT, GET, OPTIONS, DELETE"); + response.getHeaders().put("Access-Control-Max-Age", "3600"); + response.getHeaders().put("Access-Control-Allow-Headers", "Content-Type, X-Token"); + return true; + } + + @Override + public void postHandle(FullHttpRequest request, HttpResponse response) throws Exception { + } + + @Override + public void afterCompletion(FullHttpRequest request, HttpResponse response) { + } + +} diff --git a/src/main/java/cn/codeyourlife/server/ControllerFactory.java b/src/main/java/cn/codeyourlife/server/ControllerFactory.java new file mode 100644 index 0000000..1a004b8 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/ControllerFactory.java @@ -0,0 +1,201 @@ +package cn.codeyourlife.server; + +import cn.codeyourlife.server.annotation.*; +import cn.codeyourlife.server.methods.*; +import cn.codeyourlife.server.router.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Method; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; + +/** + * 请求映射工厂类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class ControllerFactory { + + private final static Logger logger = LoggerFactory.getLogger(ControllerFactory.class); + + /** + * 注册Mpping + * + * @param basePackage + */ + public void registerController(String basePackage) { + Set> controllerClasses = findClassesByPackage(basePackage); + for (Class controllerClass : controllerClasses) { + registerClass(controllerClass); + } + } + + /** + * 扫描包路径下所有的class文件 + * + * @param packageName + * @return + */ + private Set> findClassesByPackage(String packageName) { + Set> classes = new LinkedHashSet<>(64); + String pkgDirName = packageName.replace('.', '/'); + try { + Enumeration urls = ControllerFactory.class.getClassLoader().getResources(pkgDirName); + while (urls.hasMoreElements()) { + URL url = urls.nextElement(); + String protocol = url.getProtocol(); + if ("file".equals(protocol)) { + // 如果是以文件的形式保存在服务器上 + String filePath = URLDecoder.decode(url.getFile(), "UTF-8"); + // 获取包的物理路径 + findClassesByFile(filePath, packageName, classes); + } else if ("jar".equals(protocol)) { + // 如果是jar包文件 + JarFile jar = ((JarURLConnection) url.openConnection()).getJarFile(); + findClassesByJar(packageName, jar, classes); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + return classes; + } + + /** + * 扫描包下的所有class文件 + * + * @param path + * @param packageName + * @param classes + */ + private void findClassesByFile(String path, String packageName, Set> classes) { + File dir = new File(path); + if (!dir.exists() || !dir.isDirectory()) { + return; + } + File[] files = dir.listFiles(filter -> filter.isDirectory() || filter.getName().endsWith("class")); + for (File f : files) { + if (f.isDirectory()) { + findClassesByFile(packageName + "." + f.getName(), path + "/" + f.getName(), classes); + continue; + } + + // 获取类名,去掉 ".class" 后缀 + String className = f.getName(); + className = packageName + "." + className.substring(0, className.length() - 6); + + // 加载类 + Class clazz = null; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + logger.error("Class not found, {}", className); + } + if (clazz != null && clazz.getAnnotation(RestController.class) != null) { + if (clazz != null) { + classes.add(clazz); + } + } + } + } + + /** + * 扫描包路径下的所有class文件 + * + * @param packageName + * 包名 + * @param jar + * jar文件 + * @param classes + * 保存包路径下class的集合 + */ + private static void findClassesByJar(String packageName, JarFile jar, Set> classes) { + String pkgDir = packageName.replace(".", "/"); + Enumeration entry = jar.entries(); + while (entry.hasMoreElements()) { + JarEntry jarEntry = entry.nextElement(); + + String jarName = jarEntry.getName(); + if (jarName.charAt(0) == '/') { + jarName = jarName.substring(1); + } + if (jarEntry.isDirectory() || !jarName.startsWith(pkgDir) || !jarName.endsWith(".class")) { + // 非指定包路径, 非class文件 + continue; + } + + // 获取类名,去掉 ".class" 后缀 + String[] classNameSplit = jarName.split("/"); + String className = packageName + "." + classNameSplit[classNameSplit.length - 1]; + if(className.endsWith(".class")) { + className = className.substring(0, className.length() - 6); + } + + // 加载类 + Class clazz = null; + try { + clazz = Class.forName(className); + } catch (ClassNotFoundException e) { + logger.error("Class not found, {}", className); + } + if (clazz != null && clazz.getAnnotation(RestController.class) != null) { + if (clazz != null) { + classes.add(clazz); + } + } + } + } + + /** + * 注册类 + * + * @param clazz + */ + private void registerClass(Class clazz) { + String className = clazz.getName(); + logger.info("Registered REST Controller: {}", className); + ControllerBean bean = new ControllerBean(clazz, clazz.getAnnotation(RestController.class).singleton()); + ControllerMappingRegistry.registerBean(className, bean); + + String url = null; + RequestMapping requestMapping = clazz.getAnnotation(RequestMapping.class); + if (requestMapping != null) { + url = requestMapping.value(); + } + Method[] methods = clazz.getMethods(); + for (Method method : methods) { + RequestMappingRegisterStrategy strategy = null; + // 遍历所有method,生成ControllerMapping并注册。 + if(method.getAnnotation(GetMapping.class) != null) { + strategy = new GetMappingRegisterStrategy(); + } else if(method.getAnnotation(PostMapping.class) != null) { + strategy = new PostMappingRegisterStrategy(); + } else if(method.getAnnotation(PutMapping.class) != null) { + strategy = new PutMappingRegisterStrategy(); + } else if(method.getAnnotation(DeleteMapping.class) != null) { + strategy = new DeleteMappingRegisterStrategy(); + } else if(method.getAnnotation(PatchMapping.class) != null) { + strategy = new PatchMappingRegisterStrategy(); + } + + if(strategy != null) { + RequestMappingRegisterContext mappingRegCtx = new RequestMappingRegisterContext(strategy); + mappingRegCtx.registerMapping(clazz, url, method); + } + } + } + +} diff --git a/src/main/java/cn/codeyourlife/server/HandlerInitializer.java b/src/main/java/cn/codeyourlife/server/HandlerInitializer.java new file mode 100644 index 0000000..6f6ee4c --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/HandlerInitializer.java @@ -0,0 +1,90 @@ +package cn.codeyourlife.server; + +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.socket.SocketChannel; +import io.netty.handler.codec.http.HttpObjectAggregator; +import io.netty.handler.codec.http.HttpRequestDecoder; +import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.stream.ChunkedWriteHandler; +import io.netty.util.concurrent.DefaultEventExecutorGroup; +import io.netty.util.concurrent.EventExecutorGroup; +import io.netty.util.concurrent.RejectedExecutionHandlers; + +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * Netty Handler 初始化类 + * + * @author Leo + * @date 2018/3/29 + */ +final class HandlerInitializer extends ChannelInitializer { + + private int maxContentLength = 0; + + /** + * 业务线程池线程数 + * 可通过 -Dhttp.executor.threads 设置 + */ + private static int eventExecutorGroupThreads = 0; + + /** + * 业务线程池队列长度 + * 可通过 -Dhttp.executor.queues 设置 + */ + private static int eventExecutorGroupQueues = 0; + + static { + eventExecutorGroupThreads = Integer.getInteger("http.executor.threads", 0); + if(eventExecutorGroupThreads == 0) { + eventExecutorGroupThreads = Runtime.getRuntime().availableProcessors() * 2; + } + + eventExecutorGroupQueues = Integer.getInteger("http.executor.queues", 0); + if(eventExecutorGroupQueues == 0) { + eventExecutorGroupQueues = 1024; + } + } + + /** + * 业务线程组 + */ + private static final EventExecutorGroup eventExecutorGroup = new DefaultEventExecutorGroup( + eventExecutorGroupThreads, new ThreadFactory() { + private AtomicInteger threadIndex = new AtomicInteger(0); + @Override + public Thread newThread(Runnable r) { + return new Thread(r, "HttpRequestHandlerThread_" + this.threadIndex.incrementAndGet()); + } + }, eventExecutorGroupQueues, RejectedExecutionHandlers.reject()); + + public HandlerInitializer(int maxContentLength) { + this.maxContentLength = maxContentLength; + } + + @Override + protected void initChannel(SocketChannel ch) throws Exception { + /* + * ChannelInboundHandler按照注册的先后顺序执行,ChannelOutboundHandler按照注册的先后顺序逆序执行。 + * HttpRequestDecoder、HttpObjectAggregator、HttpHandler为InboundHandler + * HttpContentCompressor、HttpResponseEncoder为OutboundHandler + * 在使用Handler的过程中,需要注意: + * 1、ChannelInboundHandler之间的传递,通过调用 ctx.fireChannelRead(msg) 实现;调用ctx.write(msg) 将传递到ChannelOutboundHandler。 + * 2、ctx.write()方法执行后,需要调用flush()方法才能令它立即执行。 + * 3、ChannelOutboundHandler 在注册的时候需要放在最后一个ChannelInboundHandler之前,否则将无法传递到ChannelOutboundHandler。 + * 4、Handler的消费处理放在最后一个处理。 + */ + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast("decoder", new HttpRequestDecoder()); + pipeline.addLast("aggregator", new HttpObjectAggregator(maxContentLength)); + pipeline.addLast("encoder", new HttpResponseEncoder()); + // 启用gzip(由于使用本地存储文件,不能启用gzip) + //pipeline.addLast(new HttpContentCompressor(1)); + pipeline.addLast(new ChunkedWriteHandler()); + // 将HttpRequestHandler放在业务线程池中执行,避免阻塞worker线程。 + pipeline.addLast(eventExecutorGroup, "httpRequestHandler", new HttpRequestHandler()); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/HttpContextHolder.java b/src/main/java/cn/codeyourlife/server/HttpContextHolder.java new file mode 100644 index 0000000..e854af6 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/HttpContextHolder.java @@ -0,0 +1,65 @@ +package cn.codeyourlife.server; + +import io.netty.handler.codec.http.FullHttpRequest; + +/** + * Http 上下文持有者 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class HttpContextHolder { + + private static final ThreadLocal LOCAL_REQUEST = new ThreadLocal(); + + private static final ThreadLocal LOCAL_RESPONSE = new ThreadLocal(); + + /** + * 设置Http Request + * @param request + */ + public static void setRequest(FullHttpRequest request) { + LOCAL_REQUEST.set(request); + } + + /** + * 得到Http Request + * @return + */ + public static FullHttpRequest getRequest() { + return LOCAL_REQUEST.get(); + } + + /** + * 删除Http Request + */ + public static void removeRequest() { + LOCAL_REQUEST.remove(); + } + + /** + * 设置Http Response + * @param response + */ + public static void setResponse(HttpResponse response) { + LOCAL_RESPONSE.set(response); + } + + /** + * 得到Http Response + * @return + */ + public static HttpResponse getResponse() { + return LOCAL_RESPONSE.get(); + } + + /** + * 删除Http Response + */ + public static void removeResponse() { + LOCAL_RESPONSE.remove(); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/HttpRequestHandler.java b/src/main/java/cn/codeyourlife/server/HttpRequestHandler.java new file mode 100644 index 0000000..1f1e552 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/HttpRequestHandler.java @@ -0,0 +1,80 @@ +package cn.codeyourlife.server; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.FullHttpRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Http 请求处理器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +final class HttpRequestHandler extends SimpleChannelInboundHandler { + + private final static Logger logger = LoggerFactory.getLogger(HttpRequestHandler.class); + + @Override + public void channelReadComplete(ChannelHandlerContext ctx) { + ctx.flush(); + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + logger.error(cause.getMessage()); + ctx.close(); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { + if(WebServer.getIgnoreUrls().contains(request.uri())) { + return; + } +// if (request instanceof HttpContent) { +// HttpDataFactory factory = +// new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); +// HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request, CharsetUtil.UTF_8); +// List datas = decoder.getBodyHttpDatas(); +// for (InterfaceHttpData data : datas) { +// if(data.getHttpDataType() == HttpDataType.FileUpload) { +// FileUpload fileUpload = (FileUpload) data; +// String fileName = fileUpload.getFilename(); +// if(fileUpload.isCompleted()) { +// // 保存到磁盘 +// //fileUpload.renameTo(new File("d:\\6.png")); +// java.io.OutputStream out = null; +// try { +// out = new java.io.FileOutputStream("d:\\ok.png"); +// out.write(fileUpload.get()); +// } finally { +// out.close(); +// } +// } +// } +// } +// +// //messageOfClient = buf.toString(io.netty.util.CharsetUtil.UTF_8); +// //logger.debug(buf.toString(io.netty.util.CharsetUtil.UTF_8)); +// +//// if(content instanceof LastHttpContent){ +//// //此处对其他客户端的心跳包不予处理,因为服务端掉线之后会客户端会循环侦测连接,客户端断掉之后将服务端将不打印输出信息 +//// if(messageToSend.length()>12&&messageToSend.substring(0, 2).contentEquals("DB")){ +//// +//// //消息长度符合一定条件,则是需要向其他客户端发送的数据库消息,调用方法转发 +//// System.out.println("Server已读取数据库持久化信息,将开始向所有客户端发送"); +//// ServerDataSyncServer.channels.remove(ctx.channel()); +//// System.out.println("messageToSend "+messageToSend ); +//// String messageContent = messageToSend; +//// ServerDataSyncServerSendGroup.sendToAllChannel(messageContent); +//// messageToSend = ""; +//// } +//// } +// } + new RequestDispatcher().doDispatch(request, ctx); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/HttpResponse.java b/src/main/java/cn/codeyourlife/server/HttpResponse.java new file mode 100644 index 0000000..9e0d2c9 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/HttpResponse.java @@ -0,0 +1,79 @@ +package cn.codeyourlife.server; + +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.DefaultFullHttpResponse; +import io.netty.handler.codec.http.FullHttpResponse; +import io.netty.handler.codec.http.HttpResponseStatus; +import io.netty.util.CharsetUtil; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * Http 响应类 + * + * @author Leo + * @date 2018/3/27 + */ +public final class HttpResponse { + + public HttpResponse(ChannelHandlerContext channelHandlerContext) { + this.channelHandlerContext = channelHandlerContext; + } + + private ChannelHandlerContext channelHandlerContext; + + private Map headers = new HashMap<>(16); + + private Map cookies = new HashMap<>(16); + + public ChannelHandlerContext getChannelHandlerContext() { + return this.channelHandlerContext; + } + + public Map getHeaders() { + return this.headers; + } + + public Map getCookies() { + return this.cookies; + } + + /** + * 输出响应 + * @param status + * @param body + * @throws InterruptedException + */ + public void write(HttpStatus status, String body) { + HttpResponseStatus responstStatus = HttpResponseStatus.parseLine(String.valueOf(status.value())); + FullHttpResponse response = null; + if(body == null || body.trim().equals("")) { + response = new DefaultFullHttpResponse(HTTP_1_1, responstStatus); + } else { + response = new DefaultFullHttpResponse(HTTP_1_1, responstStatus, Unpooled.copiedBuffer(body, CharsetUtil.UTF_8)); + } + + Set> entrySet = headers.entrySet(); + for(Entry entry : entrySet) { + response.headers().add(entry.getKey(), entry.getValue()); + } + response.headers().setInt("Content-Length", response.content().readableBytes()); + channelHandlerContext.writeAndFlush(response); + } + + /** + * 关闭Channel + */ + public void closeChannel() { + if(this.channelHandlerContext != null && this.channelHandlerContext.channel() != null) { + this.channelHandlerContext.channel().close(); + } + } + +} diff --git a/src/main/java/cn/codeyourlife/server/HttpStatus.java b/src/main/java/cn/codeyourlife/server/HttpStatus.java new file mode 100644 index 0000000..fbf76ee --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/HttpStatus.java @@ -0,0 +1,63 @@ +package cn.codeyourlife.server; + +/** + * Http 状态枚举 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public enum HttpStatus { + + OK(200, "OK"), + + CREATED(201, "Created"), + + NO_CONTENT(204, "No Content"), + + BAD_REQUEST(400, "Bad Request"), + + UNAUTHORIZED(401, "Unauthorized"), + + FORBIDDEN(403, "Forbidden"), + + NOT_FOUND(404, "Not Found"), + + METHOD_NOT_ALLOWED(405, "Method Not Allowed"), + + NOT_ACCEPTABLE(406, "Not Acceptable"), + + UNSUPPORTED_MEDIA_TYPE(415, "Unsupported Media Type"), + + INTERNAL_SERVER_ERROR(500, "Internal Server Error"), + + SERVICE_UNAVAILABLE(503, "Service Unavailable"), + + HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version not supported"); + + private final int value; + + private final String reasonPhrase; + + + HttpStatus(int value, String reasonPhrase) { + this.value = value; + this.reasonPhrase = reasonPhrase; + } + + /** + * Return the integer value of this status code. + */ + public int value() { + return this.value; + } + + /** + * Return the reason phrase of this status code. + */ + public String getReasonPhrase() { + return this.reasonPhrase; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/MultipartFile.java b/src/main/java/cn/codeyourlife/server/MultipartFile.java new file mode 100644 index 0000000..62c987c --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/MultipartFile.java @@ -0,0 +1,49 @@ +package cn.codeyourlife.server; + +import java.util.Arrays; + +/** + * 上传文件类 + * + * @author Leo + * @date 2018/5/11 + */ +public final class MultipartFile { + + private String fileName; + + private String fileType; + + private byte[] fileData; + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getFileType() { + return fileType; + } + + public void setFileType(String fileType) { + this.fileType = fileType; + } + + public byte[] getFileData() { + return fileData; + } + + public void setFileData(byte[] fileData) { + this.fileData = fileData; + } + + @Override + public String toString() { + return "PostFile [fileName=" + fileName + ", fileType=" + fileType + ", fileData=" + Arrays.toString(fileData) + + "]"; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/RequestDispatcher.java b/src/main/java/cn/codeyourlife/server/RequestDispatcher.java new file mode 100644 index 0000000..bcf6e19 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/RequestDispatcher.java @@ -0,0 +1,213 @@ +package cn.codeyourlife.server; + +import cn.codeyourlife.server.interceptor.Interceptor; +import cn.codeyourlife.server.interceptor.InterceptorRegistry; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.multipart.Attribute; +import io.netty.handler.codec.http.multipart.FileUpload; +import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; +import io.netty.handler.codec.http.multipart.InterfaceHttpData; +import io.netty.util.CharsetUtil; + +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * 请求分发 + */ +public class RequestDispatcher { + /** + * 执行请求分派 + * + * @param request + * @param channelHandlerContext + * @throws Exception + */ + public void doDispatch(FullHttpRequest request, ChannelHandlerContext channelHandlerContext) throws Exception { + HttpResponse response = new HttpResponse(channelHandlerContext); + + // 执行拦截器 + java.util.Stack executedInterceptors = new java.util.Stack(); + for (Interceptor interceptor : InterceptorRegistry.getInterceptors()) { + if (this.allowExecuteInterceptor(request.uri(), interceptor)) { + executedInterceptors.push(interceptor); + if (!interceptor.preHandle(request, response)) { + // 调用已执行的所有拦截器的afterCompletion方法 + while (!executedInterceptors.isEmpty()) { + executedInterceptors.pop().afterCompletion(request, response); + } + return; + } + } + } + + ChannelFuture f = null; + if (request.method().name().equalsIgnoreCase("OPTIONS")) { + // 处理“预检”请求 + f = processOptionsRequest(request, response, channelHandlerContext); + } + + if (!request.method().name().equalsIgnoreCase("OPTIONS")) { + RequestInfo requestInfo = new RequestInfo(); + requestInfo.setRequest(request); + requestInfo.setResponse(response); + QueryStringDecoder queryStrdecoder = new QueryStringDecoder(request.uri()); + Set>> entrySet = queryStrdecoder.parameters().entrySet(); + entrySet.forEach(entry -> { + requestInfo.getParameters().put(entry.getKey(), entry.getValue().get(0)); + }); + + Set headerNames = request.headers().names(); + for (String headerName : headerNames) { + requestInfo.getHeaders().put(headerName, request.headers().get(headerName)); + } + + if (!request.method().name().equalsIgnoreCase("GET")) { + String contentType = requestInfo.getHeaders().get("Content-Type"); + if (contentType != null) { + if (contentType.contains(";")) { + contentType = contentType.split(";")[0]; + } + switch (contentType.toLowerCase()) { + case "application/json": + case "application/json;charset=utf-8": + case "text/xml": + requestInfo.setBody(request.content().toString(Charset.forName("UTF-8"))); + break; + case "application/x-www-form-urlencoded": + HttpPostRequestDecoder formDecoder = new HttpPostRequestDecoder(request); + formDecoder.offer(request); + List parmList = formDecoder.getBodyHttpDatas(); + for (InterfaceHttpData parm : parmList) { + Attribute data = (Attribute) parm; + requestInfo.getFormData().put(data.getName(), data.getValue()); + } + break; + case "multipart/form-data": + HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(request); + List datas = decoder.getBodyHttpDatas(); + for (InterfaceHttpData data : datas) { + if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.FileUpload) { + FileUpload fileUpload = (FileUpload) data; + if (fileUpload.isCompleted()) { + MultipartFile file = new MultipartFile(); + file.setFileName(fileUpload.getFilename()); + file.setFileType(fileUpload.getContentType()); + file.setFileData(fileUpload.get()); + requestInfo.getFiles().add(file); + } + continue; + } + if (data.getHttpDataType() == InterfaceHttpData.HttpDataType.Attribute) { + Attribute attribute = (Attribute) data; + requestInfo.getFormData().put(attribute.getName(), attribute.getValue()); + } + } + } + } + } + + f = new RequestHandler().handleRequest(requestInfo); + } + + // 执行拦截器 + for (Interceptor interceptor : InterceptorRegistry.getInterceptors()) { + if (this.allowExecuteInterceptor(request.uri(), interceptor)) { + interceptor.postHandle(request, response); + } + } + + // 如果是“预检”请求,则处理后关闭连接。 + if (request.method().name().equalsIgnoreCase("OPTIONS")) { + if (f != null) { + f.addListener(ChannelFutureListener.CLOSE); + } + return; + } + if (!HttpUtil.isKeepAlive(request)) { + f.addListener(ChannelFutureListener.CLOSE); + } + } + + /** + * 判断是否执行拦截器 + * + * @param url + * @param interceptor + * @return + */ + private boolean allowExecuteInterceptor(String url, Interceptor interceptor) { + List excludeMappings = InterceptorRegistry.getExcludeMappings(interceptor); + if (excludeMappings != null) { + for (String excludeMapping : excludeMappings) { + if (url.startsWith(excludeMapping)) { + return false; + } + } + } + return true; + } + + /** + * 处理Options请求 + * + * @param request + * @param response + * @return + */ + private ChannelFuture processOptionsRequest(FullHttpRequest request, HttpResponse response, ChannelHandlerContext channelHandlerContext) { + String[] requestHeaders = request.headers().get("Access-Control-Request-Headers").split(","); + for (String requestHeader : requestHeaders) { + if (!requestHeader.trim().isEmpty()) { + if (!requestHeaderAllowed(requestHeader, response.getHeaders())) { + FullHttpResponse optionsResponse = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.NOT_FOUND, + Unpooled.copiedBuffer("", CharsetUtil.UTF_8)); + HttpContextHolder.getResponse().getChannelHandlerContext().writeAndFlush(optionsResponse).addListener(ChannelFutureListener.CLOSE); + return null; + } + } + } + FullHttpResponse optionsResponse = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.OK, + Unpooled.copiedBuffer("", CharsetUtil.UTF_8)); + Map responseHeaders = response.getHeaders(); + Set> headersEntrySet = responseHeaders.entrySet(); + for (Map.Entry entry : headersEntrySet) { + optionsResponse.headers().add(entry.getKey(), entry.getValue()); + } + optionsResponse.headers().setInt("Content-Length", optionsResponse.content().readableBytes()); + return channelHandlerContext.writeAndFlush(optionsResponse); + } + + /** + * 判断请求头是否被允许 + * + * @param requestHeader + * @param responseHeaders + * @return + */ + private boolean requestHeaderAllowed(String requestHeader, Map responseHeaders) { + String allowedHeader = responseHeaders.get("Access-Control-Allow-Headers"); + if (allowedHeader != null && !allowedHeader.trim().isEmpty()) { + String[] allowedHeaders = allowedHeader.split(","); + for (String header : allowedHeaders) { + if (requestHeader.equalsIgnoreCase(header)) { + return true; + } + } + } + return false; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/RequestHandler.java b/src/main/java/cn/codeyourlife/server/RequestHandler.java new file mode 100644 index 0000000..513338d --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/RequestHandler.java @@ -0,0 +1,363 @@ +package cn.codeyourlife.server; + +import cn.codeyourlife.server.convert.Converter; +import cn.codeyourlife.server.convert.ConverterFactory; +import cn.codeyourlife.server.exception.HandleRequestException; +import cn.codeyourlife.server.exception.ResourceNotFoundException; +import cn.codeyourlife.server.router.ControllerBean; +import cn.codeyourlife.server.router.ControllerMapping; +import cn.codeyourlife.server.router.ControllerMappingParameter; +import cn.codeyourlife.server.router.ControllerMappingRegistry; +import com.alibaba.fastjson.JSON; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.DefaultFileRegion; +import io.netty.handler.codec.http.*; +import io.netty.handler.codec.http.HttpResponse; +import io.netty.handler.codec.http.cookie.Cookie; +import io.netty.handler.codec.http.cookie.DefaultCookie; +import io.netty.handler.codec.http.cookie.ServerCookieEncoder; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.stream.ChunkedFile; +import io.netty.util.CharsetUtil; + +import java.io.IOException; +import java.io.RandomAccessFile; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.Set; + +import static io.netty.handler.codec.http.HttpResponseStatus.OK; +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * 请求处理 + */ +public class RequestHandler { + /** + * 处理请求 + * + * @param requestInfo + * @return + */ + public ChannelFuture handleRequest(RequestInfo requestInfo) { + // 查找匹配的Mapping + ControllerMapping mapping = this.lookupMappings(requestInfo); + if (mapping == null) { + HttpContextHolder.setRequest(requestInfo.getRequest()); + HttpContextHolder.setResponse(requestInfo.getResponse()); + + // 全局异常处理 + if (WebServer.getExceptionHandler() != null) { + WebServer.getExceptionHandler().doHandle(new ResourceNotFoundException()); + return null; + } + throw new ResourceNotFoundException(); + } + + // 准备方法参数 + Object[] paramValues = new Object[mapping.getParameters().size()]; + Class[] paramTypes = new Class[mapping.getParameters().size()]; + for (int i = 0; i < paramValues.length; i++) { + ControllerMappingParameter cmp = mapping.getParameters().get(i); + Converter converter = null; + switch (cmp.getType()) { + case HTTP_REQUEST: + paramValues[i] = requestInfo.getRequest(); + break; + case HTTP_RESPONSE: + paramValues[i] = requestInfo.getResponse(); + break; + case REQUEST_BODY: + paramValues[i] = requestInfo.getBody(); + break; + case REQUEST_PARAM: + paramValues[i] = requestInfo.getParameters().get(cmp.getName()); + converter = ConverterFactory.create(cmp.getDataType()); + if (converter != null) { + paramValues[i] = converter.convert(paramValues[i]); + } + break; + case REQUEST_HEADER: + paramValues[i] = requestInfo.getParameters().get(cmp.getName()); + converter = ConverterFactory.create(cmp.getDataType()); + if (converter != null) { + paramValues[i] = converter.convert(requestInfo.getHeaders().get(cmp.getName())); + } + break; + case PATH_VARIABLE: + paramValues[i] = this.getPathVariable(requestInfo.getRequest().uri(), mapping.getUrl(), cmp.getName()); + converter = ConverterFactory.create(cmp.getDataType()); + if (converter != null) { + paramValues[i] = converter.convert(paramValues[i]); + } + break; + case URL_ENCODED_FORM: + paramValues[i] = requestInfo.getFormData(); + break; + case UPLOAD_FILE: + paramValues[i] = requestInfo.getFiles().size() > 0 ? requestInfo.getFiles().get(0) : null; + break; + case UPLOAD_FILES: + paramValues[i] = requestInfo.getFiles().size() > 0 ? requestInfo.getFiles() : null; + break; + } + if (cmp.getRequired() && paramValues[i] == null) { + throw new HandleRequestException("参数 " + cmp.getName() + " 为null"); + } + paramTypes[i] = cmp.getDataType(); + } + + // 执行method + try { + HttpContextHolder.setRequest(requestInfo.getRequest()); + HttpContextHolder.setResponse(requestInfo.getResponse()); + Object result = this.execute(mapping, paramTypes, paramValues); + + if (!(result instanceof ResponseEntity)) { + result = ResponseEntity.ok().build(); + } + return writeResponse((ResponseEntity) result, mapping.getJsonResponse()); + } catch (Exception e) { + // 全局异常处理 + if (WebServer.getExceptionHandler() != null) { + WebServer.getExceptionHandler().doHandle(e); + return null; + } + throw new HandleRequestException(e); + } finally { + HttpContextHolder.removeRequest(); + HttpContextHolder.removeResponse(); + } + } + + /** + * 得到Controller类的实例 + * + * @param mapping + * @return + * @throws Exception + */ + private Object execute(ControllerMapping mapping, Class[] paramTypes, Object[] paramValues) throws Exception { + ControllerBean bean = ControllerMappingRegistry.getBean(mapping.getClassName()); + Object instance = null; + if (bean.getSingleton()) { + instance = ControllerMappingRegistry.getSingleton(mapping.getClassName()); + } else { + Class clazz = Class.forName(mapping.getClassName()); + instance = clazz.newInstance(); + } + Method method = instance.getClass().getMethod(mapping.getClassMethod(), paramTypes); + return method.invoke(instance, paramValues); + } + + /** + * 输出响应结果 + * + * @param responseEntity + * @param jsonResponse + * @return + * @throws IOException + */ + private ChannelFuture writeResponse(ResponseEntity responseEntity, boolean jsonResponse) throws IOException { + if (responseEntity.getBody() instanceof RandomAccessFile) { + return writeFileResponse(responseEntity); + } + FullHttpResponse response = null; + HttpResponseStatus status = HttpResponseStatus.parseLine(String.valueOf(responseEntity.getStatus().value())); + if (responseEntity.getBody() != null) { + String jsonStr = JSON.toJSONString(responseEntity.getBody()); + response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer(jsonStr, CharsetUtil.UTF_8)); + } else { + response = new DefaultFullHttpResponse(HTTP_1_1, status); + } + + String contentType = jsonResponse ? "application/json; charset=UTF-8" : "text/plain; charset=UTF-8"; + response.headers().set("Content-Type", contentType); + + // 写入Cookie + Map cookies = HttpContextHolder.getResponse().getCookies(); + Set> cookiesEntrySet = cookies.entrySet(); + for (Map.Entry entry : cookiesEntrySet) { + Cookie cookie = new DefaultCookie(entry.getKey(), entry.getValue()); + cookie.setPath("/"); + response.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookie)); + } + + Map responseHeaders = HttpContextHolder.getResponse().getHeaders(); + Set> headersEntrySet = responseHeaders.entrySet(); + for (Map.Entry entry : headersEntrySet) { + response.headers().add(entry.getKey(), entry.getValue()); + } + response.headers().setInt("Content-Length", response.content().readableBytes()); + return HttpContextHolder.getResponse().getChannelHandlerContext().writeAndFlush(response); + } + + /** + * 输出文件响应 + * + * @param responseEntity + * @return + * @throws IOException + */ + private ChannelFuture writeFileResponse(ResponseEntity responseEntity) throws IOException { + RandomAccessFile raf = (RandomAccessFile) responseEntity.getBody(); + long fileLength = raf.length(); + + HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); + HttpUtil.setContentLength(response, fileLength); + if (responseEntity.getMimetype() != null && !responseEntity.getMimetype().trim().equals("")) { + response.headers().set(HttpHeaderNames.CONTENT_TYPE, responseEntity.getMimetype()); + } + if (responseEntity.getFileName() != null && !responseEntity.getFileName().trim().equals("")) { + String fileName = new String(responseEntity.getFileName().getBytes("gb2312"), "ISO8859-1"); + response.headers().set(HttpHeaderNames.CONTENT_DISPOSITION, "attachment; filename=" + fileName); + } + if (HttpUtil.isKeepAlive(HttpContextHolder.getRequest())) { + response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE); + } + + ChannelHandlerContext ctx = HttpContextHolder.getResponse().getChannelHandlerContext(); + ctx.write(response); + ChannelFuture sendFileFuture; + ChannelFuture lastContentFuture = null; + if (ctx.pipeline().get(SslHandler.class) == null) { + sendFileFuture = + ctx.write(new DefaultFileRegion(raf.getChannel(), 0, fileLength), ctx.newProgressivePromise()); + // Write the end marker. + lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); + } else { + sendFileFuture = ctx.writeAndFlush(new HttpChunkedInput(new ChunkedFile(raf, 0, fileLength, 8192)), + ctx.newProgressivePromise()); + // HttpChunkedInput will write the end marker (LastHttpContent) for us. + lastContentFuture = sendFileFuture; + } + return lastContentFuture; + } + + /** + * 查找映射 + * + * @param requestInfo + * @return + */ + private ControllerMapping lookupMappings(RequestInfo requestInfo) { + String lookupPath = requestInfo.getRequest().uri().endsWith("/") + ? requestInfo.getRequest().uri().substring(0, requestInfo.getRequest().uri().length() - 1) + : requestInfo.getRequest().uri(); + int paramStartIndex = lookupPath.indexOf("?"); + if (paramStartIndex > 0) { + lookupPath = lookupPath.substring(0, paramStartIndex); + } + + Map mappings = this.getMappings(requestInfo.getRequest().method().name()); + if (mappings == null || mappings.size() == 0) { + return null; + } + Set> entrySet = mappings.entrySet(); + for (Map.Entry entry : entrySet) { + // 完全匹配 + if (entry.getKey().equals(lookupPath)) { + return entry.getValue(); + } + } + for (Map.Entry entry : entrySet) { + // 包含PathVariable + String matcher = this.getMatcher(entry.getKey()); + if (lookupPath.startsWith(matcher)) { + boolean matched = true; + String[] lookupPathSplit = lookupPath.split("/"); + String[] mappingUrlSplit = entry.getKey().split("/"); + if (lookupPathSplit.length != mappingUrlSplit.length) { + continue; + } + for (int i = 0; i < lookupPathSplit.length; i++) { + if (!lookupPathSplit[i].equals(mappingUrlSplit[i])) { + if (!mappingUrlSplit[i].startsWith("{")) { + matched = false; + break; + } + } + } + if (matched) { + return entry.getValue(); + } + } + } + return null; + } + + /** + * 得到控制器映射哈希表 + * + * @param httpMethod + * @return + */ + private Map getMappings(String httpMethod) { + if (httpMethod == null) { + return null; + } + switch (httpMethod.toUpperCase()) { + case "GET": + return ControllerMappingRegistry.getGetMappings(); + case "POST": + return ControllerMappingRegistry.getPostMappings(); + case "PUT": + return ControllerMappingRegistry.getPutMappings(); + case "DELETE": + return ControllerMappingRegistry.getDeleteMappings(); + case "PATCH": + return ControllerMappingRegistry.getPatchMappings(); + default: + return null; + } + } + + /** + * 得到匹配url + * + * @param url + * @return + */ + private String getMatcher(String url) { + StringBuilder matcher = new StringBuilder(128); + for (char c : url.toCharArray()) { + if (c == '{') { + break; + } + matcher.append(c); + } + return matcher.toString(); + } + + /** + * 得到路径变量 + * + * @param url + * @param mappingUrl + * @param name + * @return + */ + private String getPathVariable(String url, String mappingUrl, String name) { + String[] urlSplit = url.split("/"); + String[] mappingUrlSplit = mappingUrl.split("/"); + for (int i = 0; i < mappingUrlSplit.length; i++) { + if (mappingUrlSplit[i].equals("{" + name + "}")) { + if (urlSplit[i].contains("?")) { + return urlSplit[i].split("[?]")[0]; + } + if (urlSplit[i].contains("&")) { + return urlSplit[i].split("&")[0]; + } + return urlSplit[i]; + } + } + return null; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/RequestInfo.java b/src/main/java/cn/codeyourlife/server/RequestInfo.java new file mode 100644 index 0000000..f1a1b9a --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/RequestInfo.java @@ -0,0 +1,80 @@ +package cn.codeyourlife.server; + +import io.netty.handler.codec.http.FullHttpRequest; +import cn.codeyourlife.server.MultipartFile; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 请求信息类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class RequestInfo { + + private FullHttpRequest request; + + private HttpResponse response; + + private Map parameters = new HashMap<>(); + + private Map headers = new HashMap<>(); + + private String body; + + private Map formData = new HashMap<>(); + + private List files = new ArrayList<>(8); + + public FullHttpRequest getRequest() { + return this.request; + } + + public void setRequest(FullHttpRequest request) { + this.request = request; + } + + public Map getParameters() { + return this.parameters; + } + + public Map getHeaders() { + return this.headers; + } + + public String getBody() { + return this.body; + } + public void setBody(String body) { + this.body = body; + } + + public Map getFormData() { + return this.formData; + } + + public HttpResponse getResponse() { + return this.response; + } + public void setResponse(HttpResponse response) { + this.response = response; + } + + public List getFiles() { + return files; + } + + @Override + public String toString() { + return "RequestInfo [request=" + request + ", response=" + response + ", parameters=" + parameters + + ", headers=" + headers + ", body=" + body + ", formData=" + formData + ", files=" + files + + "]"; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/ResponseEntity.java b/src/main/java/cn/codeyourlife/server/ResponseEntity.java new file mode 100644 index 0000000..e25ed7e --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/ResponseEntity.java @@ -0,0 +1,271 @@ +package cn.codeyourlife.server; + +import java.util.HashMap; +import java.util.Map; + +/** + * Http 响应实体类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class ResponseEntity { + + public ResponseEntity() { + + } + + public ResponseEntity(HttpStatus status) { + this.status = status; + } + + public ResponseEntity(HttpStatus status, T body) { + this.status = status; + this.body = body; + } + + public ResponseEntity(HttpStatus status, Map headers, T body) { + this.status = status; + this.headers = headers; + this.body = body; + } + + public ResponseEntity(HttpStatus status, Map headers, T body, String mimetype) { + this.status = status; + this.headers = headers; + this.body = body; + this.mimetype = mimetype; + } + + public ResponseEntity(HttpStatus status, Map headers, T body, String mimetype, String fileName) { + this.status = status; + this.headers = headers; + this.body = body; + this.mimetype = mimetype; + this.fileName = fileName; + } + + private HttpStatus status; + + private T body; + + private String mimetype; + + private String fileName; + + private Map headers = new HashMap<>(16); + + public HttpStatus getStatus() { + return this.status; + } + + public void setStatus(HttpStatus status) { + this.status = status; + } + + public T getBody() { + return this.body; + } + + public void setBody(T body) { + this.body = body; + } + + public String getMimetype() { + return this.mimetype; + } + + public String getFileName() { + return this.fileName; + } + + public Map getHeaders() { + return this.headers; + } + + /** + * 根据状态码创建ResponseBuilder + * + * @param status + * @return + */ + public static ResponseBuilder status(HttpStatus status) { + return new ResponseBuilder(status); + } + + /** + * 创建Http OK ResponseBuilder + * + * @return + */ + public static ResponseBuilder ok() { + return status(HttpStatus.OK); + } + + /** + * 创建Http OK ResponseEntity + * + * @param body + * @return + */ + public static ResponseEntity ok(T body) { + ResponseBuilder builder = ok(); + return builder.build(body); + } + + /** + * 创建Http OK ResponseEntity + * + * @param body + * @param mimetype + * @return + */ + public static ResponseEntity ok(T body, String mimetype) { + ResponseBuilder builder = ok(); + return builder.build(body, mimetype); + } + + /** + * 创建Http OK ResponseEntity + * + * @param body + * @param mimetype + * @param fileName + * @return + */ + public static ResponseEntity ok(T body, String mimetype, String fileName) { + ResponseBuilder builder = ok(); + return builder.build(body, mimetype, fileName); + } + + /** + * 创建Http Created ResponseBuilder + * + * @return + */ + public static ResponseBuilder created() { + return status(HttpStatus.CREATED); + } + + /** + * 创建Http Created ResponseEntity + * + * @param body + * @return + */ + public static ResponseEntity created(T body) { + ResponseBuilder builder = created(); + return builder.build(body); + } + + /** + * 创建Http No Content ResponseBuilder + * + * @return + */ + public static ResponseBuilder noContent() { + return status(HttpStatus.NO_CONTENT); + } + + /** + * 创建Http No Content ResponseEntity + * + * @param body + * @return + */ + public static ResponseEntity noContent(T body) { + ResponseBuilder builder = noContent(); + return builder.build(body); + } + + /** + * 创建Http Not Found ResponseBuilder + * + * @return + */ + public static ResponseBuilder notFound() { + return status(HttpStatus.NOT_FOUND); + } + + /** + * 创建Http Not Found ResponseEntity + * + * @param body + * @return + */ + public static ResponseEntity notFound(T body) { + ResponseBuilder builder = notFound(); + return builder.build(body); + } + + /** + * 创建Http Internal Server Error ResponseEntity + * + * @return + */ + public static ResponseBuilder internalServerError() { + return status(HttpStatus.INTERNAL_SERVER_ERROR); + } + + /** + * 创建Http Internal Server Error ResponseEntity + * + * @param body + * @return + */ + public static ResponseEntity internalServerError(T body) { + ResponseBuilder builder = internalServerError(); + return builder.build(body); + } + + /** + * 设置headers + * + * @param headers + * @return + */ + public ResponseEntity headers(Map headers) { + this.headers = headers; + return this; + } + + /** + * Http Response 构建器 + * + * @author Leo + */ + public static class ResponseBuilder { + + private HttpStatus status; + + private Map headers; + + public ResponseBuilder(HttpStatus status) { + this.status = status; + } + + public ResponseBuilder headers(Map headers) { + this.headers = headers; + return this; + } + + public ResponseEntity build() { + return build(null); + } + + public ResponseEntity build(T body) { + return new ResponseEntity<>(this.status, this.headers, body); + } + + public ResponseEntity build(T body, String mimetype) { + return new ResponseEntity<>(this.status, this.headers, body, mimetype); + } + + public ResponseEntity build(T body, String mimetype, String fileName) { + return new ResponseEntity<>(this.status, this.headers, body, mimetype, fileName); + } + } + +} diff --git a/src/main/java/cn/codeyourlife/server/ServerHandler.java b/src/main/java/cn/codeyourlife/server/ServerHandler.java deleted file mode 100644 index c0c8999..0000000 --- a/src/main/java/cn/codeyourlife/server/ServerHandler.java +++ /dev/null @@ -1,197 +0,0 @@ -package cn.codeyourlife.server; - -import com.alibaba.fastjson.JSONObject; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.*; -import io.netty.handler.codec.http.*; -import io.netty.handler.ssl.SslHandler; -import io.netty.handler.stream.ChunkedNioFile; -import io.netty.util.AsciiString; -import io.netty.util.CharsetUtil; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; - -import static io.netty.handler.codec.http.HttpResponseStatus.*; -import static io.netty.handler.codec.http.HttpVersion.*; - -/** - * Author: wbq813@foxmail.com - * Copyright: http://codeyourlife.cn - * Platform: Win10 Jdk8 - * Date: 2020/1/8 - */ -public class ServerHandler extends ChannelInboundHandlerAdapter { - - private static final AsciiString CONTENT_TYPE = new AsciiString("Content-Type"); - private static final AsciiString CONTENT_LENGTH = new AsciiString("Content-Length"); - private static final AsciiString CONNECTION = new AsciiString("Connection"); - private static final AsciiString KEEP_ALIVE = new AsciiString("keep-alive"); - // 资源所在路径 - private static final String LOCATION = "E:\\WebIDE\\src\\main\\resources\\static"; - - - @Override - public void channelReadComplete(ChannelHandlerContext ctx) { - ctx.flush(); - } - - @Override - public void channelRead(ChannelHandlerContext ctx, Object msg) throws IOException { - - if (msg instanceof FullHttpRequest) { - //客户端的请求对象 - FullHttpRequest request = (FullHttpRequest) msg; - //新建一个返回消息的Json对象 - JSONObject responseJson = new JSONObject(); - - //把客户端的请求数据格式化为Json对象 - JSONObject requestJson = null; - try { - requestJson = JSONObject.parseObject(parseJosnRequest(request)); - } catch (Exception e) { - ResponseJson(ctx, request, new String("error json")); - return; - } - - //获取客户端的URL - String uri = request.uri(); - // 先判断静态文件 - // 根据路径地址构建文件 - String path = LOCATION + uri; - File files = new File(path); - // 状态为1xx的话,继续请求 - if (HttpUtil.is100ContinueExpected(request)) { - send100Continue(ctx); - } - if (files.exists()) { - RandomAccessFile file = new RandomAccessFile(files, "r"); - HttpResponse response = new DefaultHttpResponse(request.protocolVersion(), HttpResponseStatus.OK); - // 设置文件格式内容 - if (path.endsWith(".html")) { - response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/html; charset=UTF-8"); - } else if (path.endsWith(".js")) { - response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "application/x-javascript"); - } else if (path.endsWith(".css")) { - response.headers().set(HttpHeaders.Names.CONTENT_TYPE, "text/css; charset=UTF-8"); - } - - boolean keepAlive = HttpUtil.isKeepAlive(request); - - if (keepAlive) { - response.headers().set(HttpHeaders.Names.CONTENT_LENGTH, file.length()); - response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE); - } - ctx.write(response); - - if (ctx.pipeline().get(SslHandler.class) == null) { - ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length())); - } else { - ctx.write(new ChunkedNioFile(file.getChannel())); - } - // 写入文件尾部 - ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT); - if (!keepAlive) { - future.addListener(ChannelFutureListener.CLOSE); - } - file.close(); - } else { - //根据不同的请求API做不同的处理(路由分发),只处理POST方法 - if (request.method() == HttpMethod.POST) { - if (request.uri().equals("/bmi")) { - //计算体重质量指数 - double height = 0.01 * requestJson.getDouble("height"); - double weight = requestJson.getDouble("weight"); - double bmi = weight / (height * height); - bmi = ((int) (bmi * 100)) / 100.0; - responseJson.put("bmi", bmi + ""); - - } else if (request.uri().equals("/bmr")) { - //计算基础代谢率 - boolean isBoy = requestJson.getBoolean("isBoy"); - double height = requestJson.getDouble("height"); - double weight = requestJson.getDouble("weight"); - int age = requestJson.getIntValue("age"); - double bmr = 0; - if (isBoy) { - //66 + ( 13.7 x 体重kg ) + ( 5 x 身高cm ) - ( 6.8 x 年龄years ) - bmr = 66 + (13.7 * weight) + (5 * height) - (6.8 * age); - - } else { - //655 + ( 9.6 x 体重kg ) + ( 1.8 x 身高cm ) - ( 4.7 x 年龄years ) - bmr = 655 + (9.6 * weight) + 1.8 * height - 4.7 * age; - } - - bmr = ((int) (bmr * 100)) / 100.0; - responseJson.put("bmr", bmr + ""); - } else { - //错误处理 - responseJson.put("error", "404 Not Find"); - } - - } else { - if (request.uri().equals("/")) { - responseJson.put("data", "Hello World"); - } else { - //错误处理 - responseJson.put("error", "404 Not Find"); - } - } - - //向客户端发送结果 - ResponseJson(ctx, request, responseJson.toString()); - } - - - } - } - - private static void send100Continue(ChannelHandlerContext ctx) { - FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE); - ctx.writeAndFlush(response); - } - - /** - * 响应HTTP的请求 - * - * @param ctx - * @param req - * @param jsonStr - */ - private void ResponseJson(ChannelHandlerContext ctx, FullHttpRequest req, String jsonStr) { - - boolean keepAlive = HttpUtil.isKeepAlive(req); - byte[] jsonByteByte = jsonStr.getBytes(); - FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(jsonByteByte)); - response.headers().set(CONTENT_TYPE, "text/json"); - response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes()); - - if (!keepAlive) { - ctx.write(response).addListener(ChannelFutureListener.CLOSE); - } else { - response.headers().set(CONNECTION, KEEP_ALIVE); - ctx.write(response); - } - } - - @Override - public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - cause.printStackTrace(); - ctx.close(); - } - - /** - * 获取请求的内容 - * - * @param request - * @return - */ - private String parseJosnRequest(FullHttpRequest request) { - ByteBuf jsonBuf = request.content(); - String jsonStr = jsonBuf.toString(CharsetUtil.UTF_8); - return jsonStr; - } -} diff --git a/src/main/java/cn/codeyourlife/server/ServerInitializer.java b/src/main/java/cn/codeyourlife/server/ServerInitializer.java deleted file mode 100644 index 10c5f8b..0000000 --- a/src/main/java/cn/codeyourlife/server/ServerInitializer.java +++ /dev/null @@ -1,37 +0,0 @@ -package cn.codeyourlife.server; - -import io.netty.channel.ChannelInitializer; -import io.netty.channel.ChannelPipeline; -import io.netty.channel.socket.SocketChannel; -import io.netty.handler.codec.http.HttpObjectAggregator; -import io.netty.handler.codec.http.HttpServerCodec; -import io.netty.handler.ssl.SslContext; - -/** - * Author: wbq813@foxmail.com - * Copyright: http://codeyourlife.cn - * Platform: Win10 Jdk8 - * Date: 2020/1/8 - */ -public class ServerInitializer extends ChannelInitializer { - - private final SslContext sslCtx; - - public ServerInitializer(SslContext sslCtx) { - this.sslCtx = sslCtx; - } - - @Override - public void initChannel(SocketChannel ch) { - ChannelPipeline p = ch.pipeline(); - if (sslCtx != null) { - p.addLast(sslCtx.newHandler(ch.alloc())); - } - /*HTTP 服务的解码器*/ - p.addLast(new HttpServerCodec()); - /*HTTP 消息的合并处理*/ - p.addLast(new HttpObjectAggregator(2048)); - /*自己写的服务器逻辑处理*/ - p.addLast(new ServerHandler()); - } -} diff --git a/src/main/java/cn/codeyourlife/server/WebServer.java b/src/main/java/cn/codeyourlife/server/WebServer.java new file mode 100644 index 0000000..51ba861 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/WebServer.java @@ -0,0 +1,154 @@ +package cn.codeyourlife.server; + +import cn.codeyourlife.server.controller.ExceptionHandler; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.buffer.PooledByteBufAllocator; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import cn.codeyourlife.server.interceptor.Interceptor; +import cn.codeyourlife.server.interceptor.InterceptorRegistry; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; + +/** + * Web 服务器类 + * + * @author Leo + * @date 2018/3/23 + */ +public final class WebServer { + + private final static Logger logger = LoggerFactory.getLogger(WebServer.class); + + /** + * 监听端口号 + */ + private int port = 0; + + /** + * Boss线程数 + */ + private int bossThreads = 1; + + /** + * Worker线程数 + */ + private int workerThreads = 2; + + /** + * REST控制器所在包名 + */ + private String controllerBasePackage = ""; + + /** + * 忽略Url列表(不搜索Mapping) + */ + private static List ignoreUrls = new ArrayList<>(16); + + /** + * 以上处理器 + */ + private static ExceptionHandler exceptionHandler; + + /** + * Http 最大内容长度,默认为10M。 + */ + private int maxContentLength = 1024 * 1024 * 10; + + public WebServer(int port) { + this.port = port; + } + + public int getBossThreads() { + return this.bossThreads; + } + public void setBossThreads(int threads) { + this.bossThreads = threads; + } + + public int getWorkerThreads() { + return this.workerThreads; + } + public void setWorkerThreads(int threads) { + this.workerThreads = threads; + } + + public int getMaxContentLength() { + return this.maxContentLength; + } + public void setMaxContentLength(int maxContentLength) { + this.maxContentLength = maxContentLength; + } + + public String getControllerBasePackage() { + return this.controllerBasePackage; + } + public void setControllerBasePackage(String controllerBasePackage) { + this.controllerBasePackage = controllerBasePackage; + } + + public void addInterceptor(Interceptor interceptor) { + try { + InterceptorRegistry.addInterceptor(interceptor); + } catch (Exception e) { + logger.error("Add filter failed, ", e.getMessage()); + } + } + + public void addInterceptor(Interceptor interceptor, String... excludeMappings) { + try { + InterceptorRegistry.addInterceptor(interceptor, excludeMappings); + } catch (Exception e) { + logger.error("Add filter failed, ", e.getMessage()); + } + } + + public static List getIgnoreUrls() { + return ignoreUrls; + } + + public static ExceptionHandler getExceptionHandler() { + return exceptionHandler; + } + + public static void setExceptionHandler(ExceptionHandler handler) { + exceptionHandler = handler; + } + + /** + * 启动服务 + * @throws InterruptedException + */ + public void start() throws InterruptedException { + // 注册所有REST Controller + new ControllerFactory().registerController(this.controllerBasePackage); + + // BossGroup处理nio的Accept事件(TCP连接) + NioEventLoopGroup bossGroup = new NioEventLoopGroup(this.bossThreads); + // Worker处理nio的Read和Write事件(通道的I/O事件) + NioEventLoopGroup workerGroup = new NioEventLoopGroup(this.workerThreads); + + try { + // handler在初始化时就会执行,而childHandler会在客户端成功connect后才执行。 + ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_BACKLOG, 128) + .childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT) + .childHandler(new HandlerInitializer(this.maxContentLength)); + + ChannelFuture f = bootstrap.bind(port).sync(); + logger.info("The netty rest server is now ready to accept requests on port {}", this.port); + f.channel().closeFuture().sync(); + } finally { + bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(); + } + } + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/DeleteMapping.java b/src/main/java/cn/codeyourlife/server/annotation/DeleteMapping.java new file mode 100644 index 0000000..24fcc91 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/DeleteMapping.java @@ -0,0 +1,22 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Http DELETE 方法注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface DeleteMapping { + + String value() default ""; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/GetMapping.java b/src/main/java/cn/codeyourlife/server/annotation/GetMapping.java new file mode 100644 index 0000000..dbf57f8 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/GetMapping.java @@ -0,0 +1,22 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Http GET 方法注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface GetMapping { + + String value() default ""; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/JsonResponse.java b/src/main/java/cn/codeyourlife/server/annotation/JsonResponse.java new file mode 100644 index 0000000..055cc8f --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/JsonResponse.java @@ -0,0 +1,20 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JSON 响应输出注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface JsonResponse { + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/PatchMapping.java b/src/main/java/cn/codeyourlife/server/annotation/PatchMapping.java new file mode 100644 index 0000000..8b9b3d5 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/PatchMapping.java @@ -0,0 +1,22 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Http PATCH 方法注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PatchMapping { + + String value() default ""; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/PathVariable.java b/src/main/java/cn/codeyourlife/server/annotation/PathVariable.java new file mode 100644 index 0000000..5649fcc --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/PathVariable.java @@ -0,0 +1,20 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * 路径变量注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface PathVariable { + + String value() default ""; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/PostMapping.java b/src/main/java/cn/codeyourlife/server/annotation/PostMapping.java new file mode 100644 index 0000000..e9c48f0 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/PostMapping.java @@ -0,0 +1,22 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Http POST 方法注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PostMapping { + + String value() default ""; + +} \ No newline at end of file diff --git a/src/main/java/cn/codeyourlife/server/annotation/PutMapping.java b/src/main/java/cn/codeyourlife/server/annotation/PutMapping.java new file mode 100644 index 0000000..0466c61 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/PutMapping.java @@ -0,0 +1,22 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Http PUT 方法注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface PutMapping { + + String value() default ""; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/RequestBody.java b/src/main/java/cn/codeyourlife/server/annotation/RequestBody.java new file mode 100644 index 0000000..3a89f0c --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/RequestBody.java @@ -0,0 +1,20 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * Request 请求体注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequestBody { + + String value() default ""; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/RequestHeader.java b/src/main/java/cn/codeyourlife/server/annotation/RequestHeader.java new file mode 100644 index 0000000..c595a44 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/RequestHeader.java @@ -0,0 +1,20 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * Http 请求头注解 + * + * @author Leo + * @date 2018/3/23 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequestHeader { + + String value() default ""; + + boolean required() default true; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/RequestMapping.java b/src/main/java/cn/codeyourlife/server/annotation/RequestMapping.java new file mode 100644 index 0000000..1587658 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/RequestMapping.java @@ -0,0 +1,22 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Http 请求映射类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +public @interface RequestMapping { + + String value() default ""; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/RequestMethodEnum.java b/src/main/java/cn/codeyourlife/server/annotation/RequestMethodEnum.java new file mode 100644 index 0000000..336cdc4 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/RequestMethodEnum.java @@ -0,0 +1,15 @@ +package cn.codeyourlife.server.annotation; + +/** + * Http Request Method 枚举 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public enum RequestMethodEnum { + + GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/RequestParam.java b/src/main/java/cn/codeyourlife/server/annotation/RequestParam.java new file mode 100644 index 0000000..ed79081 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/RequestParam.java @@ -0,0 +1,22 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * Http 请求参数注解 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RequestParam { + + String value() default ""; + + boolean required() default true; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/RestController.java b/src/main/java/cn/codeyourlife/server/annotation/RestController.java new file mode 100644 index 0000000..e2d12ea --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/RestController.java @@ -0,0 +1,18 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * REST 控制器注解 + * + * @author Leo + * @date 2018/3/19 + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface RestController { + + boolean singleton() default true; + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/UploadFile.java b/src/main/java/cn/codeyourlife/server/annotation/UploadFile.java new file mode 100644 index 0000000..0bb251f --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/UploadFile.java @@ -0,0 +1,16 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * 上传文件注解 + * + * @author Leo + * @date 2018/5/11 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface UploadFile { + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/UploadFiles.java b/src/main/java/cn/codeyourlife/server/annotation/UploadFiles.java new file mode 100644 index 0000000..87c0cc4 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/UploadFiles.java @@ -0,0 +1,16 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * UploadFiles注解 + * + * @author Leo + * @date 2018/5/11 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface UploadFiles { + +} diff --git a/src/main/java/cn/codeyourlife/server/annotation/UrlEncodedForm.java b/src/main/java/cn/codeyourlife/server/annotation/UrlEncodedForm.java new file mode 100644 index 0000000..e9f81d1 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/annotation/UrlEncodedForm.java @@ -0,0 +1,16 @@ +package cn.codeyourlife.server.annotation; + +import java.lang.annotation.*; + +/** + * UrlEncodedForm 注解 + * + * @author Leo + * @date 2018/3/20 + */ +@Target(ElementType.PARAMETER) +@Retention(RetentionPolicy.RUNTIME) +@Documented +public @interface UrlEncodedForm { + +} diff --git a/src/main/java/cn/codeyourlife/server/controller/ExceptionHandler.java b/src/main/java/cn/codeyourlife/server/controller/ExceptionHandler.java new file mode 100644 index 0000000..9b41379 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/controller/ExceptionHandler.java @@ -0,0 +1,19 @@ +package cn.codeyourlife.server.controller; + +/** + * 异常处理器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public interface ExceptionHandler { + + /** + * 处理异常 + * @param e + */ + void doHandle(Exception e); + +} diff --git a/src/main/java/cn/codeyourlife/server/convert/Converter.java b/src/main/java/cn/codeyourlife/server/convert/Converter.java new file mode 100644 index 0000000..6582cfc --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/Converter.java @@ -0,0 +1,24 @@ +package cn.codeyourlife.server.convert; + +/** + * 数据转换器接口 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * + * @param + * @param + */ +public interface Converter { + + /** + * 类型转换 + * + * @param source + * @return + */ + T convert(Object source); + +} diff --git a/src/main/java/cn/codeyourlife/server/convert/ConverterFactory.java b/src/main/java/cn/codeyourlife/server/convert/ConverterFactory.java new file mode 100644 index 0000000..f5c905e --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/ConverterFactory.java @@ -0,0 +1,42 @@ +package cn.codeyourlife.server.convert; + +import java.util.Date; + +/** + * 转换器工厂类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class ConverterFactory { + + /** + * 创建转换器 + * @param clazz + * @return + */ + public static Converter create(Class clazz) { + if (clazz.equals(String.class)) { + return new StringConverter(); + } + if (clazz.equals(int.class) || clazz.equals(Integer.class)) { + return new IntegerConverter(); + } + if (clazz.equals(long.class) || clazz.equals(Long.class)) { + return new LongConverter(); + } + if (clazz.equals(float.class) || clazz.equals(Float.class)) { + return new FloatConverter(); + } + if (clazz.equals(double.class) || clazz.equals(Double.class)) { + return new DoubleConverter(); + } + if (clazz.equals(Date.class)) { + return new DateConverter(); + } + return null; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/convert/DateConverter.java b/src/main/java/cn/codeyourlife/server/convert/DateConverter.java new file mode 100644 index 0000000..dcd448d --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/DateConverter.java @@ -0,0 +1,33 @@ +package cn.codeyourlife.server.convert; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; + +/** + * 日期转换器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +final class DateConverter implements Converter { + + /** + * 类型转换 + * + * @param source + * @return + */ + @Override + public Date convert(Object source) { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + try { + return sdf.parse(source.toString()); + } catch (ParseException e) { + return null; + } + } + +} diff --git a/src/main/java/cn/codeyourlife/server/convert/DoubleConverter.java b/src/main/java/cn/codeyourlife/server/convert/DoubleConverter.java new file mode 100644 index 0000000..5a91e4b --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/DoubleConverter.java @@ -0,0 +1,24 @@ +package cn.codeyourlife.server.convert; + +/** + * 双精度转换器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +final class DoubleConverter implements Converter { + + /** + * 类型转换 + * + * @param source + * @return + */ + @Override + public Double convert(Object source) { + return Double.parseDouble(source.toString()); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/convert/FloatConverter.java b/src/main/java/cn/codeyourlife/server/convert/FloatConverter.java new file mode 100644 index 0000000..cd642f0 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/FloatConverter.java @@ -0,0 +1,24 @@ +package cn.codeyourlife.server.convert; + +/** + * 单精度转换器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +final class FloatConverter implements Converter { + + /** + * 类型转换 + * + * @param source + * @return + */ + @Override + public Float convert(Object source) { + return Float.parseFloat(source.toString()); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/convert/IntegerConverter.java b/src/main/java/cn/codeyourlife/server/convert/IntegerConverter.java new file mode 100644 index 0000000..46d804e --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/IntegerConverter.java @@ -0,0 +1,24 @@ +package cn.codeyourlife.server.convert; + +/** + * 整数转换器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +final class IntegerConverter implements Converter { + + /** + * 类型转换 + * + * @param source + * @return + */ + @Override + public Integer convert(Object source) { + return Integer.parseInt(source.toString()); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/convert/LongConverter.java b/src/main/java/cn/codeyourlife/server/convert/LongConverter.java new file mode 100644 index 0000000..7223bda --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/LongConverter.java @@ -0,0 +1,24 @@ +package cn.codeyourlife.server.convert; + +/** + * 长整数转换器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +final class LongConverter implements Converter { + + /** + * 类型转换 + * + * @param source + * @return + */ + @Override + public Long convert(Object source) { + return Long.parseLong(source.toString()); + } + +} \ No newline at end of file diff --git a/src/main/java/cn/codeyourlife/server/convert/StringConverter.java b/src/main/java/cn/codeyourlife/server/convert/StringConverter.java new file mode 100644 index 0000000..3b0c4f0 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/convert/StringConverter.java @@ -0,0 +1,24 @@ +package cn.codeyourlife.server.convert; + +/** + * 字符串转换器 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +final class StringConverter implements Converter { + + /** + * 类型转换 + * + * @param source + * @return + */ + @Override + public String convert(Object source) { + return source.toString(); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/exception/HandleRequestException.java b/src/main/java/cn/codeyourlife/server/exception/HandleRequestException.java new file mode 100644 index 0000000..bee6a2f --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/exception/HandleRequestException.java @@ -0,0 +1,29 @@ +package cn.codeyourlife.server.exception; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * 处理请求异常类 + */ +public final class HandleRequestException extends RuntimeException { + + private static final long serialVersionUID = -630225144002649999L; + + public HandleRequestException() { + } + + public HandleRequestException(String message) { + super(message); + } + + public HandleRequestException(String message, Throwable cause) { + super(message, cause); + } + + public HandleRequestException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/exception/ResourceNotFoundException.java b/src/main/java/cn/codeyourlife/server/exception/ResourceNotFoundException.java new file mode 100644 index 0000000..1bfd76d --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/exception/ResourceNotFoundException.java @@ -0,0 +1,29 @@ +package cn.codeyourlife.server.exception; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * 资源未发现异常类 + */ +public class ResourceNotFoundException extends RuntimeException { + + private static final long serialVersionUID = 5563265404641097547L; + + public ResourceNotFoundException() { + } + + public ResourceNotFoundException(String message) { + super(message); + } + + public ResourceNotFoundException(String message, Throwable cause) { + super(message, cause); + } + + public ResourceNotFoundException(Throwable cause) { + super(cause); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/interceptor/Interceptor.java b/src/main/java/cn/codeyourlife/server/interceptor/Interceptor.java new file mode 100644 index 0000000..4592e59 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/interceptor/Interceptor.java @@ -0,0 +1,42 @@ +package cn.codeyourlife.server.interceptor; + +import io.netty.handler.codec.http.FullHttpRequest; +import cn.codeyourlife.server.HttpResponse; + +/** + * 拦截器接口 + * + * @author Leo + * @date 2018/3/23 + */ +public interface Interceptor { + + /** + * 在业务处理器处理请求之前被调用 + * 如果返回false, 从当前的拦截器往回执行所有拦截器的afterCompletion(),再退出拦截器链。 + * 如果返回true,执行下一个拦截器,直到所有的拦截器都执行完毕,再执行被拦截的Controller。 + * @param request + * @param response + * @return + * @throws Exception + */ + boolean preHandle(FullHttpRequest request, HttpResponse response) throws Exception; + + /** + * 业务处理器执行完成之后执行 + * @param request + * @param response + * @return + * @throws Exception + */ + void postHandle(FullHttpRequest request, HttpResponse response) throws Exception; + + /** + * 完全处理完请求后被调用 + * 当有拦截器抛出异常时,会从当前拦截器往回执行所有的拦截器的afterCompletion()。 + * @param request + * @param response + */ + void afterCompletion(FullHttpRequest request, HttpResponse response); + +} diff --git a/src/main/java/cn/codeyourlife/server/interceptor/InterceptorRegistry.java b/src/main/java/cn/codeyourlife/server/interceptor/InterceptorRegistry.java new file mode 100644 index 0000000..08b1530 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/interceptor/InterceptorRegistry.java @@ -0,0 +1,40 @@ +package cn.codeyourlife.server.interceptor; + +import java.util.*; + +/** + * 拦截器注册类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class InterceptorRegistry { + + private final static Set interceptors = new LinkedHashSet<>(8); + + private final static Map> excludeMappings = new HashMap>(8); + + public static void addInterceptor(Interceptor interceptor) { + interceptors.add(interceptor); + } + + public static void addInterceptor(Interceptor interceptor, String... excludeMappings) { + interceptors.add(interceptor); + List excludeMappingList = new ArrayList(8); + for(String excludeMapping : excludeMappings) { + excludeMappingList.add(excludeMapping); + } + InterceptorRegistry.excludeMappings.put(interceptor.getClass().getName(), excludeMappingList); + } + + public static Set getInterceptors() { + return interceptors; + } + + public static List getExcludeMappings(Interceptor interceptor) { + return excludeMappings.get(interceptor.getClass().getName()); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/methods/AbstractRequestMappingRegisterStrategy.java b/src/main/java/cn/codeyourlife/server/methods/AbstractRequestMappingRegisterStrategy.java new file mode 100644 index 0000000..0f886d3 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/methods/AbstractRequestMappingRegisterStrategy.java @@ -0,0 +1,230 @@ +package cn.codeyourlife.server.methods; + +import cn.codeyourlife.server.router.ControllerMapping; +import cn.codeyourlife.server.router.ControllerMappingParameter; +import cn.codeyourlife.server.router.ControllerMappingParameterTypeEnum; +import io.netty.handler.codec.http.FullHttpRequest; +import cn.codeyourlife.server.annotation.*; +import cn.codeyourlife.server.HttpResponse; +import org.objectweb.asm.*; + +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.Arrays; + +/** + * 请求映射注册抽象策略类 + * + * @author Leo + * @date 2018/3/27 + */ +abstract class AbstractRequestMappingRegisterStrategy implements RequestMappingRegisterStrategy { + + /** + * 注册请求映射 + * @param clazz + * @param baseUrl + * @param method + */ + @Override + public void register(Class clazz, String baseUrl, Method method) { + // 得到url + String methodMappingUrl = getMethodUrl(method); + String url = getMethodUrl(baseUrl, methodMappingUrl); + if(url == null || url.trim().isEmpty()) { + return; + } + + ControllerMapping mapping = new ControllerMapping(); + mapping.setUrl(url); + mapping.setClassName(clazz.getName()); + mapping.setClassMethod(method.getName()); + mapping.setJsonResponse(method.getAnnotation(JsonResponse.class) != null); + String httpMethod = getHttpMethod(); + + if(httpMethod != null) { + // 得到参数 + Parameter[] ps = method.getParameters(); + if(ps.length > 0) { + // 得到所有参数名 + String[] paramNames = getMethodParameterNamesByAsm4(clazz, method); + for(int i = 0; i < ps.length; i++) { + ControllerMappingParameter cmp = new ControllerMappingParameter(); + cmp.setDataType(ps[i].getType()); + if(ps[i].getType().equals(FullHttpRequest.class)) { + cmp.setName(paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.HTTP_REQUEST); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getType().equals(HttpResponse.class)) { + cmp.setName(paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.HTTP_RESPONSE); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getAnnotation(RequestParam.class) != null) { + RequestParam requestParam = ps[i].getAnnotation(RequestParam.class); + cmp.setName(requestParam.value()); + cmp.setRequired(requestParam.required()); + cmp.setType(ControllerMappingParameterTypeEnum.REQUEST_PARAM); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getAnnotation(RequestHeader.class) != null) { + RequestHeader requestHeader = ps[i].getAnnotation(RequestHeader.class); + cmp.setName((requestHeader.value() != null && !requestHeader.value().trim().isEmpty()) ? + requestHeader.value().trim() : paramNames[i]); + cmp.setRequired(requestHeader.required()); + cmp.setType(ControllerMappingParameterTypeEnum.REQUEST_HEADER); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getAnnotation(PathVariable.class) != null) { + PathVariable pathVariable = ps[i].getAnnotation(PathVariable.class); + cmp.setName((pathVariable.value() != null && !pathVariable.value().trim().isEmpty()) ? + pathVariable.value().trim() : paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.PATH_VARIABLE); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getAnnotation(RequestBody.class) != null) { + cmp.setName(paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.REQUEST_BODY); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getAnnotation(UrlEncodedForm.class) != null) { + cmp.setName(paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.URL_ENCODED_FORM); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getAnnotation(UploadFile.class) != null) { + cmp.setName(paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.UPLOAD_FILE); + mapping.getParameters().add(cmp); + continue; + } + + if(ps[i].getAnnotation(UploadFiles.class) != null) { + cmp.setName(paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.UPLOAD_FILES); + mapping.getParameters().add(cmp); + continue; + } + + cmp.setName(paramNames[i]); + cmp.setType(ControllerMappingParameterTypeEnum.REQUEST_PARAM); + mapping.getParameters().add(cmp); + } + } + } + registerMapping(url, mapping); + } + + /** + * 得到控制器方法的Url + * @param method + * @return + */ + abstract String getMethodUrl(Method method); + + /** + * 得到Http请求的方法类型 + * @return + */ + abstract String getHttpMethod(); + + /** + * 注册Mapping + * @param url + * @param mapping + */ + abstract void registerMapping(String url, ControllerMapping mapping); + + /** + * 得到方法Url + * @param baseUrl + * @param methodMappingUrl + * @return + */ + private String getMethodUrl(String baseUrl, String methodMappingUrl) { + StringBuilder url = new StringBuilder(256); + url.append((baseUrl == null || baseUrl.trim().isEmpty()) ? "" : baseUrl.trim()); + if(methodMappingUrl != null && !methodMappingUrl.trim().isEmpty()) { + String methodMappingUrlTrim = methodMappingUrl.trim(); + if(!methodMappingUrlTrim.startsWith("/")) { + methodMappingUrlTrim = "/" + methodMappingUrlTrim; + } + if(url.toString().endsWith("/")) { + url.setLength(url.length() - 1); + } + url.append(methodMappingUrlTrim); + } + return url.toString(); + } + + /** + * 得到方法的所有参数名称 + * @param clazz + * @param method + * @return + */ + private String[] getMethodParameterNamesByAsm4(Class clazz, final Method method) { + final Class[] parameterTypes = method.getParameterTypes(); + if (parameterTypes == null || parameterTypes.length == 0) { + return null; + } + final Type[] types = new Type[parameterTypes.length]; + for (int i = 0; i < parameterTypes.length; i++) { + types[i] = Type.getType(parameterTypes[i]); + } + final String[] parameterNames = new String[parameterTypes.length]; + + String className = clazz.getName(); + int lastDotIndex = className.lastIndexOf("."); + className = className.substring(lastDotIndex + 1) + ".class"; + InputStream is = clazz.getResourceAsStream(className); + try { + ClassReader classReader = new ClassReader(is); + classReader.accept(new ClassVisitor(Opcodes.ASM4) { + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + // 只处理指定的方法 + Type[] argumentTypes = Type.getArgumentTypes(desc); + if (!method.getName().equals(name) || !Arrays.equals(argumentTypes, types)) { + return null; + } + return new MethodVisitor(Opcodes.ASM4) { + @Override + public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { + // 静态方法第一个参数就是方法的参数,如果是实例方法,第一个参数是this + if (Modifier.isStatic(method.getModifiers())) { + parameterNames[index] = name; + } + else if (index > 0 && index <= parameterNames.length) { + parameterNames[index - 1] = name; + } + } + }; + + } + }, 0); + } catch (IOException e) { + e.printStackTrace(); + } + return parameterNames; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/methods/DeleteMappingRegisterStrategy.java b/src/main/java/cn/codeyourlife/server/methods/DeleteMappingRegisterStrategy.java new file mode 100644 index 0000000..5bb05fd --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/methods/DeleteMappingRegisterStrategy.java @@ -0,0 +1,51 @@ +package cn.codeyourlife.server.methods; + +import cn.codeyourlife.server.annotation.DeleteMapping; +import cn.codeyourlife.server.router.ControllerMapping; +import cn.codeyourlife.server.router.ControllerMappingRegistry; + +import java.lang.reflect.Method; + +/** + * DELETE 请求映射注册策略类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class DeleteMappingRegisterStrategy extends AbstractRequestMappingRegisterStrategy implements RequestMappingRegisterStrategy { + + /** + * 得到控制器方法的Url + * @param method + * @return + */ + @Override + public String getMethodUrl(Method method) { + if(method.getAnnotation(DeleteMapping.class) != null) { + return method.getAnnotation(DeleteMapping.class).value(); + } + return ""; + } + + /** + * 得到Http请求的方法类型 + * @return + */ + @Override + public String getHttpMethod() { + return "DELETE"; + } + + /** + * 注册Mapping + * @param url + * @param mapping + */ + @Override + public void registerMapping(String url, ControllerMapping mapping) { + ControllerMappingRegistry.getDeleteMappings().put(url, mapping); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/methods/GetMappingRegisterStrategy.java b/src/main/java/cn/codeyourlife/server/methods/GetMappingRegisterStrategy.java new file mode 100644 index 0000000..8a605e1 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/methods/GetMappingRegisterStrategy.java @@ -0,0 +1,51 @@ +package cn.codeyourlife.server.methods; + +import cn.codeyourlife.server.annotation.GetMapping; +import cn.codeyourlife.server.router.ControllerMapping; +import cn.codeyourlife.server.router.ControllerMappingRegistry; + +import java.lang.reflect.Method; + +/** + * GET 请求映射注册策略类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class GetMappingRegisterStrategy extends AbstractRequestMappingRegisterStrategy implements RequestMappingRegisterStrategy { + + /** + * 得到控制器方法的Url + * @param method + * @return + */ + @Override + public String getMethodUrl(Method method) { + if(method.getAnnotation(GetMapping.class) != null) { + return method.getAnnotation(GetMapping.class).value(); + } + return ""; + } + + /** + * 得到Http请求的方法类型 + * @return + */ + @Override + public String getHttpMethod() { + return "GET"; + } + + /** + * 注册Mapping + * @param url + * @param mapping + */ + @Override + public void registerMapping(String url, ControllerMapping mapping) { + ControllerMappingRegistry.getGetMappings().put(url, mapping); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/methods/PatchMappingRegisterStrategy.java b/src/main/java/cn/codeyourlife/server/methods/PatchMappingRegisterStrategy.java new file mode 100644 index 0000000..44c1298 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/methods/PatchMappingRegisterStrategy.java @@ -0,0 +1,51 @@ +package cn.codeyourlife.server.methods; + +import cn.codeyourlife.server.annotation.PatchMapping; +import cn.codeyourlife.server.router.ControllerMapping; +import cn.codeyourlife.server.router.ControllerMappingRegistry; + +import java.lang.reflect.Method; + +/** + * PATCH 请求映射注册策略类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class PatchMappingRegisterStrategy extends AbstractRequestMappingRegisterStrategy implements RequestMappingRegisterStrategy { + + /** + * 得到控制器方法的Url + * @param method + * @return + */ + @Override + public String getMethodUrl(Method method) { + if(method.getAnnotation(PatchMapping.class) != null) { + return method.getAnnotation(PatchMapping.class).value(); + } + return ""; + } + + /** + * 得到Http请求的方法类型 + * @return + */ + @Override + public String getHttpMethod() { + return "PATCH"; + } + + /** + * 注册Mapping + * @param url + * @param mapping + */ + @Override + public void registerMapping(String url, ControllerMapping mapping) { + ControllerMappingRegistry.getPatchMappings().put(url, mapping); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/methods/PostMappingRegisterStrategy.java b/src/main/java/cn/codeyourlife/server/methods/PostMappingRegisterStrategy.java new file mode 100644 index 0000000..4b6e7d4 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/methods/PostMappingRegisterStrategy.java @@ -0,0 +1,52 @@ +package cn.codeyourlife.server.methods; + + +import cn.codeyourlife.server.annotation.PostMapping; +import cn.codeyourlife.server.router.ControllerMapping; +import cn.codeyourlife.server.router.ControllerMappingRegistry; + +import java.lang.reflect.Method; + +/** + * POST 请求映射注册策略类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class PostMappingRegisterStrategy extends AbstractRequestMappingRegisterStrategy implements RequestMappingRegisterStrategy { + + /** + * 得到控制器方法的Url + * @param method + * @return + */ + @Override + public String getMethodUrl(Method method) { + if(method.getAnnotation(PostMapping.class) != null) { + return method.getAnnotation(PostMapping.class).value(); + } + return ""; + } + + /** + * 得到Http请求的方法类型 + * @return + */ + @Override + public String getHttpMethod() { + return "POST"; + } + + /** + * 注册Mapping + * @param url + * @param mapping + */ + @Override + public void registerMapping(String url, ControllerMapping mapping) { + ControllerMappingRegistry.getPostMappings().put(url, mapping); + } + +} diff --git a/src/main/java/cn/codeyourlife/server/methods/PutMappingRegisterStrategy.java b/src/main/java/cn/codeyourlife/server/methods/PutMappingRegisterStrategy.java new file mode 100644 index 0000000..c26c6bb --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/methods/PutMappingRegisterStrategy.java @@ -0,0 +1,51 @@ +package cn.codeyourlife.server.methods; + +import cn.codeyourlife.server.annotation.PutMapping; +import cn.codeyourlife.server.router.ControllerMapping; +import cn.codeyourlife.server.router.ControllerMappingRegistry; + +import java.lang.reflect.Method; + +/** + * PUT 请求映射注册策略类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class PutMappingRegisterStrategy extends AbstractRequestMappingRegisterStrategy implements RequestMappingRegisterStrategy { + + /** + * 得到控制器方法的Url + * @param method + * @return + */ + @Override + public String getMethodUrl(Method method) { + if(method.getAnnotation(PutMapping.class) != null) { + return method.getAnnotation(PutMapping.class).value(); + } + return ""; + } + + /** + * 得到Http请求的方法类型 + * @return + */ + @Override + public String getHttpMethod() { + return "PUT"; + } + + /** + * 注册Mapping + * @param url + * @param mapping + */ + @Override + public void registerMapping(String url, ControllerMapping mapping) { + ControllerMappingRegistry.getPutMappings().put(url, mapping); + } + +} \ No newline at end of file diff --git a/src/main/java/cn/codeyourlife/server/methods/RequestMappingRegisterStrategy.java b/src/main/java/cn/codeyourlife/server/methods/RequestMappingRegisterStrategy.java new file mode 100644 index 0000000..c6ee095 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/methods/RequestMappingRegisterStrategy.java @@ -0,0 +1,23 @@ +package cn.codeyourlife.server.methods; + +import java.lang.reflect.Method; + +/** + * 请求映射注册策略接口 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public interface RequestMappingRegisterStrategy { + + /** + * 注册请求映射 + * @param clazz + * @param baseUrl + * @param method + */ + void register(Class clazz, String baseUrl, Method method); + +} diff --git a/src/main/java/cn/codeyourlife/server/router/ControllerBean.java b/src/main/java/cn/codeyourlife/server/router/ControllerBean.java new file mode 100644 index 0000000..168f443 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/router/ControllerBean.java @@ -0,0 +1,33 @@ +package cn.codeyourlife.server.router; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * 控制器 Bean 类 + */ +public class ControllerBean { + public ControllerBean(Class clazz, boolean singleton) { + this.clazz = clazz; + this.singleton = singleton; + } + + private Class clazz; + + private boolean singleton; + + public Class getClazz() { + return clazz; + } + public void setClazz(Class clazz) { + this.clazz = clazz; + } + + public boolean getSingleton() { + return this.singleton; + } + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } +} diff --git a/src/main/java/cn/codeyourlife/server/router/ControllerMapping.java b/src/main/java/cn/codeyourlife/server/router/ControllerMapping.java new file mode 100644 index 0000000..8c2592e --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/router/ControllerMapping.java @@ -0,0 +1,69 @@ +package cn.codeyourlife.server.router; + +import java.util.ArrayList; +import java.util.List; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public class ControllerMapping { + private String url; + + private String className; + + private String classMethod; + + private List parameters = new ArrayList<>(); + + /** + * 是否输出结果为JSON + */ + private boolean JsonResponse; + + /** + * 单例类 + */ + private boolean singleton; + + public String getUrl() { + return this.url; + } + public void setUrl(String url) { + this.url = url; + } + + public String getClassName() { + return this.className; + } + public void setClassName(String className) { + this.className = className; + } + + public String getClassMethod() { + return this.classMethod; + } + public void setClassMethod(String classMethod) { + this.classMethod = classMethod; + } + + public List getParameters() { + return this.parameters; + } + + public boolean getJsonResponse() { + return this.JsonResponse; + } + public void setJsonResponse(boolean jsonResponse) { + this.JsonResponse = jsonResponse; + } + + public boolean getSingleton() { + return this.singleton; + } + public void setSingleton(boolean singleton) { + this.singleton = singleton; + } +} diff --git a/src/main/java/cn/codeyourlife/server/router/ControllerMappingParameter.java b/src/main/java/cn/codeyourlife/server/router/ControllerMappingParameter.java new file mode 100644 index 0000000..d84482c --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/router/ControllerMappingParameter.java @@ -0,0 +1,46 @@ +package cn.codeyourlife.server.router; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * 请求映射参数类 + */ +public class ControllerMappingParameter { + private String name; + + private Class dataType; + + private ControllerMappingParameterTypeEnum type; + + private boolean required = true; + + public String getName() { + return this.name; + } + public void setName(String name) { + this.name = name; + } + + public Class getDataType() { + return this.dataType; + } + public void setDataType(Class dataType) { + this.dataType = dataType; + } + + public ControllerMappingParameterTypeEnum getType() { + return this.type; + } + public void setType(ControllerMappingParameterTypeEnum type) { + this.type = type; + } + + public boolean getRequired() { + return this.required; + } + public void setRequired(boolean required) { + this.required = required; + } +} diff --git a/src/main/java/cn/codeyourlife/server/router/ControllerMappingParameterTypeEnum.java b/src/main/java/cn/codeyourlife/server/router/ControllerMappingParameterTypeEnum.java new file mode 100644 index 0000000..0db2272 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/router/ControllerMappingParameterTypeEnum.java @@ -0,0 +1,49 @@ +package cn.codeyourlife.server.router; + +// 请求映射参数类型枚举 +public enum ControllerMappingParameterTypeEnum { + /** + * Request Url 参数 + */ + REQUEST_PARAM, + + /** + * 路径变量 + */ + PATH_VARIABLE, + + /** + * Http Request + */ + HTTP_REQUEST, + + /** + * Http Response + */ + HTTP_RESPONSE, + + /** + * 请求体 + */ + REQUEST_BODY, + + /** + * X-WWW-FORM-URLENCODED + */ + URL_ENCODED_FORM, + + /** + * Http Reqesut Header参数 + */ + REQUEST_HEADER, + + /** + * 上传文件 + */ + UPLOAD_FILE, + + /** + * 多个上传文件 + */ + UPLOAD_FILES +} diff --git a/src/main/java/cn/codeyourlife/server/router/ControllerMappingRegistry.java b/src/main/java/cn/codeyourlife/server/router/ControllerMappingRegistry.java new file mode 100644 index 0000000..d6d6bb8 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/router/ControllerMappingRegistry.java @@ -0,0 +1,197 @@ +package cn.codeyourlife.server.router; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + * 请求映射注册类 + */ +public class ControllerMappingRegistry { + private static final Logger logger = LoggerFactory.getLogger(ControllerMappingRegistry.class); + + private static final Map getMappings = new HashMap<>(64); + + private static final Map postMappings = new HashMap<>(64); + + private static final Map putMappings = new HashMap<>(64); + + private static final Map deleteMappings = new HashMap<>(64); + + private static final Map patchMappings = new HashMap<>(64); + + /** + * 缓存 REST 控制器类 + */ + private static final Map beans = new HashMap<>(128); + + /** + * 缓存 REST 控制器类单例 + */ + private static final Map singletons = new ConcurrentHashMap<>(128); + + /** + * 注册Controller Bean + * + * @param name + * @param bean + */ + public static void registerBean(String name, ControllerBean bean) { + beans.put(name, bean); + } + + /** + * 得到Controller Bean + * + * @param name + * @return + */ + public static ControllerBean getBean(String name) { + return beans.get(name); + } + + /** + * 注册Controller类的单例 + * + * @param name + * @param singleton + */ + public static void registerSingleton(String name, Object singleton) { + singletons.put(name, singleton); + } + + /** + * 得到单例 + * + * @param name + * @return + */ + public static Object getSingleton(String name) { + if (singletons.containsKey(name)) { + return singletons.get(name); + } + + Class clazz = null; + try { + clazz = Class.forName(name); + } catch (ClassNotFoundException e) { + logger.error("Class not found: {}", name); + return null; + } + Object instance = null; + try { + instance = clazz.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + logger.error("Create class instance failure: {}", name); + return null; + } + Object result = singletons.putIfAbsent(name, instance); + if (result == null) { + return instance; + } + return result; + } + + /** + * 注册 Get Mapping + * + * @param url + * @param mapping + */ + public static void registerGetMapping(String url, ControllerMapping mapping) { + getMappings.put(url, mapping); + } + + /** + * 得到Get映射哈希表 + * + * @return + */ + public static Map getGetMappings() { + return getMappings; + } + + /** + * 注册 Post Mapping + * + * @param url + * @param mapping + */ + public static void registerPostMapping(String url, ControllerMapping mapping) { + postMappings.put(url, mapping); + } + + /** + * 得到Post映射哈希表 + * + * @return + */ + public static Map getPostMappings() { + return postMappings; + } + + /** + * 注册 Put Mapping + * + * @param url + * @param mapping + */ + public static void registerPutMapping(String url, ControllerMapping mapping) { + putMappings.put(url, mapping); + } + + /** + * 得到Put映射哈希表 + * + * @return + */ + public static Map getPutMappings() { + return putMappings; + } + + /** + * 注册 Delete Mapping + * + * @param url + * @param mapping + */ + public static void registerDeleteMapping(String url, ControllerMapping mapping) { + deleteMappings.put(url, mapping); + } + + /** + * 得到Delete映射哈希表 + * + * @return + */ + public static Map getDeleteMappings() { + return deleteMappings; + } + + /** + * 注册 Patch Mapping + * + * @param url + * @param mapping + */ + public static void registerPatchMapping(String url, ControllerMapping mapping) { + patchMappings.put(url, mapping); + } + + /** + * 得到Patch映射哈希表 + * + * @return + */ + public static Map getPatchMappings() { + return patchMappings; + } + +} diff --git a/src/main/java/cn/codeyourlife/server/router/RequestMappingRegisterContext.java b/src/main/java/cn/codeyourlife/server/router/RequestMappingRegisterContext.java new file mode 100644 index 0000000..eaf47e9 --- /dev/null +++ b/src/main/java/cn/codeyourlife/server/router/RequestMappingRegisterContext.java @@ -0,0 +1,36 @@ +package cn.codeyourlife.server.router; + +import cn.codeyourlife.server.methods.RequestMappingRegisterStrategy; + +import java.lang.reflect.Method; + +/** + * 请求映射策略上下文类 + * + * Author: wbq813@foxmail.com + * Copyright: http://codeyourlife.cn + * Platform: Win10 Jdk8 + * Date: 2020/1/13 + */ +public final class RequestMappingRegisterContext { + + private RequestMappingRegisterStrategy strategy; + + public RequestMappingRegisterContext(RequestMappingRegisterStrategy strategy) { + this.strategy = strategy; + } + + /** + * 注册 Mapping + * @param clazz + * @param baseUrl + * @param method + */ + public void registerMapping(Class clazz, String baseUrl, Method method) { + if(this.strategy == null) { + return; + } + this.strategy.register(clazz, baseUrl, method); + } + +}