├── .gitignore ├── LICENSE ├── README.md ├── README_zh.md ├── demo ├── pom.xml └── src │ └── main │ ├── java │ └── org │ │ └── pyj │ │ └── demo │ │ ├── Application.java │ │ ├── http │ │ ├── PathVariableHandler.java │ │ └── TempHandler.java │ │ └── websocket │ │ ├── WebSocket.java │ │ └── WebSocket2.java │ └── resources │ └── application.properties ├── pom.xml └── src └── main ├── java └── org │ └── pyj │ ├── HttpServerHandler.java │ ├── ServerBootStrap.java │ ├── WebSocketServerHandler.java │ ├── config │ ├── ServerEndpointConfig.java │ ├── WebProperties.java │ └── WebSocketNettyConfig.java │ ├── exception │ ├── DeploymentException.java │ ├── IllegalMethodNotAllowedException.java │ ├── IllegalPathDuplicatedException.java │ └── IllegalPathNotFoundException.java │ ├── http │ ├── NettyHttpRequest.java │ ├── NettyHttpResponse.java │ ├── annotation │ │ └── NettyHttpHandler.java │ ├── handler │ │ ├── IFunctionHandler.java │ │ ├── Result.java │ │ └── ResultJson.java │ └── path │ │ └── Path.java │ └── yeauty │ ├── annotation │ ├── BeforeHandshake.java │ ├── OnBinary.java │ ├── OnClose.java │ ├── OnError.java │ ├── OnEvent.java │ ├── OnMessage.java │ ├── OnOpen.java │ ├── PathVariable.java │ ├── RequestParam.java │ └── ServerPath.java │ ├── pojo │ ├── PojoEndpointServer.java │ ├── PojoMethodMapping.java │ └── Session.java │ ├── support │ ├── AntPathMatcherWrapper.java │ ├── ByteMethodArgumentResolver.java │ ├── DefaultPathMatcher.java │ ├── EventMethodArgumentResolver.java │ ├── HttpHeadersMethodArgumentResolver.java │ ├── MethodArgumentResolver.java │ ├── PathVariableMapMethodArgumentResolver.java │ ├── PathVariableMethodArgumentResolver.java │ ├── RequestParamMapMethodArgumentResolver.java │ ├── RequestParamMethodArgumentResolver.java │ ├── SessionMethodArgumentResolver.java │ ├── TextMethodArgumentResolver.java │ ├── ThrowableMethodArgumentResolver.java │ └── WsPathMatcher.java │ └── util │ └── SslUtils.java └── resources └── META-INF └── spring.factories /.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 | *.iml 26 | target/ 27 | demo/target/ 28 | .DS_Store 29 | 30 | #demo/ 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | netty-websocket-http-spring-boot-starter [![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 2 | =================================== 3 | 4 | [中文文档](https://github.com/pengyongjianpyj/netty-websocket-http-spring-boot-starter/blob/master/README_zh.md) (Chinese Docs) 5 | -------------------------------------------------------------------------------- /README_zh.md: -------------------------------------------------------------------------------- 1 | netty-websocket-http-spring-boot-starter [![License](http://img.shields.io/:license-apache-brightgreen.svg)](http://www.apache.org/licenses/LICENSE-2.0.html) 2 | =================================== 3 | 4 | [English Docs](https://github.com/pengyongjianpyj/netty-websocket-http-spring-boot-starter/blob/master/README.md) 5 | 6 | ### 简介 7 | 本项目帮助你在spring-boot中使用Netty来开发WebSocket服务器,并像spring-websocket的注解开发一样简单,还可以帮助你在spring-boot中使用Netty来开发简单的Http服务器 8 | 9 | 项目启动及其迅捷,经测试启动时间在1s左右;无业务逻辑http接口相应时间在4ms左右。 10 | 11 | WebSocket和Http使用统一端口(默认8080),方便网络方面的管理,另外本项目可用作微服务,只需添加注册发现相关依赖即可 12 | 13 | ### 要求 14 | - jdk版本为1.8或1.8+ 15 | 16 | 17 | ### 快速开始 18 | 19 | - 添加依赖: 20 | 21 | ```xml 22 | 23 | org.pyj 24 | netty-websocket-http-spring-boot-starter 25 | 0.1.0 26 | 27 | ``` 28 | 29 | - spring-boot启动类 30 | 31 | ```java 32 | import org.springframework.boot.SpringApplication; 33 | import org.springframework.boot.autoconfigure.SpringBootApplication; 34 | 35 | @SpringBootApplication 36 | public class Application { 37 | public static void main(String[] args) { 38 | SpringApplication.run(Application.class, args); 39 | } 40 | } 41 | ``` 42 | 43 | 44 | - http表现层接口类 45 | 46 | ```java 47 | import org.pyj.http.NettyHttpRequest; 48 | import org.pyj.http.annotation.NettyHttpHandler; 49 | import org.pyj.http.handler.IFunctionHandler; 50 | import org.pyj.http.handler.Result; 51 | 52 | @NettyHttpHandler(path = "/temp/body",method = "POST") 53 | public class TempHandler implements IFunctionHandler { 54 | 55 | @Override 56 | public Result execute(NettyHttpRequest request) { 57 | String contentText = request.contentText(); 58 | return new ResultJson(200, contentText); 59 | } 60 | } 61 | ``` 62 | 63 | 64 | - websocket入口类 在端点类上加上`@ServerPath`注解,并在相应的方法上加上`@BeforeHandshake`、`@OnOpen`、`@OnClose`、`@OnError`、`@OnMessage`、`@OnBinary`、`@OnEvent`注解,样例如下: 65 | 66 | ```java 67 | @ServerPath(path = "/connect") 68 | public class MyWebSocket { 69 | 70 | @BeforeHandshake 71 | public void handshake(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){ 72 | session.setSubprotocols("stomp"); 73 | if (!"ok".equals(req)){ 74 | System.out.println("Authentication failed!"); 75 | session.close(); 76 | } 77 | } 78 | 79 | @OnOpen 80 | public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){ 81 | System.out.println("new connection"); 82 | System.out.println(req); 83 | } 84 | 85 | @OnClose 86 | public void onClose(Session session) throws IOException { 87 | System.out.println("one connection closed"); 88 | } 89 | 90 | @OnError 91 | public void onError(Session session, Throwable throwable) { 92 | throwable.printStackTrace(); 93 | } 94 | 95 | @OnMessage 96 | public void onMessage(Session session, String message) { 97 | System.out.println(message); 98 | session.sendText("Hello Netty!"); 99 | } 100 | 101 | @OnBinary 102 | public void onBinary(Session session, byte[] bytes) { 103 | for (byte b : bytes) { 104 | System.out.println(b); 105 | } 106 | session.sendBinary(bytes); 107 | } 108 | 109 | @OnEvent 110 | public void onEvent(Session session, Object evt) { 111 | if (evt instanceof IdleStateEvent) { 112 | IdleStateEvent idleStateEvent = (IdleStateEvent) evt; 113 | switch (idleStateEvent.state()) { 114 | case READER_IDLE: 115 | System.out.println("read idle"); 116 | break; 117 | case WRITER_IDLE: 118 | System.out.println("write idle"); 119 | break; 120 | case ALL_IDLE: 121 | System.out.println("all idle"); 122 | break; 123 | default: 124 | break; 125 | } 126 | } 127 | } 128 | 129 | } 130 | ``` 131 | 132 | - 打开WebSocket客户端,连接到`ws://127.0.0.1:8080/connect` 133 | 134 | 135 | ### 注解 136 | ###### @ServerPath 137 | > 当ServerEndpointExporter类通过Spring配置进行声明并被使用,它将会去扫描带有@ServerPath注解的类 138 | > 被注解的类将被注册成为一个WebSocket的connect路由的控制中心 139 | > 路由的url可以有由注解指定 ( 如:`@ServerPath(path = "/connect")` ) 140 | 141 | ###### @BeforeHandshake 142 | > 当有新的连接进入时,对该方法进行回调 143 | > 注入参数的类型:Session、HttpHeaders... 144 | 145 | ###### @OnOpen 146 | > 当有新的WebSocket连接完成时,对该方法进行回调 147 | > 注入参数的类型:Session、HttpHeaders... 148 | 149 | ###### @OnClose 150 | > 当有WebSocket连接关闭时,对该方法进行回调 151 | > 注入参数的类型:Session 152 | 153 | ###### @OnError 154 | > 当有WebSocket抛出异常时,对该方法进行回调 155 | > 注入参数的类型:Session、Throwable 156 | 157 | ###### @OnMessage 158 | > 当接收到字符串消息时,对该方法进行回调 159 | > 注入参数的类型:Session、String 160 | 161 | ###### @OnBinary 162 | > 当接收到二进制消息时,对该方法进行回调 163 | > 注入参数的类型:Session、byte[] 164 | 165 | ###### @OnEvent 166 | > 当接收到Netty的事件时,对该方法进行回调 167 | > 注入参数的类型:Session、Object 168 | -------------------------------------------------------------------------------- /demo/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | demo 9 | org.pyj 10 | 0.1.0 11 | 12 | 13 | 14 | io.github.pengyongjianpyj 15 | netty-websocket-http-spring-boot-starter 16 | 0.1.0 17 | 18 | 19 | 20 | 21 | 22 | 23 | org.apache.maven.plugins 24 | maven-compiler-plugin 25 | 26 | 6 27 | 6 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /demo/src/main/java/org/pyj/demo/Application.java: -------------------------------------------------------------------------------- 1 | package org.pyj.demo; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | public static void main(String[] args) { 9 | SpringApplication.run(Application.class, args); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /demo/src/main/java/org/pyj/demo/http/PathVariableHandler.java: -------------------------------------------------------------------------------- 1 | 2 | package org.pyj.demo.http; 3 | 4 | import org.pyj.http.NettyHttpRequest; 5 | import org.pyj.http.annotation.NettyHttpHandler; 6 | import org.pyj.http.handler.IFunctionHandler; 7 | import org.pyj.http.handler.Result; 8 | import org.pyj.http.handler.ResultJson; 9 | 10 | /** 11 | * @Description: http接口,参数化路径接口示例 12 | * @Author: pengyongjian 13 | * @Date: 2020-04-05 10:14 14 | */ 15 | @NettyHttpHandler(path = "/temp/path/",equal = false) 16 | public class PathVariableHandler implements IFunctionHandler { 17 | 18 | @Override 19 | public Result execute(NettyHttpRequest request) { 20 | /** 21 | * 通过请求uri获取到path参数 22 | */ 23 | String id = request.getStringPathValue(3); 24 | System.out.println(id); 25 | return new ResultJson(200, id); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /demo/src/main/java/org/pyj/demo/http/TempHandler.java: -------------------------------------------------------------------------------- 1 | package org.pyj.demo.http; 2 | 3 | import org.pyj.http.NettyHttpRequest; 4 | import org.pyj.http.annotation.NettyHttpHandler; 5 | import org.pyj.http.handler.IFunctionHandler; 6 | import org.pyj.http.handler.Result; 7 | import org.pyj.http.handler.ResultJson; 8 | 9 | /** 10 | * @Description: http接口,向对应用户发送消息 11 | * @Author: pengyongjian 12 | * @Date: 2020-04-05 10:14 13 | */ 14 | @NettyHttpHandler(path = "/temp/body",method = "POST") 15 | public class TempHandler implements IFunctionHandler { 16 | 17 | @Override 18 | public Result execute(NettyHttpRequest request) { 19 | String contentText = request.contentText(); 20 | return new ResultJson(200, contentText); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /demo/src/main/java/org/pyj/demo/websocket/WebSocket.java: -------------------------------------------------------------------------------- 1 | package org.pyj.demo.websocket; 2 | 3 | import io.netty.handler.codec.http.HttpHeaders; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.pyj.yeauty.annotation.OnClose; 6 | import org.pyj.yeauty.annotation.ServerPath; 7 | import org.springframework.stereotype.Component; 8 | import org.pyj.yeauty.pojo.Session; 9 | 10 | import java.util.Map; 11 | 12 | /** 13 | * @author pengyongjian 14 | * @Description: 15 | * @date 2020/12/21 下午2:58 16 | */ 17 | @Component 18 | @Slf4j 19 | @ServerPath(path = "/connect") 20 | public class WebSocket { 21 | 22 | @org.pyj.yeauty.annotation.OnOpen 23 | public void onOpen(Session session, @org.pyj.yeauty.annotation.RequestParam Map map, HttpHeaders headers) { 24 | System.out.println(session.id()); 25 | } 26 | 27 | @OnClose 28 | public void onClose(Session session) { 29 | } 30 | 31 | @org.pyj.yeauty.annotation.OnError 32 | public void onError(Session session, Throwable throwable) { 33 | } 34 | 35 | @org.pyj.yeauty.annotation.OnMessage 36 | public void OnMessage(Session session, String message) { 37 | System.out.println(message); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /demo/src/main/java/org/pyj/demo/websocket/WebSocket2.java: -------------------------------------------------------------------------------- 1 | package org.pyj.demo.websocket; 2 | 3 | import io.netty.handler.codec.http.HttpHeaders; 4 | import java.util.Map; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.stereotype.Component; 7 | import org.pyj.yeauty.annotation.OnClose; 8 | import org.pyj.yeauty.annotation.OnError; 9 | import org.pyj.yeauty.annotation.OnMessage; 10 | import org.pyj.yeauty.annotation.OnOpen; 11 | import org.pyj.yeauty.annotation.RequestParam; 12 | import org.pyj.yeauty.annotation.ServerPath; 13 | import org.pyj.yeauty.pojo.Session; 14 | 15 | /** 16 | * @author pengyongjian 17 | * @Description: 18 | * @date 2020/12/21 下午2:58 19 | */ 20 | @Component 21 | @Slf4j 22 | @ServerPath(path = "/connect2") 23 | public class WebSocket2 { 24 | 25 | @OnOpen 26 | public void onOpen(Session session, @RequestParam Map map, HttpHeaders headers) { 27 | System.out.println(session.id()); 28 | } 29 | 30 | @OnClose 31 | public void onClose(Session session) { 32 | } 33 | 34 | @OnError 35 | public void onError(Session session, Throwable throwable) { 36 | } 37 | 38 | @OnMessage 39 | public void OnMessage(Session session, String message) { 40 | System.out.println(message); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /demo/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | server.port=8080 2 | web.pyj.bossLoopGroupThreads=1 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | io.github.pengyongjianpyj 8 | netty-websocket-http-spring-boot-starter 9 | 0.1.0 10 | 11 | netty-websocket-http-spring-boot-starter 12 | 13 | netty-websocket-http-spring-boot-starter is a Java WebSocket/http Framework based on Netty 14 | 15 | https://github.com/pengyongjianpyj/netty-websocket-http-spring-boot-starter 16 | 17 | 18 | 19 | The Apache Software License, Version 2.0 20 | http://www.apache.org/licenses/LICENSE-2.0.txt 21 | 22 | 23 | 24 | scm:git:https://github.com/pengyongjianpyj/netty-websocket-http-spring-boot-starter.git 25 | scm:git:https://github.com/pengyongjianpyj/netty-websocket-http-spring-boot-starter.git 26 | https://github.com/pengyongjianpyj/netty-websocket-http-spring-boot-starter 27 | 28 | 29 | 30 | pengyongjianpyj 31 | pengyongjianpyj@163.com 32 | 33 | 34 | 35 | 36 | 37 | ossrh 38 | https://s01.oss.sonatype.org/content/repositories/snapshots 39 | 40 | 41 | ossrh 42 | https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/ 43 | 44 | 45 | 46 | 47 | 2.0.0.RELEASE 48 | 4.1.59.Final 49 | 1.18.2 50 | 1.7.12 51 | 52 | 53 | 54 | 55 | org.springframework.boot 56 | spring-boot-autoconfigure 57 | ${spring-boot.version} 58 | true 59 | 60 | 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-starter-web 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-tomcat 69 | 70 | 71 | ${spring-boot.version} 72 | 73 | 74 | 75 | io.netty 76 | netty-codec-http 77 | ${netty.version} 78 | 79 | 80 | io.netty 81 | netty-handler 82 | ${netty.version} 83 | 84 | 85 | 86 | org.springframework.boot 87 | spring-boot-dependencies 88 | ${spring-boot.version} 89 | pom 90 | import 91 | 92 | 93 | org.springframework.boot 94 | spring-boot-starter-tomcat 95 | 96 | 97 | 98 | 99 | 100 | org.projectlombok 101 | lombok 102 | ${lombok.version} 103 | 104 | 105 | 106 | org.slf4j 107 | slf4j-api 108 | ${slf4j.version} 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | release 117 | 118 | 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-source-plugin 123 | 3.0.1 124 | 125 | 126 | 127 | jar-no-fork 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | org.apache.maven.plugins 136 | maven-javadoc-plugin 137 | 3.0.1 138 | 139 | 140 | attach-sources 141 | 142 | jar 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | org.apache.maven.plugins 157 | maven-gpg-plugin 158 | 1.6 159 | 160 | 161 | sign-artifacts 162 | verify 163 | 164 | sign 165 | 166 | 167 | 168 | 169 | 170 | 171 | org.sonatype.plugins 172 | nexus-staging-maven-plugin 173 | 1.6.8 174 | true 175 | 176 | ossrh 177 | https://s01.oss.sonatype.org/ 178 | true 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | org.apache.maven.plugins 191 | maven-compiler-plugin 192 | 193 | 194 | 1.8 195 | 1.8 196 | 197 | 198 | 199 | 200 | 201 | 202 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/HttpServerHandler.java: -------------------------------------------------------------------------------- 1 | package org.pyj; 2 | 3 | import io.netty.channel.ChannelFutureListener; 4 | import io.netty.channel.ChannelHandler; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.SimpleChannelInboundHandler; 7 | import io.netty.handler.codec.http.FullHttpRequest; 8 | import io.netty.handler.codec.http.FullHttpResponse; 9 | import io.netty.handler.codec.http.HttpResponseStatus; 10 | import java.util.Map; 11 | import java.util.Optional; 12 | import java.util.concurrent.atomic.AtomicBoolean; 13 | import java.util.function.Predicate; 14 | import java.util.stream.Stream; 15 | import lombok.extern.slf4j.Slf4j; 16 | import org.apache.commons.logging.Log; 17 | import org.apache.commons.logging.LogFactory; 18 | import org.pyj.exception.IllegalMethodNotAllowedException; 19 | import org.pyj.exception.IllegalPathNotFoundException; 20 | import org.pyj.http.NettyHttpRequest; 21 | import org.pyj.http.NettyHttpResponse; 22 | import org.pyj.http.handler.IFunctionHandler; 23 | import org.pyj.http.path.Path; 24 | 25 | /** 26 | * @author pengyongjian 27 | * @Description: 管道(信息)处理类,处理websocket/http请求 28 | * @date 2020-03-17 15:52 29 | */ 30 | @ChannelHandler.Sharable 31 | @Slf4j 32 | public class HttpServerHandler extends SimpleChannelInboundHandler { 33 | 34 | protected final Log logger = LogFactory.getLog(this.getClass()); 35 | 36 | private final WebSocketServerHandler webSocketServerHandler; 37 | 38 | private final Map functionHandlerMap; 39 | 40 | public HttpServerHandler(WebSocketServerHandler webSocketServerHandler, Map functionHandlerMap) { 41 | this.webSocketServerHandler = webSocketServerHandler; 42 | this.functionHandlerMap = functionHandlerMap; 43 | } 44 | 45 | @Override 46 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) { 47 | // 如果是websocket的请求路径,当做websocket请求来处 48 | if (!webSocketServerHandler.isWebSocket(ctx, request)) { 49 | // 否则用http请求路径来处 50 | channelReadHttp(ctx, request); 51 | } 52 | } 53 | 54 | /** 55 | * @Description: 处理http请求 56 | * @Author: pengyongjian 57 | * @Date: 2020-04-11 12:08 58 | * @param: ctx 59 | * @param: request 60 | * @return: void 61 | */ 62 | public void channelReadHttp(ChannelHandlerContext ctx, FullHttpRequest request) { 63 | ctx.executor().parent().execute(() -> { 64 | FullHttpResponse response = handleHttpRequest(new NettyHttpRequest(request)); 65 | ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE); 66 | }); 67 | } 68 | 69 | private FullHttpResponse handleHttpRequest(NettyHttpRequest request) { 70 | IFunctionHandler functionHandler = null; 71 | try { 72 | functionHandler = matchFunctionHandler(request); 73 | Object response = functionHandler.execute(request); 74 | return NettyHttpResponse.ok(response.toString()); 75 | } catch (IllegalMethodNotAllowedException error) { 76 | return NettyHttpResponse.make(HttpResponseStatus.METHOD_NOT_ALLOWED); 77 | } catch (IllegalPathNotFoundException error) { 78 | return NettyHttpResponse.make(HttpResponseStatus.NOT_FOUND); 79 | } catch (Exception error) { 80 | logger.error(functionHandler.getClass().getSimpleName() + " Error", error); 81 | return NettyHttpResponse.makeError(error); 82 | } 83 | } 84 | 85 | private IFunctionHandler matchFunctionHandler(NettyHttpRequest request) 86 | throws IllegalPathNotFoundException, IllegalMethodNotAllowedException { 87 | 88 | AtomicBoolean matched = new AtomicBoolean(false); 89 | 90 | Stream stream = functionHandlerMap.keySet().stream() 91 | .filter(((Predicate) path -> { 92 | //过滤 Path URI 不匹配的 93 | if (request.matched(path.getUri(), path.isEqual())) { 94 | matched.set(true); 95 | return matched.get(); 96 | } 97 | return false; 98 | 99 | }).and(path -> { 100 | //过滤 Method 匹配的 101 | return request.isAllowed(path.getMethod()); 102 | })); 103 | 104 | Optional optional = stream.findFirst(); 105 | 106 | stream.close(); 107 | 108 | if (!optional.isPresent() && !matched.get()) { 109 | throw new IllegalPathNotFoundException(); 110 | } 111 | 112 | if (!optional.isPresent() && matched.get()) { 113 | throw new IllegalMethodNotAllowedException(); 114 | } 115 | 116 | return functionHandlerMap.get(optional.get()); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/ServerBootStrap.java: -------------------------------------------------------------------------------- 1 | package org.pyj; 2 | 3 | import io.netty.bootstrap.ServerBootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelInitializer; 6 | import io.netty.channel.ChannelOption; 7 | import io.netty.channel.ChannelPipeline; 8 | import io.netty.channel.EventLoopGroup; 9 | import io.netty.channel.WriteBufferWaterMark; 10 | import io.netty.channel.nio.NioEventLoopGroup; 11 | import io.netty.channel.socket.nio.NioServerSocketChannel; 12 | import io.netty.channel.socket.nio.NioSocketChannel; 13 | import io.netty.handler.codec.http.HttpObjectAggregator; 14 | import io.netty.handler.codec.http.HttpServerCodec; 15 | import io.netty.handler.logging.LogLevel; 16 | import io.netty.handler.logging.LoggingHandler; 17 | import java.net.InetAddress; 18 | import java.net.InetSocketAddress; 19 | import java.net.UnknownHostException; 20 | import java.util.HashMap; 21 | import java.util.Map; 22 | import lombok.extern.slf4j.Slf4j; 23 | import org.pyj.config.ServerEndpointConfig; 24 | import org.pyj.config.WebProperties; 25 | import org.pyj.exception.DeploymentException; 26 | import org.pyj.exception.IllegalPathDuplicatedException; 27 | import org.pyj.http.annotation.NettyHttpHandler; 28 | import org.pyj.http.handler.IFunctionHandler; 29 | import org.pyj.http.path.Path; 30 | import org.pyj.yeauty.annotation.ServerPath; 31 | import org.pyj.yeauty.pojo.PojoEndpointServer; 32 | import org.pyj.yeauty.pojo.PojoMethodMapping; 33 | import org.springframework.beans.factory.BeanFactory; 34 | import org.springframework.beans.factory.BeanFactoryAware; 35 | import org.springframework.beans.factory.SmartInitializingSingleton; 36 | import org.springframework.beans.factory.annotation.Autowired; 37 | import org.springframework.beans.factory.annotation.Value; 38 | import org.springframework.beans.factory.support.AbstractBeanFactory; 39 | import org.springframework.context.ApplicationContext; 40 | import org.springframework.context.support.ApplicationObjectSupport; 41 | import org.springframework.core.annotation.AnnotatedElementUtils; 42 | 43 | /** 44 | * @author pengyongjian 45 | * @Description: 46 | * @date 2020-03-17 15:52 47 | */ 48 | @Slf4j 49 | public class ServerBootStrap extends ApplicationObjectSupport 50 | implements SmartInitializingSingleton, BeanFactoryAware { 51 | 52 | @Autowired 53 | private WebProperties webProperties; 54 | 55 | @Value("${server.port}") 56 | private Integer port; 57 | 58 | private AbstractBeanFactory beanFactory; 59 | 60 | @Override 61 | public void afterSingletonsInstantiated() { 62 | init(); 63 | } 64 | 65 | @Override 66 | public void setBeanFactory(BeanFactory beanFactory) { 67 | if (!(beanFactory instanceof AbstractBeanFactory)) { 68 | throw new IllegalArgumentException( 69 | "AutowiredAnnotationBeanPostProcessor requires a AbstractBeanFactory: " + beanFactory); 70 | } 71 | this.beanFactory = (AbstractBeanFactory) beanFactory; 72 | } 73 | 74 | private void init() { 75 | // 获取配置信息并封装到serverEndpointConfig中 76 | ServerEndpointConfig config = buildConfig(); 77 | // 获取websocket类,及其注解配置信息 78 | ApplicationContext context = getApplicationContext(); 79 | String[] endpointBeanNames = context.getBeanNamesForAnnotation(ServerPath.class); 80 | if (endpointBeanNames.length == 0) { 81 | logger.error("netty-websocket-http-spring-boot-starter no @ServerPath class "); 82 | } 83 | 84 | // 创建websocket的业务对象 85 | PojoEndpointServer pojoEndpointServer = new PojoEndpointServer(config); 86 | for (String endpointBeanName : endpointBeanNames) { 87 | Class endpointClass = context.getType(endpointBeanName); 88 | ServerPath annotation = AnnotatedElementUtils.findMergedAnnotation(endpointClass, ServerPath.class); 89 | if (annotation == null) { 90 | throw new IllegalStateException("missingAnnotation ServerEndpoint"); 91 | } 92 | // 解析获取websocket的路径path 93 | String path = annotation.value(); 94 | logger.info("WebSocket Path : " + path); 95 | 96 | // 获取websocket方法并生成映射pojoMethodMapping 97 | PojoMethodMapping pojoMethodMapping = null; 98 | try { 99 | pojoMethodMapping = new PojoMethodMapping(endpointClass, context, beanFactory); 100 | } catch (DeploymentException e) { 101 | throw new IllegalStateException("Failed to register ServerEndpointConfig: " + config, e); 102 | } 103 | pojoEndpointServer.addPathPojoMethodMapping(path, pojoMethodMapping); 104 | } 105 | // 创建处理业务类对象 106 | WebSocketServerHandler webSocketServerHandler = new WebSocketServerHandler(pojoEndpointServer, config); 107 | // 获取http接口的路径接口映射关系 108 | Map functionHandlerMap = getFunctionHandlerMap(); 109 | // 创建http处理业务类对象 110 | HttpServerHandler httpServerHandler = new HttpServerHandler(webSocketServerHandler, functionHandlerMap); 111 | // netty的web容器的启动 112 | runServer(config, httpServerHandler); 113 | 114 | } 115 | 116 | private void runServer(ServerEndpointConfig config, HttpServerHandler httpServerHandler) { 117 | try { 118 | EventLoopGroup boss = new NioEventLoopGroup(config.getBossLoopGroupThreads()); 119 | EventLoopGroup worker = new NioEventLoopGroup(config.getWorkerLoopGroupThreads()); 120 | ServerBootstrap bootstrap = new ServerBootstrap(); 121 | bootstrap.group(boss, worker) 122 | .channel(NioServerSocketChannel.class) 123 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeoutMillis()) 124 | .option(ChannelOption.SO_BACKLOG, config.getSoBacklog()) 125 | .childOption(ChannelOption.WRITE_SPIN_COUNT, config.getWriteSpinCount()) 126 | .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, 127 | new WriteBufferWaterMark(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark())) 128 | .childOption(ChannelOption.TCP_NODELAY, config.isTcpNodelay()) 129 | .childOption(ChannelOption.SO_KEEPALIVE, config.isSoKeepalive()) 130 | .childOption(ChannelOption.SO_LINGER, config.getSoLinger()) 131 | .childOption(ChannelOption.ALLOW_HALF_CLOSURE, config.isAllowHalfClosure()) 132 | .handler(new LoggingHandler(LogLevel.DEBUG)) 133 | .childHandler(new ChannelInitializer() { 134 | @Override 135 | protected void initChannel(NioSocketChannel ch) throws Exception { 136 | ChannelPipeline pipeline = ch.pipeline(); 137 | pipeline.addLast(new HttpServerCodec()); 138 | pipeline.addLast(new HttpObjectAggregator(65536)); 139 | pipeline.addLast(httpServerHandler); 140 | } 141 | }); 142 | 143 | if (config.getSoRcvbuf() != -1) { 144 | bootstrap.childOption(ChannelOption.SO_RCVBUF, config.getSoRcvbuf()); 145 | } 146 | 147 | if (config.getSoSndbuf() != -1) { 148 | bootstrap.childOption(ChannelOption.SO_SNDBUF, config.getSoSndbuf()); 149 | } 150 | 151 | ChannelFuture channelFuture; 152 | if ("0.0.0.0".equals(config.getHost())) { 153 | channelFuture = bootstrap.bind(config.getPort()); 154 | } else { 155 | try { 156 | channelFuture = 157 | bootstrap.bind(new InetSocketAddress(InetAddress.getByName(config.getHost()), config.getPort())); 158 | } catch (UnknownHostException e) { 159 | channelFuture = bootstrap.bind(config.getHost(), config.getPort()); 160 | e.printStackTrace(); 161 | } 162 | } 163 | 164 | channelFuture.addListener(future -> { 165 | if (!future.isSuccess()) { 166 | future.cause().printStackTrace(); 167 | } 168 | }); 169 | 170 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 171 | boss.shutdownGracefully().syncUninterruptibly(); 172 | worker.shutdownGracefully().syncUninterruptibly(); 173 | })); 174 | logger.info("======================================================="); 175 | logger.info("===== Netty WebSocket & Http started on port:" + config.getPort() + " ====="); 176 | logger.info("======================================================="); 177 | } catch (Exception e) { 178 | logger.error("sever init fail", e); 179 | } 180 | } 181 | 182 | private ServerEndpointConfig buildConfig() { 183 | ServerEndpointConfig serverEndpointConfig = new ServerEndpointConfig(webProperties.getHost(), port, 184 | webProperties.getBossLoopGroupThreads(), 185 | webProperties.getWorkerLoopGroupThreads(), 186 | webProperties.getUseCompressionHandler(), 187 | webProperties.getOptionConnectTimeoutMillis(), 188 | webProperties.getOptionSoBacklog(), 189 | webProperties.getChildOptionWriteSpinCount(), 190 | webProperties.getChildOptionWriteBufferHighWaterMark(), 191 | webProperties.getChildOptionWriteBufferLowWaterMark(), 192 | webProperties.getChildOptionSoRcvbuf(), 193 | webProperties.getChildOptionSoSndbuf(), 194 | webProperties.getChildOptionTcpNodelay(), 195 | webProperties.getChildOptionSoKeepalive(), 196 | webProperties.getChildOptionSoLinger(), 197 | webProperties.getChildOptionAllowHalfClosure(), 198 | webProperties.getReaderIdleTimeSeconds(), 199 | webProperties.getWriterIdleTimeSeconds(), 200 | webProperties.getAllIdleTimeSeconds(), 201 | webProperties.getMaxFramePayloadLength(), 202 | webProperties.getUseEventExecutorGroup(), 203 | webProperties.getEventExecutorGroupThreads(), 204 | webProperties.getSslKeyPassword(), 205 | webProperties.getSslKeyStore(), 206 | webProperties.getSslKeyStorePassword(), 207 | webProperties.getSslKeyStoreType(), 208 | webProperties.getSslTrustStore(), 209 | webProperties.getSslTrustStorePassword(), 210 | webProperties.getSslTrustStoreType(), 211 | webProperties.getCorsOrigins(), 212 | webProperties.getCorsAllowCredentials()); 213 | 214 | return serverEndpointConfig; 215 | } 216 | 217 | public Map getFunctionHandlerMap() { 218 | HashMap functionHandlerMap = new HashMap<>(); 219 | ApplicationContext applicationContext = getApplicationContext(); 220 | Map handlers = applicationContext.getBeansWithAnnotation(NettyHttpHandler.class); 221 | if (handlers.size() == 0) { 222 | logger.error("netty-websocket-http-spring-boot-starter no @NettyHttpHandler class "); 223 | } 224 | for (Map.Entry entry : handlers.entrySet()) { 225 | Object handler = entry.getValue(); 226 | Path path = Path.make(handler.getClass().getAnnotation(NettyHttpHandler.class)); 227 | if (functionHandlerMap.containsKey(path)) { 228 | logger.error("IFunctionHandler has duplicated :" + path.toString(), new IllegalPathDuplicatedException()); 229 | System.exit(0); 230 | } 231 | logger.info("Http Path : " + path.toString()); 232 | functionHandlerMap.put(path, (IFunctionHandler) handler); 233 | } 234 | return functionHandlerMap; 235 | } 236 | 237 | } 238 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/WebSocketServerHandler.java: -------------------------------------------------------------------------------- 1 | package org.pyj; 2 | 3 | import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_KEY; 4 | import static io.netty.handler.codec.http.HttpHeaderNames.SEC_WEBSOCKET_VERSION; 5 | import static io.netty.handler.codec.http.HttpHeaderNames.UPGRADE; 6 | import static io.netty.handler.codec.http.HttpMethod.GET; 7 | import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST; 8 | import static io.netty.handler.codec.http.HttpResponseStatus.FORBIDDEN; 9 | import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR; 10 | import static io.netty.handler.codec.http.HttpResponseStatus.OK; 11 | import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; 12 | 13 | 14 | import io.netty.buffer.ByteBuf; 15 | import io.netty.buffer.Unpooled; 16 | import io.netty.channel.Channel; 17 | import io.netty.channel.ChannelFuture; 18 | import io.netty.channel.ChannelFutureListener; 19 | import io.netty.channel.ChannelHandler; 20 | import io.netty.channel.ChannelHandlerContext; 21 | import io.netty.channel.ChannelPipeline; 22 | import io.netty.channel.SimpleChannelInboundHandler; 23 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 24 | import io.netty.handler.codec.http.FullHttpRequest; 25 | import io.netty.handler.codec.http.FullHttpResponse; 26 | import io.netty.handler.codec.http.HttpHeaderNames; 27 | import io.netty.handler.codec.http.HttpUtil; 28 | import io.netty.handler.codec.http.QueryStringDecoder; 29 | import io.netty.handler.codec.http.cors.CorsHandler; 30 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 31 | import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 32 | import io.netty.handler.codec.http.websocketx.PingWebSocketFrame; 33 | import io.netty.handler.codec.http.websocketx.PongWebSocketFrame; 34 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 35 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 36 | import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; 37 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; 38 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; 39 | import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; 40 | import io.netty.handler.timeout.IdleStateHandler; 41 | import io.netty.util.AttributeKey; 42 | import io.netty.util.CharsetUtil; 43 | import java.util.Set; 44 | import org.pyj.config.ServerEndpointConfig; 45 | import org.pyj.yeauty.pojo.PojoEndpointServer; 46 | import org.pyj.yeauty.support.WsPathMatcher; 47 | import org.springframework.beans.TypeMismatchException; 48 | import org.springframework.util.StringUtils; 49 | 50 | /** 51 | * @author pengyongjian 52 | * @Description: websocket服务处理类 53 | * @date 2020-03-16 14:07 54 | */ 55 | @ChannelHandler.Sharable 56 | public class WebSocketServerHandler extends SimpleChannelInboundHandler { 57 | 58 | private static PojoEndpointServer pojoEndpointServer; 59 | 60 | private final ServerEndpointConfig config; 61 | 62 | public WebSocketServerHandler(PojoEndpointServer pojoEndpointServer, ServerEndpointConfig config) { 63 | WebSocketServerHandler.pojoEndpointServer = pojoEndpointServer; 64 | this.config = config; 65 | } 66 | 67 | @Override 68 | protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception { 69 | handleWebSocketFrame(ctx, msg); 70 | } 71 | 72 | @Override 73 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 74 | cause.printStackTrace(); 75 | pojoEndpointServer.doOnError(ctx.channel(), cause); 76 | } 77 | 78 | @Override 79 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 80 | pojoEndpointServer.doOnClose(ctx.channel()); 81 | } 82 | 83 | @Override 84 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 85 | pojoEndpointServer.doOnEvent(ctx.channel(), evt); 86 | } 87 | 88 | private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { 89 | if (frame instanceof TextWebSocketFrame) { 90 | pojoEndpointServer.doOnMessage(ctx.channel(), frame); 91 | return; 92 | } 93 | if (frame instanceof PingWebSocketFrame) { 94 | ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); 95 | return; 96 | } 97 | if (frame instanceof CloseWebSocketFrame) { 98 | ctx.writeAndFlush(frame.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE); 99 | return; 100 | } 101 | if (frame instanceof BinaryWebSocketFrame) { 102 | pojoEndpointServer.doOnBinary(ctx.channel(), frame); 103 | return; 104 | } 105 | if (frame instanceof PongWebSocketFrame) { 106 | return; 107 | } 108 | } 109 | 110 | public boolean isWebSocket(ChannelHandlerContext ctx, FullHttpRequest request){ 111 | //path match 是否是websocket的路径 112 | Channel channel = ctx.channel(); 113 | QueryStringDecoder decoder = new QueryStringDecoder(request.uri()); 114 | String pattern = null; 115 | Set pathMatcherSet = pojoEndpointServer.getPathMatcherSet(); 116 | for (WsPathMatcher pathMatcher : pathMatcherSet) { 117 | if (pathMatcher.matchAndExtract(decoder, channel)) { 118 | pattern = pathMatcher.getPattern(); 119 | break; 120 | } 121 | } 122 | 123 | // 如果是websocket的请求路径,当做websocket请求来处理 124 | if (pattern != null) { 125 | channelReadWebSocket(ctx, request, pattern); 126 | return true; 127 | } 128 | // 否则用http请求路径来处理 129 | else { 130 | return false; 131 | } 132 | } 133 | 134 | /** 135 | * @Description: 处理websocket请求 136 | * @Author: pengyongjian 137 | * @Date: 2020-04-11 12:08 138 | * @param: ctx 139 | * @param: req 140 | * @return: void 141 | */ 142 | private void channelReadWebSocket(ChannelHandlerContext ctx, FullHttpRequest req, String pattern) { 143 | FullHttpResponse res; 144 | // Handle a bad request. 145 | if (!req.decoderResult().isSuccess()) { 146 | res = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST); 147 | sendWebSocketResponse(ctx, req, res); 148 | return; 149 | } 150 | 151 | // Allow only GET methods. 152 | if (req.method() != GET) { 153 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 154 | sendWebSocketResponse(ctx, req, res); 155 | return; 156 | } 157 | 158 | // check request host 159 | String host = req.headers().get(HttpHeaderNames.HOST); 160 | if (StringUtils.isEmpty(host)) { 161 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 162 | sendWebSocketResponse(ctx, req, res); 163 | return; 164 | } 165 | if (!StringUtils.isEmpty(pojoEndpointServer.getHost()) && !pojoEndpointServer.getHost().equals("0.0.0.0") 166 | && !pojoEndpointServer.getHost().equals(host.split(":")[0])) { 167 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 168 | sendWebSocketResponse(ctx, req, res); 169 | return; 170 | } 171 | 172 | // check wesocket's header msg 173 | if (!req.headers().contains(UPGRADE) || !req.headers().contains(SEC_WEBSOCKET_KEY) 174 | || !req.headers().contains(SEC_WEBSOCKET_VERSION)) { 175 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 176 | sendWebSocketResponse(ctx, req, res); 177 | return; 178 | } 179 | 180 | // 处理请求 181 | try { 182 | handleWebSocketRequest(ctx, req, pattern); 183 | } catch (TypeMismatchException e) { 184 | res = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST); 185 | sendWebSocketResponse(ctx, req, res); 186 | e.printStackTrace(); 187 | } catch (Exception e) { 188 | res = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR); 189 | sendWebSocketResponse(ctx, req, res); 190 | e.printStackTrace(); 191 | } 192 | } 193 | 194 | private static void sendWebSocketResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { 195 | // Generate an error page if response getStatus code is not OK (200). 196 | int statusCode = res.status().code(); 197 | if (statusCode != OK.code() && res.content().readableBytes() == 0) { 198 | ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8); 199 | res.content().writeBytes(buf); 200 | buf.release(); 201 | } 202 | HttpUtil.setContentLength(res, res.content().readableBytes()); 203 | 204 | // Send the response and close the connection if necessary. 205 | ChannelFuture f = ctx.channel().writeAndFlush(res); 206 | if (!HttpUtil.isKeepAlive(req) || statusCode != 200) { 207 | f.addListener(ChannelFutureListener.CLOSE); 208 | } 209 | } 210 | 211 | private void handleWebSocketRequest(ChannelHandlerContext ctx, FullHttpRequest req, String pattern) { 212 | 213 | String subprotocols = null; 214 | Channel channel = ctx.channel(); 215 | if (pojoEndpointServer.hasBeforeHandshake(channel, pattern)) { 216 | pojoEndpointServer.doBeforeHandshake(channel, req, pattern); 217 | if (!channel.isActive()) { 218 | return; 219 | } 220 | 221 | AttributeKey subprotocolsAttrKey = AttributeKey.valueOf("subprotocols"); 222 | if (channel.hasAttr(subprotocolsAttrKey)) { 223 | subprotocols = ctx.channel().attr(subprotocolsAttrKey).get(); 224 | } 225 | } 226 | 227 | // Handshake 228 | String fullUrl = "ws://" + req.headers().get(HttpHeaderNames.HOST) + req.uri(); 229 | WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(fullUrl, subprotocols, 230 | true, config.getmaxFramePayloadLength()); 231 | WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); 232 | if (handshaker == null) { 233 | WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel); 234 | } else { 235 | ChannelPipeline pipeline = ctx.pipeline(); 236 | pipeline.remove(ctx.name()); 237 | if (config.getReaderIdleTimeSeconds() != 0 238 | || config.getWriterIdleTimeSeconds() != 0 239 | || config.getAllIdleTimeSeconds() != 0) { 240 | pipeline.addLast(new IdleStateHandler(config.getReaderIdleTimeSeconds(), 241 | config.getWriterIdleTimeSeconds(), config.getAllIdleTimeSeconds())); 242 | } 243 | if (config.isUseCompressionHandler()) { 244 | pipeline.addLast(new WebSocketServerCompressionHandler()); 245 | } 246 | // 管道添加WebSocketServerHandler 247 | pipeline.addLast(this); 248 | String finalPattern = pattern; 249 | handshaker.handshake(channel, req).addListener(future -> { 250 | if (future.isSuccess()) { 251 | pojoEndpointServer.doOnOpen(channel, req, finalPattern); 252 | } else { 253 | handshaker.close(channel, new CloseWebSocketFrame()); 254 | } 255 | }); 256 | } 257 | } 258 | 259 | } 260 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/config/ServerEndpointConfig.java: -------------------------------------------------------------------------------- 1 | package org.pyj.config; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.net.Socket; 6 | import org.springframework.util.StringUtils; 7 | 8 | /** 9 | * @author Yeauty 10 | * @version 1.0 11 | */ 12 | public class ServerEndpointConfig { 13 | 14 | private static Integer randomPort; 15 | private final String HOST; 16 | private final int PORT; 17 | private final int BOSS_LOOP_GROUP_THREADS; 18 | private final int WORKER_LOOP_GROUP_THREADS; 19 | private final boolean USE_COMPRESSION_HANDLER; 20 | private final int CONNECT_TIMEOUT_MILLIS; 21 | private final int SO_BACKLOG; 22 | private final int WRITE_SPIN_COUNT; 23 | private final int WRITE_BUFFER_HIGH_WATER_MARK; 24 | private final int WRITE_BUFFER_LOW_WATER_MARK; 25 | private final int SO_RCVBUF; 26 | private final int SO_SNDBUF; 27 | private final boolean TCP_NODELAY; 28 | private final boolean SO_KEEPALIVE; 29 | private final int SO_LINGER; 30 | private final boolean ALLOW_HALF_CLOSURE; 31 | private final int READER_IDLE_TIME_SECONDS; 32 | private final int WRITER_IDLE_TIME_SECONDS; 33 | private final int ALL_IDLE_TIME_SECONDS; 34 | private final int MAX_FRAME_PAYLOAD_LENGTH; 35 | private final boolean USE_EVENT_EXECUTOR_GROUP; 36 | private final int EVENT_EXECUTOR_GROUP_THREADS; 37 | private final String KEY_PASSWORD; 38 | private final String KEY_STORE; 39 | private final String KEY_STORE_PASSWORD; 40 | private final String KEY_STORE_TYPE; 41 | private final String TRUST_STORE; 42 | private final String TRUST_STORE_PASSWORD; 43 | private final String TRUST_STORE_TYPE; 44 | private final String[] CORS_ORIGINS; 45 | private final Boolean CORS_ALLOW_CREDENTIALS; 46 | 47 | public ServerEndpointConfig(String host, int port, int bossLoopGroupThreads, int workerLoopGroupThreads, 48 | boolean useCompressionHandler, int connectTimeoutMillis, int soBacklog, 49 | int writeSpinCount, int writeBufferHighWaterMark, int writeBufferLowWaterMark, 50 | int soRcvbuf, int soSndbuf, boolean tcpNodelay, boolean soKeepalive, int soLinger, 51 | boolean allowHalfClosure, int readerIdleTimeSeconds, int writerIdleTimeSeconds, 52 | int allIdleTimeSeconds, int maxFramePayloadLength, boolean useEventExecutorGroup, 53 | int eventExecutorGroupThreads, String keyPassword, String keyStore, 54 | String keyStorePassword, String keyStoreType, String trustStore, 55 | String trustStorePassword, String trustStoreType, String[] corsOrigins, 56 | Boolean corsAllowCredentials) { 57 | if (StringUtils.isEmpty(host) || "0.0.0.0".equals(host) || "0.0.0.0/0.0.0.0".equals(host)) { 58 | this.HOST = "0.0.0.0"; 59 | } else { 60 | this.HOST = host; 61 | } 62 | this.PORT = getAvailablePort(port); 63 | this.BOSS_LOOP_GROUP_THREADS = bossLoopGroupThreads; 64 | this.WORKER_LOOP_GROUP_THREADS = workerLoopGroupThreads; 65 | this.USE_COMPRESSION_HANDLER = useCompressionHandler; 66 | this.CONNECT_TIMEOUT_MILLIS = connectTimeoutMillis; 67 | this.SO_BACKLOG = soBacklog; 68 | this.WRITE_SPIN_COUNT = writeSpinCount; 69 | this.WRITE_BUFFER_HIGH_WATER_MARK = writeBufferHighWaterMark; 70 | this.WRITE_BUFFER_LOW_WATER_MARK = writeBufferLowWaterMark; 71 | this.SO_RCVBUF = soRcvbuf; 72 | this.SO_SNDBUF = soSndbuf; 73 | this.TCP_NODELAY = tcpNodelay; 74 | this.SO_KEEPALIVE = soKeepalive; 75 | this.SO_LINGER = soLinger; 76 | this.ALLOW_HALF_CLOSURE = allowHalfClosure; 77 | this.READER_IDLE_TIME_SECONDS = readerIdleTimeSeconds; 78 | this.WRITER_IDLE_TIME_SECONDS = writerIdleTimeSeconds; 79 | this.ALL_IDLE_TIME_SECONDS = allIdleTimeSeconds; 80 | this.MAX_FRAME_PAYLOAD_LENGTH = maxFramePayloadLength; 81 | this.USE_EVENT_EXECUTOR_GROUP = useEventExecutorGroup; 82 | this.EVENT_EXECUTOR_GROUP_THREADS = eventExecutorGroupThreads; 83 | 84 | this.KEY_PASSWORD = keyPassword; 85 | this.KEY_STORE = keyStore; 86 | this.KEY_STORE_PASSWORD = keyStorePassword; 87 | this.KEY_STORE_TYPE = keyStoreType; 88 | this.TRUST_STORE = trustStore; 89 | this.TRUST_STORE_PASSWORD = trustStorePassword; 90 | this.TRUST_STORE_TYPE = trustStoreType; 91 | 92 | this.CORS_ORIGINS = corsOrigins; 93 | this.CORS_ALLOW_CREDENTIALS = corsAllowCredentials; 94 | } 95 | 96 | public static Integer getRandomPort() { 97 | return randomPort; 98 | } 99 | 100 | private int getAvailablePort(int port) { 101 | if (port != 0) { 102 | return port; 103 | } 104 | if (randomPort != null && randomPort != 0) { 105 | return randomPort; 106 | } 107 | InetSocketAddress inetSocketAddress = new InetSocketAddress(0); 108 | Socket socket = new Socket(); 109 | try { 110 | socket.bind(inetSocketAddress); 111 | } catch (IOException e) { 112 | e.printStackTrace(); 113 | } 114 | int localPort = socket.getLocalPort(); 115 | try { 116 | socket.close(); 117 | } catch (IOException e) { 118 | e.printStackTrace(); 119 | } 120 | randomPort = localPort; 121 | return localPort; 122 | } 123 | 124 | public String getHost() { 125 | return HOST; 126 | } 127 | 128 | /*public Set getPathSet() { 129 | 130 | return PATH_SET; 131 | }*/ 132 | 133 | public int getPort() { 134 | return PORT; 135 | } 136 | 137 | public int getBossLoopGroupThreads() { 138 | return BOSS_LOOP_GROUP_THREADS; 139 | } 140 | 141 | public int getWorkerLoopGroupThreads() { 142 | return WORKER_LOOP_GROUP_THREADS; 143 | } 144 | 145 | public boolean isUseCompressionHandler() { 146 | return USE_COMPRESSION_HANDLER; 147 | } 148 | 149 | public int getConnectTimeoutMillis() { 150 | return CONNECT_TIMEOUT_MILLIS; 151 | } 152 | 153 | public int getSoBacklog() { 154 | return SO_BACKLOG; 155 | } 156 | 157 | public int getWriteSpinCount() { 158 | return WRITE_SPIN_COUNT; 159 | } 160 | 161 | public int getWriteBufferHighWaterMark() { 162 | return WRITE_BUFFER_HIGH_WATER_MARK; 163 | } 164 | 165 | public int getWriteBufferLowWaterMark() { 166 | return WRITE_BUFFER_LOW_WATER_MARK; 167 | } 168 | 169 | public int getSoRcvbuf() { 170 | return SO_RCVBUF; 171 | } 172 | 173 | public int getSoSndbuf() { 174 | return SO_SNDBUF; 175 | } 176 | 177 | public boolean isTcpNodelay() { 178 | return TCP_NODELAY; 179 | } 180 | 181 | public boolean isSoKeepalive() { 182 | return SO_KEEPALIVE; 183 | } 184 | 185 | public int getSoLinger() { 186 | return SO_LINGER; 187 | } 188 | 189 | public boolean isAllowHalfClosure() { 190 | return ALLOW_HALF_CLOSURE; 191 | } 192 | 193 | public int getReaderIdleTimeSeconds() { 194 | return READER_IDLE_TIME_SECONDS; 195 | } 196 | 197 | public int getWriterIdleTimeSeconds() { 198 | return WRITER_IDLE_TIME_SECONDS; 199 | } 200 | 201 | public int getAllIdleTimeSeconds() { 202 | return ALL_IDLE_TIME_SECONDS; 203 | } 204 | 205 | public int getmaxFramePayloadLength() { 206 | return MAX_FRAME_PAYLOAD_LENGTH; 207 | } 208 | 209 | public boolean isUseEventExecutorGroup() { 210 | return USE_EVENT_EXECUTOR_GROUP; 211 | } 212 | 213 | public int getEventExecutorGroupThreads() { 214 | return EVENT_EXECUTOR_GROUP_THREADS; 215 | } 216 | 217 | public String getKeyPassword() { 218 | return KEY_PASSWORD; 219 | } 220 | 221 | public String getKeyStore() { 222 | return KEY_STORE; 223 | } 224 | 225 | public String getKeyStorePassword() { 226 | return KEY_STORE_PASSWORD; 227 | } 228 | 229 | public String getKeyStoreType() { 230 | return KEY_STORE_TYPE; 231 | } 232 | 233 | public String getTrustStore() { 234 | return TRUST_STORE; 235 | } 236 | 237 | public String getTrustStorePassword() { 238 | return TRUST_STORE_PASSWORD; 239 | } 240 | 241 | public String getTrustStoreType() { 242 | return TRUST_STORE_TYPE; 243 | } 244 | 245 | public String[] getCorsOrigins() { 246 | return CORS_ORIGINS; 247 | } 248 | 249 | public Boolean getCorsAllowCredentials() { 250 | return CORS_ALLOW_CREDENTIALS; 251 | } 252 | } 253 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/config/WebProperties.java: -------------------------------------------------------------------------------- 1 | package org.pyj.config; 2 | 3 | import lombok.Data; 4 | import org.springframework.boot.context.properties.ConfigurationProperties; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author pengyongjian 9 | * @Description: 10 | * @date 2021/6/10 10:22 上午 11 | */ 12 | @Data 13 | @Configuration 14 | @ConfigurationProperties("web.pyj") 15 | public class WebProperties { 16 | 17 | private String host = "0.0.0.0"; 18 | 19 | private Integer bossLoopGroupThreads = 1; 20 | 21 | private Integer workerLoopGroupThreads = 0; 22 | 23 | private Boolean useCompressionHandler = false; 24 | 25 | //------------------------- option ------------------------- 26 | 27 | private Integer optionConnectTimeoutMillis = 30000; 28 | 29 | private Integer optionSoBacklog = 128; 30 | 31 | //------------------------- childOption ------------------------- 32 | 33 | private Integer childOptionWriteSpinCount = 16; 34 | 35 | private Integer childOptionWriteBufferHighWaterMark = 65536; 36 | 37 | private Integer childOptionWriteBufferLowWaterMark = 32768; 38 | 39 | private Integer childOptionSoRcvbuf = -1; 40 | 41 | private Integer childOptionSoSndbuf = -1; 42 | 43 | private Boolean childOptionTcpNodelay = true; 44 | 45 | private Boolean childOptionSoKeepalive = false; 46 | 47 | private Integer childOptionSoLinger = -1; 48 | 49 | private Boolean childOptionAllowHalfClosure = false; 50 | 51 | //------------------------- idleEvent ------------------------- 52 | 53 | private Integer readerIdleTimeSeconds = 0; 54 | 55 | private Integer writerIdleTimeSeconds = 0; 56 | 57 | private Integer allIdleTimeSeconds = 0; 58 | 59 | //------------------------- handshake ------------------------- 60 | 61 | private Integer maxFramePayloadLength = 65536; 62 | 63 | //------------------------- eventExecutorGroup ------------------------- 64 | 65 | private Boolean useEventExecutorGroup = true; 66 | //use EventExecutorGroup(another thread pool) to perform time-consuming synchronous business logic 67 | 68 | private Integer eventExecutorGroupThreads = 16; 69 | 70 | //------------------------- ssl (refer to spring Ssl) ------------------------- 71 | 72 | /** 73 | * {@link org.springframework.boot.web.server.Ssl} 74 | */ 75 | 76 | private String sslKeyPassword = ""; 77 | 78 | private String sslKeyStore = ""; //e.g. classpath:server.jks 79 | 80 | private String sslKeyStorePassword = ""; 81 | 82 | private String sslKeyStoreType = ""; //e.g. JKS 83 | 84 | private String sslTrustStore = ""; 85 | 86 | private String sslTrustStorePassword = ""; 87 | 88 | private String sslTrustStoreType = ""; 89 | 90 | //------------------------- cors (refer to spring CrossOrigin) ------------------------- 91 | 92 | /** 93 | * {@link org.springframework.web.bind.annotation.CrossOrigin} 94 | */ 95 | 96 | private String[] corsOrigins = {}; 97 | 98 | private Boolean corsAllowCredentials = true; 99 | 100 | } 101 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/config/WebSocketNettyConfig.java: -------------------------------------------------------------------------------- 1 | package org.pyj.config; 2 | 3 | import org.pyj.ServerBootStrap; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | /** 8 | * @author pengyongjian 9 | * @Description: 配置类,用于启动服务 10 | * @date 2020-03-16 14:04 11 | */ 12 | @Configuration 13 | public class WebSocketNettyConfig { 14 | @Bean 15 | public ServerBootStrap serverEndpointExporter() { 16 | return new ServerBootStrap(); 17 | } 18 | 19 | @Bean 20 | public WebProperties webProperties() { 21 | return new WebProperties(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/exception/DeploymentException.java: -------------------------------------------------------------------------------- 1 | package org.pyj.exception; 2 | 3 | public class DeploymentException extends Exception { 4 | 5 | private static final long serialVersionUID = 1L; 6 | 7 | public DeploymentException(String message) { 8 | super(message); 9 | } 10 | 11 | public DeploymentException(String message, Throwable cause) { 12 | super(message, cause); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/exception/IllegalMethodNotAllowedException.java: -------------------------------------------------------------------------------- 1 | package org.pyj.exception; 2 | 3 | /** 4 | * @Description: 不合法的请求方法异常 5 | * @Author: pengyongjian 6 | * @Date: 2020-04-05 10:14 7 | */ 8 | public class IllegalMethodNotAllowedException extends Exception { 9 | public IllegalMethodNotAllowedException() { 10 | super("METHOD NOT ALLOWED"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/exception/IllegalPathDuplicatedException.java: -------------------------------------------------------------------------------- 1 | package org.pyj.exception; 2 | 3 | /** 4 | * @Description: 路径映射重复异常 5 | * @Author: pengyongjian 6 | * @Date: 2020-04-05 10:14 7 | */ 8 | public class IllegalPathDuplicatedException extends Exception { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/exception/IllegalPathNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.pyj.exception; 2 | 3 | /** 4 | * @Description: 不合法的请求路径异常 5 | * @Author: pengyongjian 6 | * @Date: 2020-04-05 10:14 7 | */ 8 | public class IllegalPathNotFoundException extends Exception { 9 | public IllegalPathNotFoundException() { 10 | super("PATH NOT FOUND"); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/http/NettyHttpRequest.java: -------------------------------------------------------------------------------- 1 | package org.pyj.http; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.handler.codec.DecoderResult; 5 | import io.netty.handler.codec.http.FullHttpRequest; 6 | import io.netty.handler.codec.http.HttpHeaders; 7 | import io.netty.handler.codec.http.HttpMethod; 8 | import io.netty.handler.codec.http.HttpVersion; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.Objects; 11 | 12 | /** 13 | * @Description: http请求信息类 14 | * @Author: pengyongjian 15 | * @Date: 2020-04-05 10:14 16 | */ 17 | public class NettyHttpRequest implements FullHttpRequest { 18 | 19 | private final FullHttpRequest realRequest; 20 | 21 | private String url; 22 | 23 | public NettyHttpRequest(FullHttpRequest request) { 24 | this.realRequest = request; 25 | } 26 | 27 | public String contentText() { 28 | return content().toString(StandardCharsets.UTF_8); 29 | } 30 | 31 | public long getLongPathValue(int index) { 32 | String[] paths = uri().split("/"); 33 | return Long.parseLong(paths[index]); 34 | } 35 | 36 | public String getStringPathValue(int index) { 37 | String[] paths = uri().split("/"); 38 | return paths[index]; 39 | } 40 | 41 | public int getIntPathValue(int index) { 42 | String[] paths = uri().split("/"); 43 | return Integer.parseInt(paths[index]); 44 | } 45 | 46 | public boolean isAllowed(String method) { 47 | return getMethod().name().equalsIgnoreCase(method); 48 | } 49 | 50 | public boolean matched(String path, boolean equal) { 51 | String url = getUrl().toLowerCase(); 52 | return equal ? Objects.equals(path, url) : url.startsWith(path); 53 | } 54 | 55 | @Override 56 | public ByteBuf content() { 57 | return realRequest.content(); 58 | } 59 | 60 | @Override 61 | public HttpHeaders trailingHeaders() { 62 | return realRequest.trailingHeaders(); 63 | } 64 | 65 | @Override 66 | public FullHttpRequest copy() { 67 | return realRequest.copy(); 68 | } 69 | 70 | @Override 71 | public FullHttpRequest duplicate() { 72 | return realRequest.duplicate(); 73 | } 74 | 75 | @Override 76 | public FullHttpRequest retainedDuplicate() { 77 | return realRequest.retainedDuplicate(); 78 | } 79 | 80 | @Override 81 | public FullHttpRequest replace(ByteBuf byteBuf) { 82 | return realRequest.replace(byteBuf); 83 | } 84 | 85 | @Override 86 | public FullHttpRequest retain(int i) { 87 | return realRequest.retain(i); 88 | } 89 | 90 | @Override 91 | public int refCnt() { 92 | return realRequest.refCnt(); 93 | } 94 | 95 | @Override 96 | public FullHttpRequest retain() { 97 | return realRequest.retain(); 98 | } 99 | 100 | @Override 101 | public FullHttpRequest touch() { 102 | return realRequest.touch(); 103 | } 104 | 105 | @Override 106 | public FullHttpRequest touch(Object o) { 107 | return realRequest.touch(o); 108 | } 109 | 110 | @Override 111 | public boolean release() { 112 | return realRequest.release(); 113 | } 114 | 115 | @Override 116 | public boolean release(int i) { 117 | return realRequest.release(i); 118 | } 119 | 120 | @Override 121 | public HttpVersion getProtocolVersion() { 122 | return realRequest.protocolVersion(); 123 | } 124 | 125 | @Override 126 | public HttpVersion protocolVersion() { 127 | return realRequest.protocolVersion(); 128 | } 129 | 130 | @Override 131 | public FullHttpRequest setProtocolVersion(HttpVersion httpVersion) { 132 | return realRequest.setProtocolVersion(httpVersion); 133 | } 134 | 135 | @Override 136 | public HttpHeaders headers() { 137 | return realRequest.headers(); 138 | } 139 | 140 | @Override 141 | public HttpMethod getMethod() { 142 | return realRequest.getMethod(); 143 | } 144 | 145 | @Override 146 | public HttpMethod method() { 147 | return realRequest.method(); 148 | } 149 | 150 | @Override 151 | public FullHttpRequest setMethod(HttpMethod httpMethod) { 152 | return realRequest.setMethod(httpMethod); 153 | } 154 | 155 | @Override 156 | public String getUri() { 157 | return realRequest.getUri(); 158 | } 159 | 160 | @Override 161 | public String uri() { 162 | return realRequest.uri(); 163 | } 164 | 165 | public String getUrl() { 166 | if (url != null) { 167 | return url; 168 | } 169 | synchronized (this) { 170 | String uri = realRequest.uri(); 171 | if (uri.contains("?")) { 172 | url = uri.substring(0, uri.indexOf("?")); 173 | } else { 174 | url = uri; 175 | } 176 | } 177 | if (url != null) { 178 | return url; 179 | } 180 | return getUrl(); 181 | } 182 | 183 | @Override 184 | public FullHttpRequest setUri(String s) { 185 | return realRequest.setUri(s); 186 | } 187 | 188 | @Override 189 | public DecoderResult getDecoderResult() { 190 | return realRequest.getDecoderResult(); 191 | } 192 | 193 | @Override 194 | public void setDecoderResult(DecoderResult decoderResult) { 195 | realRequest.setDecoderResult(decoderResult); 196 | } 197 | 198 | @Override 199 | public DecoderResult decoderResult() { 200 | return realRequest.decoderResult(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/http/NettyHttpResponse.java: -------------------------------------------------------------------------------- 1 | package org.pyj.http; 2 | 3 | import static io.netty.handler.codec.http.HttpHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS; 4 | import static io.netty.handler.codec.http.HttpHeaderNames.ACCESS_CONTROL_ALLOW_METHODS; 5 | import static io.netty.handler.codec.http.HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN; 6 | import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; 7 | import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; 8 | 9 | 10 | import io.netty.buffer.ByteBuf; 11 | import io.netty.buffer.PooledByteBufAllocator; 12 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 13 | import io.netty.handler.codec.http.FullHttpResponse; 14 | import io.netty.handler.codec.http.HttpResponseStatus; 15 | import io.netty.handler.codec.http.HttpVersion; 16 | 17 | /** 18 | * @Description: http响应信息类 19 | * @Author: pengyongjian 20 | * @Date: 2020-04-05 10:14 21 | */ 22 | public class NettyHttpResponse extends DefaultFullHttpResponse { 23 | 24 | private static final PooledByteBufAllocator BYTE_BUF_ALLOCATOR = new PooledByteBufAllocator(false); 25 | 26 | private static final String CONTENT_NORMAL_200 = "{\"code\":200,\"message\":\"OK\"}"; 27 | private static final String CONTENT_ERROR_401 = "{\"code\":401,\"message\":\"UNAUTHORIZED\"}"; 28 | private static final String CONTENT_ERROR_404 = "{\"code\":404,\"message\":\"REQUEST PATH NOT FOUND\"}"; 29 | private static final String CONTENT_ERROR_405 = "{\"code\":405,\"message\":\"METHOD NOT ALLOWED\"}"; 30 | private static final String CONTENT_ERROR_500 = "{\"code\":500,\"message\":\"%s\"}"; 31 | 32 | private String content; 33 | 34 | private NettyHttpResponse(HttpResponseStatus status, ByteBuf buffer) { 35 | super(HttpVersion.HTTP_1_1, status, buffer); 36 | headers().set(CONTENT_TYPE, "application/json"); 37 | headers().setInt(CONTENT_LENGTH, content().readableBytes()); 38 | 39 | /** 40 | * 支持CORS 跨域访问 41 | */ 42 | headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*"); 43 | headers().set(ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept, RCS-ACCESS-TOKEN"); 44 | headers().set(ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE"); 45 | } 46 | 47 | public static FullHttpResponse make(HttpResponseStatus status) { 48 | if (HttpResponseStatus.UNAUTHORIZED == status) { 49 | return NettyHttpResponse.make(HttpResponseStatus.UNAUTHORIZED, CONTENT_ERROR_401); 50 | } 51 | if (HttpResponseStatus.NOT_FOUND == status) { 52 | return NettyHttpResponse.make(HttpResponseStatus.NOT_FOUND, CONTENT_ERROR_404); 53 | } 54 | if (HttpResponseStatus.METHOD_NOT_ALLOWED == status) { 55 | return NettyHttpResponse.make(HttpResponseStatus.METHOD_NOT_ALLOWED, CONTENT_ERROR_405); 56 | } 57 | return NettyHttpResponse.make(HttpResponseStatus.OK, CONTENT_NORMAL_200); 58 | } 59 | 60 | public static FullHttpResponse makeError(Exception exception) { 61 | String message = exception.getClass().getName() + ":" + exception.getMessage(); 62 | return NettyHttpResponse.make(HttpResponseStatus.INTERNAL_SERVER_ERROR, String.format(CONTENT_ERROR_500, message)); 63 | } 64 | 65 | public static FullHttpResponse ok(String content) { 66 | return make(HttpResponseStatus.OK, content); 67 | } 68 | 69 | private static FullHttpResponse make(HttpResponseStatus status, String content) { 70 | byte[] body = content.getBytes(); 71 | ByteBuf buffer = BYTE_BUF_ALLOCATOR.buffer(body.length); 72 | buffer.writeBytes(body); 73 | NettyHttpResponse response = new NettyHttpResponse(status, buffer); 74 | response.content = content; 75 | return response; 76 | } 77 | 78 | @Override 79 | public String toString() { 80 | StringBuilder builder = new StringBuilder(); 81 | builder.append(protocolVersion().toString()).append(" ").append(status().toString()).append("\n"); 82 | builder.append(CONTENT_TYPE).append(": ").append(headers().get(CONTENT_TYPE)).append("\n"); 83 | builder.append(CONTENT_LENGTH).append(": ").append(headers().get(CONTENT_LENGTH)).append("\n"); 84 | builder.append("content-body").append(": ").append(content).append("\n"); 85 | return builder.toString(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/http/annotation/NettyHttpHandler.java: -------------------------------------------------------------------------------- 1 | package org.pyj.http.annotation; 2 | 3 | import java.lang.annotation.Documented; 4 | import java.lang.annotation.ElementType; 5 | import java.lang.annotation.Retention; 6 | import java.lang.annotation.RetentionPolicy; 7 | import java.lang.annotation.Target; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * @Description: 用于注解接收http请求的类 12 | * @Author: pengyongjian 13 | * @Date: 2020-04-05 10:11 14 | */ 15 | @Target({ElementType.TYPE}) 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Documented 18 | @Component 19 | public @interface NettyHttpHandler { 20 | /** 21 | * 请求路径 22 | * 23 | * @return 24 | */ 25 | String path() default ""; 26 | 27 | /** 28 | * 支持的提交方式 29 | * 30 | * @return 31 | */ 32 | String method() default "GET"; 33 | 34 | /** 35 | * path和请求路径是否需要完全匹配。 如果是PathVariable传参数,设置为false 36 | * 37 | * @return 38 | */ 39 | boolean equal() default true; 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/http/handler/IFunctionHandler.java: -------------------------------------------------------------------------------- 1 | package org.pyj.http.handler; 2 | 3 | 4 | import org.pyj.http.NettyHttpRequest; 5 | 6 | /** 7 | * @Description: http接口,所有对外http接口都需实现该接口 8 | * @Author: pengyongjian 9 | * @Date: 2020-04-05 10:14 10 | */ 11 | public interface IFunctionHandler { 12 | Result execute(NettyHttpRequest request); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/http/handler/Result.java: -------------------------------------------------------------------------------- 1 | package org.pyj.http.handler; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * @author pengyongjian 7 | * @Description: 8 | * @date 2020/12/21 下午3:43 9 | */ 10 | public interface Result extends Serializable { 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/http/handler/ResultJson.java: -------------------------------------------------------------------------------- 1 | package org.pyj.http.handler; 2 | 3 | /** 4 | * @author pengyongjian 5 | * @Description: 6 | * @date 2020/12/21 下午3:51 7 | */ 8 | public class ResultJson implements Result { 9 | 10 | private static final long serialVersionUID = 1; 11 | 12 | private int code; 13 | 14 | private T t; 15 | 16 | public ResultJson(int code, T t) { 17 | this.code = code; 18 | this.t = t; 19 | } 20 | 21 | public int getCode() { 22 | return code; 23 | } 24 | 25 | public void setCode(int code) { 26 | this.code = code; 27 | } 28 | 29 | public T getT() { 30 | return t; 31 | } 32 | 33 | public void setT(T t) { 34 | this.t = t; 35 | } 36 | 37 | @Override 38 | public String toString() { 39 | return "ResultJson{" + 40 | "code=" + code + 41 | ", t=" + t + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/http/path/Path.java: -------------------------------------------------------------------------------- 1 | package org.pyj.http.path; 2 | 3 | 4 | import org.pyj.http.annotation.NettyHttpHandler; 5 | 6 | /** 7 | * @Description: http请求路径的处理类 8 | * @Author: pengyongjian 9 | * @Date: 2020-04-05 10:14 10 | */ 11 | public class Path { 12 | private String method; 13 | private String uri; 14 | private boolean equal; 15 | 16 | public Path(NettyHttpHandler annotation) { 17 | method = annotation.method(); 18 | uri = annotation.path(); 19 | equal = annotation.equal(); 20 | } 21 | 22 | public static Path make(NettyHttpHandler annotation) { 23 | return new Path(annotation); 24 | } 25 | 26 | public String getMethod() { 27 | return method; 28 | } 29 | 30 | public void setMethod(String method) { 31 | this.method = method; 32 | } 33 | 34 | public String getUri() { 35 | return uri; 36 | } 37 | 38 | public void setUri(String uri) { 39 | this.uri = uri; 40 | } 41 | 42 | public boolean isEqual() { 43 | return equal; 44 | } 45 | 46 | public void setEqual(boolean equal) { 47 | this.equal = equal; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return method.toUpperCase() + " " + uri.toUpperCase(); 53 | } 54 | 55 | @Override 56 | public int hashCode() { 57 | return ("HTTP " + method.toUpperCase() + " " + uri.toUpperCase()).hashCode(); 58 | } 59 | 60 | @Override 61 | public boolean equals(Object object) { 62 | if (object instanceof Path) { 63 | Path path = (Path) object; 64 | return method.equalsIgnoreCase(path.method) && uri.equalsIgnoreCase(path.uri); 65 | } 66 | return false; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/BeforeHandshake.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface BeforeHandshake { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/OnBinary.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface OnBinary { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/OnClose.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface OnClose { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/OnError.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface OnError { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/OnEvent.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface OnEvent { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/OnMessage.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface OnMessage { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/OnOpen.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | @Retention(RetentionPolicy.RUNTIME) 9 | @Target(ElementType.METHOD) 10 | public @interface OnOpen { 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/PathVariable.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import org.springframework.core.annotation.AliasFor; 8 | 9 | @Target(ElementType.PARAMETER) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface PathVariable { 12 | 13 | /** 14 | * Alias for {@link #name}. 15 | */ 16 | @AliasFor("name") 17 | String value() default ""; 18 | 19 | /** 20 | * The name of the path variable to bind to. 21 | */ 22 | @AliasFor("value") 23 | String name() default ""; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/RequestParam.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import org.springframework.core.annotation.AliasFor; 8 | 9 | @Target(ElementType.PARAMETER) 10 | @Retention(RetentionPolicy.RUNTIME) 11 | public @interface RequestParam { 12 | 13 | /** 14 | * Alias for {@link #name}. 15 | */ 16 | @AliasFor("name") 17 | String value() default ""; 18 | 19 | /** 20 | * The name of the request parameter to bind to. 21 | */ 22 | @AliasFor("value") 23 | String name() default ""; 24 | 25 | /** 26 | * Whether the parameter is required. 27 | *

