├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src ├── main ├── java │ ├── com │ │ └── github │ │ │ └── netty │ │ │ ├── StartupServer.java │ │ │ ├── Version.java │ │ │ ├── core │ │ │ ├── AbstractChannelHandler.java │ │ │ ├── AbstractNettyServer.java │ │ │ ├── AbstractProtocol.java │ │ │ ├── AutoFlushChannelHandler.java │ │ │ ├── MessageToRunnable.java │ │ │ ├── Ordered.java │ │ │ ├── ProtocolHandler.java │ │ │ ├── ServerListener.java │ │ │ ├── TcpChannel.java │ │ │ └── util │ │ │ │ ├── AbortPolicyWithReport.java │ │ │ │ ├── AntPathMatcher.java │ │ │ │ ├── BytesMetricsChannelHandler.java │ │ │ │ ├── CaseInsensitiveKeyMap.java │ │ │ │ ├── ChunkedWriteHandler.java │ │ │ │ ├── ConcurrentLinkedHashMap.java │ │ │ │ ├── ConcurrentReferenceHashMap.java │ │ │ │ ├── ExpiryLRUMap.java │ │ │ │ ├── IOUtil.java │ │ │ │ ├── JVMUtil.java │ │ │ │ ├── LinkedMultiValueMap.java │ │ │ │ ├── LoggerFactoryX.java │ │ │ │ ├── LoggerX.java │ │ │ │ ├── MessageMetricsChannelHandler.java │ │ │ │ ├── NamespaceUtil.java │ │ │ │ ├── NettyThreadPoolExecutor.java │ │ │ │ ├── NettyThreadX.java │ │ │ │ ├── ReadOnlyPooledHeapByteBuf.java │ │ │ │ ├── Recyclable.java │ │ │ │ ├── RecyclableUtil.java │ │ │ │ ├── Recycler.java │ │ │ │ ├── ResourceManager.java │ │ │ │ ├── SystemPropertyUtil.java │ │ │ │ ├── ThreadFactoryX.java │ │ │ │ ├── ThreadPoolX.java │ │ │ │ ├── TypeUtil.java │ │ │ │ └── Wrapper.java │ │ │ └── protocol │ │ │ ├── DynamicProtocolChannelHandler.java │ │ │ ├── HttpServletProtocol.java │ │ │ └── servlet │ │ │ ├── DefaultServlet.java │ │ │ ├── DispatcherChannelHandler.java │ │ │ ├── NettyHttpResponse.java │ │ │ ├── NettyMessageToServletRunnable.java │ │ │ ├── NettyOutputStream.java │ │ │ ├── ServletAsyncContext.java │ │ │ ├── ServletContext.java │ │ │ ├── ServletErrorPage.java │ │ │ ├── ServletErrorPageManager.java │ │ │ ├── ServletEventListenerManager.java │ │ │ ├── ServletFilePart.java │ │ │ ├── ServletFilterChain.java │ │ │ ├── ServletFilterRegistration.java │ │ │ ├── ServletHttpAsyncRequest.java │ │ │ ├── ServletHttpAsyncResponse.java │ │ │ ├── ServletHttpExchange.java │ │ │ ├── ServletHttpForwardRequest.java │ │ │ ├── ServletHttpForwardResponse.java │ │ │ ├── ServletHttpIncludeRequest.java │ │ │ ├── ServletHttpIncludeResponse.java │ │ │ ├── ServletHttpServletRequest.java │ │ │ ├── ServletHttpServletResponse.java │ │ │ ├── ServletHttpSession.java │ │ │ ├── ServletInputStreamWrapper.java │ │ │ ├── ServletOutputStream.java │ │ │ ├── ServletOutputStreamWrapper.java │ │ │ ├── ServletPrincipal.java │ │ │ ├── ServletPrintWriter.java │ │ │ ├── ServletRegistration.java │ │ │ ├── ServletRequestDispatcher.java │ │ │ ├── ServletResetBufferIOException.java │ │ │ ├── ServletSessionCookieConfig.java │ │ │ ├── ServletTextPart.java │ │ │ ├── Session.java │ │ │ ├── SessionLocalFileServiceImpl.java │ │ │ ├── SessionLocalMemoryServiceImpl.java │ │ │ ├── SessionService.java │ │ │ ├── SslContextBuilders.java │ │ │ ├── util │ │ │ ├── ByteBufToHttpContentChannelHandler.java │ │ │ ├── FilterMapper.java │ │ │ ├── H2Util.java │ │ │ ├── HttpAbortPolicyWithReport.java │ │ │ ├── HttpConstants.java │ │ │ ├── HttpHeaderConstants.java │ │ │ ├── HttpHeaderUtil.java │ │ │ ├── HttpLazyThreadPool.java │ │ │ ├── MediaType.java │ │ │ ├── MimeMappingsX.java │ │ │ ├── Protocol.java │ │ │ ├── ServletUtil.java │ │ │ ├── SnowflakeIdWorker.java │ │ │ └── UrlMapper.java │ │ │ └── websocket │ │ │ ├── NettyMessageToWebSocketRunnable.java │ │ │ ├── WebSocketHandler.java │ │ │ ├── WebSocketHandlerEndpoint.java │ │ │ ├── WebSocketNotFoundHandlerEndpoint.java │ │ │ ├── WebSocketServerContainer.java │ │ │ ├── WebSocketServerHandshaker13Extension.java │ │ │ ├── WebSocketSession.java │ │ │ └── WebsocketServletUpgrader.java │ ├── io │ │ └── netty │ │ │ ├── channel │ │ │ ├── ChannelUtils.java │ │ │ ├── epoll │ │ │ │ ├── EpollChannelReportRunnable.java │ │ │ │ └── EpollUtils.java │ │ │ ├── kqueue │ │ │ │ └── KqueueUtils.java │ │ │ └── nio │ │ │ │ └── NioChannelReportRunnable.java │ │ │ └── handler │ │ │ └── codec │ │ │ ├── http │ │ │ └── multipart │ │ │ │ └── CompatibleHttpPostStandardRequestDecoder.java │ │ │ └── http2 │ │ │ └── HttpToHttp2FrameCodecConnectionHandlerBuilder.java │ └── javax │ │ └── servlet │ │ └── http │ │ └── Cookie.java └── resources │ ├── MimeTypeMappings.properties │ └── server.properties └── test └── java ├── com └── github │ └── netty │ └── http │ ├── FileApplication.java │ ├── HttpBootstrap.java │ ├── HttpTests.java │ └── MyHttpServlet.java └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | .idea/ 25 | #pom.xml 26 | #src/ 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netty-servlet 2 | 3 | ### 简介 4 | 5 | - 基于Netty实现的servlet容器, 可以替代tomcat或jetty. (jdk1.8+) 6 | - 解决Netty在EventLoop线程里写繁忙后不返回数据的BUG. 7 | - 解决Netty的Http遇到请求参数携带%号会报错的问题. 8 | - 从19年开始,一直跑在作者公司某产线的线上环境运行. 9 | 10 | ### 优势 11 | 12 | - 1.针对spring项目# 可以替代tomcat或jetty. 导包后一个@EnableNettyEmbedded注解即用. 13 | 14 | - 2.针对非spring项目# 本项目可以只依赖一个netty(举个使用servlet的例子) 15 | 16 | 17 | StartupServer server = new StartupServer(80); 18 | 19 | ServletContext servletContext = new ServletContext(); 20 | servletContext.setDocBase("D://static", "/webapp"); 21 | servletContext.addServlet("myServlet", new MyHttpServlet()).addMapping("/test"); 22 | server.addProtocol(new HttpServletProtocol(servletContext)); 23 | 24 | server.start(); 25 | 26 | 27 | - 3.支持# http请求聚合, 然后用 select * from id in (httpRequestList). 28 | 29 | 30 | 示例代码:com.github.netty.http.example.HttpGroupByApiController.java 31 | 32 | 33 | - 4.支持# h2c (注: 不建议用h2,h2c当rpc, 原因在文档最底部有说明) 34 | 35 | - 5.支持# 异步零拷贝。sendFile, mmap. 36 | 37 | 示例代码:com.github.netty.http.example.HttpZeroCopyController.java 38 | 39 | ((NettyOutputStream)servletResponse.getOutputStream()).write(new File("c://123.txt")); 40 | ((NettyOutputStream)servletResponse.getOutputStream()).write(MappedByteBuffer); 41 | 42 | com.github.netty.protocol.servlet.DefaultServlet#sendFile 43 | 44 | - 6.性能# HttpServlet比tomcat的NIO2高出25%/TPS。 45 | 46 | 1. Netty的池化内存,减少了GC对CPU的消耗 47 | 2. Tomcat的NIO2, 注册OP_WRITE后,tomcat会阻塞用户线程等待, 并没有释放线程. 48 | 3. 与tomcat不同,支持两种IO模型,可供用户选择 49 | 50 | 作者邮箱 : 842156727@qq.com 51 | 52 | github地址 : https://github.com/wangzihaogithub/netty-servlet 53 | 54 | --- 55 | 56 | #### 优势: 57 | 58 | 1.支持异步http请求聚合, 然后用 select * from id in (httpRequestList). 59 | 示例:https://github.com/wangzihaogithub/spring-boot-protocol# com.github.netty.http.example.HttpGroupByApiController.java 60 | 61 | 2.支持异步零拷贝。sendFile, mmap. 62 | 示例:https://github.com/wangzihaogithub/spring-boot-protocol# com.github.netty.http.example.HttpZeroCopyController.java 63 | 64 | 65 | 测试信息 : 笔记本[4g内存,4代I5(4核cpu) ], JVM参数 : -Xms300m -Xmn300m -Xmx500m -XX:+PrintGCDetails 66 | 67 | 1.单体应用,连接复用qps=10000+ , tomcat=8000+ 68 | 69 | 2.单体应用,连接不复用qps达到5100+, tomcat=4600+ 70 | 71 | ---- 72 | 73 | ### 使用方法 - 添加依赖 74 | 75 | #### 如果需要集成spring就用这个 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.wangzihaogithub/spring-boot-protocol/badge.svg)](https://search.maven.org/search?q=g:com.github.wangzihaogithub%20AND%20a:spring-boot-protocol) 76 | 77 | ```xml 78 | 79 | 80 | 81 | com.github.wangzihaogithub 82 | spring-boot-protocol 83 | 2.3.30 84 | 85 | ``` 86 | 87 | #### 如果不需要集成spring就用这个 [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.wangzihaogithub/netty-servlet/badge.svg)](https://search.maven.org/search?q=g:com.github.wangzihaogithub%20AND%20a:netty-servlet) 88 | 89 | ```xml 90 | 91 | 92 | 93 | com.github.wangzihaogithub 94 | netty-servlet 95 | 2.3.30 96 | 97 | ``` 98 | 99 | #### 3.写个main方法,启动服务 100 | 101 | public class HttpBootstrap { 102 | 103 | public static void main(String[] args) { 104 | StartupServer server = new StartupServer(80); 105 | server.addProtocol(newHttpProtocol()); 106 | server.start(); 107 | } 108 | 109 | private static HttpServletProtocol newHttpProtocol() { 110 | ServletContext servletContext = new ServletContext(); 111 | servletContext.setDocBase("D://demo", "/webapp"); // 静态资源文件夹(非必填,默认用临时目录) 112 | servletContext.addServlet("myHttpServlet", new MyHttpServlet()) 113 | .addMapping("/test"); 114 | return new HttpServletProtocol(servletContext); 115 | } 116 | } 117 | 118 | 119 | #### 4.打开浏览器 120 | 121 | 访问http://localhost:8080/test 122 | 页面显示 hi! doGet, 测试成功! 123 | 124 | 125 | --- 126 | 127 | #### 补充: Servlet的概念 128 | 129 | 1.Servlet是一个接受Request实体类, 并填充Response实体类的过程. 整个过程是为了处理一次http交互, 这个过程分为5种类型( 参考DispatcherType ) 130 | 131 | 注: 一次http交互, 可能会经历多次执行类型; 132 | 133 | 2.servlet中大部分类型, 都会遵守这样的流程: filterChain(N Filter) -> Servlet(1); 134 | 135 | 通过ServletRequest#getDispatcherType可以获得当前的执行类型. 136 | 137 | 3.执行类型逻辑如下 138 | 139 | public enum javax.servlet.DispatcherType { 140 | FORWARD ( 141 | 执行: 根据url或name找到servlet, 并执行filterChain(N Filter) -> Servlet(1) 142 | 触发: 调用RequestDispatcher#forward 143 | 特性: 可以对request,Response进行任何操作 144 | 145 | INCLUDE ( 146 | 执行: 根据url或name找到servlet, 并执行 filterChain(N Filter) -> Servlet(1) 147 | 触发: 调用RequestDispatcher#include 148 | 特性: 不能修改Response的header, status code , 重置body. 只能写入body. 149 | 150 | REQUEST( 151 | 执行: 根据url找到servlet, 并执行 filterChain(N Filter) -> Servlet(1) 152 | 触发: 收到客户端的请求后 153 | 特性: 可以对request,Response进行任何操作 (正常的流程) 154 | 155 | ASYNC( 156 | 执行: 返回AsyncContext(本质是个装有tcp长连接的实体类) 157 | 触发: 调用ServletRequest#startAsync 158 | 特性: 无阻塞并同时释放了当前线程, 不会关闭tcp连接, 并且返回AsyncContext, 159 | 用户可以将AsyncContext装在集合中, 在定时任务或者单线程中操作AsyncContext同时批量处理大量的请求), 160 | 如果一直不调用AsyncContext#complete, 则客户端阻塞(如果不是异步客户端), 服务端非阻塞. 161 | 162 | ERROR( 163 | 执行: 根据Response的status code 或 Exception 或 url找到servlet, 不执行Filter-> 执行Servlet(1) 164 | 触发: 出现连Filter都没有捕获的异常, ErrorPageManager#handleErrorPage. 注: spring是个Servlet, Servlet被Filter包裹 165 | 特性: 当的filter或者servlet都没有捕获异常, 那么会转发到错误页servlet去构造时错误页面的响应 166 | 备注: 当进入DispatcherType 执行流程后, 会被容器的try,catch代码包裹. 167 | } 168 | 169 | 170 | 4.注意: 重定向(sendRedirect)的跳转是由客户端实现的, 并不由Servlet实现. 171 | Servlet只负责设置Response的status为302 与设置header的Location字段. -------------------------------------------------------------------------------- /src/main/java/com/github/netty/Version.java: -------------------------------------------------------------------------------- 1 | package com.github.netty; 2 | 3 | import java.io.InputStream; 4 | import java.util.Properties; 5 | 6 | /** 7 | * server version 8 | * 9 | * @author wangzihao 10 | */ 11 | public final class Version { 12 | private static final String SERVER_INFO; 13 | private static final String SERVER_BUILT; 14 | private static final String SERVER_NUMBER; 15 | private static final String JVM_VERSION; 16 | private static final String ARCH; 17 | private static final String OS_NAME; 18 | 19 | static { 20 | String info = null; 21 | String built = null; 22 | String number = null; 23 | 24 | Properties props = new Properties(); 25 | try (InputStream is = Version.class.getResourceAsStream 26 | ("/server.properties")) { 27 | props.load(is); 28 | info = props.getProperty("server.info"); 29 | built = props.getProperty("server.built"); 30 | number = props.getProperty("server.number"); 31 | } catch (Throwable t) { 32 | // 33 | } 34 | if (info == null) { 35 | info = "Github NettyX/2.0.x-dev"; 36 | } 37 | if (built == null) { 38 | built = "unknown"; 39 | } 40 | if (number == null) { 41 | number = "2.0.x"; 42 | } 43 | SERVER_INFO = info; 44 | SERVER_BUILT = built; 45 | SERVER_NUMBER = number; 46 | OS_NAME = System.getProperty("os.name"); 47 | ARCH = System.getProperty("os.arch"); 48 | JVM_VERSION = System.getProperty("java.runtime.version"); 49 | } 50 | 51 | public static String getServerInfo() { 52 | return SERVER_INFO; 53 | } 54 | 55 | public static String getServerBuilt() { 56 | return SERVER_BUILT; 57 | } 58 | 59 | public static String getServerNumber() { 60 | return SERVER_NUMBER; 61 | } 62 | 63 | public static String getJvmVersion() { 64 | return JVM_VERSION; 65 | } 66 | 67 | public static String getArch() { 68 | return ARCH; 69 | } 70 | 71 | public static String getOsName() { 72 | return OS_NAME; 73 | } 74 | 75 | public static void main(String[] args) { 76 | System.out.println("Server version: " + getServerInfo()); 77 | System.out.println("Server built: " + getServerBuilt()); 78 | System.out.println("Server number: " + getServerNumber()); 79 | System.out.println("OS Name: " + 80 | getOsName()); 81 | System.out.println("OS Version: " + 82 | System.getProperty("os.version")); 83 | System.out.println("Architecture: " + 84 | getArch()); 85 | System.out.println("JVM Version: " + 86 | getJvmVersion()); 87 | System.out.println("JVM Vendor: " + 88 | System.getProperty("java.vm.vendor")); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/AbstractChannelHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core; 2 | 3 | import com.github.netty.core.util.LoggerFactoryX; 4 | import com.github.netty.core.util.LoggerX; 5 | import com.github.netty.core.util.RecyclableUtil; 6 | import io.netty.channel.ChannelDuplexHandler; 7 | import io.netty.channel.ChannelHandlerContext; 8 | import io.netty.channel.ChannelPromise; 9 | import io.netty.handler.timeout.IdleStateEvent; 10 | import io.netty.util.internal.TypeParameterMatcher; 11 | 12 | /** 13 | * An abstract netty ChannelHandler 14 | * 15 | * @author wangzihao 16 | */ 17 | public abstract class AbstractChannelHandler extends ChannelDuplexHandler { 18 | private final TypeParameterMatcher matcherInbound; 19 | private final TypeParameterMatcher matcherOutbound; 20 | private final boolean autoRelease; 21 | protected final LoggerX logger = LoggerFactoryX.getLogger(getClass()); 22 | 23 | protected AbstractChannelHandler() { 24 | this(true); 25 | } 26 | 27 | protected AbstractChannelHandler(boolean autoRelease) { 28 | matcherInbound = TypeParameterMatcher.find(this, AbstractChannelHandler.class, "I"); 29 | matcherOutbound = TypeParameterMatcher.find(this, AbstractChannelHandler.class, "O"); 30 | this.autoRelease = autoRelease; 31 | } 32 | 33 | @Override 34 | public final void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 35 | boolean release = true; 36 | try { 37 | boolean match = matcherInbound.match(msg); 38 | if (logger.isTraceEnabled()) { 39 | logger.trace("ChannelRead({}) -> match({}) ", messageToString(msg), match); 40 | } 41 | if (match) { 42 | I imsg = (I) msg; 43 | onMessageReceived(ctx, imsg); 44 | } else { 45 | release = false; 46 | ctx.fireChannelRead(msg); 47 | } 48 | } finally { 49 | if (autoRelease && release) { 50 | RecyclableUtil.release(msg); 51 | } 52 | } 53 | } 54 | 55 | protected void onMessageReceived(ChannelHandlerContext ctx, I msg) throws Exception { 56 | ctx.fireChannelRead(msg); 57 | } 58 | 59 | @Override 60 | public final void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 61 | boolean match = matcherOutbound.match(msg); 62 | if (logger.isTraceEnabled()) { 63 | logger.trace("ChannelWrite({}) -> match({}) ", messageToString(msg), match); 64 | } 65 | if (match) { 66 | O imsg = (O) msg; 67 | onMessageWriter(ctx, imsg, promise); 68 | } else { 69 | ctx.write(msg, promise); 70 | } 71 | } 72 | 73 | protected void onMessageWriter(ChannelHandlerContext ctx, O msg, ChannelPromise promise) throws Exception { 74 | ctx.write(msg, promise); 75 | } 76 | 77 | @Override 78 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 79 | if (evt instanceof IdleStateEvent) { 80 | IdleStateEvent e = (IdleStateEvent) evt; 81 | switch (e.state()) { 82 | case READER_IDLE: 83 | onReaderIdle(ctx); 84 | break; 85 | case WRITER_IDLE: 86 | onWriterIdle(ctx); 87 | break; 88 | case ALL_IDLE: 89 | onAllIdle(ctx); 90 | break; 91 | default: 92 | break; 93 | } 94 | } else { 95 | onUserEventTriggered(ctx, evt); 96 | } 97 | ctx.fireUserEventTriggered(evt); 98 | } 99 | 100 | protected void onUserEventTriggered(ChannelHandlerContext ctx, Object evt) { 101 | 102 | } 103 | 104 | protected void onAllIdle(ChannelHandlerContext ctx) { 105 | 106 | } 107 | 108 | protected void onWriterIdle(ChannelHandlerContext ctx) { 109 | 110 | } 111 | 112 | protected void onReaderIdle(ChannelHandlerContext ctx) { 113 | 114 | } 115 | 116 | public String messageToString(Object msg) { 117 | if (msg == null) { 118 | return "null"; 119 | } 120 | return msg.getClass().getSimpleName(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/AbstractProtocol.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.Channel; 5 | 6 | import java.util.concurrent.TimeUnit; 7 | 8 | /** 9 | * An abstract Protocols Register 10 | * 11 | * @author wangzihao 12 | */ 13 | public abstract class AbstractProtocol implements ProtocolHandler, ServerListener { 14 | /** 15 | * Refresh buffer data interval (milliseconds), 16 | * the benefits of open time to send is sent in bulk to bring high-throughput, 17 | * but there will be a delay. 18 | * (If the timer is greater than 0 seconds to transmit buffer data is less than 0 seconds to transmit the data in real time) 19 | */ 20 | private int autoFlushIdleMs; 21 | 22 | public int getAutoFlushIdleMs() { 23 | return autoFlushIdleMs; 24 | } 25 | 26 | public void setAutoFlushIdleMs(int autoFlushIdleMs) { 27 | this.autoFlushIdleMs = autoFlushIdleMs; 28 | } 29 | 30 | @Override 31 | public void addPipeline(Channel channel, ByteBuf clientFirstMsg) throws Exception { 32 | int autoFlushIdleTime = getAutoFlushIdleMs(); 33 | if (autoFlushIdleTime > 0) { 34 | channel.pipeline().addLast("autoflush", new AutoFlushChannelHandler(autoFlushIdleTime, TimeUnit.MILLISECONDS)); 35 | } 36 | } 37 | 38 | @Override 39 | public String toString() { 40 | return getProtocolName(); 41 | } 42 | 43 | @Override 44 | public int getOrder() { 45 | return 0; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/MessageToRunnable.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | 5 | /** 6 | * Convert the IO message to Runnable 7 | * Life cycle connection 8 | * 9 | * @author wangzihao 10 | */ 11 | @FunctionalInterface 12 | public interface MessageToRunnable { 13 | 14 | /** 15 | * Create a message handler IO task 16 | * 17 | * @param context The connection 18 | * @param msg IO messages (attention! : no automatic release, manual release is required) 19 | * @return Runnable 20 | */ 21 | Runnable onMessage(ChannelHandlerContext context, Object msg); 22 | 23 | /** 24 | * Create a error handler IO task 25 | * 26 | * @param context The connection 27 | * @param throwable Throwable 28 | * @return Runnable 29 | */ 30 | default Runnable onError(ChannelHandlerContext context, Throwable throwable) { 31 | return null; 32 | } 33 | 34 | /** 35 | * Create a close handler IO task 36 | * 37 | * @param context The connection 38 | * @return Runnable 39 | */ 40 | default Runnable onClose(ChannelHandlerContext context) { 41 | return null; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/Ordered.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core; 2 | 3 | import java.util.Comparator; 4 | import java.util.TreeSet; 5 | import java.util.function.Function; 6 | 7 | /** 8 | * Ordered 9 | * 10 | * @author wangzihao 11 | * 2019/8/31/022 12 | */ 13 | public interface Ordered { 14 | /** 15 | * Avoid TreeSet overwrites data 16 | * return -1 or 1. not return 0 17 | */ 18 | Comparator COMPARATOR = (c1, c2) -> 19 | c1 == c2 ? 0 : c1.getOrder() < c2.getOrder() ? -1 : 1; 20 | 21 | /** 22 | * test comparator 23 | * 24 | * @param args args 25 | */ 26 | static void main(String[] args) { 27 | /** no overwrites by {{@link COMPARATOR}} */ 28 | TreeSet set1 = new TreeSet<>(COMPARATOR); 29 | set1.add(() -> 1); 30 | set1.add(() -> 1); 31 | System.out.println("no overwrites. set1 = " + set1.size()); 32 | 33 | /** overwrites by jdk method {{@link Comparator#comparing(Function)}} */ 34 | TreeSet set2 = new TreeSet<>(Comparator.comparing(Ordered::getOrder)); 35 | set2.add(() -> 1); 36 | set2.add(() -> 1); 37 | System.out.println("overwrites. set2 = " + set2.size()); 38 | } 39 | 40 | /** 41 | * Priority order 42 | * 43 | * @return The smaller the value of order, the more likely it is to be executed first 44 | */ 45 | int getOrder(); 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/ProtocolHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.Channel; 5 | 6 | /** 7 | * Protocol Handler 8 | * 9 | * @author wangzihao 10 | * 2018/11/11/011 11 | */ 12 | public interface ProtocolHandler extends Ordered { 13 | 14 | /** 15 | * Get the protocol name 16 | * 17 | * @return name 18 | */ 19 | default String getProtocolName() { 20 | String name = getClass().getSimpleName(); 21 | if (name.isEmpty()) { 22 | name = getClass().getName(); 23 | } 24 | return name; 25 | } 26 | 27 | /** 28 | * Support protocol 29 | * 30 | * @param clientFirstMsg client first message 31 | * @return true=Support, false=no Support 32 | */ 33 | boolean canSupport(ByteBuf clientFirstMsg); 34 | 35 | /** 36 | * Support protocol. if receive clientFirstMsg timeout, then call canSupport(channel) 37 | * 38 | * @param channel channel 39 | * @return true=Support, false=no Support 40 | */ 41 | default boolean canSupport(Channel channel) { 42 | return false; 43 | } 44 | 45 | /** 46 | * on out of max connection count 47 | * 48 | * @param clientFirstMsg clientFirstMsg 49 | * @param tcpChannel tcpChannel 50 | * @param currentConnections currentConnections 51 | * @param maxConnections maxConnections 52 | * @return boolean. false=discard, true=keep handle 53 | */ 54 | default boolean onOutOfMaxConnection(ByteBuf clientFirstMsg, TcpChannel tcpChannel, 55 | int currentConnections, 56 | int maxConnections) { 57 | return false; 58 | } 59 | 60 | /** 61 | * add protocol pipeline support 62 | * 63 | * @param channel TCP channel 64 | * @param clientFirstMsg clientFirstMsg 65 | * @throws Exception Exception 66 | */ 67 | void addPipeline(Channel channel, ByteBuf clientFirstMsg) throws Exception; 68 | 69 | /** 70 | * default Priority order 0 71 | * 72 | * @return The smaller the value of order, the more likely it is to be executed first 73 | */ 74 | @Override 75 | default int getOrder() { 76 | return 0; 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/ServerListener.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | 5 | /** 6 | * Server listening 7 | * Created by wangzihao on 2018/11/12/012. 8 | */ 9 | public interface ServerListener extends Ordered { 10 | 11 | default void onServerStart(T server) throws Exception { 12 | } 13 | 14 | default void onServerStop(T server) throws Exception { 15 | } 16 | 17 | default void config(ServerBootstrap bootstrap) throws Exception { 18 | } 19 | 20 | /** 21 | * default Priority order 0 22 | * 23 | * @return The smaller the value of order, the more likely it is to be executed first 24 | */ 25 | @Override 26 | default int getOrder() { 27 | return 0; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/TcpChannel.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.Unpooled; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelFuture; 7 | import io.netty.channel.ChannelHandler; 8 | import io.netty.channel.ChannelId; 9 | import io.netty.util.Attribute; 10 | import io.netty.util.AttributeKey; 11 | 12 | import java.net.SocketAddress; 13 | import java.nio.charset.Charset; 14 | import java.util.Map; 15 | import java.util.Objects; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | 18 | /** 19 | * tcp channel 20 | * 21 | * @author wangzihao 22 | */ 23 | public class TcpChannel { 24 | private static final Map CHANNELS = new ConcurrentHashMap<>(32); 25 | private final Channel channel; 26 | private final ChannelHandler channelHandler; 27 | private ProtocolHandler protocol; 28 | 29 | public TcpChannel(Channel channel, ChannelHandler channelHandler) { 30 | this.channel = channel; 31 | this.channelHandler = channelHandler; 32 | } 33 | 34 | public static Map getChannels() { 35 | return CHANNELS; 36 | } 37 | 38 | public String getProtocolName() { 39 | return protocol == null ? null : protocol.getProtocolName(); 40 | } 41 | 42 | public Channel getChannel() { 43 | return channel; 44 | } 45 | 46 | public ChannelHandler getChannelHandler() { 47 | return channelHandler; 48 | } 49 | 50 | public ProtocolHandler getProtocol() { 51 | return protocol; 52 | } 53 | 54 | public void setProtocol(ProtocolHandler protocol) { 55 | this.protocol = protocol; 56 | } 57 | 58 | public boolean isActive() { 59 | return channel.isActive(); 60 | } 61 | 62 | public ChannelFuture writeAndFlush(byte[] msg) { 63 | return channel.writeAndFlush(Unpooled.wrappedBuffer(msg)); 64 | } 65 | 66 | public ChannelFuture writeAndFlush(String msg, Charset charset) { 67 | return channel.writeAndFlush(Unpooled.copiedBuffer(msg, charset)); 68 | } 69 | 70 | public ChannelFuture writeAndFlush(ByteBuf byteBuf) { 71 | return channel.writeAndFlush(byteBuf); 72 | } 73 | 74 | public ChannelFuture close() { 75 | return channel.close(); 76 | } 77 | 78 | public SocketAddress remoteAddress() { 79 | return channel.remoteAddress(); 80 | } 81 | 82 | public Attribute attr(AttributeKey key) { 83 | return channel.attr(key); 84 | } 85 | 86 | @Override 87 | public String toString() { 88 | return Objects.toString(protocol, "null") + channel; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/AbortPolicyWithReport.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.io.File; 4 | import java.io.FileOutputStream; 5 | import java.text.SimpleDateFormat; 6 | import java.util.Date; 7 | import java.util.concurrent.*; 8 | 9 | /** 10 | * Abort Policy. 11 | * Log warn info when abort. 12 | */ 13 | public class AbortPolicyWithReport extends ThreadPoolExecutor.AbortPolicy { 14 | private static final long TEN_MINUTES_MILLS = 10 * 60 * 1000; 15 | private static volatile long lastPrintTime = 0; 16 | private static final Semaphore guard = new Semaphore(1); 17 | protected final String info; 18 | protected final String threadName; 19 | protected final String dumpPath; 20 | 21 | public AbortPolicyWithReport(String threadName, String dumpPath, String info) { 22 | this.threadName = threadName; 23 | this.dumpPath = dumpPath == null ? System.getProperty("user.home") : dumpPath; 24 | this.info = info; 25 | } 26 | 27 | @Override 28 | public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 29 | String msg = String.format("Thread pool is EXHAUSTED!" + 30 | " Thread Name: %s, Pool Size: %d (active: %d, core: %d, max: %d, largest: %d), Task: %d (completed: " 31 | + "%d)," + 32 | " Executor status:(isShutdown:%s, isTerminated:%s, isTerminating:%s), in %s", 33 | threadName, e.getPoolSize(), e.getActiveCount(), e.getCorePoolSize(), e.getMaximumPoolSize(), 34 | e.getLargestPoolSize(), 35 | e.getTaskCount(), e.getCompletedTaskCount(), e.isShutdown(), e.isTerminated(), e.isTerminating(), info); 36 | LoggerFactoryX.getLogger(AbortPolicyWithReport.class).warn(msg); 37 | if (!dumpPath.isEmpty()) { 38 | dumpJStack(); 39 | } 40 | throw new RejectedExecutionException(msg); 41 | } 42 | 43 | protected void dumpJStack() { 44 | long now = System.currentTimeMillis(); 45 | 46 | //dump every 10 minutes 47 | if (now - lastPrintTime < TEN_MINUTES_MILLS) { 48 | return; 49 | } 50 | 51 | if (!guard.tryAcquire()) { 52 | return; 53 | } 54 | 55 | ExecutorService pool = Executors.newSingleThreadExecutor(); 56 | pool.execute(() -> { 57 | SimpleDateFormat sdf = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss"); 58 | 59 | String dateStr = sdf.format(new Date()); 60 | //try-with-resources 61 | try (FileOutputStream jStackStream = new FileOutputStream( 62 | new File(dumpPath, "NettyX_JStack.log" + "." + dateStr))) { 63 | JVMUtil.jstack(jStackStream); 64 | } catch (Throwable t) { 65 | LoggerFactoryX.getLogger(AbortPolicyWithReport.class).error("dump jStack error", t); 66 | } finally { 67 | guard.release(); 68 | } 69 | lastPrintTime = System.currentTimeMillis(); 70 | }); 71 | //must shutdown thread pool ,if not will lead to OOM 72 | pool.shutdown(); 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/BytesMetricsChannelHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2018 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package com.github.netty.core.util; 18 | 19 | import com.github.netty.core.AbstractChannelHandler; 20 | import io.netty.buffer.ByteBuf; 21 | import io.netty.channel.*; 22 | import io.netty.util.Attribute; 23 | import io.netty.util.AttributeKey; 24 | 25 | import java.util.concurrent.atomic.AtomicLong; 26 | 27 | /** 28 | * Packet monitoring (read write/byte) 29 | * 30 | * @author wangzihao 31 | */ 32 | @ChannelHandler.Sharable 33 | public class BytesMetricsChannelHandler extends AbstractChannelHandler { 34 | private static final AttributeKey ATTR_KEY_METRICS = AttributeKey.valueOf(BytesMetrics.class + "#BytesMetrics"); 35 | private final AtomicLong readBytes = new AtomicLong(); 36 | private final AtomicLong writeBytes = new AtomicLong(); 37 | 38 | public BytesMetricsChannelHandler() { 39 | super(false); 40 | Runtime.getRuntime().addShutdownHook(new Thread("Metrics-Hook" + hashCode()) { 41 | @Override 42 | public void run() { 43 | logger.info("Metrics bytes[read={}/byte, write={}/byte]", readBytes, writeBytes); 44 | } 45 | }); 46 | } 47 | 48 | public static BytesMetrics getOrSetMetrics(Channel channel) { 49 | Attribute attribute = channel.attr(ATTR_KEY_METRICS); 50 | BytesMetrics metrics = attribute.get(); 51 | if (metrics == null) { 52 | metrics = new BytesMetrics(); 53 | attribute.set(metrics); 54 | } 55 | return metrics; 56 | } 57 | 58 | @Override 59 | public void onMessageReceived(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 60 | BytesMetrics metrics = getOrSetMetrics(ctx.channel()); 61 | metrics.incrementRead(msg.readableBytes()); 62 | ctx.fireChannelRead(msg); 63 | } 64 | 65 | @Override 66 | protected void onMessageWriter(ChannelHandlerContext ctx, ByteBuf msg, ChannelPromise promise) throws Exception { 67 | BytesMetrics metrics = getOrSetMetrics(ctx.channel()); 68 | metrics.incrementWrote(msg.writableBytes()); 69 | if (promise.isVoid()) { 70 | ctx.write(msg, promise); 71 | } else { 72 | ctx.write(msg, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 73 | } 74 | } 75 | 76 | @Override 77 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 78 | BytesMetrics metrics = getOrSetMetrics(ctx.channel()); 79 | readBytes.getAndAdd(metrics.bytesRead()); 80 | writeBytes.getAndAdd(metrics.bytesWrote()); 81 | ctx.close(promise); 82 | } 83 | 84 | public static class BytesMetrics { 85 | 86 | private long m_bytesRead; 87 | private long m_bytesWrote; 88 | 89 | void incrementRead(long numBytes) { 90 | m_bytesRead += numBytes; 91 | } 92 | 93 | void incrementWrote(long numBytes) { 94 | m_bytesWrote += numBytes; 95 | } 96 | 97 | public long bytesRead() { 98 | return m_bytesRead; 99 | } 100 | 101 | public long bytesWrote() { 102 | return m_bytesWrote; 103 | } 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/CaseInsensitiveKeyMap.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * 不敏感大小写的map 7 | *

