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 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -30,6 +91,7 @@
@@ -44,23 +106,27 @@
-
-
+
-
+
-
-
+
+
+
+
+
+
-
-
+
+
+
@@ -75,7 +141,14 @@
-
+
+
+
+
+
+
+
+
@@ -85,19 +158,19 @@
+
-
- 1578489428831
+
+ 1578882265176
- 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. 完善页面
+
#### 运行效果

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);
+ }
+
+}