Defaults to {@code true}, leading to an exception being thrown 28 | * if the parameter is missing in the request. Switch this to 29 | * {@code false} if you prefer a {@code null} value if the parameter is 30 | * not present in the request. 31 | *

Alternatively, provide a {@link #defaultValue}, which implicitly 32 | * sets this flag to {@code false}. 33 | */ 34 | boolean required() default true; 35 | 36 | /** 37 | * The default value to use as a fallback when the request parameter is 38 | * not provided or has an empty value. 39 | *

Supplying a default value implicitly sets {@link #required} to 40 | * {@code false}. 41 | */ 42 | String defaultValue() default "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"; 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/annotation/ServerPath.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | import org.springframework.core.annotation.AliasFor; 8 | import org.springframework.stereotype.Component; 9 | 10 | /** 11 | * @author Yeauty 12 | * @version 1.0 13 | */ 14 | @Component 15 | @Retention(RetentionPolicy.RUNTIME) 16 | @Target(ElementType.TYPE) 17 | public @interface ServerPath { 18 | 19 | @AliasFor("path") 20 | String value() default "/"; 21 | 22 | @AliasFor("value") 23 | String path() default "/"; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/pojo/PojoEndpointServer.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.pojo; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 6 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 7 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 8 | import io.netty.util.Attribute; 9 | import io.netty.util.AttributeKey; 10 | import io.netty.util.internal.logging.InternalLogger; 11 | import io.netty.util.internal.logging.InternalLoggerFactory; 12 | import java.lang.reflect.Method; 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.List; 16 | import java.util.Map; 17 | import java.util.Set; 18 | import org.pyj.config.ServerEndpointConfig; 19 | import org.pyj.yeauty.support.AntPathMatcherWrapper; 20 | import org.pyj.yeauty.support.DefaultPathMatcher; 21 | import org.pyj.yeauty.support.MethodArgumentResolver; 22 | import org.pyj.yeauty.support.PathVariableMethodArgumentResolver; 23 | import org.pyj.yeauty.support.WsPathMatcher; 24 | import org.springframework.beans.TypeMismatchException; 25 | 26 | /** 27 | * @author Yeauty 28 | * @version 1.0 29 | */ 30 | public class PojoEndpointServer { 31 | 32 | public static final AttributeKey SESSION_KEY = AttributeKey.valueOf("WEBSOCKET_SESSION"); 33 | public static final AttributeKey> URI_TEMPLATE = AttributeKey.valueOf("WEBSOCKET_URI_TEMPLATE"); 34 | public static final AttributeKey>> REQUEST_PARAM = 35 | AttributeKey.valueOf("WEBSOCKET_REQUEST_PARAM"); 36 | private static final AttributeKey POJO_KEY = AttributeKey.valueOf("WEBSOCKET_IMPLEMENT"); 37 | private static final AttributeKey PATH_KEY = AttributeKey.valueOf("WEBSOCKET_PATH"); 38 | private static final InternalLogger logger = InternalLoggerFactory.getInstance(PojoEndpointServer.class); 39 | private static final Map pathMethodMappingMap = new HashMap<>(); 40 | private static final Set pathMatchers = new HashSet<>(); 41 | private final ServerEndpointConfig config; 42 | 43 | public PojoEndpointServer(ServerEndpointConfig config) { 44 | this.config = config; 45 | } 46 | 47 | public boolean hasBeforeHandshake(Channel channel, String path) { 48 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = getPojoMethodMapping(path, channel); 49 | return methodMapping.getBeforeHandshake() != null; 50 | } 51 | 52 | public void doBeforeHandshake(Channel channel, FullHttpRequest req, String path) { 53 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = null; 54 | methodMapping = getPojoMethodMapping(path, channel); 55 | 56 | Object implement = null; 57 | try { 58 | implement = methodMapping.getEndpointInstance(); 59 | } catch (Exception e) { 60 | logger.error(e); 61 | return; 62 | } 63 | channel.attr(POJO_KEY).set(implement); 64 | org.pyj.yeauty.pojo.Session session = new org.pyj.yeauty.pojo.Session(channel); 65 | channel.attr(SESSION_KEY).set(session); 66 | Method beforeHandshake = methodMapping.getBeforeHandshake(); 67 | if (beforeHandshake != null) { 68 | try { 69 | beforeHandshake.invoke(implement, methodMapping.getBeforeHandshakeArgs(channel, req)); 70 | } catch (TypeMismatchException e) { 71 | throw e; 72 | } catch (Throwable t) { 73 | logger.error(t); 74 | } 75 | } 76 | } 77 | 78 | public void doOnOpen(Channel channel, FullHttpRequest req, String path) { 79 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = getPojoMethodMapping(path, channel); 80 | 81 | Object implement = channel.attr(POJO_KEY).get(); 82 | if (implement == null) { 83 | try { 84 | implement = methodMapping.getEndpointInstance(); 85 | channel.attr(POJO_KEY).set(implement); 86 | } catch (Exception e) { 87 | logger.error(e); 88 | return; 89 | } 90 | org.pyj.yeauty.pojo.Session session = new Session(channel); 91 | channel.attr(SESSION_KEY).set(session); 92 | } 93 | 94 | Method onOpenMethod = methodMapping.getOnOpen(); 95 | if (onOpenMethod != null) { 96 | try { 97 | onOpenMethod.invoke(implement, methodMapping.getOnOpenArgs(channel, req)); 98 | } catch (TypeMismatchException e) { 99 | throw e; 100 | } catch (Throwable t) { 101 | logger.error(t); 102 | } 103 | } 104 | } 105 | 106 | public void doOnClose(Channel channel) { 107 | Attribute attrPath = channel.attr(PATH_KEY); 108 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = null; 109 | if (pathMethodMappingMap.size() == 1) { 110 | methodMapping = pathMethodMappingMap.values().iterator().next(); 111 | } else { 112 | String path = attrPath.get(); 113 | methodMapping = pathMethodMappingMap.get(path); 114 | if (methodMapping == null) { 115 | return; 116 | } 117 | } 118 | if (methodMapping.getOnClose() != null) { 119 | if (!channel.hasAttr(SESSION_KEY)) { 120 | return; 121 | } 122 | Object implement = channel.attr(POJO_KEY).get(); 123 | try { 124 | methodMapping.getOnClose().invoke(implement, 125 | methodMapping.getOnCloseArgs(channel)); 126 | } catch (Throwable t) { 127 | logger.error(t); 128 | } 129 | } 130 | } 131 | 132 | 133 | public void doOnError(Channel channel, Throwable throwable) { 134 | Attribute attrPath = channel.attr(PATH_KEY); 135 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = null; 136 | if (pathMethodMappingMap.size() == 1) { 137 | methodMapping = pathMethodMappingMap.values().iterator().next(); 138 | } else { 139 | String path = attrPath.get(); 140 | methodMapping = pathMethodMappingMap.get(path); 141 | } 142 | if (methodMapping.getOnError() != null) { 143 | if (!channel.hasAttr(SESSION_KEY)) { 144 | return; 145 | } 146 | Object implement = channel.attr(POJO_KEY).get(); 147 | try { 148 | Method method = methodMapping.getOnError(); 149 | Object[] args = methodMapping.getOnErrorArgs(channel, throwable); 150 | method.invoke(implement, args); 151 | } catch (Throwable t) { 152 | logger.error(t); 153 | } 154 | } 155 | } 156 | 157 | public void doOnMessage(Channel channel, WebSocketFrame frame) { 158 | Attribute attrPath = channel.attr(PATH_KEY); 159 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = null; 160 | if (pathMethodMappingMap.size() == 1) { 161 | methodMapping = pathMethodMappingMap.values().iterator().next(); 162 | } else { 163 | String path = attrPath.get(); 164 | methodMapping = pathMethodMappingMap.get(path); 165 | } 166 | if (methodMapping.getOnMessage() != null) { 167 | TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; 168 | Object implement = channel.attr(POJO_KEY).get(); 169 | try { 170 | methodMapping.getOnMessage().invoke(implement, methodMapping.getOnMessageArgs(channel, textFrame)); 171 | } catch (Throwable t) { 172 | logger.error(t); 173 | } 174 | } 175 | } 176 | 177 | public void doOnBinary(Channel channel, WebSocketFrame frame) { 178 | Attribute attrPath = channel.attr(PATH_KEY); 179 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = null; 180 | if (pathMethodMappingMap.size() == 1) { 181 | methodMapping = pathMethodMappingMap.values().iterator().next(); 182 | } else { 183 | String path = attrPath.get(); 184 | methodMapping = pathMethodMappingMap.get(path); 185 | } 186 | if (methodMapping.getOnBinary() != null) { 187 | BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) frame; 188 | Object implement = channel.attr(POJO_KEY).get(); 189 | try { 190 | methodMapping.getOnBinary().invoke(implement, methodMapping.getOnBinaryArgs(channel, binaryWebSocketFrame)); 191 | } catch (Throwable t) { 192 | logger.error(t); 193 | } 194 | } 195 | } 196 | 197 | public void doOnEvent(Channel channel, Object evt) { 198 | Attribute attrPath = channel.attr(PATH_KEY); 199 | org.pyj.yeauty.pojo.PojoMethodMapping methodMapping = null; 200 | if (pathMethodMappingMap.size() == 1) { 201 | methodMapping = pathMethodMappingMap.values().iterator().next(); 202 | } else { 203 | String path = attrPath.get(); 204 | methodMapping = pathMethodMappingMap.get(path); 205 | } 206 | if (methodMapping.getOnEvent() != null) { 207 | if (!channel.hasAttr(SESSION_KEY)) { 208 | return; 209 | } 210 | Object implement = channel.attr(POJO_KEY).get(); 211 | try { 212 | methodMapping.getOnEvent().invoke(implement, methodMapping.getOnEventArgs(channel, evt)); 213 | } catch (Throwable t) { 214 | logger.error(t); 215 | } 216 | } 217 | } 218 | 219 | public String getHost() { 220 | return config.getHost(); 221 | } 222 | 223 | public int getPort() { 224 | return config.getPort(); 225 | } 226 | 227 | public Set getPathMatcherSet() { 228 | return pathMatchers; 229 | } 230 | 231 | public void addPathPojoMethodMapping(String path, PojoMethodMapping pojoMethodMapping) { 232 | pathMethodMappingMap.put(path, pojoMethodMapping); 233 | for (MethodArgumentResolver onOpenArgResolver : pojoMethodMapping.getOnOpenArgResolvers()) { 234 | if (onOpenArgResolver instanceof PathVariableMethodArgumentResolver || 235 | onOpenArgResolver instanceof org.pyj.yeauty.support.PathVariableMapMethodArgumentResolver) { 236 | pathMatchers.add(new AntPathMatcherWrapper(path)); 237 | return; 238 | } 239 | } 240 | pathMatchers.add(new DefaultPathMatcher(path)); 241 | } 242 | 243 | private PojoMethodMapping getPojoMethodMapping(String path, Channel channel) { 244 | PojoMethodMapping methodMapping; 245 | if (pathMethodMappingMap.size() == 1) { 246 | methodMapping = pathMethodMappingMap.values().iterator().next(); 247 | } else { 248 | Attribute attrPath = channel.attr(PATH_KEY); 249 | attrPath.set(path); 250 | methodMapping = pathMethodMappingMap.get(path); 251 | if (methodMapping == null) { 252 | throw new RuntimeException("path " + path + " is not in pathMethodMappingMap "); 253 | } 254 | } 255 | return methodMapping; 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/pojo/PojoMethodMapping.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.pojo; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 6 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 7 | import java.lang.annotation.Annotation; 8 | import java.lang.reflect.InvocationTargetException; 9 | import java.lang.reflect.Method; 10 | import java.lang.reflect.Modifier; 11 | import java.util.ArrayList; 12 | import java.util.Arrays; 13 | import java.util.List; 14 | import org.pyj.yeauty.annotation.OnEvent; 15 | import org.pyj.exception.DeploymentException; 16 | import org.pyj.yeauty.support.SessionMethodArgumentResolver; 17 | import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; 18 | import org.springframework.beans.factory.support.AbstractBeanFactory; 19 | import org.springframework.context.ApplicationContext; 20 | import org.springframework.core.DefaultParameterNameDiscoverer; 21 | import org.springframework.core.MethodParameter; 22 | import org.springframework.core.ParameterNameDiscoverer; 23 | 24 | public class PojoMethodMapping { 25 | 26 | private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 27 | 28 | private final Method beforeHandshake; 29 | private final Method onOpen; 30 | private final Method onClose; 31 | private final Method onError; 32 | private final Method onMessage; 33 | private final Method onBinary; 34 | private final Method onEvent; 35 | private final MethodParameter[] beforeHandshakeParameters; 36 | private final MethodParameter[] onOpenParameters; 37 | private final MethodParameter[] onCloseParameters; 38 | private final MethodParameter[] onErrorParameters; 39 | private final MethodParameter[] onMessageParameters; 40 | private final MethodParameter[] onBinaryParameters; 41 | private final MethodParameter[] onEventParameters; 42 | private final org.pyj.yeauty.support.MethodArgumentResolver[] beforeHandshakeArgResolvers; 43 | private final org.pyj.yeauty.support.MethodArgumentResolver[] onOpenArgResolvers; 44 | private final org.pyj.yeauty.support.MethodArgumentResolver[] onCloseArgResolvers; 45 | private final org.pyj.yeauty.support.MethodArgumentResolver[] onErrorArgResolvers; 46 | private final org.pyj.yeauty.support.MethodArgumentResolver[] onMessageArgResolvers; 47 | private final org.pyj.yeauty.support.MethodArgumentResolver[] onBinaryArgResolvers; 48 | private final org.pyj.yeauty.support.MethodArgumentResolver[] onEventArgResolvers; 49 | private final Class pojoClazz; 50 | private final ApplicationContext applicationContext; 51 | private final AbstractBeanFactory beanFactory; 52 | 53 | public PojoMethodMapping(Class pojoClazz, ApplicationContext context, AbstractBeanFactory beanFactory) throws 54 | DeploymentException { 55 | this.applicationContext = context; 56 | this.pojoClazz = pojoClazz; 57 | this.beanFactory = beanFactory; 58 | Method handshake = null; 59 | Method open = null; 60 | Method close = null; 61 | Method error = null; 62 | Method message = null; 63 | Method binary = null; 64 | Method event = null; 65 | Method[] pojoClazzMethods = null; 66 | Class currentClazz = pojoClazz; 67 | while (!currentClazz.equals(Object.class)) { 68 | Method[] currentClazzMethods = currentClazz.getDeclaredMethods(); 69 | if (currentClazz == pojoClazz) { 70 | pojoClazzMethods = currentClazzMethods; 71 | } 72 | for (Method method : currentClazzMethods) { 73 | if (method.getAnnotation(org.pyj.yeauty.annotation.BeforeHandshake.class) != null) { 74 | checkPublic(method); 75 | if (handshake == null) { 76 | handshake = method; 77 | } else { 78 | if (currentClazz == pojoClazz || 79 | !isMethodOverride(handshake, method)) { 80 | // Duplicate annotation 81 | throw new DeploymentException( 82 | "pojoMethodMapping.duplicateAnnotation BeforeHandshake"); 83 | } 84 | } 85 | } else if (method.getAnnotation(org.pyj.yeauty.annotation.OnOpen.class) != null) { 86 | checkPublic(method); 87 | if (open == null) { 88 | open = method; 89 | } else { 90 | if (currentClazz == pojoClazz || 91 | !isMethodOverride(open, method)) { 92 | // Duplicate annotation 93 | throw new DeploymentException( 94 | "pojoMethodMapping.duplicateAnnotation OnOpen"); 95 | } 96 | } 97 | } else if (method.getAnnotation(org.pyj.yeauty.annotation.OnClose.class) != null) { 98 | checkPublic(method); 99 | if (close == null) { 100 | close = method; 101 | } else { 102 | if (currentClazz == pojoClazz || 103 | !isMethodOverride(close, method)) { 104 | // Duplicate annotation 105 | throw new DeploymentException( 106 | "pojoMethodMapping.duplicateAnnotation OnClose"); 107 | } 108 | } 109 | } else if (method.getAnnotation(org.pyj.yeauty.annotation.OnError.class) != null) { 110 | checkPublic(method); 111 | if (error == null) { 112 | error = method; 113 | } else { 114 | if (currentClazz == pojoClazz || 115 | !isMethodOverride(error, method)) { 116 | // Duplicate annotation 117 | throw new DeploymentException( 118 | "pojoMethodMapping.duplicateAnnotation OnError"); 119 | } 120 | } 121 | } else if (method.getAnnotation(org.pyj.yeauty.annotation.OnMessage.class) != null) { 122 | checkPublic(method); 123 | if (message == null) { 124 | message = method; 125 | } else { 126 | if (currentClazz == pojoClazz || 127 | !isMethodOverride(message, method)) { 128 | // Duplicate annotation 129 | throw new DeploymentException( 130 | "pojoMethodMapping.duplicateAnnotation onMessage"); 131 | } 132 | } 133 | } else if (method.getAnnotation(org.pyj.yeauty.annotation.OnBinary.class) != null) { 134 | checkPublic(method); 135 | if (binary == null) { 136 | binary = method; 137 | } else { 138 | if (currentClazz == pojoClazz || 139 | !isMethodOverride(binary, method)) { 140 | // Duplicate annotation 141 | throw new DeploymentException( 142 | "pojoMethodMapping.duplicateAnnotation OnBinary"); 143 | } 144 | } 145 | } else if (method.getAnnotation(org.pyj.yeauty.annotation.OnEvent.class) != null) { 146 | checkPublic(method); 147 | if (event == null) { 148 | event = method; 149 | } else { 150 | if (currentClazz == pojoClazz || 151 | !isMethodOverride(event, method)) { 152 | // Duplicate annotation 153 | throw new DeploymentException( 154 | "pojoMethodMapping.duplicateAnnotation OnEvent"); 155 | } 156 | } 157 | } else { 158 | // Method not annotated 159 | } 160 | } 161 | currentClazz = currentClazz.getSuperclass(); 162 | } 163 | // If the methods are not on pojoClazz and they are overridden 164 | // by a non annotated method in pojoClazz, they should be ignored 165 | if (handshake != null && handshake.getDeclaringClass() != pojoClazz) { 166 | if (isOverridenWithoutAnnotation(pojoClazzMethods, handshake, org.pyj.yeauty.annotation.BeforeHandshake.class)) { 167 | handshake = null; 168 | } 169 | } 170 | if (open != null && open.getDeclaringClass() != pojoClazz) { 171 | if (isOverridenWithoutAnnotation(pojoClazzMethods, open, org.pyj.yeauty.annotation.OnOpen.class)) { 172 | open = null; 173 | } 174 | } 175 | if (close != null && close.getDeclaringClass() != pojoClazz) { 176 | if (isOverridenWithoutAnnotation(pojoClazzMethods, close, org.pyj.yeauty.annotation.OnClose.class)) { 177 | close = null; 178 | } 179 | } 180 | if (error != null && error.getDeclaringClass() != pojoClazz) { 181 | if (isOverridenWithoutAnnotation(pojoClazzMethods, error, org.pyj.yeauty.annotation.OnError.class)) { 182 | error = null; 183 | } 184 | } 185 | if (message != null && message.getDeclaringClass() != pojoClazz) { 186 | if (isOverridenWithoutAnnotation(pojoClazzMethods, message, org.pyj.yeauty.annotation.OnMessage.class)) { 187 | message = null; 188 | } 189 | } 190 | if (binary != null && binary.getDeclaringClass() != pojoClazz) { 191 | if (isOverridenWithoutAnnotation(pojoClazzMethods, binary, org.pyj.yeauty.annotation.OnBinary.class)) { 192 | binary = null; 193 | } 194 | } 195 | if (event != null && event.getDeclaringClass() != pojoClazz) { 196 | if (isOverridenWithoutAnnotation(pojoClazzMethods, event, OnEvent.class)) { 197 | event = null; 198 | } 199 | } 200 | 201 | this.beforeHandshake = handshake; 202 | this.onOpen = open; 203 | this.onClose = close; 204 | this.onError = error; 205 | this.onMessage = message; 206 | this.onBinary = binary; 207 | this.onEvent = event; 208 | beforeHandshakeParameters = getParameters(beforeHandshake); 209 | onOpenParameters = getParameters(onOpen); 210 | onCloseParameters = getParameters(onClose); 211 | onMessageParameters = getParameters(onMessage); 212 | onErrorParameters = getParameters(onError); 213 | onBinaryParameters = getParameters(onBinary); 214 | onEventParameters = getParameters(onEvent); 215 | beforeHandshakeArgResolvers = getResolvers(beforeHandshakeParameters); 216 | onOpenArgResolvers = getResolvers(onOpenParameters); 217 | onCloseArgResolvers = getResolvers(onCloseParameters); 218 | onMessageArgResolvers = getResolvers(onMessageParameters); 219 | onErrorArgResolvers = getResolvers(onErrorParameters); 220 | onBinaryArgResolvers = getResolvers(onBinaryParameters); 221 | onEventArgResolvers = getResolvers(onEventParameters); 222 | } 223 | 224 | private static MethodParameter[] getParameters(Method m) { 225 | if (m == null) { 226 | return new MethodParameter[0]; 227 | } 228 | int count = m.getParameterCount(); 229 | MethodParameter[] result = new MethodParameter[count]; 230 | for (int i = 0; i < count; i++) { 231 | MethodParameter methodParameter = new MethodParameter(m, i); 232 | methodParameter.initParameterNameDiscovery(parameterNameDiscoverer); 233 | result[i] = methodParameter; 234 | } 235 | return result; 236 | } 237 | 238 | private void checkPublic(Method m) throws DeploymentException { 239 | if (!Modifier.isPublic(m.getModifiers())) { 240 | throw new DeploymentException( 241 | "pojoMethodMapping.methodNotPublic " + m.getName()); 242 | } 243 | } 244 | 245 | private boolean isMethodOverride(Method method1, Method method2) { 246 | return (method1.getName().equals(method2.getName()) 247 | && method1.getReturnType().equals(method2.getReturnType()) 248 | && Arrays.equals(method1.getParameterTypes(), method2.getParameterTypes())); 249 | } 250 | 251 | private boolean isOverridenWithoutAnnotation(Method[] methods, Method superclazzMethod, 252 | Class annotation) { 253 | for (Method method : methods) { 254 | if (isMethodOverride(method, superclazzMethod) 255 | && (method.getAnnotation(annotation) == null)) { 256 | return true; 257 | } 258 | } 259 | return false; 260 | } 261 | 262 | Object getEndpointInstance() 263 | throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 264 | Object implement = pojoClazz.getDeclaredConstructor().newInstance(); 265 | AutowiredAnnotationBeanPostProcessor postProcessor = 266 | applicationContext.getBean(AutowiredAnnotationBeanPostProcessor.class); 267 | postProcessor.postProcessPropertyValues(null, null, implement, null); 268 | return implement; 269 | } 270 | 271 | Method getBeforeHandshake() { 272 | return beforeHandshake; 273 | } 274 | 275 | Object[] getBeforeHandshakeArgs(Channel channel, FullHttpRequest req) throws Exception { 276 | return getMethodArgumentValues(channel, req, beforeHandshakeParameters, beforeHandshakeArgResolvers); 277 | } 278 | 279 | Method getOnOpen() { 280 | return onOpen; 281 | } 282 | 283 | Object[] getOnOpenArgs(Channel channel, FullHttpRequest req) throws Exception { 284 | return getMethodArgumentValues(channel, req, onOpenParameters, onOpenArgResolvers); 285 | } 286 | 287 | org.pyj.yeauty.support.MethodArgumentResolver[] getOnOpenArgResolvers() { 288 | return onOpenArgResolvers; 289 | } 290 | 291 | Method getOnClose() { 292 | return onClose; 293 | } 294 | 295 | Object[] getOnCloseArgs(Channel channel) throws Exception { 296 | return getMethodArgumentValues(channel, null, onCloseParameters, onCloseArgResolvers); 297 | } 298 | 299 | Method getOnError() { 300 | return onError; 301 | } 302 | 303 | Object[] getOnErrorArgs(Channel channel, Throwable throwable) throws Exception { 304 | return getMethodArgumentValues(channel, throwable, onErrorParameters, onErrorArgResolvers); 305 | } 306 | 307 | Method getOnMessage() { 308 | return onMessage; 309 | } 310 | 311 | Object[] getOnMessageArgs(Channel channel, TextWebSocketFrame textWebSocketFrame) throws Exception { 312 | return getMethodArgumentValues(channel, textWebSocketFrame, onMessageParameters, onMessageArgResolvers); 313 | } 314 | 315 | Method getOnBinary() { 316 | return onBinary; 317 | } 318 | 319 | Object[] getOnBinaryArgs(Channel channel, BinaryWebSocketFrame binaryWebSocketFrame) throws Exception { 320 | return getMethodArgumentValues(channel, binaryWebSocketFrame, onBinaryParameters, onBinaryArgResolvers); 321 | } 322 | 323 | Method getOnEvent() { 324 | return onEvent; 325 | } 326 | 327 | Object[] getOnEventArgs(Channel channel, Object evt) throws Exception { 328 | return getMethodArgumentValues(channel, evt, onEventParameters, onEventArgResolvers); 329 | } 330 | 331 | private Object[] getMethodArgumentValues(Channel channel, Object object, MethodParameter[] parameters, 332 | org.pyj.yeauty.support.MethodArgumentResolver[] resolvers) throws Exception { 333 | Object[] objects = new Object[parameters.length]; 334 | for (int i = 0; i < parameters.length; i++) { 335 | MethodParameter parameter = parameters[i]; 336 | org.pyj.yeauty.support.MethodArgumentResolver resolver = resolvers[i]; 337 | Object arg = resolver.resolveArgument(parameter, channel, object); 338 | objects[i] = arg; 339 | } 340 | return objects; 341 | } 342 | 343 | private org.pyj.yeauty.support.MethodArgumentResolver[] getResolvers(MethodParameter[] parameters) throws 344 | DeploymentException { 345 | org.pyj.yeauty.support.MethodArgumentResolver[] methodArgumentResolvers = 346 | new org.pyj.yeauty.support.MethodArgumentResolver[parameters.length]; 347 | List resolvers = getDefaultResolvers(); 348 | for (int i = 0; i < parameters.length; i++) { 349 | MethodParameter parameter = parameters[i]; 350 | for (org.pyj.yeauty.support.MethodArgumentResolver resolver : resolvers) { 351 | if (resolver.supportsParameter(parameter)) { 352 | methodArgumentResolvers[i] = resolver; 353 | break; 354 | } 355 | } 356 | if (methodArgumentResolvers[i] == null) { 357 | throw new DeploymentException( 358 | "pojoMethodMapping.paramClassIncorrect parameter name : " + parameter.getParameterName()); 359 | } 360 | } 361 | return methodArgumentResolvers; 362 | } 363 | 364 | private List getDefaultResolvers() { 365 | List resolvers = new ArrayList<>(); 366 | resolvers.add(new SessionMethodArgumentResolver()); 367 | resolvers.add(new org.pyj.yeauty.support.HttpHeadersMethodArgumentResolver()); 368 | resolvers.add(new org.pyj.yeauty.support.TextMethodArgumentResolver()); 369 | resolvers.add(new org.pyj.yeauty.support.ThrowableMethodArgumentResolver()); 370 | resolvers.add(new org.pyj.yeauty.support.ByteMethodArgumentResolver()); 371 | resolvers.add(new org.pyj.yeauty.support.RequestParamMapMethodArgumentResolver()); 372 | resolvers.add(new org.pyj.yeauty.support.RequestParamMethodArgumentResolver(beanFactory)); 373 | resolvers.add(new org.pyj.yeauty.support.PathVariableMapMethodArgumentResolver()); 374 | resolvers.add(new org.pyj.yeauty.support.PathVariableMethodArgumentResolver(beanFactory)); 375 | resolvers.add(new org.pyj.yeauty.support.EventMethodArgumentResolver(beanFactory)); 376 | return resolvers; 377 | } 378 | } 379 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/pojo/Session.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.pojo; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.buffer.ByteBufAllocator; 5 | import io.netty.channel.Channel; 6 | import io.netty.channel.ChannelConfig; 7 | import io.netty.channel.ChannelFuture; 8 | import io.netty.channel.ChannelId; 9 | import io.netty.channel.ChannelMetadata; 10 | import io.netty.channel.ChannelPipeline; 11 | import io.netty.channel.ChannelPromise; 12 | import io.netty.channel.EventLoop; 13 | import io.netty.channel.socket.DatagramChannel; 14 | import io.netty.channel.socket.DatagramPacket; 15 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 16 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 17 | import io.netty.util.AttributeKey; 18 | import java.net.InetSocketAddress; 19 | import java.net.SocketAddress; 20 | import java.nio.ByteBuffer; 21 | import org.pyj.yeauty.annotation.BeforeHandshake; 22 | 23 | /** 24 | * @author Yeauty 25 | * @version 1.0 26 | */ 27 | public class Session { 28 | 29 | private final Channel channel; 30 | 31 | Session(Channel channel) { 32 | this.channel = channel; 33 | } 34 | 35 | /** 36 | * set subprotocols on {@link BeforeHandshake} 37 | * 38 | * @param subprotocols 39 | */ 40 | public void setSubprotocols(String subprotocols) { 41 | setAttribute("subprotocols", subprotocols); 42 | } 43 | 44 | public ChannelFuture sendText(String message) { 45 | return channel.writeAndFlush(new TextWebSocketFrame(message)); 46 | } 47 | 48 | public ChannelFuture sendText(ByteBuf byteBuf) { 49 | return channel.writeAndFlush(new TextWebSocketFrame(byteBuf)); 50 | } 51 | 52 | public ChannelFuture sendText(ByteBuffer byteBuffer) { 53 | ByteBuf buffer = channel.alloc().buffer(byteBuffer.remaining()); 54 | buffer.writeBytes(byteBuffer); 55 | return channel.writeAndFlush(new TextWebSocketFrame(buffer)); 56 | } 57 | 58 | public ChannelFuture sendText(TextWebSocketFrame textWebSocketFrame) { 59 | return channel.writeAndFlush(textWebSocketFrame); 60 | } 61 | 62 | public ChannelFuture sendBinary(byte[] bytes) { 63 | ByteBuf buffer = channel.alloc().buffer(bytes.length); 64 | return channel.writeAndFlush(new BinaryWebSocketFrame(buffer.writeBytes(bytes))); 65 | } 66 | 67 | public ChannelFuture sendBinary(ByteBuf byteBuf) { 68 | return channel.writeAndFlush(new BinaryWebSocketFrame(byteBuf)); 69 | } 70 | 71 | public ChannelFuture sendBinary(ByteBuffer byteBuffer) { 72 | ByteBuf buffer = channel.alloc().buffer(byteBuffer.remaining()); 73 | buffer.writeBytes(byteBuffer); 74 | return channel.writeAndFlush(new BinaryWebSocketFrame(buffer)); 75 | } 76 | 77 | public ChannelFuture sendBinary(BinaryWebSocketFrame binaryWebSocketFrame) { 78 | return channel.writeAndFlush(binaryWebSocketFrame); 79 | } 80 | 81 | public void setAttribute(String name, T value) { 82 | AttributeKey sessionIdKey = AttributeKey.valueOf(name); 83 | channel.attr(sessionIdKey).set(value); 84 | } 85 | 86 | public T getAttribute(String name) { 87 | AttributeKey sessionIdKey = AttributeKey.valueOf(name); 88 | return channel.attr(sessionIdKey).get(); 89 | } 90 | 91 | public Channel channel() { 92 | return channel; 93 | } 94 | 95 | /** 96 | * Returns the globally unique identifier of this {@link Channel}. 97 | */ 98 | public ChannelId id() { 99 | return channel.id(); 100 | } 101 | 102 | /** 103 | * Returns the configuration of this channel. 104 | */ 105 | public ChannelConfig config() { 106 | return channel.config(); 107 | } 108 | 109 | /** 110 | * Returns {@code true} if the {@link Channel} is open and may get active later 111 | */ 112 | public boolean isOpen() { 113 | return channel.isOpen(); 114 | } 115 | 116 | /** 117 | * Returns {@code true} if the {@link Channel} is registered with an {@link EventLoop}. 118 | */ 119 | public boolean isRegistered() { 120 | return channel.isRegistered(); 121 | } 122 | 123 | /** 124 | * Return {@code true} if the {@link Channel} is active and so connected. 125 | */ 126 | public boolean isActive() { 127 | return channel.isActive(); 128 | } 129 | 130 | /** 131 | * Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature of the {@link Channel}. 132 | */ 133 | public ChannelMetadata metadata() { 134 | return channel.metadata(); 135 | } 136 | 137 | /** 138 | * Returns the local address where this channel is bound to. The returned 139 | * {@link SocketAddress} is supposed to be down-cast into more concrete 140 | * type such as {@link InetSocketAddress} to retrieve the detailed 141 | * information. 142 | * 143 | * @return the local address of this channel. 144 | * {@code null} if this channel is not bound. 145 | */ 146 | public SocketAddress localAddress() { 147 | return channel.localAddress(); 148 | } 149 | 150 | /** 151 | * Returns the remote address where this channel is connected to. The 152 | * returned {@link SocketAddress} is supposed to be down-cast into more 153 | * concrete type such as {@link InetSocketAddress} to retrieve the detailed 154 | * information. 155 | * 156 | * @return the remote address of this channel. 157 | * {@code null} if this channel is not connected. 158 | * If this channel is not connected but it can receive messages 159 | * from arbitrary remote addresses (e.g. {@link DatagramChannel}, 160 | * use {@link DatagramPacket#recipient()} to determine 161 | * the origination of the received message as this method will 162 | * return {@code null}. 163 | */ 164 | public SocketAddress remoteAddress() { 165 | return channel.remoteAddress(); 166 | } 167 | 168 | /** 169 | * Returns the {@link ChannelFuture} which will be notified when this 170 | * channel is closed. This method always returns the same future instance. 171 | */ 172 | public ChannelFuture closeFuture() { 173 | return channel.closeFuture(); 174 | } 175 | 176 | /** 177 | * Returns {@code true} if and only if the I/O thread will perform the 178 | * requested write operation immediately. Any write requests made when 179 | * this method returns {@code false} are queued until the I/O thread is 180 | * ready to process the queued write requests. 181 | */ 182 | public boolean isWritable() { 183 | return channel.isWritable(); 184 | } 185 | 186 | /** 187 | * Get how many bytes can be written until {@link #isWritable()} returns {@code false}. 188 | * This quantity will always be non-negative. If {@link #isWritable()} is {@code false} then 0. 189 | */ 190 | public long bytesBeforeUnwritable() { 191 | return channel.bytesBeforeUnwritable(); 192 | } 193 | 194 | /** 195 | * Get how many bytes must be drained from underlying buffers until {@link #isWritable()} returns {@code true}. 196 | * This quantity will always be non-negative. If {@link #isWritable()} is {@code true} then 0. 197 | */ 198 | public long bytesBeforeWritable() { 199 | return channel.bytesBeforeWritable(); 200 | } 201 | 202 | /** 203 | * Returns an internal-use-only object that provides unsafe operations. 204 | */ 205 | public Channel.Unsafe unsafe() { 206 | return channel.unsafe(); 207 | } 208 | 209 | /** 210 | * Return the assigned {@link ChannelPipeline}. 211 | */ 212 | public ChannelPipeline pipeline() { 213 | return channel.pipeline(); 214 | } 215 | 216 | /** 217 | * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s. 218 | */ 219 | public ByteBufAllocator alloc() { 220 | return channel.alloc(); 221 | } 222 | 223 | public Channel read() { 224 | return channel.read(); 225 | } 226 | 227 | public Channel flush() { 228 | return channel.flush(); 229 | } 230 | 231 | public ChannelFuture close() { 232 | return channel.close(); 233 | } 234 | 235 | public ChannelFuture close(ChannelPromise promise) { 236 | return channel.close(promise); 237 | } 238 | 239 | } 240 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/AntPathMatcherWrapper.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.QueryStringDecoder; 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | import org.pyj.yeauty.pojo.PojoEndpointServer; 8 | import org.springframework.util.AntPathMatcher; 9 | 10 | public class AntPathMatcherWrapper extends AntPathMatcher implements WsPathMatcher { 11 | 12 | private final String pattern; 13 | 14 | public AntPathMatcherWrapper(String pattern) { 15 | this.pattern = pattern; 16 | } 17 | 18 | @Override 19 | public String getPattern() { 20 | return this.pattern; 21 | } 22 | 23 | @Override 24 | public boolean matchAndExtract(QueryStringDecoder decoder, Channel channel) { 25 | Map variables = new LinkedHashMap<>(); 26 | boolean result = doMatch(pattern, decoder.path(), true, variables); 27 | if (result) { 28 | channel.attr(PojoEndpointServer.URI_TEMPLATE).set(variables); 29 | return true; 30 | } 31 | return false; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/ByteMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.Channel; 5 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 6 | import org.pyj.yeauty.annotation.OnBinary; 7 | import org.springframework.core.MethodParameter; 8 | 9 | public class ByteMethodArgumentResolver implements MethodArgumentResolver { 10 | @Override 11 | public boolean supportsParameter(MethodParameter parameter) { 12 | return parameter.getMethod().isAnnotationPresent(OnBinary.class) && 13 | byte[].class.isAssignableFrom(parameter.getParameterType()); 14 | } 15 | 16 | @Override 17 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 18 | BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) object; 19 | ByteBuf content = binaryWebSocketFrame.content(); 20 | byte[] bytes = new byte[content.readableBytes()]; 21 | content.readBytes(bytes); 22 | return bytes; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/DefaultPathMatcher.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.QueryStringDecoder; 5 | 6 | 7 | public class DefaultPathMatcher implements WsPathMatcher { 8 | 9 | private final String pattern; 10 | 11 | public DefaultPathMatcher(String pattern) { 12 | this.pattern = pattern; 13 | } 14 | 15 | @Override 16 | public String getPattern() { 17 | return this.pattern; 18 | } 19 | 20 | @Override 21 | public boolean matchAndExtract(QueryStringDecoder decoder, Channel channel) { 22 | return pattern.equals(decoder.path()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/EventMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import org.pyj.yeauty.annotation.OnEvent; 5 | import org.springframework.beans.TypeConverter; 6 | import org.springframework.beans.factory.support.AbstractBeanFactory; 7 | import org.springframework.core.MethodParameter; 8 | 9 | public class EventMethodArgumentResolver implements MethodArgumentResolver { 10 | 11 | private final AbstractBeanFactory beanFactory; 12 | 13 | public EventMethodArgumentResolver(AbstractBeanFactory beanFactory) { 14 | this.beanFactory = beanFactory; 15 | } 16 | 17 | 18 | @Override 19 | public boolean supportsParameter(MethodParameter parameter) { 20 | return parameter.getMethod().isAnnotationPresent(OnEvent.class); 21 | } 22 | 23 | @Override 24 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 25 | if (object == null) { 26 | return null; 27 | } 28 | TypeConverter typeConverter = beanFactory.getTypeConverter(); 29 | return typeConverter.convertIfNecessary(object, parameter.getParameterType()); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/HttpHeadersMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.HttpHeaders; 6 | import org.springframework.core.MethodParameter; 7 | 8 | public class HttpHeadersMethodArgumentResolver implements MethodArgumentResolver { 9 | @Override 10 | public boolean supportsParameter(MethodParameter parameter) { 11 | return HttpHeaders.class.isAssignableFrom(parameter.getParameterType()); 12 | } 13 | 14 | @Override 15 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 16 | return ((FullHttpRequest) object).headers(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/MethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import org.springframework.core.MethodParameter; 5 | import org.springframework.lang.Nullable; 6 | 7 | 8 | public interface MethodArgumentResolver { 9 | 10 | /** 11 | * Whether the given {@linkplain MethodParameter method parameter} is 12 | * supported by this resolver. 13 | * 14 | * @param parameter the method parameter to check 15 | * @return {@code true} if this resolver supports the supplied parameter; 16 | * {@code false} otherwise 17 | */ 18 | boolean supportsParameter(MethodParameter parameter); 19 | 20 | 21 | @Nullable 22 | Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/PathVariableMapMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import java.util.Collections; 5 | import java.util.Map; 6 | import org.pyj.yeauty.annotation.PathVariable; 7 | import org.pyj.yeauty.pojo.PojoEndpointServer; 8 | import org.springframework.core.MethodParameter; 9 | import org.springframework.util.CollectionUtils; 10 | import org.springframework.util.StringUtils; 11 | 12 | public class PathVariableMapMethodArgumentResolver implements MethodArgumentResolver { 13 | @Override 14 | public boolean supportsParameter(MethodParameter parameter) { 15 | PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); 16 | return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) && 17 | !StringUtils.hasText(ann.value())); 18 | } 19 | 20 | @Override 21 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 22 | PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); 23 | String name = ann.name(); 24 | if (name.isEmpty()) { 25 | name = parameter.getParameterName(); 26 | if (name == null) { 27 | throw new IllegalArgumentException( 28 | "Name for argument type [" + parameter.getNestedParameterType().getName() + 29 | "] not available, and parameter name information not found in class file either."); 30 | } 31 | } 32 | Map uriTemplateVars = channel.attr(PojoEndpointServer.URI_TEMPLATE).get(); 33 | if (!CollectionUtils.isEmpty(uriTemplateVars)) { 34 | return uriTemplateVars; 35 | } else { 36 | return Collections.emptyMap(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/PathVariableMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import java.util.Map; 5 | import org.pyj.yeauty.annotation.PathVariable; 6 | import org.pyj.yeauty.pojo.PojoEndpointServer; 7 | import org.springframework.beans.TypeConverter; 8 | import org.springframework.beans.factory.support.AbstractBeanFactory; 9 | import org.springframework.core.MethodParameter; 10 | 11 | public class PathVariableMethodArgumentResolver implements MethodArgumentResolver { 12 | 13 | private final AbstractBeanFactory beanFactory; 14 | 15 | public PathVariableMethodArgumentResolver(AbstractBeanFactory beanFactory) { 16 | this.beanFactory = beanFactory; 17 | } 18 | 19 | @Override 20 | public boolean supportsParameter(MethodParameter parameter) { 21 | return parameter.hasParameterAnnotation(org.pyj.yeauty.annotation.PathVariable.class); 22 | } 23 | 24 | @Override 25 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 26 | org.pyj.yeauty.annotation.PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); 27 | String name = ann.name(); 28 | if (name.isEmpty()) { 29 | name = parameter.getParameterName(); 30 | if (name == null) { 31 | throw new IllegalArgumentException( 32 | "Name for argument type [" + parameter.getNestedParameterType().getName() + 33 | "] not available, and parameter name information not found in class file either."); 34 | } 35 | } 36 | Map uriTemplateVars = channel.attr(PojoEndpointServer.URI_TEMPLATE).get(); 37 | Object arg = (uriTemplateVars != null ? uriTemplateVars.get(name) : null); 38 | TypeConverter typeConverter = beanFactory.getTypeConverter(); 39 | return typeConverter.convertIfNecessary(arg, parameter.getParameterType()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/RequestParamMapMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.QueryStringDecoder; 6 | import java.util.List; 7 | import java.util.Map; 8 | import org.pyj.yeauty.annotation.RequestParam; 9 | import org.pyj.yeauty.pojo.PojoEndpointServer; 10 | import org.springframework.core.MethodParameter; 11 | import org.springframework.util.LinkedMultiValueMap; 12 | import org.springframework.util.MultiValueMap; 13 | import org.springframework.util.StringUtils; 14 | 15 | public class RequestParamMapMethodArgumentResolver implements MethodArgumentResolver { 16 | 17 | @Override 18 | public boolean supportsParameter(MethodParameter parameter) { 19 | org.pyj.yeauty.annotation.RequestParam 20 | requestParam = parameter.getParameterAnnotation(org.pyj.yeauty.annotation.RequestParam.class); 21 | return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) && 22 | !StringUtils.hasText(requestParam.name())); 23 | } 24 | 25 | @Override 26 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 27 | org.pyj.yeauty.annotation.RequestParam ann = parameter.getParameterAnnotation(RequestParam.class); 28 | String name = ann.name(); 29 | if (name.isEmpty()) { 30 | name = parameter.getParameterName(); 31 | if (name == null) { 32 | throw new IllegalArgumentException( 33 | "Name for argument type [" + parameter.getNestedParameterType().getName() + 34 | "] not available, and parameter name information not found in class file either."); 35 | } 36 | } 37 | 38 | if (!channel.hasAttr(PojoEndpointServer.REQUEST_PARAM)) { 39 | QueryStringDecoder decoder = new QueryStringDecoder(((FullHttpRequest) object).uri()); 40 | channel.attr(PojoEndpointServer.REQUEST_PARAM).set(decoder.parameters()); 41 | } 42 | 43 | Map> requestParams = channel.attr(PojoEndpointServer.REQUEST_PARAM).get(); 44 | MultiValueMap multiValueMap = new LinkedMultiValueMap(requestParams); 45 | if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) { 46 | return multiValueMap; 47 | } else { 48 | return multiValueMap.toSingleValueMap(); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/RequestParamMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.FullHttpRequest; 5 | import io.netty.handler.codec.http.QueryStringDecoder; 6 | import java.util.List; 7 | import java.util.Map; 8 | import org.pyj.yeauty.annotation.RequestParam; 9 | import org.pyj.yeauty.pojo.PojoEndpointServer; 10 | import org.springframework.beans.TypeConverter; 11 | import org.springframework.beans.factory.support.AbstractBeanFactory; 12 | import org.springframework.core.MethodParameter; 13 | 14 | public class RequestParamMethodArgumentResolver implements MethodArgumentResolver { 15 | 16 | private final AbstractBeanFactory beanFactory; 17 | 18 | public RequestParamMethodArgumentResolver(AbstractBeanFactory beanFactory) { 19 | this.beanFactory = beanFactory; 20 | } 21 | 22 | @Override 23 | public boolean supportsParameter(MethodParameter parameter) { 24 | return parameter.hasParameterAnnotation(org.pyj.yeauty.annotation.RequestParam.class); 25 | } 26 | 27 | @Override 28 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 29 | org.pyj.yeauty.annotation.RequestParam ann = parameter.getParameterAnnotation(RequestParam.class); 30 | String name = ann.name(); 31 | if (name.isEmpty()) { 32 | name = parameter.getParameterName(); 33 | if (name == null) { 34 | throw new IllegalArgumentException( 35 | "Name for argument type [" + parameter.getNestedParameterType().getName() + 36 | "] not available, and parameter name information not found in class file either."); 37 | } 38 | } 39 | 40 | if (!channel.hasAttr(PojoEndpointServer.REQUEST_PARAM)) { 41 | QueryStringDecoder decoder = new QueryStringDecoder(((FullHttpRequest) object).uri()); 42 | channel.attr(PojoEndpointServer.REQUEST_PARAM).set(decoder.parameters()); 43 | } 44 | 45 | Map> requestParams = channel.attr(PojoEndpointServer.REQUEST_PARAM).get(); 46 | List arg = (requestParams != null ? requestParams.get(name) : null); 47 | TypeConverter typeConverter = beanFactory.getTypeConverter(); 48 | if (arg == null) { 49 | if ("\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n".equals(ann.defaultValue())) { 50 | return null; 51 | } else { 52 | return typeConverter.convertIfNecessary(ann.defaultValue(), parameter.getParameterType()); 53 | } 54 | } 55 | if (List.class.isAssignableFrom(parameter.getParameterType())) { 56 | return typeConverter.convertIfNecessary(arg, parameter.getParameterType()); 57 | } else { 58 | return typeConverter.convertIfNecessary(arg.get(0), parameter.getParameterType()); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/SessionMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import org.pyj.yeauty.pojo.PojoEndpointServer; 5 | import org.pyj.yeauty.pojo.Session; 6 | import org.springframework.core.MethodParameter; 7 | 8 | public class SessionMethodArgumentResolver implements MethodArgumentResolver { 9 | @Override 10 | public boolean supportsParameter(MethodParameter parameter) { 11 | return org.pyj.yeauty.pojo.Session.class.isAssignableFrom(parameter.getParameterType()); 12 | } 13 | 14 | @Override 15 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 16 | Session session = channel.attr(PojoEndpointServer.SESSION_KEY).get(); 17 | return session; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/TextMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 5 | import org.pyj.yeauty.annotation.OnMessage; 6 | import org.springframework.core.MethodParameter; 7 | 8 | public class TextMethodArgumentResolver implements MethodArgumentResolver { 9 | @Override 10 | public boolean supportsParameter(MethodParameter parameter) { 11 | return parameter.getMethod().isAnnotationPresent(OnMessage.class) && 12 | String.class.isAssignableFrom(parameter.getParameterType()); 13 | } 14 | 15 | @Override 16 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 17 | TextWebSocketFrame textFrame = (TextWebSocketFrame) object; 18 | return textFrame.text(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/ThrowableMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import org.pyj.yeauty.annotation.OnError; 5 | import org.springframework.core.MethodParameter; 6 | 7 | public class ThrowableMethodArgumentResolver implements MethodArgumentResolver { 8 | @Override 9 | public boolean supportsParameter(MethodParameter parameter) { 10 | return parameter.getMethod().isAnnotationPresent(OnError.class) && 11 | Throwable.class.isAssignableFrom(parameter.getParameterType()); 12 | } 13 | 14 | @Override 15 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 16 | if (object instanceof Throwable) { 17 | return object; 18 | } 19 | return null; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/support/WsPathMatcher.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.support; 2 | 3 | import io.netty.channel.Channel; 4 | import io.netty.handler.codec.http.QueryStringDecoder; 5 | 6 | 7 | public interface WsPathMatcher { 8 | 9 | String getPattern(); 10 | 11 | boolean matchAndExtract(QueryStringDecoder decoder, Channel channel); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/org/pyj/yeauty/util/SslUtils.java: -------------------------------------------------------------------------------- 1 | package org.pyj.yeauty.util; 2 | 3 | import io.netty.handler.ssl.SslContext; 4 | import io.netty.handler.ssl.SslContextBuilder; 5 | import java.net.URL; 6 | import java.security.KeyStore; 7 | import javax.net.ssl.KeyManagerFactory; 8 | import javax.net.ssl.SSLException; 9 | import javax.net.ssl.TrustManagerFactory; 10 | import org.springframework.util.ResourceUtils; 11 | import org.springframework.util.StringUtils; 12 | 13 | /** 14 | * refer to {@link org.springframework.boot.web.embedded.netty.SslServerCustomizer} 15 | */ 16 | public final class SslUtils { 17 | 18 | public static SslContext createSslContext(String keyPassword, String keyStoreResource, String keyStoreType, 19 | String keyStorePassword, String trustStoreResource, String trustStoreType, 20 | String trustStorePassword) throws SSLException { 21 | SslContextBuilder sslBuilder = SslContextBuilder 22 | .forServer(getKeyManagerFactory(keyStoreType, keyStoreResource, keyPassword, keyStorePassword)) 23 | .trustManager(getTrustManagerFactory(trustStoreType, trustStoreResource, trustStorePassword)); 24 | return sslBuilder.build(); 25 | } 26 | 27 | private static KeyManagerFactory getKeyManagerFactory(String type, String resource, String keyPassword, 28 | String keyStorePassword) { 29 | try { 30 | KeyStore keyStore = loadKeyStore(type, resource, keyStorePassword); 31 | KeyManagerFactory keyManagerFactory = KeyManagerFactory 32 | .getInstance(KeyManagerFactory.getDefaultAlgorithm()); 33 | char[] keyPasswordBytes = (!StringUtils.isEmpty(keyPassword) 34 | ? keyPassword.toCharArray() : null); 35 | if (keyPasswordBytes == null && !StringUtils.isEmpty(keyStorePassword)) { 36 | keyPasswordBytes = keyStorePassword.toCharArray(); 37 | } 38 | keyManagerFactory.init(keyStore, keyPasswordBytes); 39 | return keyManagerFactory; 40 | } catch (Exception ex) { 41 | throw new IllegalStateException(ex); 42 | } 43 | } 44 | 45 | private static TrustManagerFactory getTrustManagerFactory(String trustStoreType, String trustStoreResource, 46 | String trustStorePassword) { 47 | try { 48 | KeyStore store = loadKeyStore(trustStoreType, trustStoreResource, trustStorePassword); 49 | TrustManagerFactory trustManagerFactory = TrustManagerFactory 50 | .getInstance(TrustManagerFactory.getDefaultAlgorithm()); 51 | trustManagerFactory.init(store); 52 | return trustManagerFactory; 53 | } catch (Exception ex) { 54 | throw new IllegalStateException(ex); 55 | } 56 | } 57 | 58 | private static KeyStore loadKeyStore(String type, String resource, String password) 59 | throws Exception { 60 | type = (StringUtils.isEmpty(type) ? "JKS" : type); 61 | if (StringUtils.isEmpty(resource)) { 62 | return null; 63 | } 64 | KeyStore store = KeyStore.getInstance(type); 65 | URL url = ResourceUtils.getURL(resource); 66 | store.load(url.openStream(), StringUtils.isEmpty(password) ? null : password.toCharArray()); 67 | return store; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | org.pyj.config.WebSocketNettyConfig --------------------------------------------------------------------------------