diff --git a/README.md b/README.md index 77dbc8909..cf01e0737 100644 --- a/README.md +++ b/README.md @@ -64,14 +64,14 @@ Run with `Maven`: com.bladejava blade-mvc - 2.0.11.BETA + 2.0.12.ALPHA ``` or `Gradle`: ```sh -compile 'com.bladejava:blade-mvc:2.0.11.BETA' +compile 'com.bladejava:blade-mvc:2.0.12.ALPHA' ``` Write the `main` method and the `Hello World`: diff --git a/README_CN.md b/README_CN.md index 9357ddb48..eeacac06d 100644 --- a/README_CN.md +++ b/README_CN.md @@ -61,7 +61,7 @@ com.bladejava blade-mvc - 2.0.11.BETA + 2.0.12.ALPHA ``` @@ -70,7 +70,7 @@ 或者 `Gradle`: ```sh -compile 'com.bladejava:blade-mvc:2.0.11.BETA' +compile 'com.bladejava:blade-mvc:2.0.12.ALPHA' ``` 编写 `main` 函数写一个 `Hello World`: diff --git a/build.gradle b/build.gradle index d88fa509e..f058e35a3 100644 --- a/build.gradle +++ b/build.gradle @@ -4,7 +4,7 @@ apply plugin: 'eclipse' apply plugin: 'idea' group = 'com.bladejava' -version = '2.0.11.BETA' +version = '2.0.12.ALPHA' description = 'blade-mvc' diff --git a/pom.xml b/pom.xml index 33492d2df..2e35719bd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.bladejava blade-mvc - 2.0.12-SNAPSHOT + 2.0.12.ALPHA jar blade diff --git a/src/main/java/com/blade/Blade.java b/src/main/java/com/blade/Blade.java index 0d9a7faff..ed9f1a229 100644 --- a/src/main/java/com/blade/Blade.java +++ b/src/main/java/com/blade/Blade.java @@ -46,6 +46,7 @@ import java.io.BufferedReader; import java.io.IOException; +import java.net.BindException; import java.nio.file.*; import java.util.*; import java.util.concurrent.CountDownLatch; @@ -845,7 +846,9 @@ public Blade start(Class mainCls, String... args) { server.start(Blade.this); latch.countDown(); server.join(); - + } catch (BindException e) { + log.error("Bind address error\n", e); + System.exit(0); } catch (Exception e) { startupExceptionHandler.accept(e); } @@ -1045,7 +1048,9 @@ private void loadConfig(String[] args) { } Map argsMap = BladeKit.parseArgs(args); - log.info("command line args: {}", JsonKit.toString(argsMap)); + if (null != argsMap && !argsMap.isEmpty()) { + log.info(" command line args: {}", JsonKit.toString(argsMap)); + } if (StringKit.isNotEmpty(argsMap.get(ENV_KEY_APP_ENV))) { envName = argsMap.get(ENV_KEY_APP_ENV); @@ -1069,7 +1074,7 @@ private void loadConfig(String[] args) { argsMap.remove(ENV_KEY_APP_ENV); } - log.info("current environment is: {}", envName); + this.environment.set(ENV_KEY_APP_ENV, envName); this.register(this.environment); diff --git a/src/main/java/com/blade/exception/BladeException.java b/src/main/java/com/blade/exception/BladeException.java index 211815693..af49e294e 100644 --- a/src/main/java/com/blade/exception/BladeException.java +++ b/src/main/java/com/blade/exception/BladeException.java @@ -29,6 +29,10 @@ public class BladeException extends RuntimeException { protected int status; protected String name; + public BladeException(Throwable cause) { + super(cause); + } + public BladeException(int status, String name) { this.status = status; this.name = name; @@ -40,4 +44,8 @@ public BladeException(int status, String name, String message) { this.name = name; } + public static BladeException wrapper(Exception e) { + return new BladeException(e); + } + } diff --git a/src/main/java/com/blade/kit/BladeKit.java b/src/main/java/com/blade/kit/BladeKit.java index dea03f414..13a15d9e4 100644 --- a/src/main/java/com/blade/kit/BladeKit.java +++ b/src/main/java/com/blade/kit/BladeKit.java @@ -36,6 +36,7 @@ import java.io.Serializable; import java.io.UnsupportedEncodingException; import java.lang.invoke.SerializedLambda; +import java.lang.management.ManagementFactory; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -364,4 +365,16 @@ public static Map parseArgs(String[] args) { } return argsMap; } + + /** + * @return Get the process id of the current JVM process + */ + public static Integer getPID() { + String mbean = ManagementFactory.getRuntimeMXBean().getName(); + if (mbean.contains("@")) { + return Integer.valueOf(mbean.substring(0, mbean.indexOf("@"))); + } + return -1; + } + } diff --git a/src/main/java/com/blade/mvc/Const.java b/src/main/java/com/blade/mvc/Const.java index fdc9c3dcf..8f6417de0 100644 --- a/src/main/java/com/blade/mvc/Const.java +++ b/src/main/java/com/blade/mvc/Const.java @@ -31,7 +31,7 @@ public interface Const { int DEFAULT_SERVER_PORT = 9000; String DEFAULT_SERVER_ADDRESS = "0.0.0.0"; String LOCAL_IP_ADDRESS = "127.0.0.1"; - String VERSION = "2.0.11.RELEASE"; + String VERSION = "2.0.12.ALPHA"; String WEB_JARS = "/webjars/"; String CLASSPATH = BladeKit.getCurrentClassPath(); String CONTENT_TYPE_HTML = "text/html; charset=UTF-8"; @@ -44,6 +44,7 @@ public interface Const { List DEFAULT_STATICS = new ArrayList<>( Arrays.asList("/favicon.ico", "/robots.txt", "/static", "/upload", "/webjars/")); + @Deprecated String PROP_NAME0 = "classpath:app.properties"; String PROP_NAME = "classpath:application.properties"; @@ -76,13 +77,11 @@ public interface Const { String ENE_KEY_SSL_PRIVATE_KEY_PASS = "server.ssl.private-key-pass"; String ENC_KEY_NETTY_ACCEPT_THREAD_COUNT = "server.netty.accept-thread-count"; String ENV_KEY_NETTY_IO_THREAD_COUNT = "server.netty.io-thread-count"; - String ENV_KEY_NETTY_SO_BACKLOG = "server.netty.so-backlog"; String ENV_KEY_BOOT_CONF = "boot_conf"; String ENV_KEY_AUTO_REFRESH_DIR = "app.auto.refresh.dir"; // netty default config - int DEFAULT_SO_BACKLOG = 1024; int DEFAULT_ACCEPT_THREAD_COUNT = 1; int DEFAULT_IO_THREAD_COUNT = 0; diff --git a/src/main/java/com/blade/mvc/LocalContext.java b/src/main/java/com/blade/mvc/LocalContext.java deleted file mode 100644 index 9f2d777f7..000000000 --- a/src/main/java/com/blade/mvc/LocalContext.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.blade.mvc; - -import com.blade.mvc.http.HttpRequest; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder; - -/** - * Cache the request context of the current thread, use it for RequestHandler. - * - * @author biezhi - * @date 2018/10/15 - */ -public class LocalContext { - - private HttpObject msg; - private HttpRequest request; - private HttpPostRequestDecoder decoder; - - public LocalContext(HttpObject msg, HttpRequest request, HttpPostRequestDecoder decoder) { - this.msg = msg; - this.request = request; - this.decoder = decoder; - } - - public HttpObject msg() { - return this.msg; - } - - public HttpRequest request() { - return this.request; - } - - public HttpPostRequestDecoder decoder() { - return decoder; - } - - public boolean hasDecoder() { - return null != decoder; - } - - public void updateMsg(HttpObject msg) { - this.msg = msg; - } - -} diff --git a/src/main/java/com/blade/mvc/WebContext.java b/src/main/java/com/blade/mvc/WebContext.java index 0ebc443d8..5cf67b172 100644 --- a/src/main/java/com/blade/mvc/WebContext.java +++ b/src/main/java/com/blade/mvc/WebContext.java @@ -20,13 +20,14 @@ import com.blade.mvc.http.Request; import com.blade.mvc.http.Response; import com.blade.mvc.route.Route; -import com.blade.server.netty.HttpServerHandler; import io.netty.channel.ChannelHandlerContext; import lombok.NoArgsConstructor; import lombok.var; import java.util.Optional; +import static com.blade.mvc.Const.ENV_KEY_SESSION_KEY; +import static com.blade.server.netty.HttpConst.DEFAULT_SESSION_KEY; import static com.blade.server.netty.HttpServerHandler.WEB_CONTEXT_THREAD_LOCAL; /** @@ -50,6 +51,11 @@ public class WebContext { */ private static String contextPath; + /** + * Session Key, default is: SESSION + */ + private static String sessionKey; + /** * Http Request instance of current thread context */ @@ -62,16 +68,81 @@ public class WebContext { private Route route; - private LocalContext localContext; - private ChannelHandlerContext channelHandlerContext; - public WebContext(Request request, Response response, ChannelHandlerContext channelHandlerContext) { + public WebContext(Request request, Response response, + ChannelHandlerContext channelHandlerContext) { + this.request = request; this.response = response; this.channelHandlerContext = channelHandlerContext; } + public Request getRequest() { + return request; + } + + public Response getResponse() { + return response; + } + + public Environment environment() { + return blade.environment(); + } + + public static String sessionKey() { + return sessionKey; + } + + /** + * Get application environment information. + * + * @param key environment key + * @return environment optional value + */ + public Optional env(String key) { + return blade().env(key); + } + + /** + * Get application environment information. + * + * @param key environment key + * @param defaultValue default value, if value is null + * @return environment optional value + */ + public String env(String key, String defaultValue) { + return blade().env(key, defaultValue); + } + + public ChannelHandlerContext getChannelHandlerContext() { + return channelHandlerContext; + } + + public ChannelHandlerContext getHandlerContext() { + return channelHandlerContext; + } + + public Route getRoute() { + return route; + } + + public void setRoute(Route route) { + this.route = route; + } + + /** + * Initializes the project when it starts + * + * @param blade Blade instance + * @param contextPath context path + */ + public static void init(Blade blade, String contextPath) { + WebContext.blade = blade; + WebContext.contextPath = contextPath; + WebContext.sessionKey = blade.environment().get(ENV_KEY_SESSION_KEY, DEFAULT_SESSION_KEY); + } + /** * Get current thread context WebContext instance * @@ -101,44 +172,23 @@ public static Response response() { return null != webContext ? webContext.response : null; } - public static WebContext create(Request request, Response response, ChannelHandlerContext ctx, LocalContext localContext) { + public static WebContext create(Request request, Response response, ChannelHandlerContext ctx) { WebContext webContext = new WebContext(); webContext.request = request; webContext.response = response; webContext.channelHandlerContext = ctx; - webContext.localContext = localContext; WEB_CONTEXT_THREAD_LOCAL.set(webContext); return webContext; } public static void set(WebContext webContext) { - HttpServerHandler.WEB_CONTEXT_THREAD_LOCAL.set(webContext); + WEB_CONTEXT_THREAD_LOCAL.set(webContext); } public static void remove() { - HttpServerHandler.WEB_CONTEXT_THREAD_LOCAL.remove(); - } - - public Request getRequest() { - return request; - } - - public Response getResponse() { - return response; - } - - /** - * Initializes the project when it starts - * - * @param blade Blade instance - * @param contextPath context path - */ - public static void init(Blade blade, String contextPath) { - WebContext.blade = blade; - WebContext.contextPath = contextPath; + WEB_CONTEXT_THREAD_LOCAL.remove(); } - /** * Get blade instance * @@ -162,41 +212,4 @@ public static void clean() { blade = null; } - public Environment environment() { - return blade.environment(); - } - - /** - * Get application environment information. - * - * @param key environment key - * @return environment optional value - */ - public Optional env(String key) { - return blade().env(key); - } - - /** - * Get application environment information. - * - * @param key environment key - * @param defaultValue default value, if value is null - * @return environment optional value - */ - public String env(String key, String defaultValue) { - return blade().env(key, defaultValue); - } - - public ChannelHandlerContext getChannelHandlerContext() { - return channelHandlerContext; - } - - public Route getRoute() { - return route; - } - - public void setRoute(Route route) { - this.route = route; - } - } diff --git a/src/main/java/com/blade/mvc/handler/DefaultExceptionHandler.java b/src/main/java/com/blade/mvc/handler/DefaultExceptionHandler.java index 63476b976..2c7831ebe 100644 --- a/src/main/java/com/blade/mvc/handler/DefaultExceptionHandler.java +++ b/src/main/java/com/blade/mvc/handler/DefaultExceptionHandler.java @@ -61,7 +61,7 @@ protected void handleValidators(ValidatorException validatorException, Request r } protected void handleException(Exception e, Request request, Response response) { - log.error("Request Exception", e); + log.error("", e); if (null == response) { return; } @@ -85,7 +85,7 @@ protected void handleBladeException(BladeException e, Request request, Response } if (e.getStatus() == InternalErrorException.STATUS) { - log.error("Request Exception", e); + log.error("", e); this.render500(request, response); } diff --git a/src/main/java/com/blade/mvc/handler/ExceptionHandler.java b/src/main/java/com/blade/mvc/handler/ExceptionHandler.java index b1f0f3991..2a29bb9b3 100644 --- a/src/main/java/com/blade/mvc/handler/ExceptionHandler.java +++ b/src/main/java/com/blade/mvc/handler/ExceptionHandler.java @@ -19,7 +19,8 @@ public interface ExceptionHandler { void handle(Exception e); static boolean isResetByPeer(Throwable e) { - if ("Connection reset by peer".equals(e.getMessage())) { + if (null != e.getMessage() && + e.getMessage().contains("Connection reset by peer")) { return true; } return false; diff --git a/src/main/java/com/blade/mvc/handler/SessionHandler.java b/src/main/java/com/blade/mvc/handler/SessionHandler.java index d16f59de8..e49a473f4 100644 --- a/src/main/java/com/blade/mvc/handler/SessionHandler.java +++ b/src/main/java/com/blade/mvc/handler/SessionHandler.java @@ -19,14 +19,13 @@ import com.blade.kit.ReflectKit; import com.blade.kit.StringKit; import com.blade.kit.UUID; +import com.blade.mvc.WebContext; import com.blade.mvc.http.Request; import com.blade.mvc.http.Session; import com.blade.mvc.http.session.SessionManager; -import com.blade.server.netty.HttpConst; import java.time.Instant; -import static com.blade.mvc.Const.ENV_KEY_SESSION_KEY; import static com.blade.mvc.Const.ENV_KEY_SESSION_TIMEOUT; /** @@ -39,13 +38,11 @@ public class SessionHandler { private final Blade blade; private final SessionManager sessionManager; - private final String sessionKey; private final int timeout; public SessionHandler(Blade blade) { this.blade = blade; this.sessionManager = blade.sessionManager(); - this.sessionKey = blade.environment().get(ENV_KEY_SESSION_KEY, HttpConst.DEFAULT_SESSION_KEY); this.timeout = blade.environment().getInt(ENV_KEY_SESSION_TIMEOUT, 1800); } @@ -74,7 +71,7 @@ public Session createSession(Request request) { } private Session getSession(Request request) { - String cookieHeader = request.cookie(sessionKey); + String cookieHeader = request.cookie(WebContext.sessionKey()); if (StringKit.isEmpty(cookieHeader)) { return null; } diff --git a/src/main/java/com/blade/mvc/http/Body.java b/src/main/java/com/blade/mvc/http/Body.java index 78e415ab5..627f154c8 100644 --- a/src/main/java/com/blade/mvc/http/Body.java +++ b/src/main/java/com/blade/mvc/http/Body.java @@ -1,7 +1,9 @@ package com.blade.mvc.http; +import io.netty.handler.codec.http.FullHttpResponse; + public interface Body { - void write(BodyWriter writer); + FullHttpResponse write(BodyWriter writer); } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/BodyWriter.java b/src/main/java/com/blade/mvc/http/BodyWriter.java index fa037d085..21bec07f8 100644 --- a/src/main/java/com/blade/mvc/http/BodyWriter.java +++ b/src/main/java/com/blade/mvc/http/BodyWriter.java @@ -1,19 +1,20 @@ package com.blade.mvc.http; import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.http.FullHttpResponse; import java.io.Closeable; public interface BodyWriter { - void onStream(Closeable closeable); + FullHttpResponse onStream(Closeable closeable); - void onView(ViewBody body); + FullHttpResponse onView(ViewBody body); - void onRawBody(RawBody body); + FullHttpResponse onRawBody(RawBody body); - void onByteBuf(Object byteBuf); + FullHttpResponse onByteBuf(Object byteBuf); - void onByteBuf(ByteBuf byteBuf); + FullHttpResponse onByteBuf(ByteBuf byteBuf); } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/ByteBody.java b/src/main/java/com/blade/mvc/http/ByteBody.java index bd066b0b7..7f69d3b44 100644 --- a/src/main/java/com/blade/mvc/http/ByteBody.java +++ b/src/main/java/com/blade/mvc/http/ByteBody.java @@ -2,6 +2,7 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.FullHttpResponse; import java.io.File; import java.io.IOException; @@ -57,12 +58,11 @@ public static ByteBody of(ByteBuf byteBuf) { } @Override - public void write(BodyWriter writer) { + public FullHttpResponse write(BodyWriter writer) { if (null != outputStream) { - writer.onByteBuf(outputStream); - return; + return writer.onByteBuf(outputStream); } - writer.onByteBuf(byteBuf); + return writer.onByteBuf(byteBuf); } } diff --git a/src/main/java/com/blade/mvc/http/EmptyBody.java b/src/main/java/com/blade/mvc/http/EmptyBody.java index ea0277c02..741baf5a6 100644 --- a/src/main/java/com/blade/mvc/http/EmptyBody.java +++ b/src/main/java/com/blade/mvc/http/EmptyBody.java @@ -1,6 +1,7 @@ package com.blade.mvc.http; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.FullHttpResponse; import lombok.NoArgsConstructor; @NoArgsConstructor @@ -13,8 +14,8 @@ public static EmptyBody empty() { } @Override - public void write(BodyWriter writer) { - writer.onByteBuf( Unpooled.buffer(0)); + public FullHttpResponse write(BodyWriter writer) { + return writer.onByteBuf( Unpooled.buffer(0)); } } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/HttpRequest.java b/src/main/java/com/blade/mvc/http/HttpRequest.java index a7e437e79..c41ff2217 100644 --- a/src/main/java/com/blade/mvc/http/HttpRequest.java +++ b/src/main/java/com/blade/mvc/http/HttpRequest.java @@ -16,11 +16,9 @@ package com.blade.mvc.http; import com.blade.exception.HttpParseException; -import com.blade.exception.InternalErrorException; import com.blade.kit.PathKit; import com.blade.kit.StringKit; import com.blade.mvc.Const; -import com.blade.mvc.LocalContext; import com.blade.mvc.WebContext; import com.blade.mvc.handler.SessionHandler; import com.blade.mvc.http.session.SessionManager; @@ -64,8 +62,10 @@ public class HttpRequest implements Request { private static final SessionHandler SESSION_HANDLER = new SessionHandler(WebContext.blade()); static { - DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on - DiskAttribute.baseDirectory = null; // system temp directory + DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file + DiskFileUpload.baseDirectory = null; // system temp directory + DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on + DiskAttribute.baseDirectory = null; // system temp directory } private ByteBuf body = EMPTY_BUF; @@ -74,15 +74,22 @@ public class HttpRequest implements Request { private String url; private String protocol; private String method; - private String cookieString; private boolean keepAlive; private Session session; - private boolean isRequestPart; - private boolean isChunked; private boolean isMultipart; private boolean isEnd; private boolean initCookie; + private boolean initQueryParam; + + private HttpData partialContent; + + private HttpHeaders httpHeaders; + + private io.netty.handler.codec.http.HttpRequest nettyRequest; + private HttpPostRequestDecoder decoder; + + private Queue contents = new LinkedList<>(); private Map headers = null; private Map attributes = null; @@ -158,6 +165,19 @@ public String queryString() { @Override public Map> parameters() { + if (!initQueryParam) { + initQueryParam = true; + if (!url.contains("?")) { + return this.parameters; + } + + var parameters = + new QueryStringDecoder(url, CharsetUtil.UTF_8).parameters(); + + if (null != parameters) { + this.parameters.putAll(parameters); + } + } return this.parameters; } @@ -216,9 +236,12 @@ public boolean isIE() { @Override public Map cookies() { - if (!initCookie && StringKit.isNotEmpty(cookieString)) { + if (!initCookie) { initCookie = true; - ServerCookieDecoder.LAX.decode(cookieString).forEach(this::parseCookie); + String cookie = header(HttpConst.COOKIE_STRING); + if (StringKit.isNotEmpty(cookie)) { + ServerCookieDecoder.LAX.decode(cookie).forEach(this::parseCookie); + } } return this.cookies; } @@ -236,6 +259,14 @@ public Request cookie(@NonNull Cookie cookie) { @Override public Map headers() { + if (null == headers) { + headers = new HashMap<>(httpHeaders.size()); + Iterator> entryIterator = httpHeaders.iteratorAsString(); + while (entryIterator.hasNext()) { + Map.Entry next = entryIterator.next(); + headers.put(next.getKey(), next.getValue()); + } + } return this.headers; } @@ -262,182 +293,114 @@ public ByteBuf body() { return this.body; } - @Override - public boolean readChunk() { - LocalContext localContext = HttpServerHandler.getLocalContext(); - if (null == localContext) { - throw new InternalErrorException("It is impossible to run here"); - } - - HttpObject msg = localContext.msg(); - localContext.updateMsg(msg); - - if (msg instanceof LastHttpContent) { - this.isEnd = true; - - if (!localContext.request().isMultipart) { - this.body = ((HttpContent) msg).copy().content(); - } - } - - if (localContext.hasDecoder() && msg instanceof HttpContent) { - // New chunk is received - HttpContent chunk = (HttpContent) msg; - localContext.decoder().offer(chunk); - readHttpDataChunkByChunk(localContext.decoder()); - } - - return this.isEnd; - } - @Override public boolean chunkIsEnd() { return this.isEnd; } @Override - public boolean isPart() { - return isRequestPart; + public boolean isMultipart() { + return isMultipart; } - @Override - public boolean isChunked() { - return isChunked; + public void setNettyRequest(io.netty.handler.codec.http.HttpRequest nettyRequest) { + this.nettyRequest = nettyRequest; } - public static HttpRequest build(String remoteAddress, HttpObject msg) { - boolean isRequestPart = false; - - io.netty.handler.codec.http.HttpRequest nettyRequest = null; - if (msg instanceof io.netty.handler.codec.http.HttpRequest) { - isRequestPart = true; - nettyRequest = (io.netty.handler.codec.http.HttpRequest) msg; + public void appendContent(HttpContent msg) { + this.contents.add(msg.retain()); + if (msg instanceof LastHttpContent) { + this.isEnd = true; } + } - LocalContext localContext = HttpServerHandler.getLocalContext(); - if (null != localContext) { - HttpRequest request = localContext.request(); - request.isRequestPart = isRequestPart; + public void init(String remoteAddress) { + this.remoteAddress = remoteAddress.substring(1); + this.keepAlive = HttpUtil.isKeepAlive(nettyRequest); + this.url = nettyRequest.uri(); - localContext.updateMsg(msg); - return request; - } + int pathEndPos = this.url().indexOf('?'); + this.uri = pathEndPos < 0 ? this.url() : this.url().substring(0, pathEndPos); + this.protocol = nettyRequest.protocolVersion().text(); + this.method = nettyRequest.method().name(); - if (!isRequestPart) { - return null; + String cleanUri = this.uri; + if (!"/".equals(this.contextPath())) { + cleanUri = PathKit.cleanPath(cleanUri.replaceFirst(this.contextPath(), "/")); + this.uri = cleanUri; } - HttpRequest request = new HttpRequest(); - request.isRequestPart = true; - request.keepAlive = HttpUtil.isKeepAlive(nettyRequest); - request.remoteAddress = remoteAddress; - request.url = nettyRequest.uri(); - request.isChunked = HttpUtil.isTransferEncodingChunked(nettyRequest); - - int pathEndPos = request.url().indexOf('?'); - request.uri = pathEndPos < 0 ? request.url() : request.url().substring(0, pathEndPos); - request.protocol = nettyRequest.protocolVersion().text(); - request.method = nettyRequest.method().name(); - - HttpPostRequestDecoder decoder = initRequest(request, nettyRequest); - - String cleanUri = request.uri; - if (!"/".equals(request.contextPath())) { - cleanUri = PathKit.cleanPath(cleanUri.replaceFirst(request.contextPath(), "/")); - request.uri = cleanUri; - } + this.httpHeaders = nettyRequest.headers(); if (!HttpServerHandler.PERFORMANCE) { SessionManager sessionManager = WebContext.blade().sessionManager(); if (null != sessionManager) { - request.session = SESSION_HANDLER.createSession(request); + this.session = SESSION_HANDLER.createSession(this); } } - HttpServerHandler.setLocalContext(new LocalContext(msg, request, decoder)); - return request; - } - - private static HttpPostRequestDecoder initRequest( - HttpRequest request, - io.netty.handler.codec.http.HttpRequest nettyRequest) { - - // headers - var httpHeaders = nettyRequest.headers(); - if (httpHeaders.isEmpty()) { - request.headers = new HashMap<>(); - } else { - request.headers = new HashMap<>(httpHeaders.size()); - - Iterator> entryIterator = httpHeaders.iteratorAsString(); - while (entryIterator.hasNext()) { - Map.Entry next = entryIterator.next(); - request.headers.put(next.getKey(), next.getValue()); - } - } - - // request query parameters - if (request.url().contains("?")) { - var parameters = new QueryStringDecoder(request.url(), CharsetUtil.UTF_8) - .parameters(); - - if (null != parameters) { - request.parameters.putAll(parameters); - } - } - - // cookies - request.cookieString = request.header(HttpConst.COOKIE_STRING); - - if ("GET".equals(request.method())) { - return null; + if ("GET".equals(this.method())) { + return; } try { - HttpPostRequestDecoder decoder = - new HttpPostRequestDecoder(factory, nettyRequest); - - request.isMultipart = decoder.isMultipart(); - return decoder; + HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, nettyRequest); + this.isMultipart = decoder.isMultipart(); + for (HttpContent content : this.contents) { + if (!isMultipart && content instanceof LastHttpContent) { + this.body = content.duplicate().content().copy(); + } + decoder.offer(content); + this.readHttpDataChunkByChunk(decoder); + content.release(); + } } catch (Exception e) { throw new HttpParseException("build decoder fail", e); } } /** - * Reading request by chunk and getting values from chunk + * Example of reading request by chunk and getting values from chunk to chunk */ private void readHttpDataChunkByChunk(HttpPostRequestDecoder decoder) { try { while (decoder.hasNext()) { InterfaceHttpData data = decoder.next(); if (data != null) { - parseData(data); + // check if current HttpData is a FileUpload and previously set as partial + if (partialContent == data) { + partialContent = null; + } + try { + // new value + writeHttpData(data); + } finally { + data.release(); + } } } - } catch (HttpPostRequestDecoder.EndOfDataDecoderException e) { - // ignore - } catch (Exception e) { - throw new HttpParseException(e); + // Check partial decoding for a FileUpload + InterfaceHttpData data = decoder.currentPartialHttpData(); + if (data != null) { + if (partialContent == null) { + partialContent = (HttpData) data; + } + } + } catch (HttpPostRequestDecoder.EndOfDataDecoderException e1) { + // end } } - private void parseData(InterfaceHttpData data) { + private void writeHttpData(InterfaceHttpData data) { try { - switch (data.getHttpDataType()) { - case Attribute: - this.parseAttribute((Attribute) data); - break; - case FileUpload: - this.parseFileUpload((FileUpload) data); - break; - default: - break; + InterfaceHttpData.HttpDataType dataType = data.getHttpDataType(); + if (dataType == InterfaceHttpData.HttpDataType.Attribute) { + parseAttribute((Attribute) data); + } else if (dataType == InterfaceHttpData.HttpDataType.FileUpload) { + parseFileUpload((FileUpload) data); } } catch (IOException e) { log.error("Parse request parameter error", e); - } finally { - data.release(); } } @@ -502,4 +465,7 @@ private void parseCookie(io.netty.handler.codec.http.cookie.Cookie nettyCookie) this.cookies.put(cookie.name(), cookie); } + public HttpPostRequestDecoder getDecoder() { + return decoder; + } } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/HttpResponse.java b/src/main/java/com/blade/mvc/http/HttpResponse.java index 10d6567e0..548089909 100644 --- a/src/main/java/com/blade/mvc/http/HttpResponse.java +++ b/src/main/java/com/blade/mvc/http/HttpResponse.java @@ -27,7 +27,6 @@ public class HttpResponse implements Response { private Set cookies = new HashSet<>(); private int statusCode = 200; - private String contentType = HttpConst.CONTENT_TYPE_HTML; private Body body; @Override @@ -43,18 +42,17 @@ public Response status(int status) { @Override public Response contentType(@NonNull String contentType) { - this.contentType = contentType; + this.headers.put("Content-Type", contentType); return this; } @Override public String contentType() { - return null == this.contentType ? null : String.valueOf(this.contentType); + return this.headers.get("Content-Type"); } @Override public Map headers() { - this.headers.put(HttpConst.CONTENT_TYPE_STRING, this.contentType); return this.headers; } @@ -179,7 +177,6 @@ public ModelAndView modelAndView() { } public HttpResponse(Response response) { - this.contentType = response.contentType(); this.statusCode = response.statusCode(); if (null != response.headers()) { response.headers().forEach(this.headers::put); @@ -190,6 +187,7 @@ public HttpResponse(Response response) { } public HttpResponse() { + } @Override diff --git a/src/main/java/com/blade/mvc/http/RawBody.java b/src/main/java/com/blade/mvc/http/RawBody.java index 7740f84ec..9d40d45b9 100644 --- a/src/main/java/com/blade/mvc/http/RawBody.java +++ b/src/main/java/com/blade/mvc/http/RawBody.java @@ -25,8 +25,8 @@ public DefaultHttpResponse defaultHttpResponse() { } @Override - public void write(BodyWriter writer) { - writer.onRawBody(this); + public FullHttpResponse write(BodyWriter writer) { + return writer.onRawBody(this); } } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/Request.java b/src/main/java/com/blade/mvc/http/Request.java index 278c7928d..5c61ce63a 100644 --- a/src/main/java/com/blade/mvc/http/Request.java +++ b/src/main/java/com/blade/mvc/http/Request.java @@ -510,31 +510,13 @@ default Optional fileItem(@NonNull String name) { return Optional.ofNullable(fileItems().get(name)); } - /** - * @return return whether the HTTP header is currently being read - * @since 2.0.11 - */ - boolean isPart(); - - /** - * @return return current request is chunk content - * @since 2.0.11 - */ - boolean isChunked(); - /** * @return return whether Chunk content has been read * @since 2.0.11 */ boolean chunkIsEnd(); - /** - * Continue to read Chunk content, return after reading or not - * - * @return return whether Chunk content has been read - * @since 2.0.11 - */ - boolean readChunk(); + boolean isMultipart(); /** * Get current request body as ByteBuf diff --git a/src/main/java/com/blade/mvc/http/Response.java b/src/main/java/com/blade/mvc/http/Response.java index 41ae302b8..c4902d920 100644 --- a/src/main/java/com/blade/mvc/http/Response.java +++ b/src/main/java/com/blade/mvc/http/Response.java @@ -169,7 +169,9 @@ default Response notFound() { */ default void text(String text) { if (null == text) return; - this.contentType(Const.CONTENT_TYPE_TEXT); + if (null == contentType()) { + this.contentType(Const.CONTENT_TYPE_TEXT); + } this.body(text); } @@ -180,7 +182,9 @@ default void text(String text) { */ default void html(String html) { if (null == html) return; - this.contentType(Const.CONTENT_TYPE_HTML); + if (null == contentType()) { + this.contentType(Const.CONTENT_TYPE_HTML); + } this.body(html); } @@ -191,10 +195,12 @@ default void html(String html) { */ default void json(String json) { if (null == json) return; - if (Objects.requireNonNull(WebContext.request()).isIE()) { - this.contentType(Const.CONTENT_TYPE_HTML); - } else { - this.contentType(Const.CONTENT_TYPE_JSON); + if (null == contentType()) { + if (Objects.requireNonNull(WebContext.request()).isIE()) { + this.contentType(Const.CONTENT_TYPE_HTML); + } else { + this.contentType(Const.CONTENT_TYPE_JSON); + } } this.body(json); } diff --git a/src/main/java/com/blade/mvc/http/StreamBody.java b/src/main/java/com/blade/mvc/http/StreamBody.java index c4b99893d..939613011 100644 --- a/src/main/java/com/blade/mvc/http/StreamBody.java +++ b/src/main/java/com/blade/mvc/http/StreamBody.java @@ -1,5 +1,7 @@ package com.blade.mvc.http; +import io.netty.handler.codec.http.FullHttpResponse; + import java.io.InputStream; public class StreamBody implements Body { @@ -15,8 +17,8 @@ public static StreamBody of(InputStream inputStream){ } @Override - public void write(BodyWriter writer) { + public FullHttpResponse write(BodyWriter writer) { // writer.onStream(content); - writer.onByteBuf(content); + return writer.onByteBuf(content); } } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/StringBody.java b/src/main/java/com/blade/mvc/http/StringBody.java index 691f578a5..c8c4ecc23 100644 --- a/src/main/java/com/blade/mvc/http/StringBody.java +++ b/src/main/java/com/blade/mvc/http/StringBody.java @@ -1,24 +1,25 @@ package com.blade.mvc.http; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.http.FullHttpResponse; import java.nio.charset.StandardCharsets; public class StringBody implements Body { - private final String content; + private final byte[] bytes; public StringBody(final String content) { - this.content = content; + this.bytes = content.getBytes(StandardCharsets.UTF_8); } - public static StringBody of(String content){ + public static StringBody of(String content) { return new StringBody(content); } @Override - public void write(BodyWriter writer) { - writer.onByteBuf(Unpooled.wrappedBuffer(this.content.getBytes(StandardCharsets.UTF_8))); + public FullHttpResponse write(BodyWriter writer) { + return writer.onByteBuf(Unpooled.copiedBuffer(bytes)); } } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/ViewBody.java b/src/main/java/com/blade/mvc/http/ViewBody.java index 9a1a61c20..583cc0f14 100644 --- a/src/main/java/com/blade/mvc/http/ViewBody.java +++ b/src/main/java/com/blade/mvc/http/ViewBody.java @@ -1,7 +1,10 @@ package com.blade.mvc.http; import com.blade.mvc.ui.ModelAndView; +import io.netty.handler.codec.http.FullHttpResponse; +import lombok.extern.slf4j.Slf4j; +@Slf4j public class ViewBody implements Body { private final ModelAndView modelAndView; @@ -10,12 +13,17 @@ public ViewBody(ModelAndView modelAndView) { this.modelAndView = modelAndView; } + public static ViewBody of(ModelAndView modelAndView){ + return new ViewBody(modelAndView); + } + public ModelAndView modelAndView() { return modelAndView; } @Override - public void write(BodyWriter writer) { - writer.onView(this); + public FullHttpResponse write(BodyWriter writer) { + return writer.onView(this); } + } \ No newline at end of file diff --git a/src/main/java/com/blade/mvc/http/session/SessionCleaner.java b/src/main/java/com/blade/mvc/http/session/SessionCleaner.java index 3f6ea0b57..2fcb923a1 100644 --- a/src/main/java/com/blade/mvc/http/session/SessionCleaner.java +++ b/src/main/java/com/blade/mvc/http/session/SessionCleaner.java @@ -5,7 +5,6 @@ import java.time.Instant; import java.util.Collection; -import java.util.concurrent.TimeUnit; /** * Session cleaner @@ -24,20 +23,11 @@ public SessionCleaner(SessionManager sessionManager) { @Override public void run() { - while (true) { - try { - Collection sessions = sessionManager.sessionMap().values(); - sessions.parallelStream().filter(this::expires).forEach(sessionManager::destorySession); - } catch (Exception e) { - log.error("Session clean error", e); - } finally { - try { - TimeUnit.MILLISECONDS.sleep(500); - } catch (InterruptedException e) { - log.error("Session cleaner interrupted", e); - } - } - + try { + Collection sessions = sessionManager.sessionMap().values(); + sessions.parallelStream().filter(this::expires).forEach(sessionManager::destorySession); + } catch (Exception e) { + log.error("Session clean error", e); } } diff --git a/src/main/java/com/blade/server/netty/HttpConst.java b/src/main/java/com/blade/server/netty/HttpConst.java index 2ac46985b..c33fc5896 100644 --- a/src/main/java/com/blade/server/netty/HttpConst.java +++ b/src/main/java/com/blade/server/netty/HttpConst.java @@ -3,6 +3,9 @@ import com.blade.mvc.Const; import io.netty.util.AsciiString; +import java.util.HashMap; +import java.util.Map; + /** * Http headers const * @@ -10,33 +13,39 @@ * @date 2017/10/16 */ public interface HttpConst { + + String VERSION = "blade-" + Const.VERSION; + String IF_MODIFIED_SINCE = "If-Modified-Since"; String USER_AGENT = "User-Agent"; String CONTENT_TYPE_STRING = "Content-Type"; String ACCEPT_ENCODING = "Accept-Encoding"; String COOKIE_STRING = "Cookie"; String METHOD_GET = "GET"; - String METHOD_POST = "POST"; String DEFAULT_SESSION_KEY = "SESSION"; String SLASH = "/"; char CHAR_SLASH = '/'; char CHAR_POINT = '.'; - CharSequence CONNECTION = AsciiString.cached("Connection"); - CharSequence CONTENT_LENGTH = AsciiString.cached("Content-Length"); - CharSequence CONTENT_TYPE = AsciiString.cached("Content-Type"); - CharSequence CONTENT_ENCODING = AsciiString.cached("Content-Encoding"); - CharSequence DATE = AsciiString.cached("Date"); - CharSequence LOCATION = AsciiString.cached("Location"); - CharSequence EXPIRES = AsciiString.cached("Expires"); - CharSequence CACHE_CONTROL = AsciiString.cached("Cache-Control"); - CharSequence LAST_MODIFIED = AsciiString.cached("Last-Modified"); - CharSequence SERVER = AsciiString.cached("Server"); - CharSequence SET_COOKIE = AsciiString.cached("Set-Cookie"); - CharSequence KEEP_ALIVE = AsciiString.cached("keep-alive"); - - String CONTENT_TYPE_HTML = "text/html; charset=UTF-8"; - - String VERSION = "blade-" + Const.VERSION; + AsciiString CONNECTION = AsciiString.cached("Connection"); + AsciiString CONTENT_LENGTH = AsciiString.cached("Content-Length"); + AsciiString CONTENT_TYPE = AsciiString.cached("Content-Type"); + AsciiString CONTENT_ENCODING = AsciiString.cached("Content-Encoding"); + AsciiString DATE = AsciiString.cached("Date"); + AsciiString LOCATION = AsciiString.cached("Location"); + AsciiString EXPIRES = AsciiString.cached("Expires"); + AsciiString CACHE_CONTROL = AsciiString.cached("Cache-Control"); + AsciiString LAST_MODIFIED = AsciiString.cached("Last-Modified"); + AsciiString SERVER = AsciiString.cached("Server"); + AsciiString SET_COOKIE = AsciiString.cached("Set-Cookie"); + AsciiString KEEP_ALIVE = AsciiString.cached("keep-alive"); + AsciiString X_POWER_BY = AsciiString.cached("X-Powered-By"); + AsciiString HEADER_VERSION = AsciiString.cached(VERSION); + + Map CACHE = new HashMap<>(16); + + static AsciiString getAsciiString(String name) { + return CACHE.computeIfAbsent(name, AsciiString::cached); + } } diff --git a/src/main/java/com/blade/server/netty/HttpServerHandler.java b/src/main/java/com/blade/server/netty/HttpServerHandler.java index 302d48ef6..543100d9b 100644 --- a/src/main/java/com/blade/server/netty/HttpServerHandler.java +++ b/src/main/java/com/blade/server/netty/HttpServerHandler.java @@ -15,8 +15,9 @@ */ package com.blade.server.netty; +import com.blade.exception.BladeException; import com.blade.exception.NotFoundException; -import com.blade.mvc.LocalContext; +import com.blade.kit.BladeCache; import com.blade.mvc.WebContext; import com.blade.mvc.handler.ExceptionHandler; import com.blade.mvc.http.HttpRequest; @@ -31,20 +32,19 @@ import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.util.concurrent.FastThreadLocal; import lombok.extern.slf4j.Slf4j; +import java.time.Instant; import java.util.HashSet; import java.util.Optional; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; +import java.util.concurrent.Executor; -import static com.blade.mvc.Const.ENV_KEY_HTTP_REQUEST_COST; -import static com.blade.mvc.Const.ENV_KEY_PERFORMANCE; +import static com.blade.kit.BladeKit.*; +import static com.blade.mvc.Const.*; import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; /** @@ -55,31 +55,22 @@ */ @Slf4j @ChannelHandler.Sharable -public class HttpServerHandler extends SimpleChannelInboundHandler { +public class HttpServerHandler extends SimpleChannelInboundHandler { - public static final FastThreadLocal WEB_CONTEXT_THREAD_LOCAL = new FastThreadLocal<>(); - private static final FastThreadLocal LOCAL_CONTEXT_THREAD_LOCAL = new FastThreadLocal<>(); - private static final StaticFileHandler STATIC_FILE_HANDLER = new StaticFileHandler(WebContext.blade()); - private static final RouteMethodHandler ROUTE_METHOD_HANDLER = new RouteMethodHandler(); - private static final Set NOT_STATIC_URI = new HashSet<>(32); - private static final RouteMatcher ROUTE_MATCHER = WebContext.blade().routeMatcher(); + public static final FastThreadLocal WEB_CONTEXT_THREAD_LOCAL = new FastThreadLocal<>(); - static final boolean ALLOW_COST = - WebContext.blade().environment().getBoolean(ENV_KEY_HTTP_REQUEST_COST, true); + private final boolean ALLOW_COST = + WebContext.blade().environment() + .getBoolean(ENV_KEY_HTTP_REQUEST_COST, true); public static final boolean PERFORMANCE = - WebContext.blade().environment().getBoolean(ENV_KEY_PERFORMANCE, false); + WebContext.blade().environment() + .getBoolean(ENV_KEY_PERFORMANCE, false); - private static final ExecutorService LOGIC_EXECUTOR = - Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2); - - @Override - public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { - super.channelUnregistered(ctx); - if (LOCAL_CONTEXT_THREAD_LOCAL.get() != null && LOCAL_CONTEXT_THREAD_LOCAL.get().hasDecoder()) { - LOCAL_CONTEXT_THREAD_LOCAL.get().decoder().cleanFiles(); - } - } + private final StaticFileHandler staticFileHandler = new StaticFileHandler(WebContext.blade()); + private final RouteMethodHandler routeHandler = new RouteMethodHandler(); + private final Set notStaticUri = new HashSet<>(32); + private final RouteMatcher routeMatcher = WebContext.blade().routeMatcher(); @Override public void channelReadComplete(ChannelHandlerContext ctx) { @@ -87,62 +78,99 @@ public void channelReadComplete(ChannelHandlerContext ctx) { } @Override - protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) { - String remoteAddress = ctx.channel().remoteAddress().toString(); - Request request = HttpRequest.build(remoteAddress, msg); - if (null == request) { - return; - } - if (request.isPart()) { - this.executePart(ctx, request); - return; - } + protected void channelRead0(ChannelHandlerContext ctx, HttpRequest httpRequest) { + CompletableFuture future = CompletableFuture.completedFuture(httpRequest); - // content has not been read yet - if (!request.chunkIsEnd() && - !request.readChunk()) { - return; - } + Executor executor = ctx.executor(); - try { - LogicRunner asyncRunner = new LogicRunner(ROUTE_METHOD_HANDLER, WebContext.get()); + future.thenApplyAsync(req -> buildWebContext(ctx, req), executor) + .thenApplyAsync(this::executeLogic, executor) + .thenApplyAsync(this::buildResponse, executor) + .exceptionally(this::handleException) + .thenAcceptAsync(msg -> writeResponse(ctx, future, msg), ctx.channel().eventLoop()); + } - CompletableFuture future = CompletableFuture.completedFuture(asyncRunner) - .thenApplyAsync(LogicRunner::handle, LOGIC_EXECUTOR) - .thenAcceptAsync(LogicRunner::finishWrite, LOGIC_EXECUTOR); + private WebContext buildWebContext(ChannelHandlerContext ctx, + HttpRequest req) { - asyncRunner.setFuture(future); - } finally { - this.cleanContext(); - } + String remoteAddress = ctx.channel().remoteAddress().toString(); + req.init(remoteAddress); + return WebContext.create(req, new HttpResponse(), ctx); + } + + private void writeResponse(ChannelHandlerContext ctx, CompletableFuture future, FullHttpResponse msg) { + ctx.writeAndFlush(msg); + future.complete(null); } - private void executePart(ChannelHandlerContext ctx, Request request) { - Response response = new HttpResponse(); + private FullHttpResponse handleException(Throwable e) { + Request request = WebContext.request(); + Response response = WebContext.response(); + String method = request.method(); + String uri = request.uri(); + + Exception srcException = (Exception) e.getCause().getCause(); + if (srcException instanceof BladeException) { + } else { + log500(log, method, uri); + } + if (null != WebContext.blade().exceptionHandler()) { + WebContext.blade().exceptionHandler().handle(srcException); + } else { + log.error("", srcException); + } - // init web context - WebContext webContext = WebContext - .create(request, response, ctx, LOCAL_CONTEXT_THREAD_LOCAL.get()); + return routeHandler.handleResponse( + request, response, WebContext.get().getChannelHandlerContext() + ); + } - String uri = request.uri(); - String method = request.method(); + private FullHttpResponse buildResponse(WebContext webContext) { + WebContext.set(webContext); + return routeHandler.handleResponse( + webContext.getRequest(), webContext.getResponse(), + webContext.getChannelHandlerContext() + ); + } + private WebContext executeLogic(WebContext webContext) { try { + WebContext.set(webContext); + Request request = webContext.getRequest(); + String method = request.method(); + String uri = request.uri(); + Instant start = null; + + if (ALLOW_COST && !PERFORMANCE) { + start = Instant.now(); + } + if (isStaticFile(method, uri)) { - STATIC_FILE_HANDLER.handle(webContext); - this.cleanContext(); + staticFileHandler.handle(webContext); } else { - Route route = ROUTE_MATCHER.lookupRoute(method, uri); + Route route = routeMatcher.lookupRoute(method, uri); if (null != route) { webContext.setRoute(route); } else { throw new NotFoundException(uri); } + + routeHandler.handle(webContext); + + if (PERFORMANCE) { + return webContext; + } + + if (ALLOW_COST) { + long cost = log200AndCost(log, start, BladeCache.getPaddingMethod(method), uri); + request.attribute(REQUEST_COST_TIME, cost); + } else { + log200(log, BladeCache.getPaddingMethod(method), uri); + } } + return webContext; } catch (Exception e) { - ROUTE_METHOD_HANDLER.exceptionCaught(uri, method, e); - ROUTE_METHOD_HANDLER.finishWrite(webContext); - this.cleanContext(); + throw BladeException.wrapper(e); } } @@ -156,28 +184,18 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { } private boolean isStaticFile(String method, String uri) { - if ("POST".equals(method) || NOT_STATIC_URI.contains(uri)) { + if ("POST".equals(method) || notStaticUri.contains(uri)) { return false; } - Optional result = WebContext.blade().getStatics().stream().filter(s -> s.equals(uri) || uri.startsWith(s)).findFirst(); + + Optional result = WebContext.blade().getStatics().stream() + .filter(s -> s.equals(uri) || uri.startsWith(s)).findFirst(); + if (!result.isPresent()) { - NOT_STATIC_URI.add(uri); + notStaticUri.add(uri); return false; } return true; } - public static LocalContext getLocalContext() { - return LOCAL_CONTEXT_THREAD_LOCAL.get(); - } - - public static void setLocalContext(LocalContext localContext) { - LOCAL_CONTEXT_THREAD_LOCAL.set(localContext); - } - - private void cleanContext() { - LOCAL_CONTEXT_THREAD_LOCAL.remove(); - WEB_CONTEXT_THREAD_LOCAL.remove(); - } - } \ No newline at end of file diff --git a/src/main/java/com/blade/server/netty/HttpServerInitializer.java b/src/main/java/com/blade/server/netty/HttpServerInitializer.java index 72090c060..0f06ff353 100644 --- a/src/main/java/com/blade/server/netty/HttpServerInitializer.java +++ b/src/main/java/com/blade/server/netty/HttpServerInitializer.java @@ -8,14 +8,12 @@ import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpContentCompressor; -import io.netty.handler.codec.http.HttpRequestDecoder; -import io.netty.handler.codec.http.HttpResponseEncoder; +import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpServerExpectContinueHandler; import io.netty.handler.codec.http.cors.CorsConfigBuilder; import io.netty.handler.codec.http.cors.CorsHandler; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.ssl.SslContext; -import io.netty.handler.stream.ChunkedWriteHandler; import lombok.extern.slf4j.Slf4j; import java.time.LocalDateTime; @@ -64,16 +62,13 @@ protected void initChannel(SocketChannel ch) { pipeline.addLast(sslCtx.newHandler(ch.alloc())); } - pipeline.addLast(new HttpRequestDecoder()); - pipeline.addLast(new HttpResponseEncoder()); + pipeline.addLast(new HttpServerCodec()); + pipeline.addLast(new HttpServerExpectContinueHandler()); if (useGZIP) { pipeline.addLast(new HttpContentCompressor()); } - pipeline.addLast(new ChunkedWriteHandler()); - pipeline.addLast(new HttpServerExpectContinueHandler()); - if (enableCors) { pipeline.addLast(new CorsHandler(CorsConfigBuilder.forAnyOrigin().allowNullOrigin().allowCredentials().build())); } @@ -82,6 +77,7 @@ protected void initChannel(SocketChannel ch) { pipeline.addLast(new WebSocketServerProtocolHandler(blade.webSocketPath(), null, true)); pipeline.addLast(WEB_SOCKET_HANDLER); } + pipeline.addLast(new MergeRequestHandler()); pipeline.addLast(httpServerHandler); } catch (Exception e) { log.error("Add channel pipeline error", e); diff --git a/src/main/java/com/blade/server/netty/IoMultiplexer.java b/src/main/java/com/blade/server/netty/IoMultiplexer.java deleted file mode 100644 index 02c350815..000000000 --- a/src/main/java/com/blade/server/netty/IoMultiplexer.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.blade.server.netty; - -public enum IoMultiplexer { - EPOLL, KQUEUE, JDK -} \ No newline at end of file diff --git a/src/main/java/com/blade/server/netty/LogicRunner.java b/src/main/java/com/blade/server/netty/LogicRunner.java deleted file mode 100644 index 015f05508..000000000 --- a/src/main/java/com/blade/server/netty/LogicRunner.java +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Copyright (c) 2018, biezhi 王爵 nice (biezhi.me@gmail.com) - *

- * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - *

- * http://www.apache.org/licenses/LICENSE-2.0 - *

- * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.blade.server.netty; - -import com.blade.kit.BladeCache; -import com.blade.mvc.WebContext; -import com.blade.mvc.http.Request; -import lombok.extern.slf4j.Slf4j; - -import java.time.Instant; -import java.util.concurrent.CompletableFuture; - -import static com.blade.kit.BladeKit.log200; -import static com.blade.kit.BladeKit.log200AndCost; -import static com.blade.mvc.Const.REQUEST_COST_TIME; - -/** - * Http Logic Runner - * - * @author biezhi - * @date 2018/10/15 - */ -@Slf4j -public class LogicRunner { - - private CompletableFuture future; - private WebContext webContext; - private Instant started; - private RouteMethodHandler routeHandler; - private boolean isFinished; - - public LogicRunner(RouteMethodHandler routeHandler, WebContext webContext) { - this.routeHandler = routeHandler; - this.webContext = webContext; - if (!HttpServerHandler.PERFORMANCE && HttpServerHandler.ALLOW_COST) { - this.started = Instant.now(); - } - } - - /** - * Routing logic execution - */ - public LogicRunner handle() { - WebContext.set(webContext); - Request request = webContext.getRequest(); - String uri = request.uri(); - String method = request.method(); - try { - routeHandler.handle(webContext); - - if (HttpServerHandler.PERFORMANCE) { - return this; - } - - if (HttpServerHandler.ALLOW_COST) { - long cost = log200AndCost(log, this.started, BladeCache.getPaddingMethod(method), uri); - request.attribute(REQUEST_COST_TIME, cost); - } else { - log200(log, BladeCache.getPaddingMethod(method), uri); - } - } catch (Exception e) { - routeHandler.exceptionCaught(uri, method, e); - } - return this; - } - - public void finishWrite() { - WebContext.set(webContext); - routeHandler.finishWrite(webContext); - WebContext.remove(); - isFinished = true; - if (null != future) { - future.complete(null); - } - } - - public void setFuture(CompletableFuture future) { - this.future = future; - if (isFinished) { - future.complete(null); - } - } - -} diff --git a/src/main/java/com/blade/server/netty/MergeRequestHandler.java b/src/main/java/com/blade/server/netty/MergeRequestHandler.java new file mode 100644 index 000000000..ddb61ab06 --- /dev/null +++ b/src/main/java/com/blade/server/netty/MergeRequestHandler.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2018, biezhi 王爵 nice (biezhi.me@gmail.com) + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.blade.server.netty; + +import com.blade.mvc.handler.ExceptionHandler; +import com.blade.mvc.http.HttpRequest; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.codec.http.*; +import lombok.extern.slf4j.Slf4j; + +import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; + +/** + * Merge Netty HttpObject as {@link HttpRequest} + * + * @author biezhi + * 2018/10/15 + */ +@Slf4j +public class MergeRequestHandler extends SimpleChannelInboundHandler { + + private HttpRequest httpRequest; + + @Override + protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) { + if (msg instanceof io.netty.handler.codec.http.HttpRequest) { + httpRequest = new HttpRequest(); + httpRequest.setNettyRequest((io.netty.handler.codec.http.HttpRequest) msg); + return; + } + if (msg instanceof HttpContent) { + httpRequest.appendContent((HttpContent) msg); + } + if (msg instanceof LastHttpContent) { + ctx.fireChannelRead(httpRequest); + } + } + + @Override + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { + if (!ExceptionHandler.isResetByPeer(cause)) { + log.error(cause.getMessage(), cause); + FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(500)); + ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); + } + } + +} \ No newline at end of file diff --git a/src/main/java/com/blade/server/netty/NettyServer.java b/src/main/java/com/blade/server/netty/NettyServer.java index 19f640f48..11df9e543 100644 --- a/src/main/java/com/blade/server/netty/NettyServer.java +++ b/src/main/java/com/blade/server/netty/NettyServer.java @@ -51,14 +51,11 @@ import com.blade.task.cron.CronThreadPoolExecutor; import com.blade.watcher.EnvironmentWatcher; import io.netty.bootstrap.ServerBootstrap; -import io.netty.channel.Channel; -import io.netty.channel.ChannelOption; -import io.netty.channel.EventLoopGroup; +import io.netty.channel.*; import io.netty.channel.epoll.EpollChannelOption; +import io.netty.channel.nio.NioEventLoop; 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.util.ResourceLeakDetector; @@ -67,9 +64,11 @@ import java.io.File; import java.lang.reflect.Method; +import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Stream; @@ -89,6 +88,7 @@ public class NettyServer implements Server { private Blade blade; private Environment environment; private EventLoopGroup bossGroup; + private EventLoop scheduleEventLoop; private EventLoopGroup workerGroup; private Channel channel; private RouteBuilder routeBuilder; @@ -96,8 +96,6 @@ public class NettyServer implements Server { private List loaders; private List taskStruts = new ArrayList<>(); - private final int padSize = 26; - private volatile boolean isStop; @Override @@ -108,12 +106,18 @@ public void start(Blade blade) throws Exception { this.loaders = blade.loaders(); long startMs = System.currentTimeMillis(); - log.info("{} {}{}", StringKit.padRight("environment.jdk.version", padSize), getPrefixSymbol(), System.getProperty("java.version")); - log.info("{} {}{}", StringKit.padRight("environment.user.dir", padSize), getPrefixSymbol(), System.getProperty("user.dir")); - log.info("{} {}{}", StringKit.padRight("environment.java.io.tmpdir", padSize), getPrefixSymbol(), System.getProperty("java.io.tmpdir")); - log.info("{} {}{}", StringKit.padRight("environment.user.timezone", padSize), getPrefixSymbol(), System.getProperty("user.timezone")); - log.info("{} {}{}", StringKit.padRight("environment.file.encoding", padSize), getPrefixSymbol(), System.getProperty("file.encoding")); - log.info("{} {}{}", StringKit.padRight("environment.classpath", padSize), getPrefixSymbol(), CLASSPATH); + + int padSize = 16; + log.info("{} {}{}", StringKit.padRight("app.env", padSize), getPrefixSymbol(), environment.get(ENV_KEY_APP_ENV, "default")); + log.info("{} {}{}", StringKit.padRight("app.pid", padSize), getPrefixSymbol(), BladeKit.getPID()); + log.info("{} {}{}", StringKit.padRight("app.devMode", padSize), getPrefixSymbol(), blade.devMode()); + + log.info("{} {}{}", StringKit.padRight("jdk.version", padSize), getPrefixSymbol(), System.getProperty("java.version")); + log.info("{} {}{}", StringKit.padRight("user.dir", padSize), getPrefixSymbol(), System.getProperty("user.dir")); + log.info("{} {}{}", StringKit.padRight("java.io.tmpdir", padSize), getPrefixSymbol(), System.getProperty("java.io.tmpdir")); + log.info("{} {}{}", StringKit.padRight("user.timezone", padSize), getPrefixSymbol(), System.getProperty("user.timezone")); + log.info("{} {}{}", StringKit.padRight("file.encoding", padSize), getPrefixSymbol(), System.getProperty("file.encoding")); + log.info("{} {}{}", StringKit.padRight("app.classpath", padSize), getPrefixSymbol(), CLASSPATH); this.initConfig(); @@ -130,9 +134,9 @@ public void start(Blade blade) throws Exception { private void sessionCleaner() { if (null != blade.sessionManager()) { - Thread sessionCleanerThread = new Thread(new SessionCleaner(blade.sessionManager())); - sessionCleanerThread.setName("session-cleaner"); - sessionCleanerThread.start(); + scheduleEventLoop. + scheduleWithFixedDelay(new SessionCleaner(blade.sessionManager()), + 1000, 1000, TimeUnit.MILLISECONDS); } } @@ -191,13 +195,7 @@ private void startServer(long startMs) throws Exception { sslCtx = SslContextBuilder.forServer(new File(certFilePath), new File(privateKeyPath), privateKeyPassword).build(); } - // Configure the server. - int backlog = environment.getInt(ENV_KEY_NETTY_SO_BACKLOG, DEFAULT_SO_BACKLOG); - var bootstrap = new ServerBootstrap(); - bootstrap.option(ChannelOption.SO_BACKLOG, backlog); - bootstrap.option(ChannelOption.SO_REUSEADDR, true); - bootstrap.childOption(ChannelOption.SO_REUSEADDR, true); int acceptThreadCount = environment.getInt(ENC_KEY_NETTY_ACCEPT_THREAD_COUNT, DEFAULT_ACCEPT_THREAD_COUNT); int ioThreadCount = environment.getInt(ENV_KEY_NETTY_IO_THREAD_COUNT, DEFAULT_IO_THREAD_COUNT); @@ -215,12 +213,13 @@ private void startServer(long startMs) throws Exception { log.info("{}Use NioEventLoopGroup", getStartedSymbol()); this.bossGroup = new NioEventLoopGroup(acceptThreadCount, new NamedThreadFactory("boss@")); - this.workerGroup = new NioEventLoopGroup(ioThreadCount, new NamedThreadFactory("io@")); + this.workerGroup = new NioEventLoopGroup(ioThreadCount, new NamedThreadFactory("worker@")); bootstrap.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class); } - bootstrap.handler(new LoggingHandler(LogLevel.DEBUG)) - .childHandler(new HttpServerInitializer(sslCtx, blade, bossGroup.next())); + scheduleEventLoop = new DefaultEventLoop(); + + bootstrap.childHandler(new HttpServerInitializer(sslCtx, blade, scheduleEventLoop)); String address = environment.get(ENV_KEY_SERVER_ADDRESS, DEFAULT_SERVER_ADDRESS); Integer port = environment.getInt(ENV_KEY_SERVER_PORT, DEFAULT_SERVER_PORT); @@ -252,7 +251,7 @@ private void startTask() { } var jobCount = new AtomicInteger(); - for (var taskStruct: taskStruts) { + for (var taskStruct : taskStruts) { addTask(executorService, jobCount, taskStruct); } } @@ -326,10 +325,10 @@ private boolean isExceptionHandler(Class clazz) { } private void watchEnv() { - boolean watchEnv = environment.getBoolean(ENV_KEY_APP_WATCH_ENV, true); - log.info("{}Watched environment: {}", getStartedSymbol(), watchEnv, getStartedSymbol()); + boolean watchEnv = environment.getBoolean(ENV_KEY_APP_WATCH_ENV, false); if (watchEnv) { + log.info("{}Watched environment started", getStartedSymbol()); var thread = new Thread(new EnvironmentWatcher()); thread.setName("watch@thread"); thread.start(); diff --git a/src/main/java/com/blade/server/netty/RouteMethodHandler.java b/src/main/java/com/blade/server/netty/RouteMethodHandler.java index 2ec93b13e..552a40614 100644 --- a/src/main/java/com/blade/server/netty/RouteMethodHandler.java +++ b/src/main/java/com/blade/server/netty/RouteMethodHandler.java @@ -27,7 +27,6 @@ import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.*; -import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.stream.ChunkedStream; import lombok.extern.slf4j.Slf4j; import lombok.var; @@ -37,14 +36,10 @@ import java.io.StringWriter; import java.lang.reflect.Method; import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; -import static com.blade.kit.BladeKit.log404; import static com.blade.kit.BladeKit.log500; -import static com.blade.mvc.Const.ENV_KEY_SESSION_KEY; import static com.blade.server.netty.HttpConst.CONTENT_LENGTH; import static com.blade.server.netty.HttpConst.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpHeaderNames.TRANSFER_ENCODING; @@ -72,7 +67,6 @@ public void handle(WebContext webContext) throws Exception { String uri = context.uri(); Route route = webContext.getRoute(); if (null == route) { - log404(log, context.method(), context.uri()); throw new NotFoundException(context.uri()); } @@ -99,85 +93,52 @@ public void handle(WebContext webContext) throws Exception { } } - public void exceptionCaught(String uri, String method, Exception e) { - if (e instanceof BladeException) { - } else { - log500(log, method, uri); - } - if (null != WebContext.blade().exceptionHandler()) { - WebContext.blade().exceptionHandler().handle(e); - } else { - log.error("Request Exception", e); - } - } - - public void finishWrite(WebContext webContext) { - Request request = webContext.getRequest(); - Response response = webContext.getResponse(); - Session session = request.session(); - + public FullHttpResponse handleResponse(Request request, Response response, ChannelHandlerContext context) { + Session session = request.session(); if (null != session) { - String sessionKey = WebContext.blade().environment().get(ENV_KEY_SESSION_KEY, HttpConst.DEFAULT_SESSION_KEY); - Cookie cookie = new Cookie(); - cookie.name(sessionKey); + Cookie cookie = new Cookie(); + cookie.name(WebContext.sessionKey()); cookie.value(session.id()); cookie.httpOnly(true); cookie.secure(request.isSecure()); response.cookie(cookie); } - this.handleResponse(request, response, webContext.getChannelHandlerContext()); - } - public void handleResponse(Request request, Response response, ChannelHandlerContext context) { - response.body().write(new BodyWriter() { + FullHttpResponse fullHttpResponse = response.body().write(new BodyWriter() { @Override - public void onByteBuf(ByteBuf byteBuf) { - handleFullResponse( - createResponseByByteBuf(response, byteBuf), - context, - request.keepAlive()); + public FullHttpResponse onByteBuf(ByteBuf byteBuf) { + return createResponseByByteBuf(response, byteBuf); } @Override - public void onStream(Closeable closeable) { - if (closeable instanceof InputStream) { - handleStreamResponse(response, (InputStream) closeable, context, request.keepAlive()); - } + public FullHttpResponse onStream(Closeable closeable) { + // TODO + return null; } @Override - public void onView(ViewBody body) { + public FullHttpResponse onView(ViewBody body) { try { var sw = new StringWriter(); WebContext.blade().templateEngine().render(body.modelAndView(), sw); - response.contentType(Const.CONTENT_TYPE_HTML); - - handleFullResponse( - createTextResponse(response, sw.toString()), - context, - request.keepAlive()); + WebContext.response().contentType(Const.CONTENT_TYPE_HTML); + return this.onByteBuf(Unpooled.copiedBuffer(sw.toString().getBytes(StandardCharsets.UTF_8))); } catch (Exception e) { log.error("Render view error", e); } + return null; } @Override - public void onRawBody(RawBody body) { - if (null != body.httpResponse()) { - handleFullResponse(body.httpResponse(), context, request.keepAlive()); - } - if (null != body.defaultHttpResponse()) { - handleFullResponse(body.defaultHttpResponse(), context, request.keepAlive()); - } + public FullHttpResponse onRawBody(RawBody body) { + return body.httpResponse(); } @Override - public void onByteBuf(Object byteBuf) { + public FullHttpResponse onByteBuf(Object byteBuf) { var httpResponse = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(response.statusCode())); - Iterator> iterator = response.headers().entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry next = iterator.next(); + for (Map.Entry next : response.headers().entrySet()) { httpResponse.headers().set(next.getKey(), next.getValue()); } @@ -191,29 +152,19 @@ public void onByteBuf(Object byteBuf) { if (!request.keepAlive()) { lastContentFuture.addListener(ChannelFutureListener.CLOSE); } + return null; } }); - } - - private Void handleFullResponse(HttpResponse response, ChannelHandlerContext context, boolean keepAlive) { - if (context.channel().isActive()) { - if (!keepAlive) { - context.write(response).addListener(ChannelFutureListener.CLOSE); - } else { - response.headers().set(HttpConst.CONNECTION, KEEP_ALIVE); - context.write(response, context.voidPromise()); - } - context.flush(); + if (request.keepAlive()) { + fullHttpResponse.headers().set(HttpConst.CONNECTION, KEEP_ALIVE); } - return null; + return fullHttpResponse; } - public Map getDefaultHeader() { - var map = new HashMap(2); - map.put("Date", HttpServerInitializer.date); - map.put("X-Powered-By", HttpConst.VERSION); - return map; + private void setDefaultHeaders(HttpHeaders headers) { + headers.set(HttpConst.DATE, HttpServerInitializer.date); + headers.set(HttpConst.X_POWER_BY, HttpConst.HEADER_VERSION); } public Void handleStreamResponse(Response response, InputStream body, @@ -222,10 +173,9 @@ public Void handleStreamResponse(Response response, InputStream body, var httpResponse = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(response.statusCode())); httpResponse.headers().set(TRANSFER_ENCODING, HttpHeaderValues.CHUNKED); + setDefaultHeaders(httpResponse.headers()); - Iterator> iterator = response.headers().entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry next = iterator.next(); + for (Map.Entry next : response.headers().entrySet()) { httpResponse.headers().set(next.getKey(), next.getValue()); } @@ -239,55 +189,29 @@ public Void handleStreamResponse(Response response, InputStream body, return null; } - public FullHttpResponse createResponseByByteBuf(Response response, ByteBuf byteBuf) { + private FullHttpResponse createResponseByByteBuf(Response response, ByteBuf byteBuf) { Map headers = response.headers(); - headers.putAll(getDefaultHeader()); var httpResponse = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(response.statusCode()), byteBuf); httpResponse.headers().set(CONTENT_LENGTH, httpResponse.content().readableBytes()); + setDefaultHeaders(httpResponse.headers()); if (response.cookiesRaw().size() > 0) { this.appendCookie(response, httpResponse); } - Iterator> iterator = headers.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry next = iterator.next(); - httpResponse.headers().set(next.getKey(), next.getValue()); - } - return httpResponse; - } - - public FullHttpResponse createTextResponse(Response response, String body) { - Map headers = response.headers(); - headers.putAll(getDefaultHeader()); - - ByteBuf bodyBuf = Unpooled.wrappedBuffer(body.getBytes(StandardCharsets.UTF_8)); - - var httpResponse = new DefaultFullHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(response.statusCode()), - null == bodyBuf ? Unpooled.buffer(0) : bodyBuf); - - httpResponse.headers().set(CONTENT_LENGTH, httpResponse.content().readableBytes()); - - if (response.cookiesRaw().size() > 0) { - this.appendCookie(response, httpResponse); - } - - Iterator> iterator = headers.entrySet().iterator(); - while (iterator.hasNext()) { - Map.Entry next = iterator.next(); - httpResponse.headers().set(next.getKey(), next.getValue()); + for (Map.Entry next : headers.entrySet()) { + httpResponse.headers().set(HttpConst.getAsciiString(next.getKey()), next.getValue()); } return httpResponse; } private void appendCookie(Response response, DefaultFullHttpResponse httpResponse) { - Iterator iterator = response.cookiesRaw().iterator(); - while (iterator.hasNext()) { - io.netty.handler.codec.http.cookie.Cookie next = iterator.next(); - httpResponse.headers().add(HttpConst.SET_COOKIE, io.netty.handler.codec.http.cookie.ServerCookieEncoder.LAX.encode(next)); + for (io.netty.handler.codec.http.cookie.Cookie next : response.cookiesRaw()) { + httpResponse.headers().add(HttpConst.SET_COOKIE, + io.netty.handler.codec.http.cookie.ServerCookieEncoder.LAX.encode(next)); } } @@ -330,7 +254,11 @@ private void routeHandle(RouteContext context) { int len = actionMethod.getParameterTypes().length; MethodAccess methodAccess = BladeCache.getMethodAccess(target.getClass()); - Object returnParam = methodAccess.invoke(target, actionMethod.getName(), len > 0 ? context.routeParameters() : null); + + Object returnParam = methodAccess.invoke( + target, actionMethod.getName(), len > 0 ? + context.routeParameters() : null); + if (null == returnParam) { return; } @@ -340,11 +268,15 @@ private void routeHandle(RouteContext context) { return; } if (returnType == String.class) { - context.body(new ViewBody(new ModelAndView(returnParam.toString()))); + context.body( + ViewBody.of(new ModelAndView(returnParam.toString())) + ); return; } if (returnType == ModelAndView.class) { - context.body(new ViewBody((ModelAndView) returnParam)); + context.body( + ViewBody.of((ModelAndView) returnParam) + ); } } } @@ -377,7 +309,9 @@ private boolean invokeHook(RouteContext context, Route hookRoute) throws Excepti returnParam = methodAccess.invoke(target, hookMethod.getName(), context); } else if (len == 2) { MethodAccess methodAccess = BladeCache.getMethodAccess(target.getClass()); - returnParam = methodAccess.invoke(target, hookMethod.getName(), context.request(), context.response()); + + returnParam = methodAccess.invoke(target, hookMethod.getName(), + context.request(), context.response()); } else { throw new InternalErrorException("Bad web hook structure"); } @@ -394,7 +328,9 @@ private boolean invokeHook(RouteContext context, Route hookRoute) throws Excepti return true; } - private boolean invokeMiddleware(List middleware, RouteContext context) throws BladeException { + private boolean invokeMiddleware(List middleware, + RouteContext context) throws BladeException { + if (BladeKit.isEmpty(middleware)) { return true; } diff --git a/src/test/java/com/blade/BaseTestCase.java b/src/test/java/com/blade/BaseTestCase.java index 887147bb7..7e37ed87f 100644 --- a/src/test/java/com/blade/BaseTestCase.java +++ b/src/test/java/com/blade/BaseTestCase.java @@ -25,6 +25,8 @@ public class BaseTestCase { protected com.blade.mvc.http.HttpRequest mockHttpRequest(String methodName) { com.blade.mvc.http.HttpRequest request = mock(com.blade.mvc.http.HttpRequest.class); when(request.method()).thenReturn(methodName); + when(request.url()).thenReturn("/"); + when(request.uri()).thenReturn("/"); when(request.httpMethod()).thenReturn(HttpMethod.valueOf(methodName)); return request; } diff --git a/src/test/java/com/blade/EnvironmentTest.java b/src/test/java/com/blade/EnvironmentTest.java index 700a0f908..b0ce4592a 100644 --- a/src/test/java/com/blade/EnvironmentTest.java +++ b/src/test/java/com/blade/EnvironmentTest.java @@ -143,7 +143,7 @@ public void testGetValueOrNull() { public void testGetPrefix() { Environment environment = Environment.of("application.properties"); Map map = environment.getPrefix("app"); - assertEquals(6, map.size()); + assertEquals(7, map.size()); assertEquals("0.0.2", map.get("version")); } @@ -156,7 +156,7 @@ public void testHasKey() { } @Test - public void testHasValue(){ + public void testHasValue() { Environment environment = Environment.empty(); assertEquals(Boolean.FALSE, environment.hasValue("hello")); } @@ -194,7 +194,7 @@ public void testAddAll() { } @Test - public void testSet(){ + public void testSet() { Environment environment = Environment.empty(); environment.set("name", "biezhi"); diff --git a/src/test/java/com/blade/mvc/HttpRequestTest.java b/src/test/java/com/blade/mvc/HttpRequestTest.java index 5e3b3ed43..ef77734c7 100644 --- a/src/test/java/com/blade/mvc/HttpRequestTest.java +++ b/src/test/java/com/blade/mvc/HttpRequestTest.java @@ -296,6 +296,7 @@ public void testFileItems() { Map attr = new HashMap<>(); FileItem fileItem = new FileItem(); + fileItem.setName("file"); fileItem.setFileName("hello.png"); fileItem.setPath("/usr/hello.png"); fileItem.setContentType("image/png"); @@ -305,15 +306,17 @@ public void testFileItems() { when(mockRequest.fileItems()).thenReturn(attr); - Request request = new HttpRequest(mockRequest); - FileItem img = request.fileItem("img").get(); + Request request = new HttpRequest(mockRequest); + + FileItem img = request.fileItem("img").get(); assertNotNull(img); - assertNull(img.getData()); + assertNull(img.getFile()); - assertEquals("hello.png", img.getName()); - assertEquals("/usr/hello.png", img.getFileName()); + assertEquals("file", img.getName()); + assertEquals("hello.png", img.getFileName()); + assertEquals("/usr/hello.png", img.getPath()); assertEquals(Long.valueOf(20445), Optional.of(img.getLength()).get()); assertEquals("image/png", img.getContentType()); diff --git a/src/test/java/com/blade/mvc/HttpResponseTest.java b/src/test/java/com/blade/mvc/HttpResponseTest.java index 220e125e7..e1f94f38f 100644 --- a/src/test/java/com/blade/mvc/HttpResponseTest.java +++ b/src/test/java/com/blade/mvc/HttpResponseTest.java @@ -74,10 +74,10 @@ public void testHeaders() { when(mockResponse.headers()).thenReturn(new HashMap<>()); Response response = new HttpResponse(mockResponse); - assertEquals(1, response.headers().size()); + assertEquals(0, response.headers().size()); response.header("a", "123"); - assertEquals(2, response.headers().size()); + assertEquals(1, response.headers().size()); } @Test @@ -88,7 +88,7 @@ public void testHeader() { when(mockResponse.headers()).thenReturn(Collections.singletonMap("Server", "Nginx")); Response response = new HttpResponse(mockResponse); - assertEquals(2, response.headers().size()); + assertEquals(1, response.headers().size()); assertEquals("Nginx", response.headers().get("Server")); } diff --git a/src/test/java/com/blade/mvc/route/RouteMatcherDemoController.java b/src/test/java/com/blade/mvc/route/RouteMatcherDemoController.java new file mode 100644 index 000000000..cf85968c7 --- /dev/null +++ b/src/test/java/com/blade/mvc/route/RouteMatcherDemoController.java @@ -0,0 +1,9 @@ +package com.blade.mvc.route; + +public class RouteMatcherDemoController { + public void index() { + } + + public void remove() { + } +} \ No newline at end of file diff --git a/src/test/java/com/blade/mvc/route/RouteMatcherTest.java b/src/test/java/com/blade/mvc/route/RouteMatcherTest.java index d81fe8873..14a419921 100644 --- a/src/test/java/com/blade/mvc/route/RouteMatcherTest.java +++ b/src/test/java/com/blade/mvc/route/RouteMatcherTest.java @@ -83,12 +83,4 @@ public void testAddMultiParameter() throws Exception { } - class RouteMatcherDemoController { - public void index() { - } - - public void remove() { - } - } - } diff --git a/src/test/java/netty_hello/Hello.java b/src/test/java/netty_hello/Hello.java index 1580fe053..fc4b2b7c9 100644 --- a/src/test/java/netty_hello/Hello.java +++ b/src/test/java/netty_hello/Hello.java @@ -5,6 +5,7 @@ import com.blade.mvc.http.EmptyBody; import com.blade.mvc.http.ByteBody; import com.blade.mvc.http.StreamBody; +import com.blade.mvc.http.StringBody; import java.io.*; import java.util.Random; @@ -15,11 +16,10 @@ * 2017/6/5 */ public class Hello { + private static final StringBody hello = StringBody.of("Hello World."); public static void main(String[] args) { Blade.of() -// .devMode(false) -// .environment(Const.ENV_KEY_NETTY_WORKERS, Runtime.getRuntime().availableProcessors()) .get("/", ctx -> { String[] chars = new String[]{"Here a special char \" that not escaped", "And Another \\ char"}; ctx.json(chars); @@ -46,7 +46,11 @@ public static void main(String[] args) { ctx.response().contentType("text/html"); ctx.response().body(ByteBody.of(str.getBytes())); }) - .get("/hello", ctx -> ctx.text("Hello World.")) + .get("/error", ctx -> { + int a = 1 / 0; + ctx.text("ok"); + }) + .get("/hello", ctx -> ctx.body(hello)) .get("/error", ctx -> { int a = 1 / 0; ctx.text("Hello World."); @@ -64,9 +68,6 @@ public static void main(String[] args) { e.printStackTrace(); } }) -// .before("/*", ctx -> { -// System.out.println("Before..."); -// }) .get("/rand", ctx -> { try { int timeout = ctx.fromInt("timeout", new Random().nextInt(1000)); @@ -77,16 +78,10 @@ public static void main(String[] args) { } }) -// .use(new XssMiddleware()) -// .use(new CsrfMiddleware()) .event(EventType.ENVIRONMENT_CHANGED, new ConfigChanged()) .event(EventType.SESSION_DESTROY, e -> { System.out.println("session 失效了"); }) - .disableSession() -// .showFileList(true) -// .gzip(true) -// .enableCors(true) .start(Hello.class, args); } diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index 5d33599ee..d92e11aaa 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -7,9 +7,8 @@ app.maxMoney=38.1 app.sex=true app.hits=199283818033 app.startDate=2017-08-02 +app.performance=true list=hello,my,blade,word map=user:blade,version:2.0.0 -#com.blade.logger.rootLevel=error com.blade.logger.dir=./logs -com.blade.logger.console=true \ No newline at end of file diff --git a/src/test/resources/templates/upload.html b/src/test/resources/templates/upload.html index 982723d8e..93941e6e2 100644 --- a/src/test/resources/templates/upload.html +++ b/src/test/resources/templates/upload.html @@ -30,7 +30,6 @@

console.log(res); if(res.success){ alert('上传成功.'); - window.location.href = '/upload/'; } else { alert(res.msg || '上传失败'); }