8 | * A Map implementation that uses case-insensitive (using {@link 9 | * Locale#ENGLISH}) strings as keys. 10 | *

11 | * Keys must be instances of {@link String}. Note that this means that 12 | * null keys are not permitted. 13 | *

14 | * This implementation is not thread-safe. 15 | * 16 | * @param Type of values placed in this Map. 17 | */ 18 | public class CaseInsensitiveKeyMap extends AbstractMap { 19 | private final Map map; 20 | 21 | public CaseInsensitiveKeyMap() { 22 | this.map = new LinkedHashMap<>(); 23 | } 24 | 25 | public CaseInsensitiveKeyMap(int initialCapacity) { 26 | map = new LinkedHashMap<>(initialCapacity); 27 | } 28 | 29 | @Override 30 | public V get(Object key) { 31 | return map.get(Key.getInstance(key)); 32 | } 33 | 34 | @Override 35 | public V put(String key, V value) { 36 | Key caseInsensitiveKey = Key.getInstance(key); 37 | if (caseInsensitiveKey == null) { 38 | throw new NullPointerException(); 39 | } 40 | return map.put(caseInsensitiveKey, value); 41 | } 42 | 43 | 44 | /** 45 | * {@inheritDoc} 46 | *

47 | * Use this method with caution. If the input Map contains duplicate 48 | * keys when the keys are compared in a case insensitive manner then some 49 | * values will be lost when inserting via this method. 50 | */ 51 | @Override 52 | public void putAll(Map m) { 53 | super.putAll(m); 54 | } 55 | 56 | 57 | @Override 58 | public boolean containsKey(Object key) { 59 | return map.containsKey(Key.getInstance(key)); 60 | } 61 | 62 | 63 | @Override 64 | public V remove(Object key) { 65 | return map.remove(Key.getInstance(key)); 66 | } 67 | 68 | 69 | @Override 70 | public Set> entrySet() { 71 | return new EntrySet<>(map.entrySet()); 72 | } 73 | 74 | 75 | private static class EntrySet extends AbstractSet> { 76 | 77 | private final Set> entrySet; 78 | 79 | public EntrySet(Set> entrySet) { 80 | this.entrySet = entrySet; 81 | } 82 | 83 | @Override 84 | public Iterator> iterator() { 85 | return new EntryIterator<>(entrySet.iterator()); 86 | } 87 | 88 | @Override 89 | public int size() { 90 | return entrySet.size(); 91 | } 92 | } 93 | 94 | 95 | private static class EntryIterator implements Iterator> { 96 | 97 | private final Iterator> iterator; 98 | 99 | public EntryIterator(Iterator> iterator) { 100 | this.iterator = iterator; 101 | } 102 | 103 | @Override 104 | public boolean hasNext() { 105 | return iterator.hasNext(); 106 | } 107 | 108 | @Override 109 | public Entry next() { 110 | Entry entry = iterator.next(); 111 | return new EntryImpl<>(entry.getKey().getKey(), entry.getValue()); 112 | } 113 | 114 | @Override 115 | public void remove() { 116 | iterator.remove(); 117 | } 118 | } 119 | 120 | 121 | private static class EntryImpl implements Entry { 122 | 123 | private final String key; 124 | private final V value; 125 | 126 | public EntryImpl(String key, V value) { 127 | this.key = key; 128 | this.value = value; 129 | } 130 | 131 | @Override 132 | public String getKey() { 133 | return key; 134 | } 135 | 136 | @Override 137 | public V getValue() { 138 | return value; 139 | } 140 | 141 | @Override 142 | public V setValue(V value) { 143 | throw new UnsupportedOperationException(); 144 | } 145 | } 146 | 147 | private static class Key { 148 | 149 | private final String key; 150 | private final String lcKey; 151 | 152 | private Key(String key) { 153 | this.key = key; 154 | this.lcKey = key.toLowerCase(Locale.ENGLISH); 155 | } 156 | 157 | public static Key getInstance(Object o) { 158 | if (o instanceof String) { 159 | return new Key((String) o); 160 | } 161 | return null; 162 | } 163 | 164 | public String getKey() { 165 | return key; 166 | } 167 | 168 | @Override 169 | public int hashCode() { 170 | return lcKey.hashCode(); 171 | } 172 | 173 | @Override 174 | public boolean equals(Object obj) { 175 | if (this == obj) { 176 | return true; 177 | } 178 | if (obj == null) { 179 | return false; 180 | } 181 | if (getClass() != obj.getClass()) { 182 | return false; 183 | } 184 | Key other = (Key) obj; 185 | return lcKey.equals(other.lcKey); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/JVMUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.io.OutputStream; 4 | import java.lang.management.*; 5 | 6 | public class JVMUtil { 7 | 8 | public static void jstack(OutputStream stream) throws Exception { 9 | ThreadMXBean threadMxBean = ManagementFactory.getThreadMXBean(); 10 | for (ThreadInfo threadInfo : threadMxBean.dumpAllThreads(true, true)) { 11 | stream.write(getThreadDumpString(threadInfo).getBytes()); 12 | } 13 | } 14 | 15 | private static String getThreadDumpString(ThreadInfo threadInfo) { 16 | StringBuilder sb = new StringBuilder("\"" + threadInfo.getThreadName() + "\"" + 17 | " Id=" + threadInfo.getThreadId() + " " + 18 | threadInfo.getThreadState()); 19 | if (threadInfo.getLockName() != null) { 20 | sb.append(" on ").append(threadInfo.getLockName()); 21 | } 22 | if (threadInfo.getLockOwnerName() != null) { 23 | sb.append(" owned by \"").append(threadInfo.getLockOwnerName()).append("\" Id=").append(threadInfo.getLockOwnerId()); 24 | } 25 | if (threadInfo.isSuspended()) { 26 | sb.append(" (suspended)"); 27 | } 28 | if (threadInfo.isInNative()) { 29 | sb.append(" (in native)"); 30 | } 31 | sb.append('\n'); 32 | int i = 0; 33 | 34 | StackTraceElement[] stackTrace = threadInfo.getStackTrace(); 35 | MonitorInfo[] lockedMonitors = threadInfo.getLockedMonitors(); 36 | for (; i < stackTrace.length && i < 32; i++) { 37 | StackTraceElement ste = stackTrace[i]; 38 | sb.append("\tat ").append(ste.toString()); 39 | sb.append('\n'); 40 | if (i == 0 && threadInfo.getLockInfo() != null) { 41 | Thread.State ts = threadInfo.getThreadState(); 42 | switch (ts) { 43 | case BLOCKED: 44 | sb.append("\t- blocked on ").append(threadInfo.getLockInfo()); 45 | sb.append('\n'); 46 | break; 47 | case WAITING: 48 | sb.append("\t- waiting on ").append(threadInfo.getLockInfo()); 49 | sb.append('\n'); 50 | break; 51 | case TIMED_WAITING: 52 | sb.append("\t- timed waiting on ").append(threadInfo.getLockInfo()); 53 | sb.append('\n'); 54 | break; 55 | default: 56 | } 57 | } 58 | 59 | for (MonitorInfo mi : lockedMonitors) { 60 | if (mi.getLockedStackDepth() == i) { 61 | sb.append("\t- locked ").append(mi); 62 | sb.append('\n'); 63 | } 64 | } 65 | } 66 | if (i < stackTrace.length) { 67 | sb.append("\t..."); 68 | sb.append('\n'); 69 | } 70 | 71 | LockInfo[] locks = threadInfo.getLockedSynchronizers(); 72 | if (locks.length > 0) { 73 | sb.append("\n\tNumber of locked synchronizers = ").append(locks.length); 74 | sb.append('\n'); 75 | for (LockInfo li : locks) { 76 | sb.append("\t- ").append(li); 77 | sb.append('\n'); 78 | } 79 | } 80 | sb.append('\n'); 81 | return sb.toString(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/LinkedMultiValueMap.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.io.Serializable; 4 | import java.util.*; 5 | 6 | /** 7 | *

This Map implementation is generally not thread-safe. It is primarily designed 8 | * for data structures exposed from request objects, for use in a single thread only. 9 | * 10 | * @param the key type 11 | * @param the value element type 12 | * @author Arjen Poutsma 13 | * @author Juergen Hoeller 14 | * @since 3.0 15 | */ 16 | public class LinkedMultiValueMap implements Map>, Serializable, Cloneable { 17 | 18 | private static final long serialVersionUID = 3801124242820219131L; 19 | 20 | private final Map> targetMap; 21 | 22 | 23 | /** 24 | * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap}. 25 | */ 26 | public LinkedMultiValueMap() { 27 | this.targetMap = new LinkedHashMap<>(); 28 | } 29 | 30 | /** 31 | * Create a new LinkedMultiValueMap that wraps a {@link LinkedHashMap} 32 | * with the given initial capacity. 33 | * 34 | * @param initialCapacity the initial capacity 35 | */ 36 | public LinkedMultiValueMap(int initialCapacity) { 37 | this.targetMap = new LinkedHashMap<>(initialCapacity); 38 | } 39 | 40 | 41 | public LinkedMultiValueMap(Map> targetMap) { 42 | this.targetMap = Objects.requireNonNull(targetMap); 43 | } 44 | 45 | 46 | public V getFirst(K key) { 47 | List values = this.targetMap.get(key); 48 | return (values != null ? values.get(0) : null); 49 | } 50 | 51 | 52 | public void add(K key, V value) { 53 | List values = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(2)); 54 | values.add(value); 55 | } 56 | 57 | 58 | public void addAll(K key, List values) { 59 | List currentValues = this.targetMap.computeIfAbsent(key, k -> new ArrayList<>(values.size())); 60 | currentValues.addAll(values); 61 | } 62 | 63 | public void set(K key, V value) { 64 | List values = new ArrayList<>(1); 65 | values.add(value); 66 | this.targetMap.put(key, values); 67 | } 68 | 69 | 70 | public void setAll(Map values) { 71 | values.forEach(this::set); 72 | } 73 | 74 | 75 | public Map toSingleValueMap() { 76 | LinkedHashMap singleValueMap = new LinkedHashMap<>(this.targetMap.size()); 77 | this.targetMap.forEach((key, value) -> singleValueMap.put(key, value.get(0))); 78 | return singleValueMap; 79 | } 80 | 81 | 82 | // Map implementation 83 | 84 | @Override 85 | public int size() { 86 | return this.targetMap.size(); 87 | } 88 | 89 | @Override 90 | public boolean isEmpty() { 91 | return this.targetMap.isEmpty(); 92 | } 93 | 94 | @Override 95 | public boolean containsKey(Object key) { 96 | return this.targetMap.containsKey(key); 97 | } 98 | 99 | @Override 100 | public boolean containsValue(Object value) { 101 | return this.targetMap.containsValue(value); 102 | } 103 | 104 | @Override 105 | public List get(Object key) { 106 | return this.targetMap.get(key); 107 | } 108 | 109 | @Override 110 | public List put(K key, List value) { 111 | return this.targetMap.put(key, value); 112 | } 113 | 114 | @Override 115 | public List remove(Object key) { 116 | return this.targetMap.remove(key); 117 | } 118 | 119 | @Override 120 | public void putAll(Map> map) { 121 | this.targetMap.putAll(map); 122 | } 123 | 124 | @Override 125 | public void clear() { 126 | this.targetMap.clear(); 127 | } 128 | 129 | @Override 130 | public Set keySet() { 131 | return this.targetMap.keySet(); 132 | } 133 | 134 | @Override 135 | public Collection> values() { 136 | return this.targetMap.values(); 137 | } 138 | 139 | @Override 140 | public Set>> entrySet() { 141 | return this.targetMap.entrySet(); 142 | } 143 | 144 | @Override 145 | public boolean equals(Object obj) { 146 | return this.targetMap.equals(obj); 147 | } 148 | 149 | 150 | @Override 151 | public int hashCode() { 152 | return this.targetMap.hashCode(); 153 | } 154 | 155 | @Override 156 | public String toString() { 157 | return this.targetMap.toString(); 158 | } 159 | 160 | } 161 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/LoggerFactoryX.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | /** 4 | * @author wangzihao 5 | * 2018/8/25/025 6 | */ 7 | public class LoggerFactoryX { 8 | 9 | public static LoggerX getLogger(Class clazz) { 10 | return new LoggerX(clazz); 11 | } 12 | 13 | public static LoggerX getLogger(String clazz) { 14 | return new LoggerX(clazz); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/LoggerX.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import io.netty.util.internal.logging.InternalLogger; 4 | import io.netty.util.internal.logging.InternalLoggerFactory; 5 | 6 | /** 7 | * JDK logging level [SEVERE WARNING INFO CONFIG FINE FINER FINEST] 8 | * 9 | * @author wangzihao 10 | * 2018/8/25/025 11 | */ 12 | public class LoggerX { 13 | 14 | private static final long serialVersionUID = 1L; 15 | private transient InternalLogger logger; 16 | 17 | LoggerX() { 18 | this.logger = InternalLoggerFactory.getInstance(LoggerX.class); 19 | } 20 | 21 | LoggerX(Class clazz) { 22 | this.logger = InternalLoggerFactory.getInstance(clazz); 23 | } 24 | 25 | LoggerX(String name) { 26 | this.logger = InternalLoggerFactory.getInstance(name); 27 | } 28 | 29 | public boolean isTraceEnabled() { 30 | return logger.isTraceEnabled(); 31 | } 32 | 33 | public void trace(String msg) { 34 | logger.trace(msg); 35 | } 36 | 37 | public void trace(String format, Object arg) { 38 | logger.trace(format, arg); 39 | } 40 | 41 | public void trace(String format, Object argA, Object argB) { 42 | logger.trace(format, argA, argB); 43 | } 44 | 45 | public void trace(String format, Object... arguments) { 46 | logger.trace(format, arguments); 47 | } 48 | 49 | 50 | public void trace(String msg, Throwable t) { 51 | logger.trace(msg, t); 52 | } 53 | 54 | 55 | public boolean isDebugEnabled() { 56 | return logger.isDebugEnabled(); 57 | } 58 | 59 | 60 | public void debug(String msg) { 61 | logger.debug(msg); 62 | } 63 | 64 | 65 | public void debug(String format, Object arg) { 66 | logger.debug(format, arg); 67 | } 68 | 69 | 70 | public void debug(String format, Object argA, Object argB) { 71 | logger.debug(format, argA, argB); 72 | } 73 | 74 | 75 | public void debug(String format, Object... arguments) { 76 | logger.debug(format, arguments); 77 | } 78 | 79 | 80 | public void debug(String msg, Throwable t) { 81 | logger.debug(msg, t); 82 | } 83 | 84 | 85 | public boolean isInfoEnabled() { 86 | return logger.isInfoEnabled(); 87 | } 88 | 89 | 90 | public void info(String msg) { 91 | logger.info(msg); 92 | } 93 | 94 | 95 | public void info(String format, Object arg) { 96 | logger.info(format, arg); 97 | } 98 | 99 | 100 | public void info(String format, Object argA, Object argB) { 101 | logger.info(format, argA, argB); 102 | } 103 | 104 | 105 | public void info(String format, Object... arguments) { 106 | logger.info(format, arguments); 107 | } 108 | 109 | 110 | public void info(String msg, Throwable t) { 111 | logger.info(msg, t); 112 | } 113 | 114 | 115 | public boolean isWarnEnabled() { 116 | return logger.isWarnEnabled(); 117 | } 118 | 119 | 120 | public void warn(String msg) { 121 | logger.warn(msg); 122 | } 123 | 124 | 125 | public void warn(String format, Object arg) { 126 | logger.warn(format, arg); 127 | } 128 | 129 | 130 | public void warn(String format, Object argA, Object argB) { 131 | logger.warn(format, argA, argB); 132 | } 133 | 134 | 135 | public void warn(String format, Object... arguments) { 136 | logger.warn(format, arguments); 137 | } 138 | 139 | 140 | public void warn(String msg, Throwable t) { 141 | logger.warn(msg, t); 142 | } 143 | 144 | 145 | public boolean isErrorEnabled() { 146 | return logger.isErrorEnabled(); 147 | } 148 | 149 | 150 | public void error(String msg) { 151 | logger.error(msg); 152 | } 153 | 154 | 155 | public void error(String format, Object arg) { 156 | logger.error(format, arg); 157 | } 158 | 159 | 160 | public void error(String format, Object argA, Object argB) { 161 | logger.error(format, argA, argB); 162 | } 163 | 164 | 165 | public void error(String format, Object... arguments) { 166 | logger.error(format, arguments); 167 | } 168 | 169 | 170 | public void error(String msg, Throwable t) { 171 | logger.error(msg, t); 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/MessageMetricsChannelHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2012-2018 The original author or authors 3 | * ------------------------------------------------------ 4 | * All rights reserved. This program and the accompanying materials 5 | * are made available under the terms of the Eclipse Public License v1.0 6 | * and Apache License v2.0 which accompanies this distribution. 7 | * 8 | * The Eclipse Public License is available at 9 | * http://www.eclipse.org/legal/epl-v10.html 10 | * 11 | * The Apache License v2.0 is available at 12 | * http://www.opensource.org/licenses/apache2.0.php 13 | * 14 | * You may elect to redistribute this code under either of these licenses. 15 | */ 16 | 17 | package com.github.netty.core.util; 18 | 19 | import com.github.netty.core.AbstractChannelHandler; 20 | import io.netty.channel.*; 21 | import io.netty.util.Attribute; 22 | import io.netty.util.AttributeKey; 23 | 24 | import java.util.concurrent.atomic.AtomicLong; 25 | 26 | /** 27 | * Communication monitoring (read write/time) 28 | * 29 | * @author wangzihao 30 | */ 31 | @ChannelHandler.Sharable 32 | public class MessageMetricsChannelHandler extends AbstractChannelHandler { 33 | private static final AttributeKey ATTR_KEY_METRICS = AttributeKey.valueOf(MessageMetrics.class + "#MessageMetrics"); 34 | private final AtomicLong readMessages = new AtomicLong(); 35 | private final AtomicLong writeMessages = new AtomicLong(); 36 | 37 | public MessageMetricsChannelHandler() { 38 | super(false); 39 | Runtime.getRuntime().addShutdownHook(new Thread("Metrics-Hook" + hashCode()) { 40 | @Override 41 | public void run() { 42 | logger.info("Metrics messages[read={}/count, write={}/count]", readMessages, writeMessages); 43 | } 44 | }); 45 | } 46 | 47 | public static MessageMetrics getOrSetMetrics(Channel channel) { 48 | Attribute attribute = channel.attr(ATTR_KEY_METRICS); 49 | MessageMetrics metrics = attribute.get(); 50 | if (metrics == null) { 51 | metrics = new MessageMetrics(); 52 | attribute.set(metrics); 53 | } 54 | return metrics; 55 | } 56 | 57 | @Override 58 | public void onMessageReceived(ChannelHandlerContext ctx, Object msg) throws Exception { 59 | MessageMetrics metrics = getOrSetMetrics(ctx.channel()); 60 | metrics.incrementRead(1); 61 | ctx.fireChannelRead(msg); 62 | } 63 | 64 | @Override 65 | protected void onMessageWriter(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 66 | MessageMetrics metrics = getOrSetMetrics(ctx.channel()); 67 | metrics.incrementWrote(1); 68 | if (promise.isVoid()) { 69 | ctx.write(msg, promise); 70 | } else { 71 | ctx.write(msg, promise).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); 72 | } 73 | } 74 | 75 | @Override 76 | public void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception { 77 | MessageMetrics metrics = getOrSetMetrics(ctx.channel()); 78 | readMessages.getAndAdd(metrics.messagesRead()); 79 | writeMessages.getAndAdd(metrics.messagesWrote()); 80 | ctx.close(promise); 81 | } 82 | 83 | public static class MessageMetrics { 84 | 85 | private long m_messagesRead; 86 | private long m_messageWrote; 87 | 88 | void incrementRead(long numMessages) { 89 | m_messagesRead += numMessages; 90 | } 91 | 92 | void incrementWrote(long numMessages) { 93 | m_messageWrote += numMessages; 94 | } 95 | 96 | public long messagesRead() { 97 | return m_messagesRead; 98 | } 99 | 100 | public long messagesWrote() { 101 | return m_messageWrote; 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/NamespaceUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.util.Map; 4 | import java.util.WeakHashMap; 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | /** 8 | * @author wangzihao 9 | */ 10 | public class NamespaceUtil { 11 | 12 | private static Namespace defaultNamespace; 13 | 14 | private NamespaceUtil() { 15 | } 16 | 17 | public static String newIdName(Class obj) { 18 | String name = firstUpperCase(obj.getSimpleName()); 19 | return getDefaultNamespace().newIdName(obj, name); 20 | } 21 | 22 | public static String firstUpperCase(String str) { 23 | if (str == null || str.isEmpty() || Character.isUpperCase(str.charAt(0))) { 24 | return str; 25 | } 26 | 27 | char[] cs = str.toCharArray(); 28 | cs[0] -= 32; 29 | return new String(cs); 30 | } 31 | 32 | public static String newIdName(String preName, Class obj) { 33 | return preName + newIdName(obj); 34 | } 35 | 36 | private static Namespace getDefaultNamespace() { 37 | if (defaultNamespace == null) { 38 | synchronized (NamespaceUtil.class) { 39 | if (defaultNamespace == null) { 40 | defaultNamespace = new Namespace(); 41 | } 42 | } 43 | } 44 | return defaultNamespace; 45 | } 46 | 47 | static class Namespace { 48 | private final Map idIncrMap; 49 | private final Map idMap; 50 | 51 | Namespace() { 52 | idIncrMap = new WeakHashMap<>(16); 53 | idMap = new WeakHashMap<>(16); 54 | } 55 | 56 | public String newIdName(Object obj, String name) { 57 | return name + "_" + newId(obj); 58 | } 59 | 60 | public int getId(Object obj) { 61 | Integer id = idMap.get(obj); 62 | if (id != null) { 63 | return id; 64 | } 65 | id = newId(obj); 66 | idMap.put(obj, id); 67 | return id; 68 | } 69 | 70 | private int newId(Object obj) { 71 | AtomicInteger atomicInteger = idIncrMap.get(obj); 72 | if (atomicInteger == null) { 73 | synchronized (idIncrMap) { 74 | atomicInteger = idIncrMap.get(obj); 75 | if (atomicInteger == null) { 76 | atomicInteger = new AtomicInteger(0); 77 | idIncrMap.put(obj, atomicInteger); 78 | } 79 | } 80 | } 81 | return atomicInteger.incrementAndGet(); 82 | } 83 | 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/NettyThreadPoolExecutor.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import io.netty.util.concurrent.DefaultThreadFactory; 4 | 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.RejectedExecutionHandler; 7 | import java.util.concurrent.ThreadPoolExecutor; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | /** 11 | * Use netty thread 12 | * 13 | * @author wangzihaogithub 2020-11-21 14 | * @see DefaultThreadFactory 15 | * @see io.netty.util.concurrent.FastThreadLocalThread 16 | * @see io.netty.util.internal.InternalThreadLocalMap#handlerSharableCache() 17 | */ 18 | public class NettyThreadPoolExecutor extends ThreadPoolExecutor { 19 | 20 | public NettyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, 21 | BlockingQueue workQueue, String poolName, int priority, boolean daemon, 22 | RejectedExecutionHandler handler) { 23 | super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new DefaultThreadFactory(poolName, daemon, priority, 24 | System.getSecurityManager() == null ? Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup()) { 25 | @Override 26 | protected Thread newThread(Runnable r, String name) { 27 | return new NettyThreadX(threadGroup, r, name); 28 | } 29 | }, handler); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/NettyThreadX.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import io.netty.util.concurrent.FastThreadLocalThread; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | import java.util.function.Consumer; 8 | 9 | /** 10 | * Created by wangzihao on 2018/9/9/009. 11 | */ 12 | public class NettyThreadX extends FastThreadLocalThread { 13 | private List> threadStopListenerList; 14 | 15 | public NettyThreadX() { 16 | super(); 17 | } 18 | 19 | public NettyThreadX(Runnable target) { 20 | super(target); 21 | } 22 | 23 | public NettyThreadX(ThreadGroup group, Runnable target) { 24 | super(group, target); 25 | } 26 | 27 | public NettyThreadX(String name) { 28 | super(name); 29 | } 30 | 31 | public NettyThreadX(ThreadGroup group, String name) { 32 | super(group, name); 33 | } 34 | 35 | public NettyThreadX(Runnable target, String name) { 36 | super(target, name); 37 | } 38 | 39 | public NettyThreadX(ThreadGroup group, Runnable target, String name) { 40 | super(group, target, name); 41 | } 42 | 43 | public NettyThreadX(ThreadGroup group, Runnable target, String name, long stackSize) { 44 | super(group, target, name, stackSize); 45 | } 46 | 47 | @Override 48 | public void run() { 49 | try { 50 | super.run(); 51 | } finally { 52 | if (threadStopListenerList != null) { 53 | for (Consumer threadStopListener : threadStopListenerList) { 54 | threadStopListener.accept(this); 55 | } 56 | } 57 | } 58 | } 59 | 60 | public void addThreadStopListener(Consumer threadStopListener) { 61 | if (threadStopListenerList == null) { 62 | threadStopListenerList = new ArrayList<>(); 63 | } 64 | threadStopListenerList.add(threadStopListener); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/Recyclable.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.util.function.Consumer; 4 | 5 | /** 6 | * recycled 7 | * 8 | * @author wangzihao 9 | */ 10 | public interface Recyclable { 11 | 12 | /** 13 | * recycle 14 | */ 15 | default void recycle() { 16 | } 17 | 18 | /** 19 | * async recycle 20 | * 21 | * @param consumer callback 22 | * @param last recycle object 23 | */ 24 | default void recycle(Consumer consumer) { 25 | if (consumer == null) { 26 | recycle(); 27 | } else { 28 | consumer.accept(null); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/RecyclableUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.EmptyByteBuf; 5 | import io.netty.util.ReferenceCounted; 6 | import io.netty.util.concurrent.FastThreadLocal; 7 | import io.netty.util.internal.InternalThreadLocalMap; 8 | import io.netty.util.internal.RecyclableArrayList; 9 | 10 | /** 11 | * RecyclableUtil 12 | * 13 | * @author wangzihao 14 | */ 15 | public class RecyclableUtil { 16 | 17 | private static final FastThreadLocal MULTI_VALUE_MAP_FAST_THREAD_LOCAL = new FastThreadLocal() { 18 | @Override 19 | protected LinkedMultiValueMap initialValue() throws Exception { 20 | return new LinkedMultiValueMap(); 21 | } 22 | }; 23 | 24 | public static RecyclableArrayList newRecyclableList(int minCapacity) { 25 | RecyclableArrayList finishListeners = RecyclableArrayList.newInstance(minCapacity); 26 | return finishListeners; 27 | } 28 | 29 | public static StringBuilder newStringBuilder() { 30 | return InternalThreadLocalMap.get().stringBuilder(); 31 | } 32 | 33 | public static LinkedMultiValueMap newLinkedMultiValueMap() { 34 | LinkedMultiValueMap map = MULTI_VALUE_MAP_FAST_THREAD_LOCAL.get(); 35 | map.clear(); 36 | return map; 37 | } 38 | 39 | public static ByteBuf newReadOnlyBuffer(byte[] bytes) { 40 | return ReadOnlyPooledHeapByteBuf.newInstance(bytes); 41 | } 42 | 43 | 44 | public static boolean release(Object obj) { 45 | if (obj == null) { 46 | return false; 47 | } 48 | if (obj instanceof EmptyByteBuf) { 49 | return true; 50 | } 51 | 52 | if (obj instanceof ReferenceCounted) { 53 | ReferenceCounted counted = (ReferenceCounted) obj; 54 | try { 55 | int refCnt = counted.refCnt(); 56 | if (refCnt > 0) { 57 | counted.release(); 58 | return true; 59 | } else { 60 | return false; 61 | } 62 | } catch (IllegalStateException e) { 63 | throw e; 64 | } 65 | } 66 | if (obj instanceof Recyclable) { 67 | ((Recyclable) obj).recycle(); 68 | return true; 69 | } 70 | return false; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/Recycler.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import io.netty.util.concurrent.FastThreadLocal; 4 | import io.netty.util.internal.PlatformDependent; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Date; 8 | import java.util.List; 9 | import java.util.Queue; 10 | import java.util.concurrent.CountDownLatch; 11 | import java.util.concurrent.atomic.LongAdder; 12 | import java.util.function.Supplier; 13 | 14 | /** 15 | * Collector (can control the number of instances, ensure the stability of instances, no explosion, no explosion, reduce the number of gc) 16 | * 17 | * @author wangzihao 18 | */ 19 | public class Recycler { 20 | public static final LongAdder HIT_COUNT = new LongAdder(); 21 | public static final LongAdder MISS_COUNT = new LongAdder(); 22 | private static final int DEFAULT_INSTANCE_COUNT = SystemPropertyUtil.getInt("netty-core.recyclerCount", 2); 23 | private static final boolean ENABLE = SystemPropertyUtil.getBoolean("netty-core.recyclerEnable", true); 24 | /** 25 | * All recyclers 26 | */ 27 | private static final List RECYCLER_LIST = new ArrayList<>(); 28 | private final int instanceCount; 29 | /** 30 | * The instance queue of the current object 31 | */ 32 | private final FastThreadLocal> queue = new FastThreadLocal>() { 33 | @Override 34 | protected Queue initialValue() throws Exception { 35 | // return new LinkedBlockingQueue<>(); 36 | return PlatformDependent.newFixedMpscQueue(instanceCount); 37 | } 38 | }; 39 | /** 40 | * New instance factory for the current object 41 | */ 42 | private Supplier supplier; 43 | // private StackTraceElement formStack; 44 | private Thread formThread; 45 | 46 | public Recycler(Supplier supplier) { 47 | this(DEFAULT_INSTANCE_COUNT, supplier); 48 | } 49 | 50 | public Recycler(int instanceCount, Supplier supplier) { 51 | this.instanceCount = instanceCount; 52 | this.supplier = supplier; 53 | RECYCLER_LIST.add(this); 54 | this.formThread = Thread.currentThread(); 55 | // this.formStack = formThread.getStackTrace()[3]; 56 | } 57 | 58 | /** 59 | * Gets a list of all recyclers 60 | * 61 | * @return List 62 | */ 63 | public static List getRecyclerList() { 64 | return RECYCLER_LIST; 65 | } 66 | 67 | public static void main(String[] args) throws InterruptedException { 68 | int count = 10000; 69 | Recycler recycler = new Recycler<>(() -> { 70 | try { 71 | Thread.sleep(10); 72 | } catch (InterruptedException e) { 73 | e.printStackTrace(); 74 | } 75 | return new Date(); 76 | }); 77 | 78 | 79 | ThreadPoolX threadPool = new ThreadPoolX("Test", 16); 80 | long begin = System.currentTimeMillis(); 81 | CountDownLatch latch = new CountDownLatch(count); 82 | for (int i = 0; i < count; i++) { 83 | threadPool.execute(() -> { 84 | Date instance = recycler.getInstance(); 85 | recycler.recycleInstance(instance); 86 | latch.countDown(); 87 | }); 88 | } 89 | latch.await(); 90 | 91 | long time = System.currentTimeMillis() - begin; 92 | System.out.printf("time = %d/ms, hit = %s/c, mis = %s/c\n", time, Recycler.HIT_COUNT, Recycler.MISS_COUNT); 93 | } 94 | 95 | /** 96 | * Get an instance 97 | * 98 | * @return object 99 | */ 100 | public T getInstance() { 101 | if (ENABLE) { 102 | T value = queue.get().poll(); 103 | if (value == null) { 104 | value = supplier.get(); 105 | MISS_COUNT.increment(); 106 | } else { 107 | HIT_COUNT.increment(); 108 | } 109 | return value; 110 | } else { 111 | return supplier.get(); 112 | } 113 | } 114 | 115 | /** 116 | * Recycling instance 117 | * 118 | * @param value value 119 | */ 120 | public void recycleInstance(T value) { 121 | queue.get().offer(value); 122 | } 123 | 124 | @Override 125 | public String toString() { 126 | return "Recycler{" + 127 | "size=" + queue.get().size() + 128 | // ", formStack=" + StringUtil.simpleClassName(formStack.getClassName()) + 129 | ", formThread=" + formThread + 130 | '}'; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/SystemPropertyUtil.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.security.AccessController; 4 | import java.security.PrivilegedAction; 5 | 6 | /** 7 | * Created by wangzihao on 2018/11/8/008. 8 | * A collection of utility methods to retrieve and parse the values of the Java system properties. 9 | */ 10 | public final class SystemPropertyUtil { 11 | private static final LoggerX LOGGER = LoggerFactoryX.getLogger(SystemPropertyUtil.class); 12 | 13 | private SystemPropertyUtil() { 14 | } 15 | 16 | /** 17 | * Returns {@code true} if and only if the system property with the specified {@code key} 18 | * exists. 19 | * 20 | * @param key key 21 | * @return contains boolean 22 | */ 23 | public static boolean contains(String key) { 24 | return get(key) != null; 25 | } 26 | 27 | /** 28 | * Returns the value of the Java system property with the specified 29 | * {@code key}, while falling back to {@code null} if the property access fails. 30 | * 31 | * @param key key 32 | * @return the property value or {@code null} 33 | */ 34 | public static String get(String key) { 35 | return get(key, null); 36 | } 37 | 38 | /** 39 | * Returns the value of the Java system property with the specified 40 | * 41 | * @param key while falling back to the specified default value if the property access fails. 42 | * @param def if there's no such property or if an access to the specified property is not allowed. 43 | * @return the property value. 44 | */ 45 | public static String get(final String key, String def) { 46 | if (key == null) { 47 | throw new NullPointerException("key"); 48 | } 49 | if (key.isEmpty()) { 50 | throw new IllegalArgumentException("key must not be empty."); 51 | } 52 | 53 | String value = null; 54 | try { 55 | if (System.getSecurityManager() == null) { 56 | value = System.getProperty(key); 57 | } else { 58 | value = AccessController.doPrivileged(new PrivilegedAction() { 59 | @Override 60 | public String run() { 61 | return System.getProperty(key); 62 | } 63 | }); 64 | } 65 | } catch (SecurityException e) { 66 | LOGGER.warn("Unable to retrieve a system property '{}'; default values will be used.", key, e); 67 | } 68 | 69 | if (value == null) { 70 | return def; 71 | } 72 | 73 | return value; 74 | } 75 | 76 | /** 77 | * Returns the value of the Java system property with the specified 78 | * 79 | * @param key while falling back to the specified default value if the property access fails. 80 | * @param def if there's no such property or if an access to the specified property is not allowed. 81 | * @return the property value. 82 | */ 83 | public static boolean getBoolean(String key, boolean def) { 84 | String value = get(key); 85 | if (value == null) { 86 | return def; 87 | } 88 | 89 | value = value.trim().toLowerCase(); 90 | if (value.isEmpty()) { 91 | return def; 92 | } 93 | 94 | if ("true".equals(value) || "yes".equals(value) || "1".equals(value)) { 95 | return true; 96 | } 97 | 98 | if ("false".equals(value) || "no".equals(value) || "0".equals(value)) { 99 | return false; 100 | } 101 | 102 | LOGGER.warn( 103 | "Unable to parse the boolean system property '{}':{} - using the default value: {}", 104 | key, value, def 105 | ); 106 | 107 | return def; 108 | } 109 | 110 | /** 111 | * Returns the value of the Java system property with the specified 112 | * 113 | * @param key while falling back to the specified default value if the property access fails. 114 | * @param def if there's no such property or if an access to the specified property is not allowed. 115 | * @return the property value. 116 | */ 117 | public static float getFloat(String key, float def) { 118 | String value = get(key); 119 | if (value == null) { 120 | return def; 121 | } 122 | 123 | value = value.trim(); 124 | try { 125 | return Float.parseFloat(value); 126 | } catch (Exception e) { 127 | // Ignore 128 | } 129 | 130 | LOGGER.warn( 131 | "Unable to parse the float system property '{}':{} - using the default value: {}", 132 | key, value, def 133 | ); 134 | 135 | return def; 136 | } 137 | 138 | /** 139 | * Returns the value of the Java system property with the specified 140 | * 141 | * @param key while falling back to the specified default value if the property access fails. 142 | * @param def if there's no such property or if an access to the specified property is not allowed. 143 | * @return the property value. 144 | */ 145 | public static int getInt(String key, int def) { 146 | String value = get(key); 147 | if (value == null) { 148 | return def; 149 | } 150 | 151 | value = value.trim(); 152 | try { 153 | return Integer.parseInt(value); 154 | } catch (Exception e) { 155 | // Ignore 156 | } 157 | 158 | LOGGER.warn( 159 | "Unable to parse the integer system property '{}':{} - using the default value: {}", 160 | key, value, def 161 | ); 162 | 163 | return def; 164 | } 165 | 166 | /** 167 | * Returns the value of the Java system property with the specified 168 | * 169 | * @param key while falling back to the specified default value if the property access fails. 170 | * @param def if there's no such property or if an access to the specified property is not allowed. 171 | * @return the property value. 172 | */ 173 | public static long getLong(String key, long def) { 174 | String value = get(key); 175 | if (value == null) { 176 | return def; 177 | } 178 | 179 | value = value.trim(); 180 | try { 181 | return Long.parseLong(value); 182 | } catch (Exception e) { 183 | // Ignore 184 | } 185 | 186 | LOGGER.warn( 187 | "Unable to parse the long integer system property '{}':{} - using the default value: {}", 188 | key, value, def 189 | ); 190 | 191 | return def; 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/ThreadFactoryX.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import io.netty.util.concurrent.DefaultThreadFactory; 4 | 5 | /** 6 | * Created by wangzihao on 2018/8/25/025. 7 | */ 8 | public class ThreadFactoryX extends DefaultThreadFactory implements java.util.concurrent.ThreadFactory { 9 | 10 | private final String preName; 11 | private boolean daemon = false; 12 | private ThreadGroup threadGroup; 13 | 14 | public ThreadFactoryX(String preName, Class poolType) { 15 | this(preName, poolType, Thread.MAX_PRIORITY, false); 16 | } 17 | 18 | public ThreadFactoryX(String preName, Class poolType, int priority, boolean daemon) { 19 | super(NamespaceUtil.newIdName(poolType), priority); 20 | this.preName = preName; 21 | this.daemon = daemon; 22 | this.threadGroup = System.getSecurityManager() == null ? 23 | Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup(); 24 | } 25 | 26 | public ThreadFactoryX(String poolName, String preName, boolean daemon) { 27 | super(poolName); 28 | this.preName = preName; 29 | this.threadGroup = System.getSecurityManager() == null ? 30 | Thread.currentThread().getThreadGroup() : System.getSecurityManager().getThreadGroup(); 31 | this.daemon = daemon; 32 | } 33 | 34 | @Override 35 | protected Thread newThread(Runnable r, String name) { 36 | Thread thread = new NettyThreadX(threadGroup, r, name); 37 | if (preName != null && preName.length() > 0) { 38 | thread.setName("NettyX-" + preName + "-" + thread.getName()); 39 | } 40 | if (daemon) { 41 | thread.setDaemon(true); 42 | } 43 | return thread; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/ThreadPoolX.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | import java.util.concurrent.RejectedExecutionException; 4 | import java.util.concurrent.RejectedExecutionHandler; 5 | import java.util.concurrent.ScheduledThreadPoolExecutor; 6 | import java.util.concurrent.ThreadPoolExecutor; 7 | 8 | /** 9 | * @author wangzihao 10 | */ 11 | public class ThreadPoolX extends ScheduledThreadPoolExecutor { 12 | 13 | private static ThreadPoolX DEFAULT_INSTANCE; 14 | 15 | public ThreadPoolX(int corePoolSize) { 16 | this("", corePoolSize); 17 | } 18 | 19 | public ThreadPoolX(String preName, int corePoolSize) { 20 | this(preName, corePoolSize, Thread.MAX_PRIORITY, false); 21 | } 22 | 23 | public ThreadPoolX(String preName, int corePoolSize, int priority, boolean daemon) { 24 | super(corePoolSize, new ThreadFactoryX(preName, ThreadPoolX.class, priority, daemon), new RejectedExecutionHandlerX()); 25 | } 26 | 27 | public static ThreadPoolX getDefaultInstance() { 28 | if (DEFAULT_INSTANCE == null) { 29 | synchronized (ThreadPoolX.class) { 30 | if (DEFAULT_INSTANCE == null) { 31 | DEFAULT_INSTANCE = new ThreadPoolX("Default", SystemPropertyUtil.getInt("netty-core.defaultThreadPoolCount", 3)); 32 | } 33 | } 34 | } 35 | return DEFAULT_INSTANCE; 36 | } 37 | 38 | @Override 39 | public void execute(Runnable command) { 40 | super.execute(command); 41 | } 42 | 43 | @Override 44 | protected void beforeExecute(Thread t, Runnable r) { 45 | // 46 | } 47 | 48 | @Override 49 | protected void afterExecute(Runnable r, Throwable t) { 50 | // 51 | } 52 | 53 | private static class RejectedExecutionHandlerX implements RejectedExecutionHandler { 54 | 55 | private RejectedExecutionHandlerX() { 56 | } 57 | 58 | @Override 59 | public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 60 | throw new RejectedExecutionException("Task " + r.toString() + " rejected from " + e.toString()); 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/core/util/Wrapper.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.core.util; 2 | 3 | /** 4 | * Wrapper 5 | * 6 | * @author wangzihao 7 | * 2018/7/31/031 8 | */ 9 | public interface Wrapper { 10 | 11 | /** 12 | * wrap 13 | * 14 | * @param source source object 15 | */ 16 | void wrap(T source); 17 | 18 | /** 19 | * get source object 20 | * 21 | * @return source object 22 | */ 23 | T unwrap(); 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/DispatcherChannelHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.core.AbstractChannelHandler; 4 | import com.github.netty.core.MessageToRunnable; 5 | import com.github.netty.core.util.RecyclableUtil; 6 | import com.github.netty.protocol.servlet.util.Protocol; 7 | import io.netty.channel.Channel; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.util.AttributeKey; 10 | 11 | import java.io.IOException; 12 | import java.util.concurrent.Executor; 13 | 14 | /** 15 | * Servlet processor (portal to the server) 16 | * 17 | * @author wangzihao 18 | * 2018/7/1/001 19 | */ 20 | public class DispatcherChannelHandler extends AbstractChannelHandler { 21 | public static final AttributeKey CHANNEL_ATTR_KEY_MESSAGE_TO_RUNNABLE = AttributeKey.valueOf(MessageToRunnable.class + "#MessageToRunnable"); 22 | protected final ServletContext servletContext; 23 | protected final long maxContentLength; 24 | protected final Protocol protocol; 25 | protected final boolean ssl; 26 | 27 | public DispatcherChannelHandler(ServletContext servletContext, long maxContentLength, Protocol protocol, boolean ssl) { 28 | super(false); 29 | this.servletContext = servletContext; 30 | this.maxContentLength = maxContentLength; 31 | this.protocol = protocol; 32 | this.ssl = ssl; 33 | } 34 | 35 | /** 36 | * Place the IO task package factory class on this connection 37 | * 38 | * @param channel channel 39 | * @param messageToRunnable messageToRunnable 40 | */ 41 | public static void setMessageToRunnable(Channel channel, MessageToRunnable messageToRunnable) { 42 | channel.attr(CHANNEL_ATTR_KEY_MESSAGE_TO_RUNNABLE).set(messageToRunnable); 43 | } 44 | 45 | /** 46 | * Pull out the IO task package factory class on this connection 47 | * 48 | * @param channel channel 49 | * @return MessageToRunnable 50 | */ 51 | public static MessageToRunnable getMessageToRunnable(Channel channel) { 52 | MessageToRunnable taskFactory = channel.attr(CHANNEL_ATTR_KEY_MESSAGE_TO_RUNNABLE).get(); 53 | return taskFactory; 54 | } 55 | 56 | @Override 57 | public void handlerAdded(ChannelHandlerContext ctx) { 58 | // Dynamic binding protocol for switching protocol 59 | DispatcherChannelHandler.setMessageToRunnable(ctx.channel(), new NettyMessageToServletRunnable(servletContext, maxContentLength, protocol, ssl)); 60 | } 61 | 62 | @Override 63 | protected void onMessageReceived(ChannelHandlerContext context, Object msg) { 64 | try { 65 | MessageToRunnable messageToRunnable = getMessageToRunnable(context.channel()); 66 | if (messageToRunnable != null) { 67 | Runnable runnable = messageToRunnable.onMessage(context, msg); 68 | if (runnable != null) { 69 | run(runnable); 70 | } 71 | } else { 72 | logger.warn("no handler message = {}", msg.getClass()); 73 | RecyclableUtil.release(msg); 74 | } 75 | } catch (Exception e) { 76 | RecyclableUtil.release(msg); 77 | context.pipeline().fireExceptionCaught(e); 78 | } 79 | } 80 | 81 | protected void run(Runnable task) { 82 | switch (protocol) { 83 | case h2c: 84 | case h2: { 85 | servletContext.getExecutor().execute(task); 86 | break; 87 | } 88 | default: { 89 | Executor executor = servletContext.getAsyncExecutor(); 90 | if (executor != null) { 91 | executor.execute(task); 92 | } else { 93 | task.run(); 94 | } 95 | } 96 | } 97 | } 98 | 99 | @Override 100 | public void channelInactive(ChannelHandlerContext context) throws Exception { 101 | MessageToRunnable messageToRunnable = getMessageToRunnable(context.channel()); 102 | if (messageToRunnable != null) { 103 | Runnable runnable = messageToRunnable.onClose(context); 104 | if (runnable != null) { 105 | run(runnable); 106 | } 107 | } 108 | super.channelInactive(context); 109 | } 110 | 111 | @Override 112 | public void exceptionCaught(ChannelHandlerContext context, Throwable cause) { 113 | if (cause.getClass().getName().startsWith("io.netty.handler.codec.http2")) { 114 | // ignore 115 | } else if (IOException.class.isAssignableFrom(cause.getClass())) { 116 | if (logger.isDebugEnabled()) { 117 | logger.debug("handler IOException. case={}, channel={}", cause.toString(), context.channel(), cause); 118 | } 119 | } else { 120 | logger.warn("handler exception. case={}, channel={}", cause.toString(), context.channel(), cause); 121 | } 122 | MessageToRunnable messageToRunnable = getMessageToRunnable(context.channel()); 123 | if (messageToRunnable != null) { 124 | Runnable runnable = messageToRunnable.onError(context, cause); 125 | if (runnable != null) { 126 | run(runnable); 127 | } 128 | } 129 | } 130 | 131 | public Protocol getProtocol() { 132 | return protocol; 133 | } 134 | 135 | public ServletContext getServletContext() { 136 | return servletContext; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/NettyOutputStream.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelProgressivePromise; 5 | import io.netty.handler.stream.*; 6 | import io.netty.util.concurrent.GenericFutureListener; 7 | import io.netty.util.concurrent.GenericProgressiveFutureListener; 8 | 9 | import javax.servlet.ServletOutputStream; 10 | import java.io.Closeable; 11 | import java.io.File; 12 | import java.io.Flushable; 13 | import java.io.IOException; 14 | import java.nio.ByteBuffer; 15 | import java.nio.MappedByteBuffer; 16 | 17 | /** 18 | * use netty zero copy 19 | * if you need flush {@link ServletOutputStream#flush()} 20 | * 21 | * @author wangzihaogithub 22 | * 2020-06-07 14:13:36 23 | */ 24 | public interface NettyOutputStream extends Flushable, Closeable { 25 | 26 | /** 27 | * batch send packet. Immediately other side peer receive write finish data 28 | * Try not to use this method, it is possible for maximum performance. 29 | * Unless you need to need to get to the end part of the data received immediately 30 | * 31 | * @throws IOException if close 32 | */ 33 | @Override 34 | void flush() throws IOException; 35 | 36 | /** 37 | * close http. 38 | * if keepAlive. It does not turn off tcpConnection. Otherwise, turn off tcpConnection 39 | */ 40 | @Override 41 | void close(); 42 | 43 | /** 44 | * direct write to tcp outputStream. 45 | * 46 | * @param httpBody jdk ByteBuffer httpBody 47 | * @return ChannelProgressivePromise 48 | * @throws IOException if close 49 | * @see MappedByteBuffer 50 | * @see ByteBuffer 51 | */ 52 | ChannelProgressivePromise write(ByteBuffer httpBody) throws IOException; 53 | 54 | /** 55 | * direct write to tcp outputStream 56 | * 57 | * @param httpBody netty ByteBuf httpBody 58 | * @return ChannelProgressivePromise 59 | * @throws IOException if close 60 | * @see ChunkedFile 61 | * @see ChunkedNioStream 62 | * @see ChunkedNioFile 63 | * @see ChunkedStream 64 | */ 65 | ChannelProgressivePromise write(ByteBuf httpBody) throws IOException; 66 | 67 | /** 68 | * use netty batch write 69 | * 70 | * @param httpBody ChunkedInput httpBody 71 | * @return ChannelProgressivePromise 72 | * @throws IOException if close 73 | * @see ChunkedFile 74 | * @see ChunkedNioStream 75 | * @see ChunkedNioFile 76 | * @see ChunkedStream 77 | */ 78 | ChannelProgressivePromise write(ChunkedInput httpBody) throws IOException; 79 | 80 | /** 81 | * use netty zero copy 82 | * 83 | * @param httpBody File httpBody 84 | * @param count count 85 | * @param position position 86 | * @return ChannelProgressivePromise {@link ChannelProgressivePromise#addListener(GenericFutureListener)} } 87 | * @throws IOException if close 88 | * @see GenericProgressiveFutureListener 89 | */ 90 | ChannelProgressivePromise write(File httpBody, long position, long count) throws IOException; 91 | 92 | /** 93 | * use netty zero copy 94 | * 95 | * @param httpBody File httpBody 96 | * @return ChannelProgressivePromise {@link ChannelProgressivePromise#addListener(GenericFutureListener)} } 97 | * @throws IOException if close 98 | * @see GenericProgressiveFutureListener 99 | */ 100 | ChannelProgressivePromise write(File httpBody) throws IOException; 101 | 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletErrorPage.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import java.io.UnsupportedEncodingException; 4 | import java.net.URLDecoder; 5 | 6 | /** 7 | * Error page 8 | * 9 | * @author wangzihao 10 | */ 11 | public class ServletErrorPage { 12 | private final int status; 13 | private final Class exception; 14 | private final String path; 15 | 16 | public ServletErrorPage(int status, Class exception, String path) { 17 | this.status = status; 18 | this.exception = exception; 19 | try { 20 | this.path = URLDecoder.decode(path, "UTF-8"); 21 | } catch (UnsupportedEncodingException e) { 22 | throw new IllegalStateException(e); 23 | } 24 | } 25 | 26 | public int getStatus() { 27 | return status; 28 | } 29 | 30 | public Class getException() { 31 | return exception; 32 | } 33 | 34 | public String getPath() { 35 | return path; 36 | } 37 | 38 | public String getExceptionName() { 39 | return (this.exception != null) ? this.exception.getName() : null; 40 | } 41 | 42 | public boolean isGlobal() { 43 | return (this.status == 0 && this.exception == null); 44 | } 45 | 46 | public String getExceptionType() { 47 | return (this.exception != null) ? this.exception.getName() : null; 48 | } 49 | 50 | @Override 51 | public boolean equals(Object o) { 52 | if (this == o) { 53 | return true; 54 | } 55 | if (!(o instanceof ServletErrorPage)) { 56 | return false; 57 | } 58 | 59 | ServletErrorPage errorPage = (ServletErrorPage) o; 60 | return status == errorPage.status && (exception != null ? exception.equals(errorPage.exception) : errorPage.exception == null) && (path != null ? path.equals(errorPage.path) : errorPage.path == null); 61 | } 62 | 63 | @Override 64 | public int hashCode() { 65 | int result = status; 66 | result = 31 * result + (exception != null ? exception.hashCode() : 0); 67 | result = 31 * result + (path != null ? path.hashCode() : 0); 68 | return result; 69 | } 70 | 71 | @Override 72 | public String toString() { 73 | return "ServletErrorPage{" + 74 | "status=" + status + 75 | ", exception=" + exception + 76 | ", path='" + path + '\'' + 77 | '}'; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletFilePart.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.core.util.CaseInsensitiveKeyMap; 4 | import com.github.netty.core.util.ResourceManager; 5 | import com.github.netty.protocol.servlet.util.HttpHeaderConstants; 6 | import io.netty.buffer.ByteBufInputStream; 7 | import io.netty.handler.codec.http.multipart.FileUpload; 8 | 9 | import javax.servlet.http.Part; 10 | import java.io.FileInputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | import java.util.Map; 16 | import java.util.function.Supplier; 17 | 18 | /** 19 | * formData File blocks 20 | * 21 | * @author wangzihao 22 | */ 23 | public class ServletFilePart implements Part { 24 | private final FileUpload fileUpload; 25 | private ResourceManager resourceManager; 26 | private final Supplier resourceManagerSupplier; 27 | private Map headerMap; 28 | 29 | public ServletFilePart(FileUpload fileUpload, Supplier resourceManagerSupplier) { 30 | this.fileUpload = fileUpload; 31 | this.resourceManagerSupplier = resourceManagerSupplier; 32 | } 33 | 34 | @Override 35 | public InputStream getInputStream() throws IOException { 36 | InputStream inputStream; 37 | if (fileUpload.isInMemory()) { 38 | inputStream = new ByteBufInputStream(fileUpload.getByteBuf().retainedDuplicate(), true); 39 | } else { 40 | inputStream = new FileInputStream(fileUpload.getFile()); 41 | } 42 | return inputStream; 43 | } 44 | 45 | @Override 46 | public String getContentType() { 47 | return fileUpload.getContentType(); 48 | } 49 | 50 | @Override 51 | public String getName() { 52 | return fileUpload.getName(); 53 | } 54 | 55 | @Override 56 | public String getSubmittedFileName() { 57 | return fileUpload.getFilename(); 58 | } 59 | 60 | @Override 61 | public long getSize() { 62 | return fileUpload.length(); 63 | } 64 | 65 | @Override 66 | public void write(String fileName) throws IOException { 67 | if (resourceManager == null) { 68 | resourceManager = resourceManagerSupplier.get(); 69 | } 70 | resourceManager.writeFile(getInputStream(), "/", fileName); 71 | } 72 | 73 | @Override 74 | public void delete() throws IOException { 75 | if (!fileUpload.isInMemory()) { 76 | fileUpload.delete(); 77 | } 78 | } 79 | 80 | @Override 81 | public String getHeader(String name) { 82 | return getHeaderMap().get(name); 83 | } 84 | 85 | @Override 86 | public Collection getHeaders(String name) { 87 | String value = getHeaderMap().get(name); 88 | if (value == null) { 89 | return Collections.emptyList(); 90 | } else { 91 | return Collections.singletonList(value); 92 | } 93 | } 94 | 95 | @Override 96 | public Collection getHeaderNames() { 97 | return getHeaderMap().keySet(); 98 | } 99 | 100 | private Map getHeaderMap() { 101 | if (headerMap == null) { 102 | Map headerMap = new CaseInsensitiveKeyMap<>(2); 103 | headerMap.put(HttpHeaderConstants.CONTENT_DISPOSITION.toString(), 104 | HttpHeaderConstants.FORM_DATA + "; " + HttpHeaderConstants.NAME + "=\"" + getName() + "\"; " + HttpHeaderConstants.FILENAME + "=\"" + fileUpload.getFilename() + "\""); 105 | headerMap.put(HttpHeaderConstants.CONTENT_LENGTH.toString(), String.valueOf(fileUpload.length())); 106 | if (fileUpload.getCharset() != null) { 107 | headerMap.put(HttpHeaderConstants.CONTENT_TYPE.toString(), HttpHeaderConstants.CHARSET.toString() + '=' + fileUpload.getCharset().name()); 108 | } 109 | this.headerMap = headerMap; 110 | } 111 | return headerMap; 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return fileUpload.toString(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletFilterChain.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.core.util.LoggerFactoryX; 4 | import com.github.netty.core.util.LoggerX; 5 | import com.github.netty.core.util.Recyclable; 6 | import com.github.netty.core.util.Recycler; 7 | import com.github.netty.protocol.servlet.util.FilterMapper; 8 | import com.github.netty.protocol.servlet.util.ServletUtil; 9 | 10 | import javax.servlet.*; 11 | import javax.servlet.http.HttpServletResponse; 12 | import java.io.IOException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** 17 | * The servlet filter chain 18 | * 19 | * @author wangzihao 20 | */ 21 | public class ServletFilterChain implements FilterChain, Recyclable { 22 | private static final LoggerX logger = LoggerFactoryX.getLogger(ServletEventListenerManager.class); 23 | private static final Recycler RECYCLER = new Recycler<>(ServletFilterChain::new); 24 | /** 25 | * Consider that each request is handled by only one thread, and that the ServletContext will create a new SimpleFilterChain object on each request 26 | * therefore, the FilterChain's Iterator is used as a private variable of the FilterChain, without thread safety problems 27 | */ 28 | final List> filterRegistrationList = new ArrayList<>(16); 29 | ServletRegistration servletRegistration; 30 | private ServletContext servletContext; 31 | 32 | // public static final Set FILTER_SET = new HashSet<>(); 33 | // public static final AtomicLong SERVLET_TIME = new AtomicLong(); 34 | // public static final AtomicLong FILTER_TIME = new AtomicLong(); 35 | // private long beginTime; 36 | private int pos; 37 | 38 | protected ServletFilterChain() { 39 | } 40 | 41 | public static ServletFilterChain newInstance(ServletContext servletContext, ServletRegistration servletRegistration) { 42 | ServletFilterChain instance = RECYCLER.getInstance(); 43 | instance.servletContext = servletContext; 44 | instance.servletRegistration = servletRegistration; 45 | // instance.beginTime = System.currentTimeMillis(); 46 | return instance; 47 | } 48 | 49 | /** 50 | * each Filter calls the FilterChain method after processing the request. 51 | * this should find the next Filter, call its doFilter() method. 52 | * if there is no next one, you should call the servlet's service() method 53 | */ 54 | @Override 55 | public void doFilter(ServletRequest request, ServletResponse response) throws IOException, ServletException { 56 | ServletEventListenerManager listenerManager = servletContext.servletEventListenerManager; 57 | 58 | //Initialization request 59 | if (pos == 0) { 60 | ServletHttpServletRequest httpServletRequest = ServletUtil.unWrapper(request); 61 | if (httpServletRequest != null) { 62 | httpServletRequest.multipartConfigElement = servletRegistration.multipartConfigElement; 63 | httpServletRequest.servletSecurityElement = servletRegistration.servletSecurityElement; 64 | } 65 | } 66 | 67 | //Initialization Servlet 68 | try { 69 | if (!servletRegistration.isInitServlet()) { 70 | synchronized (servletRegistration.getServlet()) { 71 | if (!servletRegistration.isInitServlet()) { 72 | servletRegistration.getServlet().init(servletRegistration.getServletConfig()); 73 | if (listenerManager.hasServletRequestListener()) { 74 | listenerManager.onServletRequestInitialized(new ServletRequestEvent(servletContext, request)); 75 | } 76 | } 77 | } 78 | servletRegistration.setInitServlet(true); 79 | } 80 | } catch (Throwable t) { 81 | String msg = String.format("servlet init fail! cant do filter() and service(). servlet = %s, class = %s, error = %s", 82 | servletRegistration.getName(), servletRegistration.getClassName(), t.toString()); 83 | logger.warn(msg, t); 84 | response.setCharacterEncoding("utf-8"); 85 | response.setContentType("text/html"); 86 | if (response instanceof HttpServletResponse) { 87 | ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); 88 | } 89 | response.getWriter().write( 90 | "\n" + 91 | "\n" + 92 | "\n" + 93 | " \n" + 94 | " Servlet init fail!\n" + 95 | "\n" + 96 | "\n" + 97 | "

" + msg + "

\n" + 98 | "\n" + 99 | ""); 100 | return; 101 | } 102 | 103 | // do filter() and service() 104 | if (pos < filterRegistrationList.size()) { 105 | FilterMapper.Element element = filterRegistrationList.get(pos); 106 | pos++; 107 | Filter filter = element.getObject().getFilter(); 108 | filter.doFilter(request, response, this); 109 | } else { 110 | try { 111 | servletRegistration.getServlet().service(request, response); 112 | } finally { 113 | if (listenerManager.hasServletRequestListener()) { 114 | listenerManager.onServletRequestDestroyed(new ServletRequestEvent(servletContext, request)); 115 | } 116 | } 117 | } 118 | } 119 | 120 | public boolean isFilterEnd() { 121 | return pos == filterRegistrationList.size(); 122 | } 123 | 124 | public ServletFilterRegistration getFilterRegistration() { 125 | if (isFilterEnd()) { 126 | return null; 127 | } 128 | return filterRegistrationList.get(pos).getObject(); 129 | } 130 | 131 | public ServletRegistration getServletRegistration() { 132 | return servletRegistration; 133 | } 134 | 135 | public List> getFilterRegistrationList() { 136 | return filterRegistrationList; 137 | } 138 | 139 | @Override 140 | public void recycle() { 141 | pos = 0; 142 | servletContext = null; 143 | filterRegistrationList.clear(); 144 | servletRegistration = null; 145 | RECYCLER.recycleInstance(this); 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletFilterRegistration.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.protocol.servlet.util.FilterMapper; 4 | 5 | import javax.servlet.DispatcherType; 6 | import javax.servlet.Filter; 7 | import javax.servlet.FilterConfig; 8 | import javax.servlet.FilterRegistration; 9 | import java.util.*; 10 | import java.util.concurrent.atomic.AtomicBoolean; 11 | 12 | /** 13 | * servlet Filter registration 14 | * 15 | * @author wangzihao 16 | * 2018/7/14/014 17 | */ 18 | public class ServletFilterRegistration implements FilterRegistration, FilterRegistration.Dynamic { 19 | 20 | final Set servletNameMappingSet = new HashSet<>(); 21 | private final String filterName; 22 | private final Filter filter; 23 | private final FilterConfig filterConfig; 24 | private final ServletContext servletContext; 25 | private final FilterMapper urlMapper; 26 | private final MappingSet mappingSet = new MappingSet(); 27 | private final AtomicBoolean initFilter = new AtomicBoolean(); 28 | private boolean asyncSupported = true; 29 | private Map initParameterMap = new LinkedHashMap<>(); 30 | 31 | public ServletFilterRegistration(String filterName, Filter servlet, ServletContext servletContext, FilterMapper urlMapper) { 32 | this.filterName = filterName; 33 | this.filter = servlet; 34 | this.servletContext = servletContext; 35 | this.urlMapper = urlMapper; 36 | this.filterConfig = new FilterConfig() { 37 | @Override 38 | public String getFilterName() { 39 | return ServletFilterRegistration.this.filterName; 40 | } 41 | 42 | @Override 43 | public ServletContext getServletContext() { 44 | return ServletFilterRegistration.this.servletContext; 45 | } 46 | 47 | @Override 48 | public String getInitParameter(String name) { 49 | return ServletFilterRegistration.this.getInitParameter(name); 50 | } 51 | 52 | @Override 53 | public Enumeration getInitParameterNames() { 54 | return Collections.enumeration(ServletFilterRegistration.this.getInitParameters().keySet()); 55 | } 56 | }; 57 | } 58 | 59 | public FilterConfig getFilterConfig() { 60 | return filterConfig; 61 | } 62 | 63 | public Filter getFilter() { 64 | return filter; 65 | } 66 | 67 | public boolean isAsyncSupported() { 68 | return asyncSupported; 69 | } 70 | 71 | @Override 72 | public void setAsyncSupported(boolean isAsyncSupported) { 73 | this.asyncSupported = isAsyncSupported; 74 | } 75 | 76 | public boolean isInitFilterCas(boolean expect, boolean update) { 77 | return initFilter.compareAndSet(expect, update); 78 | } 79 | 80 | public boolean isInitFilter() { 81 | return initFilter.get(); 82 | } 83 | 84 | @Override 85 | public String getName() { 86 | return filterName; 87 | } 88 | 89 | @Override 90 | public String getClassName() { 91 | return filter.getClass().getName(); 92 | } 93 | 94 | @Override 95 | public boolean setInitParameter(String name, String value) { 96 | return initParameterMap.put(name, value) != null; 97 | } 98 | 99 | @Override 100 | public String getInitParameter(String name) { 101 | return initParameterMap.get(name); 102 | } 103 | 104 | @Override 105 | public Set setInitParameters(Map initParameters) { 106 | this.initParameterMap = initParameters; 107 | return initParameterMap.keySet(); 108 | } 109 | 110 | @Override 111 | public Map getInitParameters() { 112 | return initParameterMap; 113 | } 114 | 115 | @Override 116 | public void addMappingForServletNames(EnumSet dispatcherTypes, boolean isMatchAfter, String... servletNames) { 117 | if (servletNames != null) { 118 | servletNameMappingSet.addAll(Arrays.asList(servletNames)); 119 | } 120 | } 121 | 122 | @Override 123 | public Collection getServletNameMappings() { 124 | return servletNameMappingSet; 125 | } 126 | 127 | @Override 128 | public void addMappingForUrlPatterns(EnumSet dispatcherTypes, boolean isMatchAfter, String... urlPatterns) { 129 | if (urlPatterns != null) { 130 | for (String urlPattern : urlPatterns) { 131 | mappingSet.add(urlPattern, isMatchAfter, dispatcherTypes); 132 | } 133 | } 134 | } 135 | 136 | @Override 137 | public Collection getUrlPatternMappings() { 138 | return mappingSet; 139 | } 140 | 141 | @Override 142 | public String toString() { 143 | return getName(); 144 | } 145 | 146 | class MappingSet extends LinkedHashSet { 147 | @Override 148 | public boolean add(String pattern) { 149 | return add(pattern, false, null); 150 | } 151 | 152 | @Override 153 | public boolean addAll(Collection c) { 154 | for (Object o : c) { 155 | add(o.toString()); 156 | } 157 | return c.size() > 0; 158 | } 159 | 160 | public boolean add(String pattern, boolean isMatchAfter, EnumSet dispatcherTypes) { 161 | urlMapper.addMapping(pattern, ServletFilterRegistration.this, filterName, isMatchAfter, dispatcherTypes); 162 | return super.add(pattern); 163 | } 164 | 165 | @Override 166 | public void clear() { 167 | urlMapper.clear(); 168 | super.clear(); 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletHttpAsyncResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.protocol.servlet.util.MediaType; 4 | 5 | import javax.servlet.ServletResponse; 6 | import javax.servlet.http.HttpServletResponseWrapper; 7 | import java.io.IOException; 8 | import java.io.PrintWriter; 9 | import java.nio.charset.Charset; 10 | 11 | /** 12 | * Servlet asynchronous response, (note: control of the output stream is transferred to the new servlet, and the original servlet can no longer manipulate the output stream) 13 | * 14 | * @author wangzihao 15 | * 2018/7/15/015 16 | */ 17 | public class ServletHttpAsyncResponse extends HttpServletResponseWrapper { 18 | private final ServletHttpExchange servletHttpExchange; 19 | private final ServletOutputStreamWrapper outWrapper = new ServletOutputStreamWrapper(null); 20 | private PrintWriter writer; 21 | 22 | public ServletHttpAsyncResponse(ServletHttpServletResponse response, ServletOutputStream outputStream) { 23 | super(response); 24 | this.servletHttpExchange = response.servletHttpExchange; 25 | this.outWrapper.wrap(outputStream); 26 | } 27 | 28 | @Override 29 | public ServletOutputStreamWrapper getOutputStream() throws IOException { 30 | return outWrapper; 31 | } 32 | 33 | @Override 34 | public int getBufferSize() { 35 | return 0; 36 | } 37 | 38 | @Override 39 | public void setBufferSize(int size) { 40 | 41 | } 42 | 43 | @Override 44 | public void reset() { 45 | checkCommitted(); 46 | super.reset(); 47 | if (outWrapper.unwrap() == null) { 48 | return; 49 | } 50 | outWrapper.resetBuffer(); 51 | } 52 | 53 | @Override 54 | public void resetBuffer() { 55 | checkCommitted(); 56 | if (outWrapper.unwrap() == null) { 57 | return; 58 | } 59 | outWrapper.resetBuffer(); 60 | } 61 | 62 | @Override 63 | public void flushBuffer() throws IOException { 64 | outWrapper.flush(); 65 | } 66 | 67 | @Override 68 | public PrintWriter getWriter() throws IOException { 69 | if (writer != null) { 70 | return writer; 71 | } 72 | 73 | String characterEncoding = getCharacterEncoding(); 74 | Charset charset; 75 | if (characterEncoding == null || characterEncoding.isEmpty()) { 76 | if (MediaType.isHtmlType(getContentType())) { 77 | characterEncoding = MediaType.DEFAULT_DOCUMENT_CHARACTER_ENCODING; 78 | charset = MediaType.DEFAULT_DOCUMENT_CHARACTER_ENCODING_CHARSET; 79 | } else { 80 | characterEncoding = servletHttpExchange.servletContext.responseCharacterEncoding; 81 | charset = servletHttpExchange.servletContext.responseCharacterEncodingCharset; 82 | } 83 | setCharacterEncoding(characterEncoding); 84 | } else { 85 | charset = Charset.forName(characterEncoding); 86 | } 87 | 88 | writer = new ServletPrintWriter(outWrapper, charset); 89 | return writer; 90 | } 91 | 92 | @Override 93 | public void setResponse(ServletResponse response) { 94 | throw new UnsupportedOperationException("Unsupported Method On Forward setResponse "); 95 | } 96 | 97 | /** 98 | * Check the submission status 99 | * 100 | * @throws IllegalStateException 101 | */ 102 | private void checkCommitted() throws IllegalStateException { 103 | if (isCommitted()) { 104 | throw new IllegalStateException("Cannot perform this operation after response has been committed"); 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletHttpForwardResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.protocol.servlet.util.MediaType; 4 | 5 | import javax.servlet.ServletResponse; 6 | import javax.servlet.http.HttpServletResponseWrapper; 7 | import java.io.IOException; 8 | import java.io.PrintWriter; 9 | import java.nio.charset.Charset; 10 | 11 | /** 12 | * Servlet response forwarding, (note: control of the output stream is transferred to the new servlet, and the original servlet can no longer operate the output stream) 13 | * 14 | * @author wangzihao 15 | * 2018/7/15/015 16 | */ 17 | public class ServletHttpForwardResponse extends HttpServletResponseWrapper { 18 | private final ServletHttpExchange servletHttpExchange; 19 | private final ServletOutputStreamWrapper outWrapper = new ServletOutputStreamWrapper(null); 20 | private PrintWriter writer; 21 | 22 | public ServletHttpForwardResponse(ServletHttpServletResponse response, ServletOutputStream outputStream) { 23 | super(response); 24 | this.servletHttpExchange = response.servletHttpExchange; 25 | this.outWrapper.wrap(outputStream); 26 | } 27 | 28 | @Override 29 | public ServletOutputStreamWrapper getOutputStream() throws IOException { 30 | return outWrapper; 31 | } 32 | 33 | @Override 34 | public int getBufferSize() { 35 | return 0; 36 | } 37 | 38 | @Override 39 | public void setBufferSize(int size) { 40 | 41 | } 42 | 43 | @Override 44 | public void reset() { 45 | checkCommitted(); 46 | super.reset(); 47 | if (outWrapper.unwrap() == null) { 48 | return; 49 | } 50 | outWrapper.resetBuffer(); 51 | } 52 | 53 | @Override 54 | public void resetBuffer() { 55 | checkCommitted(); 56 | if (outWrapper.unwrap() == null) { 57 | return; 58 | } 59 | outWrapper.resetBuffer(); 60 | } 61 | 62 | @Override 63 | public void flushBuffer() throws IOException { 64 | outWrapper.flush(); 65 | } 66 | 67 | @Override 68 | public PrintWriter getWriter() throws IOException { 69 | if (writer != null) { 70 | return writer; 71 | } 72 | 73 | String characterEncoding = getCharacterEncoding(); 74 | Charset charset; 75 | if (characterEncoding == null || characterEncoding.isEmpty()) { 76 | if (MediaType.isHtmlType(getContentType())) { 77 | characterEncoding = MediaType.DEFAULT_DOCUMENT_CHARACTER_ENCODING; 78 | charset = MediaType.DEFAULT_DOCUMENT_CHARACTER_ENCODING_CHARSET; 79 | } else { 80 | characterEncoding = servletHttpExchange.servletContext.responseCharacterEncoding; 81 | charset = servletHttpExchange.servletContext.responseCharacterEncodingCharset; 82 | } 83 | setCharacterEncoding(characterEncoding); 84 | } else { 85 | charset = Charset.forName(characterEncoding); 86 | } 87 | 88 | writer = new ServletPrintWriter(outWrapper, charset); 89 | return writer; 90 | } 91 | 92 | @Override 93 | public void setResponse(ServletResponse response) { 94 | throw new UnsupportedOperationException("Unsupported Method On Forward setResponse "); 95 | } 96 | 97 | /** 98 | * Check the submission status 99 | * 100 | * @throws IllegalStateException 101 | */ 102 | private void checkCommitted() throws IllegalStateException { 103 | if (isCommitted()) { 104 | throw new IllegalStateException("Cannot perform this operation after response has been committed"); 105 | } 106 | } 107 | 108 | } 109 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletHttpIncludeResponse.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import javax.servlet.http.Cookie; 4 | import javax.servlet.http.HttpServletResponse; 5 | import javax.servlet.http.HttpServletResponseWrapper; 6 | import java.io.IOException; 7 | import java.util.Locale; 8 | 9 | /** 10 | * Servlet response introduction 11 | * 12 | * @author wangzihao 13 | * 2018/7/15/015 14 | */ 15 | public class ServletHttpIncludeResponse extends HttpServletResponseWrapper { 16 | public ServletHttpIncludeResponse(HttpServletResponse response) { 17 | super(response); 18 | } 19 | 20 | /** 21 | * Disallow reset() calls on a included response. 22 | * 23 | * @throws IllegalStateException if the response has already 24 | * been committed 25 | */ 26 | @Override 27 | public void reset() { 28 | 29 | } 30 | 31 | 32 | /** 33 | * Disallow setContentLength(int) calls on an included 34 | * response. 35 | * 36 | * @param len The new content length 37 | */ 38 | @Override 39 | public void setContentLength(int len) { 40 | 41 | } 42 | 43 | 44 | /** 45 | * Disallow setContentLengthLong(long) calls on an included 46 | * response. 47 | * 48 | * @param len The new content length 49 | */ 50 | @Override 51 | public void setContentLengthLong(long len) { 52 | 53 | } 54 | 55 | 56 | /** 57 | * Disallow setContentType() calls on an included response. 58 | * 59 | * @param type The new content type 60 | */ 61 | @Override 62 | public void setContentType(String type) { 63 | 64 | } 65 | 66 | 67 | /** 68 | * Disallow setLocale() calls on an included response. 69 | * 70 | * @param loc The new locale 71 | */ 72 | @Override 73 | public void setLocale(Locale loc) { 74 | 75 | } 76 | 77 | 78 | /** 79 | * Ignore setBufferSize() calls on an included response. 80 | * 81 | * @param size The buffer size 82 | */ 83 | @Override 84 | public void setBufferSize(int size) { 85 | 86 | } 87 | 88 | 89 | // -------------------------------------------- HttpServletResponse Methods 90 | 91 | 92 | /** 93 | * Disallow addCookie() calls on an included response. 94 | * 95 | * @param cookie The new cookie 96 | */ 97 | @Override 98 | public void addCookie(Cookie cookie) { 99 | 100 | } 101 | 102 | 103 | /** 104 | * Disallow addDateHeader() calls on an included response. 105 | * 106 | * @param name The new header name 107 | * @param value The new header value 108 | */ 109 | @Override 110 | public void addDateHeader(String name, long value) { 111 | 112 | } 113 | 114 | 115 | /** 116 | * Disallow addHeader() calls on an included response. 117 | * 118 | * @param name The new header name 119 | * @param value The new header value 120 | */ 121 | @Override 122 | public void addHeader(String name, String value) { 123 | 124 | } 125 | 126 | 127 | /** 128 | * Disallow addIntHeader() calls on an included response. 129 | * 130 | * @param name The new header name 131 | * @param value The new header value 132 | */ 133 | @Override 134 | public void addIntHeader(String name, int value) { 135 | 136 | } 137 | 138 | 139 | /** 140 | * Disallow sendError() calls on an included response. 141 | * 142 | * @param sc The new status code 143 | * @throws IOException if an input/output error occurs 144 | */ 145 | @Override 146 | public void sendError(int sc) throws IOException { 147 | 148 | } 149 | 150 | 151 | /** 152 | * Disallow sendError() calls on an included response. 153 | * 154 | * @param sc The new status code 155 | * @param msg The new message 156 | * @throws IOException if an input/output error occurs 157 | */ 158 | @Override 159 | public void sendError(int sc, String msg) throws IOException { 160 | 161 | } 162 | 163 | 164 | /** 165 | * Disallow sendRedirect() calls on an included response. 166 | * 167 | * @param location The new location 168 | * @throws IOException if an input/output error occurs 169 | */ 170 | @Override 171 | public void sendRedirect(String location) throws IOException { 172 | 173 | } 174 | 175 | /** 176 | * Disallow setDateHeader() calls on an included response. 177 | * 178 | * @param name The new header name 179 | * @param value The new header value 180 | */ 181 | @Override 182 | public void setDateHeader(String name, long value) { 183 | 184 | } 185 | 186 | 187 | /** 188 | * Disallow setHeader() calls on an included response. 189 | * 190 | * @param name The new header name 191 | * @param value The new header value 192 | */ 193 | @Override 194 | public void setHeader(String name, String value) { 195 | 196 | } 197 | 198 | 199 | /** 200 | * Disallow setIntHeader() calls on an included response. 201 | * 202 | * @param name The new header name 203 | * @param value The new header value 204 | */ 205 | @Override 206 | public void setIntHeader(String name, int value) { 207 | 208 | } 209 | 210 | 211 | /** 212 | * Disallow setStatus() calls on an included response. 213 | * 214 | * @param sc The new status code 215 | */ 216 | @Override 217 | public void setStatus(int sc) { 218 | 219 | } 220 | 221 | 222 | /** 223 | * Disallow setStatus() calls on an included response. 224 | * 225 | * @param sc The new status code 226 | * @param msg The new message 227 | * @deprecated As of version 2.1, due to ambiguous meaning of the message 228 | * parameter. To set a status code use 229 | * setStatus(int), to send an error with a 230 | * description use sendError(int, String). 231 | */ 232 | @Deprecated 233 | @Override 234 | public void setStatus(int sc, String msg) { 235 | 236 | } 237 | 238 | } 239 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletOutputStreamWrapper.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.core.util.Recyclable; 4 | import com.github.netty.core.util.Wrapper; 5 | import io.netty.buffer.ByteBuf; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelProgressivePromise; 8 | import io.netty.handler.stream.ChunkedInput; 9 | 10 | import javax.servlet.WriteListener; 11 | import java.io.File; 12 | import java.io.IOException; 13 | import java.nio.ByteBuffer; 14 | import java.util.function.Consumer; 15 | 16 | /** 17 | * Servlets output streams (wrapper classes) that control access to the flow 18 | * 19 | * @author wangzihao 20 | */ 21 | public class ServletOutputStreamWrapper extends javax.servlet.ServletOutputStream 22 | implements Wrapper, Recyclable, NettyOutputStream { 23 | /** 24 | * The source data 25 | */ 26 | private ServletOutputStream source; 27 | /** 28 | * Pause symbol 29 | */ 30 | private boolean suspendFlag = false; 31 | 32 | private final ChannelFutureListener closeListener; 33 | 34 | public ServletOutputStreamWrapper(ChannelFutureListener closeListener) { 35 | this.closeListener = closeListener; 36 | } 37 | 38 | /** 39 | * Whether to pause the operation output stream 40 | * 41 | * @return boolean suspendFlag 42 | */ 43 | public boolean isSuspendFlag() { 44 | return suspendFlag; 45 | } 46 | 47 | /** 48 | * Set (on/off) to pause the output operation 49 | * 50 | * @param suspendFlag True = pause, false= resume 51 | */ 52 | public void setSuspendFlag(boolean suspendFlag) { 53 | this.suspendFlag = suspendFlag; 54 | } 55 | 56 | @Override 57 | public ChannelProgressivePromise write(ByteBuffer httpBody) throws IOException { 58 | return source.write(httpBody); 59 | } 60 | 61 | @Override 62 | public ChannelProgressivePromise write(ByteBuf httpBody) throws IOException { 63 | return source.write(httpBody); 64 | } 65 | 66 | @Override 67 | public ChannelProgressivePromise write(ChunkedInput httpBody) throws IOException { 68 | return source.write(httpBody); 69 | } 70 | 71 | @Override 72 | public ChannelProgressivePromise write(File file, long position, long count) throws IOException { 73 | return source.write(file, position, count); 74 | } 75 | 76 | @Override 77 | public ChannelProgressivePromise write(File file) throws IOException { 78 | return source.write(file); 79 | } 80 | 81 | @Override 82 | public boolean isReady() { 83 | return source != null && source.isReady(); 84 | } 85 | 86 | @Override 87 | public void setWriteListener(WriteListener listener) { 88 | if(source != null) { 89 | source.setWriteListener(listener); 90 | } 91 | } 92 | 93 | @Override 94 | public void write(int b) throws IOException { 95 | if (isSuspendFlag() || source == null) { 96 | return; 97 | } 98 | source.write(b); 99 | } 100 | 101 | @Override 102 | public void close() { 103 | if (isSuspendFlag() || source == null) { 104 | return; 105 | } 106 | source.close(); 107 | } 108 | 109 | @Override 110 | public void flush() throws IOException { 111 | if (isSuspendFlag() || source == null) { 112 | return; 113 | } 114 | source.flush(); 115 | } 116 | 117 | public void resetBuffer() { 118 | if (isSuspendFlag() || source == null) { 119 | return; 120 | } 121 | source.resetBuffer(); 122 | } 123 | 124 | @Override 125 | public void write(byte[] b, int off, int len) throws IOException { 126 | if (isSuspendFlag() || source == null) { 127 | return; 128 | } 129 | source.write(b, off, len); 130 | } 131 | 132 | @Override 133 | public void write(byte[] b) throws IOException { 134 | if (isSuspendFlag() || source == null) { 135 | return; 136 | } 137 | source.write(b); 138 | } 139 | 140 | @Override 141 | public void wrap(ServletOutputStream source) { 142 | if (closeListener != null) { 143 | source.setCloseListener(closeListener); 144 | } 145 | this.source = source; 146 | } 147 | 148 | @Override 149 | public ServletOutputStream unwrap() { 150 | return source; 151 | } 152 | 153 | @Override 154 | public void recycle(Consumer consumer) { 155 | ServletOutputStream out = source; 156 | if (out != null) { 157 | source = null; 158 | out.recycle(consumer); 159 | } else { 160 | consumer.accept(null); 161 | } 162 | suspendFlag = false; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletPrincipal.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import java.io.Serializable; 4 | import java.security.Principal; 5 | 6 | /** 7 | * The servlet identity 8 | * 9 | * @author wangzihao 10 | */ 11 | public class ServletPrincipal implements Principal, Serializable { 12 | 13 | private final String name; 14 | private String password; 15 | 16 | public ServletPrincipal(String name, String password) { 17 | if (name == null) { 18 | throw new NullPointerException("null name is illegal"); 19 | } 20 | this.name = name; 21 | this.password = password; 22 | } 23 | 24 | /** 25 | * Compares this principal to the specified object. 26 | * 27 | * @param object The object to compare this principal against. 28 | * @return true if they are equal; false otherwise. 29 | */ 30 | @Override 31 | public boolean equals(Object object) { 32 | if (this == object) { 33 | return true; 34 | } 35 | if (object instanceof ServletPrincipal) { 36 | return name.equals(((ServletPrincipal) object).getName()); 37 | } 38 | return false; 39 | } 40 | 41 | /** 42 | * Returns a hash code for this principal. 43 | * 44 | * @return The principal's hash code. 45 | */ 46 | @Override 47 | public int hashCode() { 48 | return name.hashCode(); 49 | } 50 | 51 | /** 52 | * Returns the name of this principal. 53 | * 54 | * @return The principal's name. 55 | */ 56 | @Override 57 | public String getName() { 58 | return name; 59 | } 60 | 61 | public String getPassword() { 62 | return password; 63 | } 64 | 65 | /** 66 | * Returns a string representation of this principal. 67 | * 68 | * @return The principal's name. 69 | */ 70 | @Override 71 | public String toString() { 72 | return name; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletPrintWriter.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import java.io.*; 4 | import java.nio.charset.Charset; 5 | import java.util.Formatter; 6 | import java.util.Locale; 7 | 8 | /** 9 | * Printing flow 10 | * 11 | * @author wangzihao 12 | */ 13 | public class ServletPrintWriter extends PrintWriter { 14 | private static final Writer EMPTY_WRITER = new StringWriter(0); 15 | private final OutputStream out; 16 | private final Charset charset; 17 | private final String lineSeparator = System.lineSeparator(); 18 | private boolean error = false; 19 | 20 | ServletPrintWriter(OutputStream out, Charset charset) { 21 | super(EMPTY_WRITER, false); 22 | this.out = out; 23 | this.charset = charset; 24 | } 25 | 26 | @Override 27 | public void flush() { 28 | try { 29 | out.flush(); 30 | } catch (IOException e) { 31 | error = true; 32 | } 33 | } 34 | 35 | @Override 36 | public void close() { 37 | try { 38 | out.close(); 39 | } catch (IOException e) { 40 | error = true; 41 | } 42 | } 43 | 44 | @Override 45 | public boolean checkError() { 46 | return error; 47 | } 48 | 49 | @Override 50 | protected void setError() { 51 | if (error) { 52 | return; 53 | } 54 | this.error = true; 55 | } 56 | 57 | @Override 58 | protected void clearError() { 59 | error = false; 60 | } 61 | 62 | @Override 63 | public void write(int c) { 64 | write(String.valueOf((char) c)); 65 | } 66 | 67 | @Override 68 | public void write(char[] buf, int off, int len) { 69 | write(String.valueOf(buf, off, len)); 70 | } 71 | 72 | @Override 73 | public void write(char[] buf) { 74 | write(String.valueOf(buf)); 75 | } 76 | 77 | @Override 78 | public void write(String s, int off, int len) { 79 | String writeStr; 80 | if (off == 0 && s.length() == len) { 81 | writeStr = s; 82 | } else { 83 | writeStr = s.substring(off, off + len); 84 | } 85 | byte[] bytes = writeStr.getBytes(charset); 86 | 87 | try { 88 | out.write(bytes); 89 | } catch (IOException e) { 90 | setError(); 91 | } 92 | } 93 | 94 | @Override 95 | public void write(String s) { 96 | write(s, 0, s.length()); 97 | } 98 | 99 | @Override 100 | public void print(boolean b) { 101 | write(b ? "true" : "false"); 102 | } 103 | 104 | @Override 105 | public void print(char c) { 106 | write(String.valueOf(c)); 107 | } 108 | 109 | @Override 110 | public void print(int i) { 111 | write(String.valueOf(i)); 112 | } 113 | 114 | @Override 115 | public void print(long l) { 116 | write(String.valueOf(l)); 117 | } 118 | 119 | @Override 120 | public void print(float f) { 121 | write(String.valueOf(f)); 122 | } 123 | 124 | @Override 125 | public void print(double d) { 126 | write(String.valueOf(d)); 127 | } 128 | 129 | @Override 130 | public void print(char[] s) { 131 | write(s); 132 | } 133 | 134 | @Override 135 | public void print(String s) { 136 | write(s); 137 | } 138 | 139 | @Override 140 | public void print(Object obj) { 141 | write(String.valueOf(obj)); 142 | } 143 | 144 | @Override 145 | public void println() { 146 | write(lineSeparator); 147 | } 148 | 149 | @Override 150 | public void println(boolean b) { 151 | write((b ? "true" : "false") + lineSeparator); 152 | } 153 | 154 | @Override 155 | public void println(char x) { 156 | write(x + lineSeparator); 157 | } 158 | 159 | @Override 160 | public void println(int x) { 161 | write(x + lineSeparator); 162 | } 163 | 164 | @Override 165 | public void println(long x) { 166 | write(x + lineSeparator); 167 | } 168 | 169 | @Override 170 | public void println(float x) { 171 | write(x + lineSeparator); 172 | } 173 | 174 | @Override 175 | public void println(double x) { 176 | write(x + lineSeparator); 177 | } 178 | 179 | @Override 180 | public void println(char[] x) { 181 | write(String.valueOf(x) + lineSeparator); 182 | } 183 | 184 | @Override 185 | public void println(String x) { 186 | write(x + lineSeparator); 187 | } 188 | 189 | @Override 190 | public void println(Object x) { 191 | write(x + lineSeparator); 192 | } 193 | 194 | @Override 195 | public PrintWriter printf(String format, Object... args) { 196 | format(Locale.getDefault(), format, args); 197 | return this; 198 | } 199 | 200 | @Override 201 | public PrintWriter printf(Locale l, String format, Object... args) { 202 | format(l, format, args); 203 | return this; 204 | } 205 | 206 | @Override 207 | public PrintWriter format(String format, Object... args) { 208 | format(Locale.getDefault(), format, args); 209 | return this; 210 | } 211 | 212 | @Override 213 | public PrintWriter format(Locale l, String format, Object... args) { 214 | StringBuilder sb = new StringBuilder(); 215 | Formatter formatter = new Formatter(sb, l); 216 | formatter.format(l, format, args); 217 | write(sb.toString()); 218 | return this; 219 | } 220 | 221 | @Override 222 | public PrintWriter append(CharSequence csq) { 223 | if (csq == null) { 224 | write("null"); 225 | } else { 226 | write(csq.toString()); 227 | } 228 | return this; 229 | } 230 | 231 | @Override 232 | public PrintWriter append(CharSequence csq, int start, int end) { 233 | CharSequence cs = (csq == null ? "null" : csq); 234 | write(cs.subSequence(start, end).toString()); 235 | return this; 236 | } 237 | 238 | @Override 239 | public PrintWriter append(char c) { 240 | write(c); 241 | return this; 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletRegistration.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.protocol.servlet.util.UrlMapper; 4 | 5 | import javax.servlet.MultipartConfigElement; 6 | import javax.servlet.Servlet; 7 | import javax.servlet.ServletConfig; 8 | import javax.servlet.ServletSecurityElement; 9 | import java.util.*; 10 | 11 | /** 12 | * The servlet supportPipeline 13 | * 14 | * @author wangzihao 15 | * 2018/7/14/014 16 | */ 17 | public class ServletRegistration implements javax.servlet.ServletRegistration, javax.servlet.ServletRegistration.Dynamic { 18 | private final Servlet servlet; 19 | private final String servletName; 20 | private final ServletConfig servletConfig; 21 | private final ServletContext servletContext; 22 | private final UrlMapper urlMapper; 23 | MultipartConfigElement multipartConfigElement; 24 | ServletSecurityElement servletSecurityElement; 25 | private String roleName; 26 | boolean asyncSupported = true; 27 | private int loadOnStartup = -1; 28 | private Map initParameterMap = new LinkedHashMap<>(); 29 | private final Set mappingSet = new LinkedHashSet() { 30 | @Override 31 | public boolean add(String pattern) { 32 | urlMapper.addMapping(pattern, ServletRegistration.this, servletName); 33 | return super.add(pattern); 34 | } 35 | 36 | @Override 37 | public boolean addAll(Collection c) { 38 | for (Object o : c) { 39 | add(o.toString()); 40 | } 41 | return !c.isEmpty(); 42 | } 43 | 44 | @Override 45 | public void clear() { 46 | urlMapper.clear(); 47 | super.clear(); 48 | } 49 | }; 50 | private volatile boolean initServlet = false; 51 | private final Set servletSecuritys = new LinkedHashSet<>(); 52 | 53 | public ServletRegistration(String servletName, Servlet servlet, ServletContext servletContext, UrlMapper urlMapper) { 54 | this.servletName = servletName; 55 | this.servlet = Objects.requireNonNull(servlet); 56 | this.servletContext = servletContext; 57 | this.urlMapper = urlMapper; 58 | this.servletConfig = new ServletConfig() { 59 | @Override 60 | public String getServletName() { 61 | return ServletRegistration.this.servletName; 62 | } 63 | 64 | @Override 65 | public javax.servlet.ServletContext getServletContext() { 66 | return ServletRegistration.this.servletContext; 67 | } 68 | 69 | @Override 70 | public String getInitParameter(String name) { 71 | return ServletRegistration.this.getInitParameter(name); 72 | } 73 | 74 | @Override 75 | public Enumeration getInitParameterNames() { 76 | return Collections.enumeration(ServletRegistration.this.getInitParameters().keySet()); 77 | } 78 | }; 79 | } 80 | 81 | public ServletSecurityElement getServletSecurityElement() { 82 | return servletSecurityElement; 83 | } 84 | 85 | public MultipartConfigElement getMultipartConfigElement() { 86 | return multipartConfigElement; 87 | } 88 | 89 | public ServletConfig getServletConfig() { 90 | return servletConfig; 91 | } 92 | 93 | public Servlet getServlet() { 94 | return servlet; 95 | } 96 | 97 | public Boolean isAsyncSupported() { 98 | return asyncSupported; 99 | } 100 | 101 | public int getLoadOnStartup() { 102 | return loadOnStartup; 103 | } 104 | 105 | @Override 106 | public void setLoadOnStartup(int loadOnStartup) { 107 | this.loadOnStartup = loadOnStartup; 108 | } 109 | 110 | public boolean isInitServlet() { 111 | return initServlet; 112 | } 113 | 114 | public void setInitServlet(boolean initServlet) { 115 | this.initServlet = initServlet; 116 | } 117 | 118 | @Override 119 | public Set addMapping(String... urlPatterns) { 120 | mappingSet.addAll(Arrays.asList(urlPatterns)); 121 | return mappingSet; 122 | } 123 | 124 | @Override 125 | public Collection getMappings() { 126 | return mappingSet; 127 | } 128 | 129 | @Override 130 | public String getRunAsRole() { 131 | return roleName; 132 | } 133 | 134 | @Override 135 | public void setRunAsRole(String roleName) { 136 | this.roleName = roleName; 137 | } 138 | 139 | @Override 140 | public String getName() { 141 | return servletName; 142 | } 143 | 144 | @Override 145 | public String getClassName() { 146 | return servlet.getClass().getName(); 147 | } 148 | 149 | @Override 150 | public boolean setInitParameter(String name, String value) { 151 | return initParameterMap.put(name, value) != null; 152 | } 153 | 154 | @Override 155 | public String getInitParameter(String name) { 156 | return initParameterMap.get(name); 157 | } 158 | 159 | @Override 160 | public Set setInitParameters(Map initParameters) { 161 | this.initParameterMap = initParameters; 162 | return initParameterMap.keySet(); 163 | } 164 | 165 | @Override 166 | public Map getInitParameters() { 167 | return initParameterMap; 168 | } 169 | 170 | @Override 171 | public Set setServletSecurity(ServletSecurityElement constraint) { 172 | this.servletSecurityElement = constraint; 173 | servletSecuritys.addAll(servletSecurityElement.getMethodNames()); 174 | return servletSecuritys; 175 | } 176 | 177 | @Override 178 | public void setMultipartConfig(MultipartConfigElement multipartConfig) { 179 | this.multipartConfigElement = multipartConfig; 180 | } 181 | 182 | @Override 183 | public void setAsyncSupported(boolean isAsyncSupported) { 184 | this.asyncSupported = isAsyncSupported; 185 | } 186 | 187 | @Override 188 | public String toString() { 189 | return getName(); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletResetBufferIOException.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import javax.servlet.http.HttpServletResponse; 4 | import java.io.IOException; 5 | 6 | /** 7 | * @author wangzihaogithub 8 | * 2020-06-07 00:07:16 9 | * @see HttpServletResponse#resetBuffer() 10 | */ 11 | public class ServletResetBufferIOException extends IOException { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletSessionCookieConfig.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import javax.servlet.SessionCookieConfig; 4 | 5 | /** 6 | * Configuration of session cookies 7 | * 8 | * @author wangzihao 9 | * 2018/7/14/014 10 | */ 11 | public class ServletSessionCookieConfig implements SessionCookieConfig { 12 | boolean httpOnly; 13 | boolean secure; 14 | /** 15 | * Unit seconds 16 | */ 17 | private int maxAge = -1; 18 | private String comment; 19 | String domain; 20 | private String name; 21 | private String path; 22 | 23 | public ServletSessionCookieConfig() { 24 | } 25 | 26 | 27 | @Override 28 | public String getComment() { 29 | return comment; 30 | } 31 | 32 | @Override 33 | public void setComment(String comment) { 34 | this.comment = comment; 35 | } 36 | 37 | @Override 38 | public String getDomain() { 39 | return domain; 40 | } 41 | 42 | @Override 43 | public void setDomain(String domain) { 44 | this.domain = domain; 45 | } 46 | 47 | @Override 48 | public int getMaxAge() { 49 | return maxAge; 50 | } 51 | 52 | @Override 53 | public void setMaxAge(int maxAge) { 54 | this.maxAge = maxAge; 55 | } 56 | 57 | @Override 58 | public String getName() { 59 | return name; 60 | } 61 | 62 | @Override 63 | public void setName(String name) { 64 | this.name = name; 65 | } 66 | 67 | @Override 68 | public String getPath() { 69 | return path; 70 | } 71 | 72 | @Override 73 | public void setPath(String path) { 74 | this.path = path; 75 | } 76 | 77 | @Override 78 | public boolean isHttpOnly() { 79 | return httpOnly; 80 | } 81 | 82 | @Override 83 | public void setHttpOnly(boolean httpOnly) { 84 | this.httpOnly = httpOnly; 85 | } 86 | 87 | @Override 88 | public boolean isSecure() { 89 | return secure; 90 | } 91 | 92 | @Override 93 | public void setSecure(boolean secure) { 94 | this.secure = secure; 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/ServletTextPart.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.core.util.CaseInsensitiveKeyMap; 4 | import com.github.netty.core.util.ResourceManager; 5 | import com.github.netty.protocol.servlet.util.HttpHeaderConstants; 6 | import io.netty.buffer.ByteBufInputStream; 7 | import io.netty.handler.codec.http.multipart.Attribute; 8 | 9 | import javax.servlet.http.Part; 10 | import java.io.FileInputStream; 11 | import java.io.IOException; 12 | import java.io.InputStream; 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | import java.util.Map; 16 | import java.util.function.Supplier; 17 | 18 | /** 19 | * formData Text block 20 | * 21 | * @author wangzihao 22 | */ 23 | public class ServletTextPart implements Part { 24 | private final Attribute attribute; 25 | private ResourceManager resourceManager; 26 | private final Supplier resourceManagerSupplier; 27 | private Map headerMap; 28 | 29 | public ServletTextPart(Attribute attribute, Supplier resourceManagerSupplier) { 30 | this.attribute = attribute; 31 | this.resourceManagerSupplier = resourceManagerSupplier; 32 | } 33 | 34 | @Override 35 | public InputStream getInputStream() throws IOException { 36 | InputStream inputStream; 37 | if (attribute.isInMemory()) { 38 | inputStream = new ByteBufInputStream(attribute.getByteBuf().retainedDuplicate(), true); 39 | } else { 40 | inputStream = new FileInputStream(attribute.getFile()); 41 | } 42 | return inputStream; 43 | } 44 | 45 | @Override 46 | public String getContentType() { 47 | return null; 48 | } 49 | 50 | @Override 51 | public String getName() { 52 | return attribute.getName(); 53 | } 54 | 55 | @Override 56 | public String getSubmittedFileName() { 57 | return null; 58 | } 59 | 60 | @Override 61 | public long getSize() { 62 | return attribute.length(); 63 | } 64 | 65 | @Override 66 | public void write(String fileName) throws IOException { 67 | if (resourceManager == null) { 68 | resourceManager = resourceManagerSupplier.get(); 69 | } 70 | resourceManager.writeFile(getInputStream(), "/", fileName); 71 | } 72 | 73 | @Override 74 | public void delete() throws IOException { 75 | if (!attribute.isInMemory()) { 76 | attribute.delete(); 77 | } 78 | } 79 | 80 | @Override 81 | public String getHeader(String name) { 82 | return getHeaderMap().get(name); 83 | } 84 | 85 | @Override 86 | public Collection getHeaders(String name) { 87 | String value = getHeaderMap().get(name); 88 | if (value == null) { 89 | return Collections.emptyList(); 90 | } else { 91 | return Collections.singletonList(value); 92 | } 93 | } 94 | 95 | @Override 96 | public Collection getHeaderNames() { 97 | return getHeaderMap().keySet(); 98 | } 99 | 100 | private Map getHeaderMap() { 101 | if (headerMap == null) { 102 | Map headerMap = new CaseInsensitiveKeyMap<>(2); 103 | headerMap.put(HttpHeaderConstants.CONTENT_DISPOSITION.toString(), 104 | HttpHeaderConstants.FORM_DATA + "; " + HttpHeaderConstants.NAME + "=\"" + getName() + "\"; "); 105 | headerMap.put(HttpHeaderConstants.CONTENT_LENGTH.toString(), attribute.length() + ""); 106 | if (attribute.getCharset() != null) { 107 | headerMap.put(HttpHeaderConstants.CONTENT_TYPE.toString(), HttpHeaderConstants.CHARSET.toString() + '=' + attribute.getCharset().name()); 108 | } 109 | this.headerMap = headerMap; 110 | } 111 | return headerMap; 112 | } 113 | 114 | @Override 115 | public String toString() { 116 | return attribute.toString(); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/Session.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import java.io.Serializable; 4 | import java.util.Map; 5 | 6 | /** 7 | * Session entity class 8 | * 9 | * @author wangzihao 10 | * 2018/8/18/018 11 | */ 12 | public class Session implements Serializable { 13 | private static final long serialVersionUID = 1L; 14 | 15 | private String id; 16 | private Map attributeMap; 17 | private long creationTime; 18 | private long lastAccessedTime; 19 | /** 20 | * Unit seconds 21 | */ 22 | private int maxInactiveInterval; 23 | private int accessCount; 24 | 25 | public Session() { 26 | } 27 | 28 | public Session(String id, int maxInactiveInterval) { 29 | this.id = id; 30 | long currTime = System.currentTimeMillis(); 31 | this.creationTime = currTime; 32 | this.lastAccessedTime = currTime; 33 | this.maxInactiveInterval = maxInactiveInterval; 34 | } 35 | 36 | /** 37 | * The validity of 38 | * 39 | * @return True is valid, false is not 40 | */ 41 | public boolean isValid() { 42 | return System.currentTimeMillis() < (creationTime + (maxInactiveInterval * 1000L)); 43 | } 44 | 45 | public String getId() { 46 | return id; 47 | } 48 | 49 | public void setId(String id) { 50 | this.id = id; 51 | } 52 | 53 | public Map getAttributeMap() { 54 | return attributeMap; 55 | } 56 | 57 | public void setAttributeMap(Map attributeMap) { 58 | this.attributeMap = attributeMap; 59 | } 60 | 61 | public long getCreationTime() { 62 | return creationTime; 63 | } 64 | 65 | public void setCreationTime(long creationTime) { 66 | this.creationTime = creationTime; 67 | } 68 | 69 | public long getLastAccessedTime() { 70 | return lastAccessedTime; 71 | } 72 | 73 | public void setLastAccessedTime(long lastAccessedTime) { 74 | this.lastAccessedTime = lastAccessedTime; 75 | } 76 | 77 | public int getMaxInactiveInterval() { 78 | return maxInactiveInterval; 79 | } 80 | 81 | public void setMaxInactiveInterval(int maxInactiveInterval) { 82 | this.maxInactiveInterval = maxInactiveInterval; 83 | } 84 | 85 | public int getAccessCount() { 86 | return accessCount; 87 | } 88 | 89 | public void setAccessCount(int accessCount) { 90 | this.accessCount = accessCount; 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return "Session{" + 96 | "id='" + id + '\'' + 97 | ", accessCount=" + accessCount + 98 | '}'; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/SessionLocalMemoryServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import com.github.netty.core.util.ExpiryLRUMap; 4 | import com.github.netty.core.util.NamespaceUtil; 5 | 6 | import java.util.List; 7 | import java.util.RandomAccess; 8 | 9 | /** 10 | * Local memory session service 11 | * 12 | * @author wangzihao 13 | * 2018/8/19/019 14 | */ 15 | public class SessionLocalMemoryServiceImpl implements SessionService { 16 | private final String name = NamespaceUtil.newIdName(getClass()); 17 | private final ExpiryLRUMap sessionMap = new ExpiryLRUMap<>(); 18 | private final ServletContext servletContext; 19 | 20 | public SessionLocalMemoryServiceImpl(ServletContext servletContext) { 21 | this.servletContext = servletContext; 22 | sessionMap.setOnExpiryConsumer(this::onInvalidate); 23 | sessionMap.setOnRemoveConsumer(this::onInvalidate); 24 | } 25 | 26 | private void onInvalidate(ExpiryLRUMap.Node node) { 27 | if (node.isCovered()) { 28 | return; 29 | } 30 | ServletHttpSession httpSession = new ServletHttpSession(node.getData(), servletContext); 31 | if (httpSession.hasListener()) { 32 | servletContext.getDefaultExecutorSupplier().get().execute(httpSession::invalidate0); 33 | } else { 34 | httpSession.invalidate0(); 35 | } 36 | } 37 | 38 | @Override 39 | public void saveSession(Session session) { 40 | if (session == null) { 41 | return; 42 | } 43 | sessionMap.put(session.getId(), session, session.getMaxInactiveInterval() * 1000L); 44 | } 45 | 46 | @Override 47 | public void removeSession(String sessionId) { 48 | sessionMap.remove(sessionId); 49 | } 50 | 51 | @Override 52 | public void removeSessionBatch(List sessionIdList) { 53 | if (sessionIdList == null || sessionIdList.isEmpty()) { 54 | return; 55 | } 56 | 57 | //Reduce the creation of iterators 58 | if (sessionIdList instanceof RandomAccess) { 59 | int size = sessionIdList.size(); 60 | for (int i = 0; i < size; i++) { 61 | String id = sessionIdList.get(i); 62 | sessionMap.remove(id); 63 | } 64 | } else { 65 | for (String id : sessionIdList) { 66 | sessionMap.remove(id); 67 | } 68 | } 69 | } 70 | 71 | @Override 72 | public Session getSession(String sessionId) { 73 | return sessionMap.get(sessionId); 74 | } 75 | 76 | @Override 77 | public void changeSessionId(String oldSessionId, String newSessionId) { 78 | Session session = sessionMap.remove(oldSessionId); 79 | if (session != null) { 80 | long expireTimestamp = session.getCreationTime() + (session.getMaxInactiveInterval() * 1000L); 81 | long timeout = expireTimestamp - System.currentTimeMillis(); 82 | if (timeout > 0) { 83 | sessionMap.put(newSessionId, session, timeout); 84 | } 85 | } 86 | } 87 | 88 | @Override 89 | public int count() { 90 | return sessionMap.size(); 91 | } 92 | 93 | @Override 94 | public String toString() { 95 | return name; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/SessionService.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import java.util.List; 4 | 5 | /** 6 | * Session service 7 | * 8 | * @author wangzihao 9 | * 2018/8/19/019 10 | */ 11 | public interface SessionService { 12 | 13 | /** 14 | * Get session (by id) 15 | * 16 | * @param sessionId sessionId 17 | * @return Session 18 | */ 19 | Session getSession(String sessionId); 20 | 21 | /** 22 | * Save the session 23 | * 24 | * @param session session 25 | */ 26 | void saveSession( Session session); 27 | 28 | /** 29 | * Delete session 30 | * 31 | * @param sessionId sessionId 32 | */ 33 | void removeSession(String sessionId); 34 | 35 | /** 36 | * Delete session (batch) 37 | * 38 | * @param sessionIdList sessionIdList 39 | */ 40 | void removeSessionBatch(List sessionIdList); 41 | 42 | /** 43 | * Change the sessionId 44 | * 45 | * @param oldSessionId oldSessionId 46 | * @param newSessionId newSessionId 47 | */ 48 | void changeSessionId(String oldSessionId, String newSessionId); 49 | 50 | /** 51 | * Get the number of sessions 52 | * 53 | * @return count 54 | */ 55 | int count(); 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/SslContextBuilders.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet; 2 | 3 | import io.netty.handler.ssl.*; 4 | 5 | import javax.net.ssl.KeyManagerFactory; 6 | import javax.net.ssl.SSLException; 7 | import java.io.File; 8 | import java.io.FileInputStream; 9 | import java.io.IOException; 10 | import java.lang.reflect.Method; 11 | import java.nio.file.Files; 12 | import java.security.KeyStore; 13 | import java.security.KeyStoreException; 14 | import java.security.NoSuchAlgorithmException; 15 | import java.security.UnrecoverableKeyException; 16 | import java.security.cert.CertificateException; 17 | 18 | public class SslContextBuilders { 19 | 20 | private static final boolean IS_ALPN_SUPPORTED; 21 | 22 | static { 23 | boolean supportIsalpnsupported; 24 | try { 25 | Method isAlpnSupported = SslProvider.class.getDeclaredMethod("isAlpnSupported", SslProvider.class); 26 | supportIsalpnsupported = Boolean.TRUE.equals(isAlpnSupported.invoke(null, SslProvider.OPENSSL)); 27 | } catch (Throwable e) { 28 | supportIsalpnsupported = false; 29 | } 30 | IS_ALPN_SUPPORTED = supportIsalpnsupported; 31 | } 32 | 33 | public static SslContextBuilder newSslContextBuilderJks(File jksKeyFile, File jksPassword) throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, CertificateException { 34 | String password = jksPassword == null ? null : new String(Files.readAllBytes(jksPassword.toPath())); 35 | return newSslContextBuilderJks(jksKeyFile, password); 36 | } 37 | 38 | public static SslContextBuilder newSslContextBuilderJks(File jksKeyFile, String jksPassword) throws IOException, NoSuchAlgorithmException, UnrecoverableKeyException, KeyStoreException, CertificateException { 39 | char[] password = jksPassword == null ? null : jksPassword.toCharArray(); 40 | KeyStore keyStore = KeyStore.getInstance("JKS"); 41 | keyStore.load(new FileInputStream(jksKeyFile), password); 42 | 43 | KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); 44 | keyManagerFactory.init(keyStore, password); 45 | return SslContextBuilder.forServer(keyManagerFactory); 46 | } 47 | 48 | public static SslContextBuilder newSslContextBuilderPem(File crtFile, File pemFile) { 49 | return SslContextBuilder.forServer(crtFile, pemFile); 50 | } 51 | 52 | public static SslContext newSslContext(SslContextBuilder builder, boolean h2) throws SSLException { 53 | String[] protocols = h2 ? new String[]{ApplicationProtocolNames.HTTP_2, ApplicationProtocolNames.HTTP_1_1} 54 | : new String[]{ApplicationProtocolNames.HTTP_1_1}; 55 | return builder.sslProvider(IS_ALPN_SUPPORTED ? 56 | SslProvider.OPENSSL : 57 | SslProvider.JDK) 58 | .applicationProtocolConfig(new ApplicationProtocolConfig( 59 | ApplicationProtocolConfig.Protocol.ALPN, 60 | ApplicationProtocolConfig.SelectorFailureBehavior.NO_ADVERTISE, 61 | ApplicationProtocolConfig.SelectedListenerFailureBehavior.ACCEPT, protocols)) 62 | .build(); 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/ByteBufToHttpContentChannelHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelOutboundHandlerAdapter; 7 | import io.netty.channel.ChannelPromise; 8 | import io.netty.handler.codec.http.DefaultHttpContent; 9 | 10 | @ChannelHandler.Sharable 11 | public class ByteBufToHttpContentChannelHandler extends ChannelOutboundHandlerAdapter { 12 | public static final ByteBufToHttpContentChannelHandler INSTANCE = new ByteBufToHttpContentChannelHandler(); 13 | 14 | @Override 15 | public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { 16 | if (msg instanceof ByteBuf) { 17 | // convert ByteBuf to HttpContent to make it work with compression. This is needed as we use the 18 | // ChunkedWriteHandler to send files when compression is enabled. 19 | ByteBuf buff = (ByteBuf) msg; 20 | if (buff.isReadable()) { 21 | // We only encode non empty buffers, as empty buffers can be used for determining when 22 | // the content has been flushed and it confuses the HttpContentCompressor 23 | // if we let it go 24 | msg = new DefaultHttpContent(buff); 25 | } 26 | } 27 | super.write(ctx, msg, promise); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/H2Util.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import io.netty.handler.codec.http.HttpServerUpgradeHandler; 4 | import io.netty.handler.codec.http2.*; 5 | import io.netty.handler.logging.LogLevel; 6 | import io.netty.util.AsciiString; 7 | 8 | public class H2Util { 9 | 10 | public static Http2ConnectionHandler newHttp2Handler(LogLevel logLevel, 11 | int http2MaxReservedStreams, int maxContentLength, boolean enableContentCompression) { 12 | DefaultHttp2Connection connection = new DefaultHttp2Connection(true, http2MaxReservedStreams); 13 | InboundHttp2ToHttpAdapter listener = new InboundHttp2ToHttpAdapterBuilder(connection) 14 | .propagateSettings(false) 15 | .validateHttpHeaders(true) 16 | .maxContentLength(maxContentLength) 17 | .build(); 18 | 19 | HttpToHttp2FrameCodecConnectionHandlerBuilder build = new HttpToHttp2FrameCodecConnectionHandlerBuilder() 20 | .frameListener(listener) 21 | .connection(connection) 22 | .compressor(enableContentCompression); 23 | if (logLevel != null) { 24 | build.frameLogger(new Http2FrameLogger(logLevel)); 25 | } 26 | return build.build(); 27 | } 28 | 29 | public static HttpServerUpgradeHandler.UpgradeCodecFactory newUpgradeCodecFactory(LogLevel logLevel, int http2MaxReservedStreams, int maxContentLength, boolean enableContentCompression) { 30 | return new HttpServerUpgradeHandler.UpgradeCodecFactory() { 31 | @Override 32 | public HttpServerUpgradeHandler.UpgradeCodec newUpgradeCodec(CharSequence protocol) { 33 | if (AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)) { 34 | return new Http2ServerUpgradeCodec(newHttp2Handler(logLevel, http2MaxReservedStreams, maxContentLength, enableContentCompression)); 35 | } else { 36 | return null; 37 | } 38 | } 39 | }; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/HttpAbortPolicyWithReport.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import com.github.netty.core.util.AbortPolicyWithReport; 4 | import com.github.netty.protocol.servlet.NettyMessageToServletRunnable; 5 | import com.github.netty.protocol.servlet.ServletHttpExchange; 6 | 7 | import javax.servlet.http.HttpServletResponse; 8 | import java.util.concurrent.ThreadPoolExecutor; 9 | 10 | /** 11 | * http refusal to handle the strategy, send 503 status code 12 | *

13 | * Status code (503) indicating that the HTTP server is 14 | * temporarily overloaded, and unable to handle the request. 15 | * 16 | * @author wangzihaogithub 2020-11-21 17 | * @see #rejectedExecution(NettyMessageToServletRunnable.HttpRunnable, ThreadPoolExecutor, ServletHttpExchange) 18 | */ 19 | public class HttpAbortPolicyWithReport extends AbortPolicyWithReport { 20 | public HttpAbortPolicyWithReport(String threadName, String dumpPath, String info) { 21 | super(threadName, dumpPath, info); 22 | } 23 | 24 | @Override 25 | public void rejectedExecution(Runnable r, ThreadPoolExecutor e) { 26 | if (r instanceof NettyMessageToServletRunnable.HttpRunnable) { 27 | NettyMessageToServletRunnable.HttpRunnable httpRunnable = (NettyMessageToServletRunnable.HttpRunnable) r; 28 | rejectedExecution(httpRunnable, e, httpRunnable.getExchange()); 29 | } else { 30 | super.rejectedExecution(r, e); 31 | } 32 | } 33 | 34 | protected void rejectedExecution(NettyMessageToServletRunnable.HttpRunnable httpRunnable, 35 | ThreadPoolExecutor e, ServletHttpExchange exchange) { 36 | exchange.getResponse().setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE); 37 | exchange.getResponse().setHeader("Connection", "Close"); 38 | super.rejectedExecution(httpRunnable, e); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/HttpConstants.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import io.netty.util.AsciiString; 4 | 5 | import java.nio.charset.Charset; 6 | 7 | /** 8 | * @author wangzihao 9 | * 2018/7/15/015 10 | */ 11 | public class HttpConstants { 12 | 13 | public static final String JSESSION_ID_COOKIE = "JSESSIONID"; 14 | public static final String JSESSION_ID_URL = "jsessionid"; 15 | 16 | public static final String HTTPS = "https"; 17 | public static final int HTTPS_PORT = 443; 18 | public static final int HTTP_PORT = 80; 19 | public static final String HTTP = "http"; 20 | public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); 21 | public static final String DEFAULT_SESSION_COOKIE_PATH = "/"; 22 | public static final AsciiString H2_EXT_STREAM_ID = AsciiString.cached("x-http2-stream-id"); 23 | public static final AsciiString H2_EXT_SCHEME = AsciiString.cached("x-http2-scheme"); 24 | public static final boolean EXIST_DEPENDENCY_H2; 25 | public static final boolean EXIST_JAVAX_WEBSOCKET; 26 | 27 | static { 28 | boolean isExistH2; 29 | try { 30 | Class.forName("io.netty.handler.codec.http2.Http2ConnectionHandler"); 31 | isExistH2 = true; 32 | } catch (Throwable e) { 33 | isExistH2 = false; 34 | } 35 | EXIST_DEPENDENCY_H2 = isExistH2; 36 | } 37 | 38 | static { 39 | boolean existJavaxWebsocket; 40 | try { 41 | Class.forName("javax.websocket.Endpoint"); 42 | existJavaxWebsocket = true; 43 | } catch (Throwable e) { 44 | existJavaxWebsocket = false; 45 | } 46 | EXIST_JAVAX_WEBSOCKET = existJavaxWebsocket; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/HttpHeaderConstants.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import io.netty.util.AsciiString; 4 | 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | import java.util.function.Supplier; 8 | 9 | /** 10 | * @author wangzihao 11 | */ 12 | public class HttpHeaderConstants { 13 | private static final Map CACHE_HEADER_STRING_MAP = new ConcurrentHashMap<>(); 14 | private static final Map> CACHE_GROUP_HEADER_STRING_MAP = new ConcurrentHashMap<>(); 15 | 16 | public static final CharSequence PATH = cacheAsciiString("Path"); 17 | 18 | public static final CharSequence MAX_AGE_1 = cacheAsciiString("Max-Age"); 19 | 20 | public static final CharSequence DOMAIN = cacheAsciiString("Domain"); 21 | 22 | public static final CharSequence SECURE = cacheAsciiString("Secure"); 23 | 24 | public static final CharSequence HTTPONLY = cacheAsciiString("HttpOnly"); 25 | /** 26 | * {@code "Accept-Language"} 27 | */ 28 | public static final CharSequence ACCEPT_LANGUAGE = cacheAsciiString("Accept-Language"); 29 | /** 30 | * {@code "Connection"} 31 | */ 32 | public static final CharSequence CONNECTION = cacheAsciiString("Connection"); 33 | /** 34 | * {@code "Content-Language"} 35 | */ 36 | public static final CharSequence CONTENT_LANGUAGE = cacheAsciiString("Content-Language"); 37 | /** 38 | * {@code "Content-Length"} 39 | */ 40 | public static final CharSequence CONTENT_LENGTH = cacheAsciiString("Content-Length"); 41 | /** 42 | * {@code "Content-Type"} 43 | */ 44 | public static final CharSequence CONTENT_TYPE = cacheAsciiString("Content-Type"); 45 | /** 46 | * {@code "Cookie"} 47 | */ 48 | public static final CharSequence COOKIE = cacheAsciiString("Cookie"); 49 | /** 50 | * {@code "Date"} 51 | */ 52 | public static final CharSequence DATE = cacheAsciiString("Date"); 53 | /** 54 | * {@code "Expect"} 55 | */ 56 | public static final CharSequence EXPECT = cacheAsciiString("Expect"); 57 | /** 58 | * {@code "Host"} 59 | */ 60 | public static final CharSequence HOST = cacheAsciiString("Host"); 61 | /** 62 | * {@code "Location"} 63 | */ 64 | public static final CharSequence LOCATION = cacheAsciiString("Location"); 65 | /** 66 | * {@code "Sec-WebSocket-Key1"} 67 | */ 68 | public static final CharSequence SEC_WEBSOCKET_KEY1 = cacheAsciiString("Sec-WebSocket-Key1"); 69 | /** 70 | * {@code "Sec-WebSocket-Key2"} 71 | */ 72 | public static final CharSequence SEC_WEBSOCKET_KEY2 = cacheAsciiString("Sec-WebSocket-Key2"); 73 | /** 74 | * {@code "Sec-WebSocket-Location"} 75 | */ 76 | public static final CharSequence SEC_WEBSOCKET_LOCATION = cacheAsciiString("Sec-WebSocket-Location"); 77 | /** 78 | * {@code "Sec-WebSocket-Origin"} 79 | */ 80 | public static final CharSequence SEC_WEBSOCKET_ORIGIN = cacheAsciiString("Sec-WebSocket-Origin"); 81 | /** 82 | * {@code "Server"} 83 | */ 84 | public static final CharSequence SERVER = cacheAsciiString("Server"); 85 | /** 86 | * {@code "Set-Cookie"} 87 | */ 88 | public static final CharSequence SET_COOKIE = cacheAsciiString("Set-Cookie"); 89 | /** 90 | * {@code "TE"} 91 | */ 92 | public static final CharSequence TE = cacheAsciiString("TE"); 93 | /** 94 | * {@code "Trailer"} 95 | */ 96 | public static final CharSequence TRAILER = cacheAsciiString("Trailer"); 97 | /** 98 | * {@code "Transfer-Encoding"} 99 | */ 100 | public static final CharSequence TRANSFER_ENCODING = cacheAsciiString("Transfer-Encoding"); 101 | /** 102 | * {@code "charset"} 103 | */ 104 | public static final CharSequence CHARSET = cacheAsciiString("charset"); 105 | /** 106 | * {@code "chunked"} 107 | */ 108 | public static final CharSequence CHUNKED = cacheAsciiString("chunked"); 109 | /** 110 | * {@code "close"} 111 | */ 112 | public static final CharSequence CLOSE = cacheAsciiString("close"); 113 | /** 114 | * {@code "100-continue"} 115 | */ 116 | public static final CharSequence CONTINUE = cacheAsciiString("100-continue"); 117 | /** 118 | * {@code "keep-alive"} 119 | */ 120 | public static final CharSequence KEEP_ALIVE = cacheAsciiString("keep-alive"); 121 | /** 122 | * {@code "Sec-WebSocket-Extensions"} 123 | */ 124 | public static final CharSequence SEC_WEBSOCKET_EXTENSIONS = cacheAsciiString("Sec-WebSocket-Extensions"); 125 | /** 126 | * {@code "X-Forwarded-Port"} 127 | */ 128 | public static final CharSequence X_FORWARDED_PORT = cacheAsciiString("X-Forwarded-Port"); 129 | /** 130 | * {@code "X-Forwarded-Proto"} 131 | */ 132 | public static final CharSequence X_FORWARDED_PROTO = cacheAsciiString("X-Forwarded-Proto"); 133 | 134 | public static final CharSequence CONTENT_DISPOSITION = cacheAsciiString("Content-Disposition"); 135 | public static final CharSequence NAME = cacheAsciiString("name"); 136 | public static final CharSequence FILENAME = cacheAsciiString("filename"); 137 | public static final CharSequence FORM_DATA = cacheAsciiString("form-data"); 138 | 139 | public static CharSequence cacheAsciiString(String key) { 140 | return CACHE_HEADER_STRING_MAP.computeIfAbsent(key, AsciiString::new); 141 | } 142 | 143 | public static CharSequence cacheAsciiString(String key, String group, Supplier supplier) { 144 | return CACHE_GROUP_HEADER_STRING_MAP.computeIfAbsent(group, k -> new ConcurrentHashMap<>()) 145 | .computeIfAbsent(key, k -> supplier.get()); 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/HttpLazyThreadPool.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import com.github.netty.core.util.NettyThreadPoolExecutor; 4 | 5 | import java.util.concurrent.Executor; 6 | import java.util.concurrent.RejectedExecutionHandler; 7 | import java.util.concurrent.SynchronousQueue; 8 | import java.util.concurrent.TimeUnit; 9 | import java.util.function.Supplier; 10 | 11 | public class HttpLazyThreadPool implements Supplier { 12 | private final String poolName; 13 | private volatile NettyThreadPoolExecutor executor; 14 | 15 | public HttpLazyThreadPool(String poolName) { 16 | this.poolName = poolName; 17 | } 18 | 19 | @Override 20 | public NettyThreadPoolExecutor get() { 21 | if (executor == null) { 22 | synchronized (this) { 23 | if (executor == null) { 24 | int coreThreads = 2; 25 | int maxThreads = 200; 26 | int keepAliveSeconds = 180; 27 | int priority = Thread.NORM_PRIORITY; 28 | boolean daemon = false; 29 | RejectedExecutionHandler handler = new HttpAbortPolicyWithReport(poolName, System.getProperty("user.home"), "Http Servlet"); 30 | executor = new NettyThreadPoolExecutor( 31 | coreThreads, maxThreads, keepAliveSeconds, TimeUnit.SECONDS, 32 | new SynchronousQueue<>(), poolName, priority, daemon, handler); 33 | } 34 | } 35 | } 36 | return executor; 37 | } 38 | } -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/MimeMappingsX.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import java.util.*; 4 | 5 | /** 6 | * Simple container-independent abstraction for servlet mime mappings. Roughly equivalent 7 | * to the {@literal <mime-mapping>} element traditionally found in web.xml. 8 | * 9 | * @author Phillip Webb 10 | */ 11 | public class MimeMappingsX implements Iterable { 12 | private final Map map; 13 | 14 | /** 15 | * Create a new empty {@link MimeMappingsX} instance. 16 | */ 17 | public MimeMappingsX() { 18 | this.map = new LinkedHashMap(); 19 | } 20 | 21 | @Override 22 | public Iterator iterator() { 23 | return getAll().iterator(); 24 | } 25 | 26 | /** 27 | * Returns all defined mappings. 28 | * 29 | * @return the mappings. 30 | */ 31 | public Collection getAll() { 32 | return this.map.values(); 33 | } 34 | 35 | /** 36 | * Add a new mime mapping. 37 | * 38 | * @param extension the file extension (excluding '.') 39 | * @param mimeType the mime type to map 40 | * @return any previous mapping or {@code null} 41 | */ 42 | public String add(String extension, String mimeType) { 43 | MappingX previous = this.map.put(extension, new MappingX(extension, mimeType)); 44 | return (previous == null ? null : previous.getMimeType()); 45 | } 46 | 47 | /** 48 | * Get a mime mapping for the given extension. 49 | * 50 | * @param extension the file extension (excluding '.') 51 | * @return a mime mapping or {@code null} 52 | */ 53 | public String get(String extension) { 54 | MappingX mapping = this.map.get(extension); 55 | return (mapping == null ? null : mapping.getMimeType()); 56 | } 57 | 58 | /** 59 | * Remove an existing mapping. 60 | * 61 | * @param extension the file extension (excluding '.') 62 | * @return the removed mime mapping or {@code null} if no item was removed 63 | */ 64 | public String remove(String extension) { 65 | MappingX previous = this.map.remove(extension); 66 | return (previous == null ? null : previous.getMimeType()); 67 | } 68 | 69 | @Override 70 | public int hashCode() { 71 | return this.map.hashCode(); 72 | } 73 | 74 | @Override 75 | public boolean equals(Object obj) { 76 | if (obj == null) { 77 | return false; 78 | } 79 | if (obj == this) { 80 | return true; 81 | } 82 | if (obj instanceof MimeMappingsX) { 83 | MimeMappingsX other = (MimeMappingsX) obj; 84 | return this.map.equals(other.map); 85 | } 86 | return false; 87 | } 88 | 89 | /** 90 | * A single mime mapping. 91 | */ 92 | public final class MappingX { 93 | 94 | private final String extension; 95 | 96 | private final String mimeType; 97 | 98 | public MappingX(String extension, String mimeType) { 99 | Objects.requireNonNull(extension, "Extension must not be null"); 100 | Objects.requireNonNull(mimeType, "MimeType must not be null"); 101 | this.extension = extension; 102 | this.mimeType = mimeType; 103 | } 104 | 105 | public String getExtension() { 106 | return this.extension; 107 | } 108 | 109 | public String getMimeType() { 110 | return this.mimeType; 111 | } 112 | 113 | @Override 114 | public int hashCode() { 115 | return this.extension.hashCode(); 116 | } 117 | 118 | @Override 119 | public boolean equals(Object obj) { 120 | if (obj == null) { 121 | return false; 122 | } 123 | if (obj == this) { 124 | return true; 125 | } 126 | if (obj instanceof MappingX) { 127 | MappingX other = (MappingX) obj; 128 | return this.extension.equals(other.extension) 129 | && this.mimeType.equals(other.mimeType); 130 | } 131 | return false; 132 | } 133 | 134 | @Override 135 | public String toString() { 136 | return "Mapping [extension=" + this.extension + ", mimeType=" + this.mimeType 137 | + "]"; 138 | } 139 | 140 | } 141 | 142 | } -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/Protocol.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import com.github.netty.core.util.IOUtil; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufUtil; 6 | import io.netty.buffer.Unpooled; 7 | import io.netty.handler.codec.http.HttpConstants; 8 | 9 | import java.nio.charset.Charset; 10 | 11 | public enum Protocol { 12 | /**/ 13 | http1_1(false), 14 | https1_1(false), 15 | h2(true), 16 | h2c(true), 17 | h2c_prior_knowledge(true); 18 | 19 | private static final ByteBuf CONNECTION_PREFACE = Unpooled.unreleasableBuffer(Unpooled.directBuffer(24).writeBytes("PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n".getBytes(Charset.forName("UTF-8")))).asReadOnly(); 20 | private final boolean http2; 21 | 22 | Protocol(boolean http2) { 23 | this.http2 = http2; 24 | } 25 | 26 | public static boolean isHttpPacket(ByteBuf packet) { 27 | int protocolEndIndex = IOUtil.indexOf(packet, HttpConstants.LF); 28 | if (protocolEndIndex == -1 && packet.readableBytes() > 7) { 29 | int readerIndex = packet.readerIndex(); 30 | // client multiple write packages. cause browser out of length. 31 | if (packet.getByte(readerIndex) == 'G' 32 | && packet.getByte(readerIndex + 1) == 'E' 33 | && packet.getByte(readerIndex + 2) == 'T' 34 | && packet.getByte(readerIndex + 3) == ' ' 35 | && packet.getByte(readerIndex + 4) == '/') { 36 | return true; 37 | } else if (packet.getByte(readerIndex) == 'P' 38 | && packet.getByte(readerIndex + 1) == 'O' 39 | && packet.getByte(readerIndex + 2) == 'S' 40 | && packet.getByte(readerIndex + 3) == 'T' 41 | && packet.getByte(readerIndex + 4) == ' ' 42 | && packet.getByte(readerIndex + 5) == '/') { 43 | return true; 44 | } else if (packet.getByte(readerIndex) == 'O' 45 | && packet.getByte(readerIndex + 1) == 'P' 46 | && packet.getByte(readerIndex + 2) == 'T' 47 | && packet.getByte(readerIndex + 3) == 'I' 48 | && packet.getByte(readerIndex + 4) == 'O' 49 | && packet.getByte(readerIndex + 5) == 'N' 50 | && packet.getByte(readerIndex + 6) == 'S' 51 | && packet.getByte(readerIndex + 7) == ' ' 52 | && packet.getByte(readerIndex + 8) == '/') { 53 | return true; 54 | } else if (packet.getByte(readerIndex) == 'P' 55 | && packet.getByte(readerIndex + 1) == 'U' 56 | && packet.getByte(readerIndex + 2) == 'T' 57 | && packet.getByte(readerIndex + 3) == ' ' 58 | && packet.getByte(readerIndex + 4) == '/') { 59 | return true; 60 | } else if (packet.getByte(readerIndex) == 'D' 61 | && packet.getByte(readerIndex + 1) == 'E' 62 | && packet.getByte(readerIndex + 2) == 'L' 63 | && packet.getByte(readerIndex + 3) == 'E' 64 | && packet.getByte(readerIndex + 4) == 'T' 65 | && packet.getByte(readerIndex + 5) == 'E' 66 | && packet.getByte(readerIndex + 6) == ' ' 67 | && packet.getByte(readerIndex + 7) == '/') { 68 | return true; 69 | } else { 70 | return packet.getByte(readerIndex) == 'P' 71 | && packet.getByte(readerIndex + 1) == 'A' 72 | && packet.getByte(readerIndex + 2) == 'T' 73 | && packet.getByte(readerIndex + 3) == 'C' 74 | && packet.getByte(readerIndex + 4) == 'H' 75 | && packet.getByte(readerIndex + 5) == ' ' 76 | && packet.getByte(readerIndex + 6) == '/'; 77 | } 78 | } else if (protocolEndIndex < 9) { 79 | return false; 80 | } else { 81 | return packet.getByte(protocolEndIndex - 9) == 'H' 82 | && packet.getByte(protocolEndIndex - 8) == 'T' 83 | && packet.getByte(protocolEndIndex - 7) == 'T' 84 | && packet.getByte(protocolEndIndex - 6) == 'P'; 85 | } 86 | } 87 | 88 | public static boolean isPriHttp2(ByteBuf clientFirstMsg) { 89 | int prefaceLength = CONNECTION_PREFACE.readableBytes(); 90 | int bytesRead = Math.min(clientFirstMsg.readableBytes(), prefaceLength); 91 | return ByteBufUtil.equals(CONNECTION_PREFACE, CONNECTION_PREFACE.readerIndex(), 92 | clientFirstMsg, clientFirstMsg.readerIndex(), bytesRead); 93 | } 94 | 95 | public boolean isHttp2() { 96 | return http2; 97 | } 98 | 99 | } -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/util/SnowflakeIdWorker.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.util; 2 | 3 | import java.util.Random; 4 | import java.util.concurrent.ThreadLocalRandom; 5 | 6 | /** 7 | * Twitter_Snowflake 8 | * The structure of SnowFlake is as follows (each part is separated by -): 9 | * 0-0000000000 0000000000 0000000000 0-0000-0000-000000000 10 | * 1 bit id. Since the long base type is signed in Java, the highest bit is the sign bit, positive is 0, negative is 1, so id is generally positive, and the highest bit is 0 11 | * 41-bit time cutoff (in milliseconds). Note that the 41-bit time cutoff does not store the current time cutoff, but rather the difference in the current time cutoff (current time cutoff - start time cutoff). 12 | * , the starting time intercept, which is generally the time our id generator starts to use, is specified by our program (startTime attribute of IdWorker class below). The 41-bit time slice, which can be used for 69 years, 13 | * 10-bit data machine bits, which can be deployed on 1024 nodes, including 5-bit datacenterId and 5-bit workerId 14 | * 12-bit sequence, counting in milliseconds, 12-bit counting serial number supports each node to generate 4096 ID serial Numbers per millisecond (same machine, same time slice) 15 | * adds up to exactly 64 bits, making it a Long. 16 | * the advantage of SnowFlake is that it will sort itself in terms of time on the whole, and will not produce ID collisions across the distributed system (separated by data center ids and machine ids), and it will be highly efficient. As tested, SnowFlake will generate about 260,000 ids per second. 17 | * 18 | * @author wangzihao 19 | */ 20 | public class SnowflakeIdWorker { 21 | 22 | // ==============================Fields=========================================== 23 | /** 24 | * Start time cutoff (object creation time) 25 | */ 26 | private final long twepoch = System.currentTimeMillis(); 27 | 28 | /** 29 | * The number of bits of machine id 30 | */ 31 | private final long workerIdBits = 5L; 32 | 33 | /** 34 | * The number of digits occupied by the data id 35 | */ 36 | private final long datacenterIdBits = 5L; 37 | 38 | /** 39 | * The maximum machine id supported, which is 31 (this shift algorithm can quickly calculate the maximum number of decimal digits that can be represented by several binary digits) 40 | */ 41 | private final long maxWorkerId = -1L ^ (-1L << workerIdBits); 42 | 43 | /** 44 | * The maximum supported data id, which is 31 45 | */ 46 | private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits); 47 | 48 | /** 49 | * The number of bits of a sequence in an id 50 | */ 51 | private final long sequenceBits = 12L; 52 | 53 | /** 54 | * The machine ID moves 12 bits to the left 55 | */ 56 | private final long workerIdShift = sequenceBits; 57 | 58 | /** 59 | * Data id moves 17 bits to the left (12+5) 60 | */ 61 | private final long datacenterIdShift = sequenceBits + workerIdBits; 62 | 63 | /** 64 | * So our time intercept is going to be 22 to the left. 65 | */ 66 | private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits; 67 | 68 | /** 69 | * Generate the mask for the sequence, which is 4095 (0b111111111111=0xfff=4095) 70 | */ 71 | private final long sequenceMask = -1L ^ (-1L << sequenceBits); 72 | 73 | /** 74 | * Working machine ID(0~31) 75 | */ 76 | private long workerId; 77 | 78 | /** 79 | * Data center ID(0~31) 80 | */ 81 | private long datacenterId; 82 | 83 | /** 84 | * Millisecond sequence(0~4095) 85 | */ 86 | private long sequence = 0L; 87 | 88 | /** 89 | * The last time the ID was generated 90 | */ 91 | private long lastTimestamp = -1L; 92 | 93 | //==============================Constructors===================================== 94 | 95 | /** 96 | * The constructor 97 | */ 98 | public SnowflakeIdWorker() { 99 | this(new Random().nextInt(31), new Random().nextInt(31)); 100 | } 101 | 102 | /** 103 | * The constructor 104 | * 105 | * @param workerId Work ID (0~31) 106 | * @param datacenterId Data center ID (0~31) 107 | */ 108 | private SnowflakeIdWorker(long workerId, long datacenterId) { 109 | if (workerId > maxWorkerId || workerId < 0) { 110 | throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId)); 111 | } 112 | if (datacenterId > maxDatacenterId || datacenterId < 0) { 113 | throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId)); 114 | } 115 | this.workerId = workerId; 116 | this.datacenterId = datacenterId; 117 | } 118 | 119 | // ==============================Methods========================================== 120 | 121 | /** 122 | * Get the next ID 123 | * 124 | * @return SnowflakeId 125 | */ 126 | public long nextId() { 127 | long timestamp = System.currentTimeMillis(); 128 | 129 | //If the current time is less than the timestamp generated by the last ID, an exception should be thrown when the system clock has gone back 130 | if (timestamp < lastTimestamp) { 131 | timestamp = Math.abs(ThreadLocalRandom.current().nextLong()); 132 | } 133 | 134 | //If it is generated at the same time, the millisecond sequence is performed 135 | if (lastTimestamp == timestamp) { 136 | sequence = (sequence + 1) & sequenceMask; 137 | //Sequence overflow in milliseconds 138 | if (sequence == 0) { 139 | //Block to the next millisecond and get the new timestamp 140 | timestamp = tilNextMillis(lastTimestamp); 141 | } 142 | } else { 143 | //The timestamp changes and the sequence is reset in milliseconds 144 | sequence = 0L; 145 | } 146 | 147 | //The last time the ID was generated 148 | lastTimestamp = timestamp; 149 | 150 | //The shift and or operations are combined to form a 64-bit ID 151 | return ((timestamp - twepoch) << timestampLeftShift) // 152 | | (datacenterId << datacenterIdShift) // 153 | | (workerId << workerIdShift) // 154 | | sequence; 155 | } 156 | 157 | /** 158 | * Block to the next millisecond until a new timestamp is obtained 159 | * 160 | * @param lastTimestamp The last time the ID was generated 161 | * @return Current timestamp 162 | */ 163 | private long tilNextMillis(long lastTimestamp) { 164 | long timestamp = System.currentTimeMillis(); 165 | while (timestamp <= lastTimestamp) { 166 | timestamp = System.currentTimeMillis(); 167 | } 168 | return timestamp; 169 | } 170 | } -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/websocket/NettyMessageToWebSocketRunnable.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.websocket; 2 | 3 | import com.github.netty.core.MessageToRunnable; 4 | import com.github.netty.core.util.*; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.http.websocketx.*; 7 | 8 | import javax.websocket.MessageHandler; 9 | import javax.websocket.PongMessage; 10 | import java.nio.ByteBuffer; 11 | import java.util.Set; 12 | 13 | /** 14 | * WebSocketMessageToRunnable 15 | * Life cycle connection 16 | * 17 | * @author wangzihao 18 | */ 19 | public class NettyMessageToWebSocketRunnable implements MessageToRunnable { 20 | private static final Recycler RECYCLER = new Recycler<>(WebsocketRunnable::new); 21 | private final MessageToRunnable parent; 22 | 23 | public NettyMessageToWebSocketRunnable(MessageToRunnable parent) { 24 | this.parent = parent; 25 | } 26 | 27 | @Override 28 | public Runnable onMessage(ChannelHandlerContext context, Object msg) { 29 | if (msg instanceof WebSocketFrame) { 30 | WebsocketRunnable task = RECYCLER.getInstance(); 31 | task.context = context; 32 | task.frame = (WebSocketFrame) msg; 33 | return task; 34 | } 35 | if (parent != null) { 36 | return parent.onMessage(context, msg); 37 | } 38 | throw new IllegalStateException("[" + msg.getClass().getName() + "] Message data type that cannot be processed"); 39 | } 40 | 41 | @Override 42 | public Runnable onClose(ChannelHandlerContext context) { 43 | if (parent != null) { 44 | return parent.onClose(context); 45 | } 46 | return null; 47 | } 48 | 49 | /** 50 | * Websocket task 51 | */ 52 | public static class WebsocketRunnable implements Runnable, Recyclable { 53 | private ChannelHandlerContext context; 54 | private WebSocketFrame frame; 55 | 56 | public WebSocketSession getWebSocketSession() { 57 | return WebSocketSession.getSession(context.channel()); 58 | } 59 | 60 | public WebSocketFrame getFrame() { 61 | return frame; 62 | } 63 | 64 | public void setFrame(WebSocketFrame frame) { 65 | this.frame = frame; 66 | } 67 | 68 | public ChannelHandlerContext getContext() { 69 | return context; 70 | } 71 | 72 | @Override 73 | public void run() { 74 | try { 75 | WebSocketSession wsSession = getWebSocketSession(); 76 | if (wsSession == null) { 77 | return; 78 | } 79 | 80 | // Close the message 81 | if (frame instanceof CloseWebSocketFrame) { 82 | wsSession.closeByClient((CloseWebSocketFrame) frame); 83 | return; 84 | } 85 | 86 | // Ping message 87 | if (frame instanceof PingWebSocketFrame) { 88 | onWebsocketMessage(wsSession, frame, IOUtil.heap(frame.content()), PongMessage.class); 89 | return; 90 | } 91 | 92 | // Binary message 93 | if (frame instanceof BinaryWebSocketFrame) { 94 | onWebsocketMessage(wsSession, frame, IOUtil.heap(frame.content()), ByteBuffer.class); 95 | return; 96 | } 97 | 98 | // String message 99 | if (frame instanceof TextWebSocketFrame) { 100 | onWebsocketMessage(wsSession, frame, ((TextWebSocketFrame) frame).text(), String.class); 101 | } 102 | } finally { 103 | WebsocketRunnable.this.recycle(); 104 | } 105 | } 106 | 107 | private void onWebsocketMessage(WebSocketSession wsSession, WebSocketFrame frame, Object message, Class messageType) { 108 | Set messageHandlers = wsSession.getMessageHandlers(); 109 | for (MessageHandler handler : messageHandlers) { 110 | if (handler instanceof MessageHandler.Partial) { 111 | MessageHandler.Partial partial = ((MessageHandler.Partial) handler); 112 | TypeUtil.TypeResult typeResult = TypeUtil.getGenericType(MessageHandler.Partial.class, partial.getClass()); 113 | if (typeResult == null 114 | || typeResult.getClazz() == Object.class 115 | || typeResult.getClazz() == messageType) { 116 | try { 117 | boolean finalFragment = frame.isFinalFragment(); 118 | if (frame instanceof PingWebSocketFrame) { 119 | ByteBuffer applicationData = ByteBuffer.wrap((byte[]) message); 120 | partial.onMessage((PongMessage) () -> applicationData, finalFragment); 121 | } else if (frame instanceof BinaryWebSocketFrame) { 122 | partial.onMessage(ByteBuffer.wrap((byte[]) message), finalFragment); 123 | } else { 124 | partial.onMessage(message, finalFragment); 125 | } 126 | } catch (Throwable e) { 127 | wsSession.onError(e); 128 | } 129 | } 130 | continue; 131 | } 132 | 133 | if (handler instanceof MessageHandler.Whole) { 134 | MessageHandler.Whole whole = ((MessageHandler.Whole) handler); 135 | TypeUtil.TypeResult typeResult = TypeUtil.getGenericType(MessageHandler.Whole.class, whole.getClass()); 136 | if (typeResult == null 137 | || typeResult.getClazz() == Object.class 138 | || typeResult.getClazz() == messageType) { 139 | try { 140 | if (frame instanceof PingWebSocketFrame) { 141 | ByteBuffer applicationData = ByteBuffer.wrap((byte[]) message); 142 | whole.onMessage((PongMessage) () -> applicationData); 143 | } else if (frame instanceof BinaryWebSocketFrame) { 144 | whole.onMessage(ByteBuffer.wrap((byte[]) message)); 145 | } else { 146 | whole.onMessage(message); 147 | } 148 | } catch (Throwable e) { 149 | wsSession.onError(e); 150 | } 151 | } 152 | } 153 | } 154 | } 155 | 156 | @Override 157 | public void recycle() { 158 | if (context instanceof Recyclable) { 159 | ((Recyclable) context).recycle(); 160 | } 161 | context = null; 162 | RecyclableUtil.release(frame); 163 | frame = null; 164 | } 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/websocket/WebSocketHandler.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.websocket; 2 | 3 | import javax.websocket.CloseReason; 4 | import javax.websocket.PongMessage; 5 | import javax.websocket.Session; 6 | import java.nio.ByteBuffer; 7 | 8 | public interface WebSocketHandler { 9 | 10 | default void afterConnectionEstablished(Session session) throws Exception { 11 | } 12 | 13 | default void handleTextMessage(Session session, String message, boolean isLast) throws Exception { 14 | } 15 | 16 | default void handleBinaryMessage(Session session, ByteBuffer message, boolean isLast) throws Exception { 17 | } 18 | 19 | default void handlePongMessage(Session session, PongMessage message, boolean isLast) throws Exception { 20 | } 21 | 22 | default void handleTransportError(Session session, Throwable exception) throws Exception { 23 | } 24 | 25 | default void afterConnectionClosed(Session session, CloseReason closeStatus) throws Exception { 26 | } 27 | 28 | } 29 | 30 | -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/websocket/WebSocketHandlerEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.websocket; 2 | 3 | import com.github.netty.core.util.LoggerFactoryX; 4 | import com.github.netty.core.util.LoggerX; 5 | import io.netty.util.internal.PlatformDependent; 6 | 7 | import javax.websocket.*; 8 | import java.nio.ByteBuffer; 9 | import java.util.Objects; 10 | 11 | public class WebSocketHandlerEndpoint extends Endpoint { 12 | private static final LoggerX logger = LoggerFactoryX.getLogger(WebSocketHandlerEndpoint.class); 13 | 14 | private final WebSocketHandler handler; 15 | 16 | public WebSocketHandlerEndpoint(WebSocketHandler handler) { 17 | this.handler = Objects.requireNonNull(handler, "WebSocketHandler"); 18 | } 19 | 20 | public static void tryCloseWithError(Session session, Throwable exception) { 21 | if (logger.isWarnEnabled()) { 22 | logger.warn("Closing session due to exception for " + session, exception); 23 | } 24 | if (session.isOpen()) { 25 | try { 26 | session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, null)); 27 | } catch (Throwable e) { 28 | // ignore 29 | } 30 | } 31 | } 32 | 33 | @Override 34 | public void onOpen(Session session, EndpointConfig endpointConfig) { 35 | session.addMessageHandler(new MessageHandler.Partial() { 36 | @Override 37 | public void onMessage(Object message, boolean last) { 38 | try { 39 | if (message instanceof PongMessage) { 40 | handler.handlePongMessage(session, (PongMessage) message, last); 41 | } else if (message instanceof String) { 42 | handler.handleTextMessage(session, (String) message, last); 43 | } else if (message instanceof ByteBuffer) { 44 | handler.handleBinaryMessage(session, (ByteBuffer) message, last); 45 | } 46 | } catch (Exception e) { 47 | PlatformDependent.throwException(e); 48 | } 49 | } 50 | }); 51 | try { 52 | this.handler.afterConnectionEstablished(session); 53 | } catch (Exception exception) { 54 | tryCloseWithError(session, exception); 55 | } 56 | } 57 | 58 | @Override 59 | public void onError(Session session, Throwable exception) { 60 | try { 61 | this.handler.handleTransportError(session, exception); 62 | } catch (Exception ex) { 63 | tryCloseWithError(session, ex); 64 | } 65 | } 66 | 67 | @Override 68 | public void onClose(Session session, CloseReason closeReason) { 69 | try { 70 | this.handler.afterConnectionClosed(session, closeReason); 71 | } catch (Exception ex) { 72 | if (logger.isWarnEnabled()) { 73 | logger.warn("Unhandled on-close exception for " + session, ex); 74 | } 75 | } 76 | } 77 | 78 | } -------------------------------------------------------------------------------- /src/main/java/com/github/netty/protocol/servlet/websocket/WebSocketNotFoundHandlerEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.protocol.servlet.websocket; 2 | 3 | import javax.websocket.CloseReason; 4 | import javax.websocket.Endpoint; 5 | import javax.websocket.EndpointConfig; 6 | import javax.websocket.Session; 7 | import java.io.IOException; 8 | 9 | public class WebSocketNotFoundHandlerEndpoint extends Endpoint { 10 | public static final WebSocketNotFoundHandlerEndpoint INSTANCE = new WebSocketNotFoundHandlerEndpoint(); 11 | 12 | @Override 13 | public void onOpen(Session session, EndpointConfig config) { 14 | session.getAsyncRemote().sendText("close! cause not found endpoint! "); 15 | try { 16 | session.close(new CloseReason(CloseReason.CloseCodes.UNEXPECTED_CONDITION, null)); 17 | } catch (IOException ignored) { 18 | 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/netty/channel/ChannelUtils.java: -------------------------------------------------------------------------------- 1 | package io.netty.channel; 2 | 3 | import io.netty.channel.epoll.EpollUtils; 4 | import io.netty.channel.kqueue.KqueueUtils; 5 | import io.netty.channel.nio.AbstractNioChannel; 6 | 7 | public class ChannelUtils { 8 | 9 | public static void forceFlush(Channel channel) { 10 | Channel.Unsafe unsafe = channel.unsafe(); 11 | if (EpollUtils.forceFlush(unsafe)) { 12 | return; 13 | } 14 | if (KqueueUtils.forceFlush(unsafe)) { 15 | return; 16 | } 17 | if (unsafe instanceof AbstractNioChannel.NioUnsafe) { 18 | ((AbstractNioChannel.NioUnsafe) unsafe).forceFlush(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/io/netty/channel/epoll/EpollChannelReportRunnable.java: -------------------------------------------------------------------------------- 1 | package io.netty.channel.epoll; 2 | 3 | import com.github.netty.core.TcpChannel; 4 | import com.github.netty.core.util.LoggerX; 5 | import io.netty.channel.ChannelOutboundBuffer; 6 | import io.netty.channel.EventLoop; 7 | import io.netty.channel.SingleThreadEventLoop; 8 | 9 | public class EpollChannelReportRunnable implements Runnable { 10 | private LoggerX logger; 11 | 12 | public EpollChannelReportRunnable(LoggerX logger) { 13 | this.logger = logger; 14 | } 15 | 16 | @Override 17 | public void run() { 18 | if (!TcpChannel.getChannels().values().isEmpty()) { 19 | for (TcpChannel ctx : TcpChannel.getChannels().values()) { 20 | AbstractEpollChannel channel = ((AbstractEpollChannel) ctx.getChannel()); 21 | boolean isFlushPending = channel.isFlagSet(Native.EPOLLOUT); 22 | ChannelOutboundBuffer outboundBuffer = ctx.getChannel().unsafe().outboundBuffer(); 23 | long totalPendingWriteBytes = outboundBuffer == null ? 0 : outboundBuffer.totalPendingWriteBytes(); 24 | EventLoop eventLoop = ctx.getChannel().eventLoop(); 25 | 26 | boolean inEventLoop = eventLoop.inEventLoop(); 27 | int pendingTasks = ((SingleThreadEventLoop) ctx.getChannel().eventLoop()).pendingTasks(); 28 | 29 | logger.info("remote = {}, isFlushPending = {}, totalPendingWriteBytes = {}/B, eventLoop = {}, pendingTasks = {}", 30 | ctx.getChannel().remoteAddress(), 31 | isFlushPending, 32 | totalPendingWriteBytes, 33 | eventLoop, 34 | pendingTasks); 35 | } 36 | logger.info("-----------------------"); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/io/netty/channel/epoll/EpollUtils.java: -------------------------------------------------------------------------------- 1 | package io.netty.channel.epoll; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | public class EpollUtils { 6 | 7 | public static boolean forceFlush(Channel.Unsafe unsafe) { 8 | if (unsafe instanceof AbstractEpollChannel.AbstractEpollUnsafe) { 9 | AbstractEpollChannel.AbstractEpollUnsafe epollUnsafe = (AbstractEpollChannel.AbstractEpollUnsafe) unsafe; 10 | epollUnsafe.epollOutReady(); 11 | return true; 12 | } else { 13 | return false; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/netty/channel/kqueue/KqueueUtils.java: -------------------------------------------------------------------------------- 1 | package io.netty.channel.kqueue; 2 | 3 | import io.netty.channel.Channel; 4 | 5 | public class KqueueUtils { 6 | 7 | public static boolean forceFlush(Channel.Unsafe unsafe) { 8 | if (unsafe instanceof AbstractKQueueChannel.AbstractKQueueUnsafe) { 9 | AbstractKQueueChannel.AbstractKQueueUnsafe epollUnsafe = (AbstractKQueueChannel.AbstractKQueueUnsafe) unsafe; 10 | epollUnsafe.writeReady(); 11 | return true; 12 | } else { 13 | return false; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/io/netty/channel/nio/NioChannelReportRunnable.java: -------------------------------------------------------------------------------- 1 | package io.netty.channel.nio; 2 | 3 | import com.github.netty.core.TcpChannel; 4 | import com.github.netty.core.util.LoggerX; 5 | import io.netty.channel.ChannelOutboundBuffer; 6 | import io.netty.channel.EventLoop; 7 | import io.netty.channel.SingleThreadEventLoop; 8 | 9 | import java.nio.channels.SelectionKey; 10 | 11 | public class NioChannelReportRunnable implements Runnable { 12 | private LoggerX logger; 13 | 14 | public NioChannelReportRunnable(LoggerX logger) { 15 | this.logger = logger; 16 | } 17 | 18 | @Override 19 | public void run() { 20 | if (!TcpChannel.getChannels().values().isEmpty()) { 21 | for (TcpChannel ctx : TcpChannel.getChannels().values()) { 22 | SelectionKey selectionKey = ((AbstractNioChannel) ctx.getChannel()).selectionKey(); 23 | boolean isFlushPending = selectionKey.isValid() && (selectionKey.interestOps() & SelectionKey.OP_WRITE) != 0; 24 | ChannelOutboundBuffer outboundBuffer = ctx.getChannel().unsafe().outboundBuffer(); 25 | long totalPendingWriteBytes = outboundBuffer == null ? 0 : outboundBuffer.totalPendingWriteBytes(); 26 | EventLoop eventLoop = ctx.getChannel().eventLoop(); 27 | 28 | boolean inEventLoop = eventLoop.inEventLoop(); 29 | int pendingTasks = ((SingleThreadEventLoop) ctx.getChannel().eventLoop()).pendingTasks(); 30 | 31 | logger.info("remote = {}, isFlushPending = {}, totalPendingWriteBytes = {}/B, eventLoop = {}, pendingTasks = {}", 32 | ctx.getChannel().remoteAddress(), 33 | isFlushPending, 34 | totalPendingWriteBytes, 35 | eventLoop, 36 | pendingTasks); 37 | } 38 | logger.info("-----------------------"); 39 | } 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/io/netty/handler/codec/http2/HttpToHttp2FrameCodecConnectionHandlerBuilder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 The Netty Project 3 | * 4 | * The Netty Project licenses this file to you under the Apache License, 5 | * version 2.0 (the "License"); you may not use this file except in compliance 6 | * with the License. You may obtain a copy of the License at: 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.netty.handler.codec.http2; 18 | 19 | import io.netty.util.internal.UnstableApi; 20 | 21 | @UnstableApi 22 | public final class HttpToHttp2FrameCodecConnectionHandlerBuilder extends 23 | AbstractHttp2ConnectionHandlerBuilder { 24 | private static final boolean SUPPORT_DECOUPLECLOSEANDGOAWAY; 25 | 26 | static { 27 | boolean supportDecouplecloseandgoaway; 28 | try { 29 | Class clazz = Class.forName("io.netty.handler.codec.http2.AbstractHttp2ConnectionHandlerBuilder"); 30 | clazz.getDeclaredMethod("decoupleCloseAndGoAway"); 31 | supportDecouplecloseandgoaway = true; 32 | } catch (Throwable e) { 33 | supportDecouplecloseandgoaway = false; 34 | } 35 | SUPPORT_DECOUPLECLOSEANDGOAWAY = supportDecouplecloseandgoaway; 36 | } 37 | 38 | private boolean compressor = true; 39 | 40 | @Override 41 | public HttpToHttp2FrameCodecConnectionHandlerBuilder connection(Http2Connection connection) { 42 | return super.connection(connection); 43 | } 44 | 45 | @Override 46 | public HttpToHttp2FrameCodecConnectionHandlerBuilder frameListener(Http2FrameListener frameListener) { 47 | return super.frameListener(frameListener); 48 | } 49 | 50 | @Override 51 | public HttpToHttp2FrameCodecConnectionHandlerBuilder frameLogger(Http2FrameLogger frameLogger) { 52 | return super.frameLogger(frameLogger); 53 | } 54 | 55 | public HttpToHttp2FrameCodecConnectionHandlerBuilder compressor(boolean compressor) { 56 | this.compressor = compressor; 57 | return self(); 58 | } 59 | 60 | @Override 61 | public Http2ConnectionHandler build() { 62 | return super.build(); 63 | } 64 | 65 | @Override 66 | protected Http2ConnectionHandler build(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder, 67 | Http2Settings initialSettings) { 68 | if (compressor) { 69 | encoder = new CompressorHttp2ConnectionEncoder(encoder); 70 | } 71 | if (SUPPORT_DECOUPLECLOSEANDGOAWAY) { 72 | return new HttpToHttp2ConnectionHandler(decoder, encoder, initialSettings, 73 | decoupleCloseAndGoAway(), isValidateHeaders()); 74 | } else { 75 | // 兼容netty老版本 76 | return new HttpToHttp2ConnectionHandler(decoder, encoder, initialSettings, isValidateHeaders()); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/main/resources/server.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one or more 2 | # contributor license agreements. See the NOTICE file distributed with 3 | # this work for additional information regarding copyright ownership. 4 | # The ASF licenses this file to You under the Apache License, Version 2.0 5 | # (the "License"); you may not use this file except in compliance with 6 | # the License. You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | server.info=Github Netty-servlet/2.3.30 17 | server.number=2.3.30 18 | server.built=Apr 29 2025 18:36:21 UTC+8 -------------------------------------------------------------------------------- /src/test/java/com/github/netty/http/FileApplication.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.http; 2 | 3 | import com.github.netty.StartupServer; 4 | import com.github.netty.protocol.HttpServletProtocol; 5 | import com.github.netty.protocol.servlet.DefaultServlet; 6 | import com.github.netty.protocol.servlet.ServletContext; 7 | 8 | public class FileApplication { 9 | public static void main(String[] args) { 10 | StartupServer server = new StartupServer(80); 11 | server.addProtocol(newHttpProtocol()); 12 | server.start(); 13 | // http://localhost/myfile.html 14 | // http://localhost/a/myfile.html 15 | } 16 | 17 | private static HttpServletProtocol newHttpProtocol() { 18 | ServletContext servletContext = new ServletContext(); 19 | // servletContext.setDocBase("D://demo", "/webapp"); 20 | servletContext.setDocBase(System.getProperty("user.dir"), "/webapp"); 21 | 22 | HttpServletProtocol protocol = new HttpServletProtocol(servletContext); 23 | // protocol.setEnableH2c(true); 24 | // protocol.setSslFileJks(); 25 | return protocol; 26 | } 27 | } -------------------------------------------------------------------------------- /src/test/java/com/github/netty/http/HttpBootstrap.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.http; 2 | 3 | import com.github.netty.StartupServer; 4 | import com.github.netty.protocol.HttpServletProtocol; 5 | import com.github.netty.protocol.servlet.ServletContext; 6 | 7 | public class HttpBootstrap { 8 | 9 | public static void main(String[] args) { 10 | StartupServer server = new StartupServer(8089); 11 | server.addProtocol(newHttpProtocol()); 12 | server.start(); 13 | } 14 | 15 | private static HttpServletProtocol newHttpProtocol() { 16 | ServletContext servletContext = new ServletContext(); 17 | servletContext.setDocBase(System.getProperty("user.dir"), "/webapp"); 18 | servletContext.addServlet("ServletDefaultHttpServlet", new MyHttpServlet()) 19 | .addMapping("/hello"); 20 | 21 | HttpServletProtocol protocol = new HttpServletProtocol(servletContext); 22 | return protocol; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/test/java/com/github/netty/http/HttpTests.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.http; 2 | 3 | import com.github.netty.core.util.IOUtil; 4 | 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.net.URL; 8 | import java.util.Objects; 9 | 10 | public class HttpTests { 11 | 12 | public static void main(String[] args) throws IOException { 13 | URL url = new URL("http://localhost:8080/test/hello?name=xiaowang"); 14 | InputStream inputStream = url.openStream(); 15 | String responseBody = IOUtil.readInput(inputStream); 16 | 17 | assert Objects.equals("hi! xiaowang",responseBody); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/test/java/com/github/netty/http/MyHttpServlet.java: -------------------------------------------------------------------------------- 1 | package com.github.netty.http; 2 | 3 | import com.github.netty.core.util.IOUtil; 4 | 5 | import javax.servlet.ServletException; 6 | import javax.servlet.http.HttpServlet; 7 | import javax.servlet.http.HttpServletRequest; 8 | import javax.servlet.http.HttpServletResponse; 9 | import java.io.*; 10 | import java.nio.ByteBuffer; 11 | import java.util.Iterator; 12 | import java.util.concurrent.atomic.AtomicInteger; 13 | 14 | public class MyHttpServlet extends HttpServlet { 15 | @Override 16 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 17 | for (int i = 0; i < 1; i++) { 18 | PrintWriter writer = response.getWriter(); 19 | writer.write("hello"); 20 | writer.flush(); 21 | } 22 | } 23 | 24 | public static void main(String[] args) throws IOException { 25 | createBigFile(1024 * 1024 * 1024); 26 | } 27 | 28 | private static void createBigFile(int chunkBytes) throws IOException { 29 | AtomicInteger i = new AtomicInteger(); 30 | StringBuilder sb = new StringBuilder(); 31 | for (int j = 0; j < 1024; j++) { 32 | sb.append(j); 33 | } 34 | sb.append("\n"); 35 | byte[] bytes = sb.toString().getBytes(); 36 | 37 | IOUtil.writeFile(new Iterator() { 38 | @Override 39 | public boolean hasNext() { 40 | return i.incrementAndGet() < chunkBytes; 41 | } 42 | 43 | @Override 44 | public ByteBuffer next() { 45 | return ByteBuffer.wrap(bytes); 46 | } 47 | }, "D://", "aaa.txt", true); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 7 | 8 | jvisualvm jconsole 9 | 10 | 你好 netty! 11 | 12 | 2020/1/3 13 | 14 | 现象:压测时,客户端一直处于收不到服务端数据的状态。 15 | 16 | 复现: 17 | 18 | 并发高的时候,不在eventLoop上执行的时候, 19 | DefaultHttpResponse response = new DefaultHttpResponse(); 20 | response.set('content-length',50); 21 | write(response); 22 | write(new DefaultHttpContent(ByteBuff(50))); 23 | write(LastHttpContent.EMPTY); 24 | 25 | 会出现如果存在设置响应体长度,LastHttpContent 26 | 正两个 27 | 28 | 这时候当处理完DefaultHttpResponse,和DefaultHttpContent, 客户端就会进行下一次请求。 29 | 30 | DefaultHttpResponse response = new DefaultHttpResponse(); 31 | response.set('content-length',50); 32 | write(response); 33 | write(new DefaultHttpContent(ByteBuff(50))); 34 | 35 | 36 | 与此同时,write(LastHttpContent.EMPTY); 37 | 这个写任务还堆积在EventLoop的taskQueue中, 导致HttpObjectEncoder的state属性没有变回ST_INIT。 38 | 39 | 这时候服务端收到了客户端的读事件,并且没来及处理上一次的写任务 write(LastHttpContent.EMPTY), 40 | 就会导致HttpObjectEncoder抛出 unexpected message type: 41 | 因为抛出了异常,导致本次的DefaultHttpResponse没有返回给客户端,这时客户端会一直等待服务端回应并阻塞直至超时。 42 | 43 | 44 | 45 | 46 | 47 | * 在这里可以进行优化操作 48 | * @author wangzihao 49 | * 2018/8/25/025 50 | * 51 | * 1.优化记录 (2018年8月25日 22:16:13), 52 | * 发现问题 : rpc调用不论设置超时时间多长, 总是会超时. 53 | * 而且不会因为客户端的连接数增大而改善,反而降低性能, 1个线程=400qps, 100个线程=100-250qps 54 | * 而且服务端只要不发生gc,执行时间都是在3纳秒以内 55 | * 超时时间=10秒, 100个客户端线程, 108750次调用, 80次超时 = 0.027%的超时几率, 100-250的qps 56 | * 57 | * 猜测1 : 估计是客户端的原因, 客户端的执行方法, 有堵塞的调用, 而且堵塞范围过广, 可以从缩小堵塞的影响范围入手 58 | * 猜测2 : 网络丢包, 因为请求后, 没有发现有响应对应的请求id 59 | * 60 | * 猜测3 : id生成问题, 因为测出以下结果, ID(63604)还未请求 就已经先响应 61 | 2018-08-26 00:35:55.359 ERROR 6688 --- [rkerGroup@1-4-1] c.g.n.s.impl.RemoteSessionServiceImpl : 1 save requestTimeout : maxTimeout is [10000] 62 | 2018-08-26 00:36:09.878 ERROR 6688 --- [rkerGroup@3-5-1] c.g.n.c.rpc.RpcClient$RpcClientHandler : 超时的响应[] : null 63 | :requestId: 63604 64 | status: 200 65 | message: "ok" 66 | encode: 1 67 | 68 | 2018-08-26 00:36:09.879 ERROR 6688 --- [rkerGroup@3-5-1] c.g.n.c.rpc.RpcClient$RpcClientHandler : 超时的响应[] : null 69 | :requestId: 63605 70 | status: 200 71 | message: "ok" 72 | encode: 1 73 | 74 | 2018-08-26 00:36:19.878 ERROR 6688 --- [rkerGroup@1-4-2] c.g.n.s.impl.RemoteSessionServiceImpl : 超时的请求 : requestId: 63604 75 | requestMappingName: "/_inner/db" 76 | methodName: "get" 77 | data: "[\"dc177356b505447cbf3338537a90f27a\"]" 78 | 79 | 2018-08-26 00:36:19.878 ERROR 6688 --- [rkerGroup@1-4-2] c.g.n.s.impl.RemoteSessionServiceImpl : 2 get requestTimeout : maxTimeout is [10000] 80 | 2018-08-26 00:36:19.880 ERROR 6688 --- [rkerGroup@1-4-1] c.g.n.s.impl.RemoteSessionServiceImpl : 超时的请求 : requestId: 63605 81 | requestMappingName: "/_inner/db" 82 | methodName: "get" 83 | data: "[\"d7cc6c7a8f7f4c9792c56ee8b472ec6d\"]" 84 | 猜测4 : 思路 : 1.如果客户端不发送请求, 服务端是不可能响应的 85 | 2.从发送请求的地方寻找线索, 86 | 3.客户端判断服务端超时的依据是 : 服务端响应后, 如果获取不到该请求的锁, 则判定为超时 87 | 4.那么会不会是因为客户端的锁还没放进去, 服务端就响应了 88 | 5.于是调换执行顺序, 先放锁, 再发送请求 89 | 6.验证成功! 就是这个原因 ( : 看来有时rpc的执行速度比map.put要快) 90 | 91 | 解决问题 (已解决超时情况): 92 | 原因1. : 旧: RpcLock lock = new RpcLock(); 93 | getSocketChannel().writeAndFlush(rpcRequest); 这里发送早了,锁还没放进去, 就响应了, 导致响应拿不到锁 94 | requestLockMap.put(requestId,lock); 95 | 96 | 新: RpcLock lock = new RpcLock(); 97 | requestLockMap.put(requestId,lock); 先放锁, 再请求, 问题解决 98 | getSocketChannel().writeAndFlush(rpcRequest); 99 | 100 | 原因2 : session服务端的jvm内存不足, 导致大量gc, 101 | 参数改为后 : -Xms600m -Xmn600m -Xmx1000m 102 | 103 | 验证 : 测试结果 104 | 第(100)次统计, 时间 = 500001毫秒[8分20秒], 总调用次数 = 1435015, 总超时次数 = 0, 成功率 = 100 105 | 第(387)次统计, 时间 = 1935001毫秒[32分15秒], 总调用次数 = 4314961, 总超时次数 = 8, 成功率 = 100.00 (注 : 这个是因为session服务端的内存满了) 106 | session服务端gc情况 : [Full GC (Allocation Failure) [Tenured: 409600K->409599K(409600K), 1.7072878 secs] 962559K->620546K(962560K), [Metaspace: 9959K->9959K(10624K)], 1.7075061 secs] [Times: user=1.70 sys=0.00, real=1.71 secs] 107 | 108 | 30分钟, 143万次请求, 431万次rpc调用, 8次rpc超时, 服务端保存143万个会话 109 | 配置参数 : getClientEventLoopWorkerCount = 3 110 | getServerEventLoopWorkerCount = 2 111 | getServerEventLoopIoRatio=100 112 | getClientEventLoopIoRatio=100 113 | getSessionClientSocketChannelCount=1 114 | isSessionClientEnableAutoReconnect=true 115 | isEnableExecuteHold=false 116 | isEnableLog=true 117 | RpcDBService.timeout=1000毫秒 118 | 119 | 新的问题 : 120 | qps 还是一直在1000左右, 121 | 下面是测试结果 122 | 15:07:30.720 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(1)次统计, 时间 = 5002毫秒[0分5秒], 成功 = 2843, 失败 = 0, qps = 568.37 123 | 15:07:35.720 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(2)次统计, 时间 = 10022毫秒[0分10秒], 成功 = 9421, 失败 = 0, qps = 940.03 124 | 15:07:40.721 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(3)次统计, 时间 = 14723毫秒[0分14秒], 成功 = 15178, 失败 = 0, qps = 1030.90 125 | 15:07:45.761 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(4)次统计, 时间 = 19463毫秒[0分19秒], 成功 = 21329, 失败 = 0, qps = 1095.87 126 | 15:07:55.762 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(6)次统计, 时间 = 29164毫秒[0分29秒], 成功 = 32479, 失败 = 0, qps = 1113.67 127 | 15:08:00.762 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(7)次统计, 时间 = 34164毫秒[0分34秒], 成功 = 39302, 失败 = 0, qps = 1150.39 128 | 15:08:20.765 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(11)次统计, 时间 = 53267毫秒[0分53秒], 成功 = 62640, 失败 = 0, qps = 1175.96 129 | 15:08:25.765 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(12)次统计, 时间 = 58267毫秒[0分58秒], 成功 = 67813, 失败 = 0, qps = 1163.83 130 | 15:08:55.768 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(18)次统计, 时间 = 87370毫秒[1分27秒], 成功 = 98933, 失败 = 0, qps = 1132.35 131 | 15:09:40.772 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(27)次统计, 时间 = 130574毫秒[2分10秒], 成功 = 151317, 失败 = 0, qps = 1158.86 132 | 15:11:30.787 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(49)次统计, 时间 = 237289毫秒[3分57秒], 成功 = 264122, 失败 = 0, qps = 1113.08 133 | 15:14:10.828 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(81)次统计, 时间 = 392830毫秒[6分32秒], 成功 = 414227, 失败 = 0, qps = 1054.47 134 | 15:15:20.836 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(95)次统计, 时间 = 460738毫秒[7分40秒], 成功 = 462517, 失败 = 0, qps = 1003.86 135 | 15:15:40.838 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(99)次统计, 时间 = 480140毫秒[8分0秒], 成功 = 479827, 失败 = 0, qps = 999.35 136 | 15:17:40.849 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(123)次统计, 时间 = 596551毫秒[9分56秒], 成功 = 562187, 失败 = 0, qps = 942.40 137 | 15:18:40.856 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(135)次统计, 时间 = 654758毫秒[10分54秒], 成功 = 611696, 失败 = 0, qps = 934.23 138 | 15:21:45.884 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(172)次统计, 时间 = 834686毫秒[13分54秒], 成功 = 762595, 失败 = 0, qps = 913.63 139 | 15:38:18.034 [QpsPrintThread] INFO QpsRunningTest$PrintThread - 第(350)次统计, 时间 = 1801035毫秒[30分1秒], 成功 = 1438254, 失败 = 0, qps = 798.57 140 | Exception in thread "vert.x-eventloop-thread-1" java.lang.OutOfMemoryError: Java heap space 141 | java.lang.OutOfMemoryError: Java heap space 142 | java.lang.OutOfMemoryError: Java heap space 143 | java.lang.OutOfMemoryError: Java heap space 144 | java.lang.OutOfMemoryError: Java heap space 145 | * 146 | * 147 | * 2.优化记录(2018年8月26日 15:34:04) 148 | * 发现问题 : 希望可以将qps优化到5000+, 目前是1000,而且还不稳定 像本地方法调用一样快速(本地方法qps是5000+) 149 | * 入手点1 : 优化RpcLock.lock方法 -> 原因 : 通过jvisualvm工具发现 RpcLock.lock方法居然占用并堵塞唯一2条线程60%的工作时间 150 | * 151 | * 实现方式1 : 在lock的实现方法中,增加了自旋, 如果自旋后没有获取到响应, 再进行堵塞 (不过几乎看不到效果,) 152 | * 测试结果 : 第75次统计, 时间=6分15秒, 调用数=1030483, 自旋成功数=138, 自旋成功率=0.01 153 | * 154 | 155 | --------------------------------------------------------------------------------