├── .gitignore ├── LICENSE ├── Netty-WebSocket-Starter ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── chachae │ │ └── webrtc │ │ └── netty │ │ └── socket │ │ ├── annotation │ │ ├── BeforeHandshake.java │ │ ├── EnableWebSocket.java │ │ ├── NettyWebSocketSelector.java │ │ ├── OnBinary.java │ │ ├── OnClose.java │ │ ├── OnError.java │ │ ├── OnEvent.java │ │ ├── OnMessage.java │ │ ├── OnOpen.java │ │ ├── PathVariable.java │ │ ├── RequestParam.java │ │ └── ServerEndpoint.java │ │ ├── autoconfigure │ │ └── NettyWebSocketAutoConfigure.java │ │ ├── exception │ │ └── DeploymentException.java │ │ ├── pojo │ │ ├── PojoEndpointServer.java │ │ ├── PojoMethodMapping.java │ │ └── Session.java │ │ ├── standard │ │ ├── HttpServerHandler.java │ │ ├── ServerEndpointConfig.java │ │ ├── ServerEndpointExporter.java │ │ ├── WebSocketServerHandler.java │ │ └── WebsocketServer.java │ │ └── support │ │ ├── AntPathMatcherWraaper.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 │ └── resources │ └── META-INF │ └── spring.factories ├── README.md ├── WebRTC-Common ├── pom.xml └── src │ └── main │ └── java │ └── com │ └── chachae │ └── webrtc │ └── common │ ├── AbstractRedisCache.java │ ├── ApiException.java │ ├── R.java │ └── RedisService.java ├── WebRTC-Netty ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── chachae │ │ └── webrtc │ │ └── netty │ │ ├── WebrtcNettyApplication.java │ │ ├── config │ │ ├── RedisPublishConfig.java │ │ └── SessionConfig.java │ │ ├── constant │ │ ├── MsgTypeConstant.java │ │ └── SocketConfigConstant.java │ │ ├── controller │ │ ├── SocketMessageController.java │ │ └── ViewController.java │ │ ├── entity │ │ └── Message.java │ │ ├── handle │ │ └── SysWebSocket.java │ │ ├── service │ │ └── RedisMessage.java │ │ └── util │ │ └── IdUtil.java │ └── resources │ ├── ValidationMessages.properties │ ├── application.yml │ └── templates │ ├── index.html │ └── monitor.html ├── WebRTC-WebSocket ├── pom.xml └── src │ └── main │ ├── java │ └── com │ │ └── chachae │ │ └── webrtc │ │ ├── WebrtcApplication.java │ │ ├── cache │ │ └── RoomCache.java │ │ ├── config │ │ ├── RedisConfig.java │ │ ├── RedisPublishConfig.java │ │ └── WebSocketConfig.java │ │ ├── constant │ │ └── MessageConstant.java │ │ ├── controller │ │ ├── MessageController.java │ │ └── PageController.java │ │ ├── entity │ │ ├── ConnectionSystemUser.java │ │ ├── Message.java │ │ ├── Room.java │ │ └── SystemUser.java │ │ ├── handler │ │ ├── BaseExceptionHandler.java │ │ └── WebSocketMessageHandler.java │ │ ├── interceptor │ │ └── WebSocketInterceptor.java │ │ └── service │ │ ├── IMessageForwardService.java │ │ ├── IRoomService.java │ │ ├── RedisMessage.java │ │ └── impl │ │ ├── MessageForwardServiceImpl.java │ │ └── RoomServiceImpl.java │ └── resources │ ├── ValidationMessages.properties │ ├── application.yml │ ├── server.keystore │ └── templates │ ├── index.html │ └── monitor.html ├── images ├── index-1.png └── monitor-2.png └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2020] [chachae] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | WebRTC-One-Way-Monitor 7 | com.chachae.webrtc 8 | 1.0 9 | 10 | 4.0.0 11 | 12 | Netty-WebSocket-Starter 13 | 14 | 15 | 4.1.50.Final 16 | 17 | 18 | 19 | 20 | org.springframework.boot 21 | spring-boot-autoconfigure 22 | ${spring-boot.version} 23 | true 24 | 25 | 26 | 27 | io.netty 28 | netty-codec-http 29 | ${netty.version} 30 | 31 | 32 | io.netty 33 | netty-handler 34 | ${netty.version} 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/BeforeHandshake.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/EnableWebSocket.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.annotation; 2 | 3 | import org.springframework.context.annotation.Import; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @Target(ElementType.TYPE) 12 | @Import(NettyWebSocketSelector.class) 13 | public @interface EnableWebSocket { 14 | } 15 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/NettyWebSocketSelector.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.annotation; 2 | 3 | import com.chachae.webrtc.netty.socket.standard.ServerEndpointExporter; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @ConditionalOnMissingBean(ServerEndpointExporter.class) 9 | @Configuration 10 | public class NettyWebSocketSelector { 11 | 12 | @Bean 13 | public ServerEndpointExporter serverEndpointExporter() { 14 | return new ServerEndpointExporter(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/OnBinary.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/OnClose.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/OnError.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/OnEvent.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/OnMessage.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/OnOpen.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/PathVariable.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.annotation; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.PARAMETER) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface PathVariable { 13 | 14 | /** 15 | * Alias for {@link #name}. 16 | */ 17 | @AliasFor("name") 18 | String value() default ""; 19 | 20 | /** 21 | * The name of the path variable to bind to. 22 | */ 23 | @AliasFor("value") 24 | String name() default ""; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/RequestParam.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.annotation; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | @Target(ElementType.PARAMETER) 11 | @Retention(RetentionPolicy.RUNTIME) 12 | public @interface RequestParam { 13 | 14 | /** 15 | * Alias for {@link #name}. 16 | */ 17 | @AliasFor("name") 18 | String value() default ""; 19 | 20 | /** 21 | * The name of the request parameter to bind to. 22 | */ 23 | @AliasFor("value") 24 | String name() default ""; 25 | 26 | /** 27 | * Whether the parameter is required. 28 | *

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

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

Supplying a default value implicitly sets {@link #required} to 41 | * {@code false}. 42 | */ 43 | String defaultValue() default "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"; 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/annotation/ServerEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.annotation; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * @author Yeauty 13 | * @version 1.0 14 | */ 15 | @Component 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target(ElementType.TYPE) 18 | public @interface ServerEndpoint { 19 | 20 | @AliasFor("path") 21 | String value() default "/"; 22 | 23 | @AliasFor("value") 24 | String path() default "/"; 25 | 26 | String host() default "0.0.0.0"; 27 | 28 | String port() default "80"; 29 | 30 | String bossLoopGroupThreads() default "0"; 31 | 32 | String workerLoopGroupThreads() default "0"; 33 | 34 | String useCompressionHandler() default "false"; 35 | 36 | //------------------------- option ------------------------- 37 | 38 | String optionConnectTimeoutMillis() default "30000"; 39 | 40 | String optionSoBacklog() default "128"; 41 | 42 | //------------------------- childOption ------------------------- 43 | 44 | String childOptionWriteSpinCount() default "16"; 45 | 46 | String childOptionWriteBufferHighWaterMark() default "65536"; 47 | 48 | String childOptionWriteBufferLowWaterMark() default "32768"; 49 | 50 | String childOptionSoRcvbuf() default "-1"; 51 | 52 | String childOptionSoSndbuf() default "-1"; 53 | 54 | String childOptionTcpNodelay() default "true"; 55 | 56 | String childOptionSoKeepalive() default "false"; 57 | 58 | String childOptionSoLinger() default "-1"; 59 | 60 | String childOptionAllowHalfClosure() default "false"; 61 | 62 | //------------------------- idleEvent ------------------------- 63 | 64 | String readerIdleTimeSeconds() default "0"; 65 | 66 | String writerIdleTimeSeconds() default "0"; 67 | 68 | String allIdleTimeSeconds() default "0"; 69 | 70 | //------------------------- handshake ------------------------- 71 | 72 | String maxFramePayloadLength() default "65536"; 73 | 74 | } 75 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/autoconfigure/NettyWebSocketAutoConfigure.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.autoconfigure; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.EnableWebSocket; 4 | 5 | @EnableWebSocket 6 | public class NettyWebSocketAutoConfigure { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/exception/DeploymentException.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/pojo/PojoEndpointServer.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.pojo; 2 | 3 | import com.chachae.webrtc.netty.socket.standard.ServerEndpointConfig; 4 | import com.chachae.webrtc.netty.socket.support.AntPathMatcherWraaper; 5 | import com.chachae.webrtc.netty.socket.support.DefaultPathMatcher; 6 | import com.chachae.webrtc.netty.socket.support.MethodArgumentResolver; 7 | import com.chachae.webrtc.netty.socket.support.PathVariableMapMethodArgumentResolver; 8 | import com.chachae.webrtc.netty.socket.support.PathVariableMethodArgumentResolver; 9 | import com.chachae.webrtc.netty.socket.support.WsPathMatcher; 10 | import io.netty.channel.Channel; 11 | import io.netty.handler.codec.http.FullHttpRequest; 12 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 13 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 14 | import io.netty.handler.codec.http.websocketx.WebSocketFrame; 15 | import io.netty.util.Attribute; 16 | import io.netty.util.AttributeKey; 17 | import io.netty.util.internal.logging.InternalLogger; 18 | import io.netty.util.internal.logging.InternalLoggerFactory; 19 | import java.lang.reflect.Method; 20 | import java.util.HashMap; 21 | import java.util.HashSet; 22 | import java.util.List; 23 | import java.util.Map; 24 | import java.util.Set; 25 | import org.springframework.beans.TypeMismatchException; 26 | 27 | /** 28 | * @author Yeauty 29 | * @version 1.0 30 | */ 31 | public class PojoEndpointServer { 32 | 33 | private static final AttributeKey POJO_KEY = AttributeKey.valueOf("WEBSOCKET_IMPLEMENT"); 34 | 35 | public static final AttributeKey SESSION_KEY = AttributeKey.valueOf("WEBSOCKET_SESSION"); 36 | 37 | private static final AttributeKey PATH_KEY = AttributeKey.valueOf("WEBSOCKET_PATH"); 38 | 39 | public static final AttributeKey> URI_TEMPLATE = AttributeKey.valueOf("WEBSOCKET_URI_TEMPLATE"); 40 | 41 | public static final AttributeKey>> REQUEST_PARAM = AttributeKey.valueOf("WEBSOCKET_REQUEST_PARAM"); 42 | 43 | private final Map pathMethodMappingMap = new HashMap<>(); 44 | 45 | private final ServerEndpointConfig config; 46 | 47 | private final Set pathMatchers = new HashSet<>(); 48 | 49 | private static final InternalLogger logger = InternalLoggerFactory.getInstance(PojoEndpointServer.class); 50 | 51 | public PojoEndpointServer(PojoMethodMapping methodMapping, ServerEndpointConfig config, String path) { 52 | addPathPojoMethodMapping(path, methodMapping); 53 | this.config = config; 54 | } 55 | 56 | public boolean hasBeforeHandshake(Channel channel, String path) { 57 | PojoMethodMapping methodMapping = getPojoMethodMapping(path, channel); 58 | return methodMapping.getBeforeHandshake() != null; 59 | } 60 | 61 | public void doBeforeHandshake(Channel channel, FullHttpRequest req, String path) { 62 | PojoMethodMapping methodMapping = null; 63 | methodMapping = getPojoMethodMapping(path, channel); 64 | 65 | Object implement = null; 66 | try { 67 | implement = methodMapping.getEndpointInstance(); 68 | } catch (Exception e) { 69 | logger.error(e); 70 | return; 71 | } 72 | channel.attr(POJO_KEY).set(implement); 73 | Session session = new Session(channel); 74 | channel.attr(SESSION_KEY).set(session); 75 | Method beforeHandshake = methodMapping.getBeforeHandshake(); 76 | if (beforeHandshake != null) { 77 | try { 78 | beforeHandshake.invoke(implement, methodMapping.getBeforeHandshakeArgs(channel, req)); 79 | } catch (TypeMismatchException e) { 80 | throw e; 81 | } catch (Throwable t) { 82 | logger.error(t); 83 | } 84 | } 85 | } 86 | 87 | public void doOnOpen(Channel channel, FullHttpRequest req, String path) { 88 | PojoMethodMapping methodMapping = getPojoMethodMapping(path, channel); 89 | 90 | Object implement = channel.attr(POJO_KEY).get(); 91 | if (implement == null) { 92 | try { 93 | implement = methodMapping.getEndpointInstance(); 94 | channel.attr(POJO_KEY).set(implement); 95 | } catch (Exception e) { 96 | logger.error(e); 97 | return; 98 | } 99 | Session session = new Session(channel); 100 | channel.attr(SESSION_KEY).set(session); 101 | } 102 | 103 | Method onOpenMethod = methodMapping.getOnOpen(); 104 | if (onOpenMethod != null) { 105 | try { 106 | onOpenMethod.invoke(implement, methodMapping.getOnOpenArgs(channel, req)); 107 | } catch (TypeMismatchException e) { 108 | throw e; 109 | } catch (Throwable t) { 110 | logger.error(t); 111 | } 112 | } 113 | } 114 | 115 | public void doOnClose(Channel channel) { 116 | Attribute attrPath = channel.attr(PATH_KEY); 117 | PojoMethodMapping methodMapping = null; 118 | if (pathMethodMappingMap.size() == 1) { 119 | methodMapping = pathMethodMappingMap.values().iterator().next(); 120 | } else { 121 | String path = attrPath.get(); 122 | methodMapping = pathMethodMappingMap.get(path); 123 | if (methodMapping == null) { 124 | return; 125 | } 126 | } 127 | if (methodMapping.getOnClose() != null) { 128 | if (!channel.hasAttr(SESSION_KEY)) { 129 | return; 130 | } 131 | Object implement = channel.attr(POJO_KEY).get(); 132 | try { 133 | methodMapping.getOnClose().invoke(implement, 134 | methodMapping.getOnCloseArgs(channel)); 135 | } catch (Throwable t) { 136 | logger.error(t); 137 | } 138 | } 139 | } 140 | 141 | 142 | public void doOnError(Channel channel, Throwable throwable) { 143 | Attribute attrPath = channel.attr(PATH_KEY); 144 | PojoMethodMapping methodMapping = null; 145 | if (pathMethodMappingMap.size() == 1) { 146 | methodMapping = pathMethodMappingMap.values().iterator().next(); 147 | } else { 148 | String path = attrPath.get(); 149 | methodMapping = pathMethodMappingMap.get(path); 150 | } 151 | if (methodMapping.getOnError() != null) { 152 | if (!channel.hasAttr(SESSION_KEY)) { 153 | return; 154 | } 155 | Object implement = channel.attr(POJO_KEY).get(); 156 | try { 157 | Method method = methodMapping.getOnError(); 158 | Object[] args = methodMapping.getOnErrorArgs(channel, throwable); 159 | method.invoke(implement, args); 160 | } catch (Throwable t) { 161 | logger.error(t); 162 | } 163 | } 164 | } 165 | 166 | public void doOnMessage(Channel channel, WebSocketFrame frame) { 167 | Attribute attrPath = channel.attr(PATH_KEY); 168 | PojoMethodMapping methodMapping = null; 169 | if (pathMethodMappingMap.size() == 1) { 170 | methodMapping = pathMethodMappingMap.values().iterator().next(); 171 | } else { 172 | String path = attrPath.get(); 173 | methodMapping = pathMethodMappingMap.get(path); 174 | } 175 | if (methodMapping.getOnMessage() != null) { 176 | TextWebSocketFrame textFrame = (TextWebSocketFrame) frame; 177 | Object implement = channel.attr(POJO_KEY).get(); 178 | try { 179 | methodMapping.getOnMessage().invoke(implement, methodMapping.getOnMessageArgs(channel, textFrame)); 180 | } catch (Throwable t) { 181 | logger.error(t); 182 | } 183 | } 184 | } 185 | 186 | public void doOnBinary(Channel channel, WebSocketFrame frame) { 187 | Attribute attrPath = channel.attr(PATH_KEY); 188 | PojoMethodMapping methodMapping = null; 189 | if (pathMethodMappingMap.size() == 1) { 190 | methodMapping = pathMethodMappingMap.values().iterator().next(); 191 | } else { 192 | String path = attrPath.get(); 193 | methodMapping = pathMethodMappingMap.get(path); 194 | } 195 | if (methodMapping.getOnBinary() != null) { 196 | BinaryWebSocketFrame binaryWebSocketFrame = (BinaryWebSocketFrame) frame; 197 | Object implement = channel.attr(POJO_KEY).get(); 198 | try { 199 | methodMapping.getOnBinary().invoke(implement, methodMapping.getOnBinaryArgs(channel, binaryWebSocketFrame)); 200 | } catch (Throwable t) { 201 | logger.error(t); 202 | } 203 | } 204 | } 205 | 206 | public void doOnEvent(Channel channel, Object evt) { 207 | Attribute attrPath = channel.attr(PATH_KEY); 208 | PojoMethodMapping methodMapping = null; 209 | if (pathMethodMappingMap.size() == 1) { 210 | methodMapping = pathMethodMappingMap.values().iterator().next(); 211 | } else { 212 | String path = attrPath.get(); 213 | methodMapping = pathMethodMappingMap.get(path); 214 | } 215 | if (methodMapping.getOnEvent() != null) { 216 | if (!channel.hasAttr(SESSION_KEY)) { 217 | return; 218 | } 219 | Object implement = channel.attr(POJO_KEY).get(); 220 | try { 221 | methodMapping.getOnEvent().invoke(implement, methodMapping.getOnEventArgs(channel, evt)); 222 | } catch (Throwable t) { 223 | logger.error(t); 224 | } 225 | } 226 | } 227 | 228 | public String getHost() { 229 | return config.getHost(); 230 | } 231 | 232 | public int getPort() { 233 | return config.getPort(); 234 | } 235 | 236 | public Set getPathMatcherSet() { 237 | return pathMatchers; 238 | } 239 | 240 | public void addPathPojoMethodMapping(String path, PojoMethodMapping pojoMethodMapping) { 241 | pathMethodMappingMap.put(path, pojoMethodMapping); 242 | for (MethodArgumentResolver onOpenArgResolver : pojoMethodMapping.getOnOpenArgResolvers()) { 243 | if (onOpenArgResolver instanceof PathVariableMethodArgumentResolver || onOpenArgResolver instanceof PathVariableMapMethodArgumentResolver) { 244 | pathMatchers.add(new AntPathMatcherWraaper(path)); 245 | return; 246 | } 247 | } 248 | pathMatchers.add(new DefaultPathMatcher(path)); 249 | } 250 | 251 | private PojoMethodMapping getPojoMethodMapping(String path, Channel channel) { 252 | PojoMethodMapping methodMapping; 253 | if (pathMethodMappingMap.size() == 1) { 254 | methodMapping = pathMethodMappingMap.values().iterator().next(); 255 | } else { 256 | Attribute attrPath = channel.attr(PATH_KEY); 257 | attrPath.set(path); 258 | methodMapping = pathMethodMappingMap.get(path); 259 | if (methodMapping == null) { 260 | throw new RuntimeException("path " + path + " is not in pathMethodMappingMap "); 261 | } 262 | } 263 | return methodMapping; 264 | } 265 | } 266 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/pojo/PojoMethodMapping.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.pojo; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.BeforeHandshake; 4 | import com.chachae.webrtc.netty.socket.annotation.OnBinary; 5 | import com.chachae.webrtc.netty.socket.annotation.OnClose; 6 | import com.chachae.webrtc.netty.socket.annotation.OnError; 7 | import com.chachae.webrtc.netty.socket.annotation.OnEvent; 8 | import com.chachae.webrtc.netty.socket.annotation.OnMessage; 9 | import com.chachae.webrtc.netty.socket.annotation.OnOpen; 10 | import com.chachae.webrtc.netty.socket.exception.DeploymentException; 11 | import com.chachae.webrtc.netty.socket.support.ByteMethodArgumentResolver; 12 | import com.chachae.webrtc.netty.socket.support.EventMethodArgumentResolver; 13 | import com.chachae.webrtc.netty.socket.support.HttpHeadersMethodArgumentResolver; 14 | import com.chachae.webrtc.netty.socket.support.MethodArgumentResolver; 15 | import com.chachae.webrtc.netty.socket.support.PathVariableMapMethodArgumentResolver; 16 | import com.chachae.webrtc.netty.socket.support.PathVariableMethodArgumentResolver; 17 | import com.chachae.webrtc.netty.socket.support.RequestParamMapMethodArgumentResolver; 18 | import com.chachae.webrtc.netty.socket.support.RequestParamMethodArgumentResolver; 19 | import com.chachae.webrtc.netty.socket.support.SessionMethodArgumentResolver; 20 | import com.chachae.webrtc.netty.socket.support.TextMethodArgumentResolver; 21 | import com.chachae.webrtc.netty.socket.support.ThrowableMethodArgumentResolver; 22 | import io.netty.channel.Channel; 23 | import io.netty.handler.codec.http.FullHttpRequest; 24 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 25 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 26 | import java.lang.annotation.Annotation; 27 | import java.lang.reflect.InvocationTargetException; 28 | import java.lang.reflect.Method; 29 | import java.lang.reflect.Modifier; 30 | import java.util.ArrayList; 31 | import java.util.Arrays; 32 | import java.util.List; 33 | import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor; 34 | import org.springframework.beans.factory.support.AbstractBeanFactory; 35 | import org.springframework.context.ApplicationContext; 36 | import org.springframework.core.DefaultParameterNameDiscoverer; 37 | import org.springframework.core.MethodParameter; 38 | import org.springframework.core.ParameterNameDiscoverer; 39 | 40 | public class PojoMethodMapping { 41 | 42 | private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); 43 | 44 | private final Method beforeHandshake; 45 | private final Method onOpen; 46 | private final Method onClose; 47 | private final Method onError; 48 | private final Method onMessage; 49 | private final Method onBinary; 50 | private final Method onEvent; 51 | private final MethodParameter[] beforeHandshakeParameters; 52 | private final MethodParameter[] onOpenParameters; 53 | private final MethodParameter[] onCloseParameters; 54 | private final MethodParameter[] onErrorParameters; 55 | private final MethodParameter[] onMessageParameters; 56 | private final MethodParameter[] onBinaryParameters; 57 | private final MethodParameter[] onEventParameters; 58 | private final MethodArgumentResolver[] beforeHandshakeArgResolvers; 59 | private final MethodArgumentResolver[] onOpenArgResolvers; 60 | private final MethodArgumentResolver[] onCloseArgResolvers; 61 | private final MethodArgumentResolver[] onErrorArgResolvers; 62 | private final MethodArgumentResolver[] onMessageArgResolvers; 63 | private final MethodArgumentResolver[] onBinaryArgResolvers; 64 | private final MethodArgumentResolver[] onEventArgResolvers; 65 | private final Class pojoClazz; 66 | private final ApplicationContext applicationContext; 67 | private final AbstractBeanFactory beanFactory; 68 | 69 | public PojoMethodMapping(Class pojoClazz, ApplicationContext context, AbstractBeanFactory beanFactory) throws DeploymentException { 70 | this.applicationContext = context; 71 | this.pojoClazz = pojoClazz; 72 | this.beanFactory = beanFactory; 73 | Method handshake = null; 74 | Method open = null; 75 | Method close = null; 76 | Method error = null; 77 | Method message = null; 78 | Method binary = null; 79 | Method event = null; 80 | Method[] pojoClazzMethods = null; 81 | Class currentClazz = pojoClazz; 82 | while (!currentClazz.equals(Object.class)) { 83 | Method[] currentClazzMethods = currentClazz.getDeclaredMethods(); 84 | if (currentClazz == pojoClazz) { 85 | pojoClazzMethods = currentClazzMethods; 86 | } 87 | for (Method method : currentClazzMethods) { 88 | if (method.getAnnotation(BeforeHandshake.class) != null) { 89 | checkPublic(method); 90 | if (handshake == null) { 91 | handshake = method; 92 | } else { 93 | if (currentClazz == pojoClazz || 94 | !isMethodOverride(handshake, method)) { 95 | // Duplicate annotation 96 | throw new DeploymentException("pojoMethodMapping.duplicateAnnotation BeforeHandshake"); 97 | } 98 | } 99 | } else if (method.getAnnotation(OnOpen.class) != null) { 100 | checkPublic(method); 101 | if (open == null) { 102 | open = method; 103 | } else { 104 | if (currentClazz == pojoClazz || 105 | !isMethodOverride(open, method)) { 106 | // Duplicate annotation 107 | throw new DeploymentException( 108 | "pojoMethodMapping.duplicateAnnotation OnOpen"); 109 | } 110 | } 111 | } else if (method.getAnnotation(OnClose.class) != null) { 112 | checkPublic(method); 113 | if (close == null) { 114 | close = method; 115 | } else { 116 | if (currentClazz == pojoClazz || 117 | !isMethodOverride(close, method)) { 118 | // Duplicate annotation 119 | throw new DeploymentException( 120 | "pojoMethodMapping.duplicateAnnotation OnClose"); 121 | } 122 | } 123 | } else if (method.getAnnotation(OnError.class) != null) { 124 | checkPublic(method); 125 | if (error == null) { 126 | error = method; 127 | } else { 128 | if (currentClazz == pojoClazz || 129 | !isMethodOverride(error, method)) { 130 | // Duplicate annotation 131 | throw new DeploymentException( 132 | "pojoMethodMapping.duplicateAnnotation OnError"); 133 | } 134 | } 135 | } else if (method.getAnnotation(OnMessage.class) != null) { 136 | checkPublic(method); 137 | if (message == null) { 138 | message = method; 139 | } else { 140 | if (currentClazz == pojoClazz || 141 | !isMethodOverride(message, method)) { 142 | // Duplicate annotation 143 | throw new DeploymentException( 144 | "pojoMethodMapping.duplicateAnnotation onMessage"); 145 | } 146 | } 147 | } else if (method.getAnnotation(OnBinary.class) != null) { 148 | checkPublic(method); 149 | if (binary == null) { 150 | binary = method; 151 | } else { 152 | if (currentClazz == pojoClazz || 153 | !isMethodOverride(binary, method)) { 154 | // Duplicate annotation 155 | throw new DeploymentException( 156 | "pojoMethodMapping.duplicateAnnotation OnBinary"); 157 | } 158 | } 159 | } else if (method.getAnnotation(OnEvent.class) != null) { 160 | checkPublic(method); 161 | if (event == null) { 162 | event = method; 163 | } else { 164 | if (currentClazz == pojoClazz || 165 | !isMethodOverride(event, method)) { 166 | // Duplicate annotation 167 | throw new DeploymentException( 168 | "pojoMethodMapping.duplicateAnnotation OnEvent"); 169 | } 170 | } 171 | } else { 172 | // Method not annotated 173 | } 174 | } 175 | currentClazz = currentClazz.getSuperclass(); 176 | } 177 | // If the methods are not on pojoClazz and they are overridden 178 | // by a non annotated method in pojoClazz, they should be ignored 179 | if (handshake != null && handshake.getDeclaringClass() != pojoClazz) { 180 | if (isOverridenWithoutAnnotation(pojoClazzMethods, handshake, BeforeHandshake.class)) { 181 | handshake = null; 182 | } 183 | } 184 | if (open != null && open.getDeclaringClass() != pojoClazz) { 185 | if (isOverridenWithoutAnnotation(pojoClazzMethods, open, OnOpen.class)) { 186 | open = null; 187 | } 188 | } 189 | if (close != null && close.getDeclaringClass() != pojoClazz) { 190 | if (isOverridenWithoutAnnotation(pojoClazzMethods, close, OnClose.class)) { 191 | close = null; 192 | } 193 | } 194 | if (error != null && error.getDeclaringClass() != pojoClazz) { 195 | if (isOverridenWithoutAnnotation(pojoClazzMethods, error, OnError.class)) { 196 | error = null; 197 | } 198 | } 199 | if (message != null && message.getDeclaringClass() != pojoClazz) { 200 | if (isOverridenWithoutAnnotation(pojoClazzMethods, message, OnMessage.class)) { 201 | message = null; 202 | } 203 | } 204 | if (binary != null && binary.getDeclaringClass() != pojoClazz) { 205 | if (isOverridenWithoutAnnotation(pojoClazzMethods, binary, OnBinary.class)) { 206 | binary = null; 207 | } 208 | } 209 | if (event != null && event.getDeclaringClass() != pojoClazz) { 210 | if (isOverridenWithoutAnnotation(pojoClazzMethods, event, OnEvent.class)) { 211 | event = null; 212 | } 213 | } 214 | 215 | this.beforeHandshake = handshake; 216 | this.onOpen = open; 217 | this.onClose = close; 218 | this.onError = error; 219 | this.onMessage = message; 220 | this.onBinary = binary; 221 | this.onEvent = event; 222 | beforeHandshakeParameters = getParameters(beforeHandshake); 223 | onOpenParameters = getParameters(onOpen); 224 | onCloseParameters = getParameters(onClose); 225 | onMessageParameters = getParameters(onMessage); 226 | onErrorParameters = getParameters(onError); 227 | onBinaryParameters = getParameters(onBinary); 228 | onEventParameters = getParameters(onEvent); 229 | beforeHandshakeArgResolvers = getResolvers(beforeHandshakeParameters); 230 | onOpenArgResolvers = getResolvers(onOpenParameters); 231 | onCloseArgResolvers = getResolvers(onCloseParameters); 232 | onMessageArgResolvers = getResolvers(onMessageParameters); 233 | onErrorArgResolvers = getResolvers(onErrorParameters); 234 | onBinaryArgResolvers = getResolvers(onBinaryParameters); 235 | onEventArgResolvers = getResolvers(onEventParameters); 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, Class annotation) { 252 | for (Method method : methods) { 253 | if (isMethodOverride(method, superclazzMethod) 254 | && (method.getAnnotation(annotation) == null)) { 255 | return true; 256 | } 257 | } 258 | return false; 259 | } 260 | 261 | Object getEndpointInstance() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { 262 | Object implement = pojoClazz.getDeclaredConstructor().newInstance(); 263 | AutowiredAnnotationBeanPostProcessor postProcessor = applicationContext.getBean(AutowiredAnnotationBeanPostProcessor.class); 264 | // postProcessor.postProcessPropertyValues(null, null, implement, null); 265 | postProcessor.postProcessProperties(null,implement,null); 266 | return implement; 267 | } 268 | 269 | Method getBeforeHandshake() { 270 | return beforeHandshake; 271 | } 272 | 273 | Object[] getBeforeHandshakeArgs(Channel channel, FullHttpRequest req) throws Exception { 274 | return getMethodArgumentValues(channel, req, beforeHandshakeParameters, beforeHandshakeArgResolvers); 275 | } 276 | 277 | Method getOnOpen() { 278 | return onOpen; 279 | } 280 | 281 | Object[] getOnOpenArgs(Channel channel, FullHttpRequest req) throws Exception { 282 | return getMethodArgumentValues(channel, req, onOpenParameters, onOpenArgResolvers); 283 | } 284 | 285 | MethodArgumentResolver[] getOnOpenArgResolvers() { 286 | return onOpenArgResolvers; 287 | } 288 | 289 | Method getOnClose() { 290 | return onClose; 291 | } 292 | 293 | Object[] getOnCloseArgs(Channel channel) throws Exception { 294 | return getMethodArgumentValues(channel, null, onCloseParameters, onCloseArgResolvers); 295 | } 296 | 297 | Method getOnError() { 298 | return onError; 299 | } 300 | 301 | Object[] getOnErrorArgs(Channel channel, Throwable throwable) throws Exception { 302 | return getMethodArgumentValues(channel, throwable, onErrorParameters, onErrorArgResolvers); 303 | } 304 | 305 | Method getOnMessage() { 306 | return onMessage; 307 | } 308 | 309 | Object[] getOnMessageArgs(Channel channel, TextWebSocketFrame textWebSocketFrame) throws Exception { 310 | return getMethodArgumentValues(channel, textWebSocketFrame, onMessageParameters, onMessageArgResolvers); 311 | } 312 | 313 | Method getOnBinary() { 314 | return onBinary; 315 | } 316 | 317 | Object[] getOnBinaryArgs(Channel channel, BinaryWebSocketFrame binaryWebSocketFrame) throws Exception { 318 | return getMethodArgumentValues(channel, binaryWebSocketFrame, onBinaryParameters, onBinaryArgResolvers); 319 | } 320 | 321 | Method getOnEvent() { 322 | return onEvent; 323 | } 324 | 325 | Object[] getOnEventArgs(Channel channel, Object evt) throws Exception { 326 | return getMethodArgumentValues(channel, evt, onEventParameters, onEventArgResolvers); 327 | } 328 | 329 | private Object[] getMethodArgumentValues(Channel channel, Object object, MethodParameter[] parameters, MethodArgumentResolver[] resolvers) throws Exception { 330 | Object[] objects = new Object[parameters.length]; 331 | for (int i = 0; i < parameters.length; i++) { 332 | MethodParameter parameter = parameters[i]; 333 | MethodArgumentResolver resolver = resolvers[i]; 334 | Object arg = resolver.resolveArgument(parameter, channel, object); 335 | objects[i] = arg; 336 | } 337 | return objects; 338 | } 339 | 340 | private MethodArgumentResolver[] getResolvers(MethodParameter[] parameters) throws DeploymentException { 341 | MethodArgumentResolver[] methodArgumentResolvers = new MethodArgumentResolver[parameters.length]; 342 | List resolvers = getDefaultResolvers(); 343 | for (int i = 0; i < parameters.length; i++) { 344 | MethodParameter parameter = parameters[i]; 345 | for (MethodArgumentResolver resolver : resolvers) { 346 | if (resolver.supportsParameter(parameter)) { 347 | methodArgumentResolvers[i] = resolver; 348 | break; 349 | } 350 | } 351 | if (methodArgumentResolvers[i] == null) { 352 | throw new DeploymentException("pojoMethodMapping.paramClassIncorrect parameter name : " + parameter.getParameterName()); 353 | } 354 | } 355 | return methodArgumentResolvers; 356 | } 357 | 358 | private List getDefaultResolvers() { 359 | List resolvers = new ArrayList<>(); 360 | resolvers.add(new SessionMethodArgumentResolver()); 361 | resolvers.add(new HttpHeadersMethodArgumentResolver()); 362 | resolvers.add(new TextMethodArgumentResolver()); 363 | resolvers.add(new ThrowableMethodArgumentResolver()); 364 | resolvers.add(new ByteMethodArgumentResolver()); 365 | resolvers.add(new RequestParamMapMethodArgumentResolver()); 366 | resolvers.add(new RequestParamMethodArgumentResolver(beanFactory)); 367 | resolvers.add(new PathVariableMapMethodArgumentResolver()); 368 | resolvers.add(new PathVariableMethodArgumentResolver(beanFactory)); 369 | resolvers.add(new EventMethodArgumentResolver(beanFactory)); 370 | return resolvers; 371 | } 372 | 373 | private static MethodParameter[] getParameters(Method m) { 374 | if (m == null) { 375 | return new MethodParameter[0]; 376 | } 377 | int count = m.getParameterCount(); 378 | MethodParameter[] result = new MethodParameter[count]; 379 | for (int i = 0; i < count; i++) { 380 | MethodParameter methodParameter = new MethodParameter(m, i); 381 | methodParameter.initParameterNameDiscovery(parameterNameDiscoverer); 382 | result[i] = methodParameter; 383 | } 384 | return result; 385 | } 386 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/pojo/Session.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.pojo; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.BeforeHandshake; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.ByteBufAllocator; 6 | import io.netty.channel.*; 7 | import io.netty.channel.socket.DatagramChannel; 8 | import io.netty.channel.socket.DatagramPacket; 9 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 10 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 11 | import io.netty.util.AttributeKey; 12 | 13 | import java.net.InetSocketAddress; 14 | import java.net.SocketAddress; 15 | import java.nio.ByteBuffer; 16 | 17 | /** 18 | * @author Yeauty 19 | * @version 1.0 20 | */ 21 | public class Session { 22 | 23 | private final Channel channel; 24 | 25 | Session(Channel channel) { 26 | this.channel = channel; 27 | } 28 | 29 | /** 30 | * set subprotocols on {@link BeforeHandshake} 31 | * 32 | * @param subprotocols 33 | */ 34 | public void setSubprotocols(String subprotocols) { 35 | setAttribute("subprotocols", subprotocols); 36 | } 37 | 38 | public ChannelFuture sendText(String message) { 39 | return channel.writeAndFlush(new TextWebSocketFrame(message)); 40 | } 41 | 42 | public ChannelFuture sendText(ByteBuf byteBuf) { 43 | return channel.writeAndFlush(new TextWebSocketFrame(byteBuf)); 44 | } 45 | 46 | public ChannelFuture sendText(ByteBuffer byteBuffer) { 47 | ByteBuf buffer = channel.alloc().buffer(byteBuffer.remaining()); 48 | buffer.writeBytes(byteBuffer); 49 | return channel.writeAndFlush(new TextWebSocketFrame(buffer)); 50 | } 51 | 52 | public ChannelFuture sendText(TextWebSocketFrame textWebSocketFrame) { 53 | return channel.writeAndFlush(textWebSocketFrame); 54 | } 55 | 56 | public ChannelFuture sendBinary(byte[] bytes) { 57 | ByteBuf buffer = channel.alloc().buffer(bytes.length); 58 | return channel.writeAndFlush(new BinaryWebSocketFrame(buffer.writeBytes(bytes))); 59 | } 60 | 61 | public ChannelFuture sendBinary(ByteBuf byteBuf) { 62 | return channel.writeAndFlush(new BinaryWebSocketFrame(byteBuf)); 63 | } 64 | 65 | public ChannelFuture sendBinary(ByteBuffer byteBuffer) { 66 | ByteBuf buffer = channel.alloc().buffer(byteBuffer.remaining()); 67 | buffer.writeBytes(byteBuffer); 68 | return channel.writeAndFlush(new BinaryWebSocketFrame(buffer)); 69 | } 70 | 71 | public ChannelFuture sendBinary(BinaryWebSocketFrame binaryWebSocketFrame) { 72 | return channel.writeAndFlush(binaryWebSocketFrame); 73 | } 74 | 75 | public void setAttribute(String name, T value) { 76 | AttributeKey sessionIdKey = AttributeKey.valueOf(name); 77 | channel.attr(sessionIdKey).set(value); 78 | } 79 | 80 | public T getAttribute(String name) { 81 | AttributeKey sessionIdKey = AttributeKey.valueOf(name); 82 | return channel.attr(sessionIdKey).get(); 83 | } 84 | 85 | public Channel channel() { 86 | return channel; 87 | } 88 | 89 | /** 90 | * Returns the globally unique identifier of this {@link Channel}. 91 | */ 92 | public ChannelId id() { 93 | return channel.id(); 94 | } 95 | 96 | /** 97 | * Returns the configuration of this channel. 98 | */ 99 | public ChannelConfig config() { 100 | return channel.config(); 101 | } 102 | 103 | /** 104 | * Returns {@code true} if the {@link Channel} is open and may get active later 105 | */ 106 | public boolean isOpen() { 107 | return channel.isOpen(); 108 | } 109 | 110 | /** 111 | * Returns {@code true} if the {@link Channel} is registered with an {@link EventLoop}. 112 | */ 113 | public boolean isRegistered() { 114 | return channel.isRegistered(); 115 | } 116 | 117 | /** 118 | * Return {@code true} if the {@link Channel} is active and so connected. 119 | */ 120 | public boolean isActive() { 121 | return channel.isActive(); 122 | } 123 | 124 | /** 125 | * Return the {@link ChannelMetadata} of the {@link Channel} which describe the nature of the {@link Channel}. 126 | */ 127 | public ChannelMetadata metadata() { 128 | return channel.metadata(); 129 | } 130 | 131 | /** 132 | * Returns the local address where this channel is bound to. The returned {@link SocketAddress} is supposed to be down-cast into more concrete type such as {@link InetSocketAddress} to retrieve the 133 | * detailed information. 134 | * 135 | * @return the local address of this channel. {@code null} if this channel is not bound. 136 | */ 137 | public SocketAddress localAddress() { 138 | return channel.localAddress(); 139 | } 140 | 141 | /** 142 | * Returns the remote address where this channel is connected to. The returned {@link SocketAddress} is supposed to be down-cast into more concrete type such as {@link InetSocketAddress} to 143 | * retrieve the detailed information. 144 | * 145 | * @return the remote address of this channel. {@code null} if this channel is not connected. If this channel is not connected but it can receive messages from arbitrary remote addresses (e.g. 146 | * {@link DatagramChannel}, use {@link DatagramPacket#recipient()} to determine the origination of the received message as this method will return {@code null}. 147 | */ 148 | public SocketAddress remoteAddress() { 149 | return channel.remoteAddress(); 150 | } 151 | 152 | /** 153 | * Returns the {@link ChannelFuture} which will be notified when this channel is closed. This method always returns the same future instance. 154 | */ 155 | public ChannelFuture closeFuture() { 156 | return channel.closeFuture(); 157 | } 158 | 159 | /** 160 | * Returns {@code true} if and only if the I/O thread will perform the requested write operation immediately. Any write requests made when this method returns {@code false} are queued until the I/O 161 | * thread is ready to process the queued write requests. 162 | */ 163 | public boolean isWritable() { 164 | return channel.isWritable(); 165 | } 166 | 167 | /** 168 | * Get how many bytes can be written until {@link #isWritable()} returns {@code false}. This quantity will always be non-negative. If {@link #isWritable()} is {@code false} then 0. 169 | */ 170 | public long bytesBeforeUnwritable() { 171 | return channel.bytesBeforeUnwritable(); 172 | } 173 | 174 | /** 175 | * Get how many bytes must be drained from underlying buffers until {@link #isWritable()} returns {@code true}. This quantity will always be non-negative. If {@link #isWritable()} is {@code true} 176 | * then 0. 177 | */ 178 | public long bytesBeforeWritable() { 179 | return channel.bytesBeforeWritable(); 180 | } 181 | 182 | /** 183 | * Returns an internal-use-only object that provides unsafe operations. 184 | */ 185 | public Channel.Unsafe unsafe() { 186 | return channel.unsafe(); 187 | } 188 | 189 | /** 190 | * Return the assigned {@link ChannelPipeline}. 191 | */ 192 | public ChannelPipeline pipeline() { 193 | return channel.pipeline(); 194 | } 195 | 196 | /** 197 | * Return the assigned {@link ByteBufAllocator} which will be used to allocate {@link ByteBuf}s. 198 | */ 199 | public ByteBufAllocator alloc() { 200 | return channel.alloc(); 201 | } 202 | 203 | public Channel read() { 204 | return channel.read(); 205 | } 206 | 207 | public Channel flush() { 208 | return channel.flush(); 209 | } 210 | 211 | public ChannelFuture close() { 212 | return channel.close(); 213 | } 214 | 215 | public ChannelFuture close(ChannelPromise promise) { 216 | return channel.close(promise); 217 | } 218 | 219 | } 220 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/standard/HttpServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.standard; 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.NOT_FOUND; 11 | import static io.netty.handler.codec.http.HttpResponseStatus.OK; 12 | import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1; 13 | 14 | import com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer; 15 | import com.chachae.webrtc.netty.socket.support.WsPathMatcher; 16 | import io.netty.buffer.ByteBuf; 17 | import io.netty.buffer.ByteBufAllocator; 18 | import io.netty.buffer.Unpooled; 19 | import io.netty.channel.Channel; 20 | import io.netty.channel.ChannelFuture; 21 | import io.netty.channel.ChannelFutureListener; 22 | import io.netty.channel.ChannelHandlerContext; 23 | import io.netty.channel.ChannelPipeline; 24 | import io.netty.channel.SimpleChannelInboundHandler; 25 | import io.netty.handler.codec.http.DefaultFullHttpResponse; 26 | import io.netty.handler.codec.http.FullHttpRequest; 27 | import io.netty.handler.codec.http.FullHttpResponse; 28 | import io.netty.handler.codec.http.HttpHeaderNames; 29 | import io.netty.handler.codec.http.HttpHeaders; 30 | import io.netty.handler.codec.http.HttpUtil; 31 | import io.netty.handler.codec.http.QueryStringDecoder; 32 | import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame; 33 | import io.netty.handler.codec.http.websocketx.WebSocketFrameAggregator; 34 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker; 35 | import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory; 36 | import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; 37 | import io.netty.handler.timeout.IdleStateHandler; 38 | import io.netty.util.AttributeKey; 39 | import io.netty.util.CharsetUtil; 40 | import java.io.InputStream; 41 | import java.util.Set; 42 | import org.springframework.beans.TypeMismatchException; 43 | import org.springframework.util.StringUtils; 44 | 45 | class HttpServerHandler extends SimpleChannelInboundHandler { 46 | 47 | private final PojoEndpointServer pojoEndpointServer; 48 | private final ServerEndpointConfig config; 49 | 50 | private static ByteBuf faviconByteBuf = null; 51 | private static ByteBuf notFoundByteBuf = null; 52 | private static ByteBuf badRequestByteBuf = null; 53 | private static ByteBuf forbiddenByteBuf = null; 54 | private static ByteBuf internalServerErrorByteBuf = null; 55 | 56 | static { 57 | faviconByteBuf = buildStaticRes("/favicon.ico"); 58 | notFoundByteBuf = buildStaticRes("/public/error/404.html"); 59 | badRequestByteBuf = buildStaticRes("/public/error/400.html"); 60 | forbiddenByteBuf = buildStaticRes("/public/error/403.html"); 61 | internalServerErrorByteBuf = buildStaticRes("/public/error/500.html"); 62 | if (notFoundByteBuf == null) { 63 | notFoundByteBuf = buildStaticRes("/public/error/4xx.html"); 64 | } 65 | if (badRequestByteBuf == null) { 66 | badRequestByteBuf = buildStaticRes("/public/error/4xx.html"); 67 | } 68 | if (forbiddenByteBuf == null) { 69 | forbiddenByteBuf = buildStaticRes("/public/error/4xx.html"); 70 | } 71 | if (internalServerErrorByteBuf == null) { 72 | internalServerErrorByteBuf = buildStaticRes("/public/error/5xx.html"); 73 | } 74 | } 75 | 76 | private static ByteBuf buildStaticRes(String resPath) { 77 | try { 78 | InputStream inputStream = HttpServerHandler.class.getResourceAsStream(resPath); 79 | if (inputStream != null) { 80 | int available = inputStream.available(); 81 | if (available != 0) { 82 | byte[] bytes = new byte[available]; 83 | inputStream.read(bytes); 84 | return ByteBufAllocator.DEFAULT.buffer(bytes.length).writeBytes(bytes); 85 | } 86 | } 87 | } catch (Exception e) { 88 | } 89 | return null; 90 | } 91 | 92 | public HttpServerHandler(PojoEndpointServer pojoEndpointServer, ServerEndpointConfig config) { 93 | this.pojoEndpointServer = pojoEndpointServer; 94 | this.config = config; 95 | } 96 | 97 | @Override 98 | protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg) throws Exception { 99 | try { 100 | handleHttpRequest(ctx, msg); 101 | } catch (TypeMismatchException e) { 102 | FullHttpResponse res = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST); 103 | sendHttpResponse(ctx, msg, res); 104 | e.printStackTrace(); 105 | } catch (Exception e) { 106 | FullHttpResponse res; 107 | if (internalServerErrorByteBuf != null) { 108 | res = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR, internalServerErrorByteBuf.retainedDuplicate()); 109 | } else { 110 | res = new DefaultFullHttpResponse(HTTP_1_1, INTERNAL_SERVER_ERROR); 111 | } 112 | sendHttpResponse(ctx, msg, res); 113 | e.printStackTrace(); 114 | } 115 | } 116 | 117 | @Override 118 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 119 | pojoEndpointServer.doOnError(ctx.channel(), cause); 120 | } 121 | 122 | @Override 123 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 124 | pojoEndpointServer.doOnClose(ctx.channel()); 125 | super.channelInactive(ctx); 126 | } 127 | 128 | private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) { 129 | FullHttpResponse res; 130 | // Handle a bad request. 131 | if (!req.decoderResult().isSuccess()) { 132 | if (badRequestByteBuf != null) { 133 | res = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST, badRequestByteBuf.retainedDuplicate()); 134 | } else { 135 | res = new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST); 136 | } 137 | sendHttpResponse(ctx, req, res); 138 | return; 139 | } 140 | 141 | // Allow only GET methods. 142 | if (req.method() != GET) { 143 | if (forbiddenByteBuf != null) { 144 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN, forbiddenByteBuf.retainedDuplicate()); 145 | } else { 146 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 147 | } 148 | sendHttpResponse(ctx, req, res); 149 | return; 150 | } 151 | 152 | HttpHeaders headers = req.headers(); 153 | String host = headers.get(HttpHeaderNames.HOST); 154 | if (StringUtils.isEmpty(host)) { 155 | if (forbiddenByteBuf != null) { 156 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN, forbiddenByteBuf.retainedDuplicate()); 157 | } else { 158 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 159 | } 160 | sendHttpResponse(ctx, req, res); 161 | return; 162 | } 163 | 164 | if (!StringUtils.isEmpty(pojoEndpointServer.getHost()) && !pojoEndpointServer.getHost().equals("0.0.0.0") && !pojoEndpointServer.getHost().equals(host.split(":")[0])) { 165 | if (forbiddenByteBuf != null) { 166 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN, forbiddenByteBuf.retainedDuplicate()); 167 | } else { 168 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 169 | } 170 | sendHttpResponse(ctx, req, res); 171 | return; 172 | } 173 | 174 | QueryStringDecoder decoder = new QueryStringDecoder(req.uri()); 175 | String path = decoder.path(); 176 | if ("/favicon.ico".equals(path)) { 177 | if (faviconByteBuf != null) { 178 | res = new DefaultFullHttpResponse(HTTP_1_1, OK, faviconByteBuf.retainedDuplicate()); 179 | } else { 180 | res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND); 181 | } 182 | sendHttpResponse(ctx, req, res); 183 | return; 184 | } 185 | 186 | Channel channel = ctx.channel(); 187 | 188 | //path match 189 | String pattern = null; 190 | Set pathMatcherSet = pojoEndpointServer.getPathMatcherSet(); 191 | for (WsPathMatcher pathMatcher : pathMatcherSet) { 192 | if (pathMatcher.matchAndExtract(decoder, channel)) { 193 | pattern = pathMatcher.getPattern(); 194 | break; 195 | } 196 | } 197 | 198 | if (pattern == null) { 199 | if (notFoundByteBuf != null) { 200 | res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND, notFoundByteBuf.retainedDuplicate()); 201 | } else { 202 | res = new DefaultFullHttpResponse(HTTP_1_1, NOT_FOUND); 203 | } 204 | sendHttpResponse(ctx, req, res); 205 | return; 206 | } 207 | 208 | if (!req.headers().contains(UPGRADE) || !req.headers().contains(SEC_WEBSOCKET_KEY) || !req.headers().contains(SEC_WEBSOCKET_VERSION)) { 209 | if (forbiddenByteBuf != null) { 210 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN, forbiddenByteBuf.retainedDuplicate()); 211 | } else { 212 | res = new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN); 213 | } 214 | sendHttpResponse(ctx, req, res); 215 | return; 216 | } 217 | 218 | String subprotocols = null; 219 | 220 | if (pojoEndpointServer.hasBeforeHandshake(channel, pattern)) { 221 | pojoEndpointServer.doBeforeHandshake(channel, req, pattern); 222 | if (!channel.isActive()) { 223 | return; 224 | } 225 | 226 | AttributeKey subprotocolsAttrKey = AttributeKey.valueOf("subprotocols"); 227 | if (channel.hasAttr(subprotocolsAttrKey)) { 228 | subprotocols = ctx.channel().attr(subprotocolsAttrKey).get(); 229 | } 230 | } 231 | 232 | // Handshake 233 | WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(getWebSocketLocation(req), subprotocols, true, config.getmaxFramePayloadLength()); 234 | WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); 235 | if (handshaker == null) { 236 | WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(channel); 237 | } else { 238 | ChannelPipeline pipeline = ctx.pipeline(); 239 | pipeline.remove(ctx.name()); 240 | if (config.getReaderIdleTimeSeconds() != 0 || config.getWriterIdleTimeSeconds() != 0 || config.getAllIdleTimeSeconds() != 0) { 241 | pipeline.addLast(new IdleStateHandler(config.getReaderIdleTimeSeconds(), config.getWriterIdleTimeSeconds(), config.getAllIdleTimeSeconds())); 242 | } 243 | if (config.isUseCompressionHandler()) { 244 | pipeline.addLast(new WebSocketServerCompressionHandler()); 245 | } 246 | pipeline.addLast(new WebSocketFrameAggregator(Integer.MAX_VALUE)); 247 | pipeline.addLast(new WebSocketServerHandler(pojoEndpointServer)); 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 | private static void sendHttpResponse( 261 | ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) { 262 | // Generate an error page if response getStatus code is not OK (200). 263 | int statusCode = res.status().code(); 264 | if (statusCode != OK.code() && res.content().readableBytes() == 0) { 265 | ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8); 266 | res.content().writeBytes(buf); 267 | buf.release(); 268 | } 269 | HttpUtil.setContentLength(res, res.content().readableBytes()); 270 | 271 | // Send the response and close the connection if necessary. 272 | ChannelFuture f = ctx.channel().writeAndFlush(res); 273 | if (!HttpUtil.isKeepAlive(req) || statusCode != 200) { 274 | f.addListener(ChannelFutureListener.CLOSE); 275 | } 276 | } 277 | 278 | private static String getWebSocketLocation(FullHttpRequest req) { 279 | String location = req.headers().get(HttpHeaderNames.HOST) + req.uri(); 280 | return "ws://" + location; 281 | } 282 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/standard/ServerEndpointConfig.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.standard; 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 final String HOST; 15 | private final int PORT; 16 | // private final Set PATH_SET; 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 static Integer randomPort; 36 | 37 | public ServerEndpointConfig(String host, int port, String path, int bossLoopGroupThreads, int workerLoopGroupThreads, boolean useCompressionHandler, int connectTimeoutMillis, int soBacklog, 38 | int writeSpinCount, int writeBufferHighWaterMark, int writeBufferLowWaterMark, int soRcvbuf, int soSndbuf, boolean tcpNodelay, boolean soKeepalive, int soLinger, boolean allowHalfClosure, 39 | int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds, int maxFramePayloadLength) { 40 | if (StringUtils.isEmpty(host) || "0.0.0.0".equals(host) || "0.0.0.0/0.0.0.0".equals(host)) { 41 | this.HOST = "0.0.0.0"; 42 | } else { 43 | this.HOST = host; 44 | } 45 | this.PORT = getAvailablePort(port); 46 | // PATH_SET = new HashSet<>(); 47 | // addPath(path); 48 | this.BOSS_LOOP_GROUP_THREADS = bossLoopGroupThreads; 49 | this.WORKER_LOOP_GROUP_THREADS = workerLoopGroupThreads; 50 | this.USE_COMPRESSION_HANDLER = useCompressionHandler; 51 | this.CONNECT_TIMEOUT_MILLIS = connectTimeoutMillis; 52 | this.SO_BACKLOG = soBacklog; 53 | this.WRITE_SPIN_COUNT = writeSpinCount; 54 | this.WRITE_BUFFER_HIGH_WATER_MARK = writeBufferHighWaterMark; 55 | this.WRITE_BUFFER_LOW_WATER_MARK = writeBufferLowWaterMark; 56 | this.SO_RCVBUF = soRcvbuf; 57 | this.SO_SNDBUF = soSndbuf; 58 | this.TCP_NODELAY = tcpNodelay; 59 | this.SO_KEEPALIVE = soKeepalive; 60 | this.SO_LINGER = soLinger; 61 | this.ALLOW_HALF_CLOSURE = allowHalfClosure; 62 | this.READER_IDLE_TIME_SECONDS = readerIdleTimeSeconds; 63 | this.WRITER_IDLE_TIME_SECONDS = writerIdleTimeSeconds; 64 | this.ALL_IDLE_TIME_SECONDS = allIdleTimeSeconds; 65 | this.MAX_FRAME_PAYLOAD_LENGTH = maxFramePayloadLength; 66 | } 67 | 68 | 69 | /*public String addPath(String path) { 70 | if (StringUtils.isEmpty(path)) { 71 | path = "/"; 72 | } 73 | if (PATH_SET.contains(path)) { 74 | throw new RuntimeException("ServerEndpointConfig.addPath path:" + path + " are repeat."); 75 | } 76 | this.PATH_SET.add(path); 77 | return path; 78 | }*/ 79 | 80 | private int getAvailablePort(int port) { 81 | if (port != 0) { 82 | return port; 83 | } 84 | if (randomPort != null && randomPort != 0) { 85 | return randomPort; 86 | } 87 | InetSocketAddress inetSocketAddress = new InetSocketAddress(0); 88 | Socket socket = new Socket(); 89 | try { 90 | socket.bind(inetSocketAddress); 91 | } catch (IOException e) { 92 | e.printStackTrace(); 93 | } 94 | int localPort = socket.getLocalPort(); 95 | try { 96 | socket.close(); 97 | } catch (IOException e) { 98 | e.printStackTrace(); 99 | } 100 | randomPort = localPort; 101 | return localPort; 102 | } 103 | 104 | public String getHost() { 105 | return HOST; 106 | } 107 | 108 | public int getPort() { 109 | return PORT; 110 | } 111 | 112 | /*public Set getPathSet() { 113 | 114 | return PATH_SET; 115 | }*/ 116 | 117 | public int getBossLoopGroupThreads() { 118 | return BOSS_LOOP_GROUP_THREADS; 119 | } 120 | 121 | public int getWorkerLoopGroupThreads() { 122 | return WORKER_LOOP_GROUP_THREADS; 123 | } 124 | 125 | public boolean isUseCompressionHandler() { 126 | return USE_COMPRESSION_HANDLER; 127 | } 128 | 129 | public int getConnectTimeoutMillis() { 130 | return CONNECT_TIMEOUT_MILLIS; 131 | } 132 | 133 | public int getSoBacklog() { 134 | return SO_BACKLOG; 135 | } 136 | 137 | public int getWriteSpinCount() { 138 | return WRITE_SPIN_COUNT; 139 | } 140 | 141 | public int getWriteBufferHighWaterMark() { 142 | return WRITE_BUFFER_HIGH_WATER_MARK; 143 | } 144 | 145 | public int getWriteBufferLowWaterMark() { 146 | return WRITE_BUFFER_LOW_WATER_MARK; 147 | } 148 | 149 | public int getSoRcvbuf() { 150 | return SO_RCVBUF; 151 | } 152 | 153 | public int getSoSndbuf() { 154 | return SO_SNDBUF; 155 | } 156 | 157 | public boolean isTcpNodelay() { 158 | return TCP_NODELAY; 159 | } 160 | 161 | public boolean isSoKeepalive() { 162 | return SO_KEEPALIVE; 163 | } 164 | 165 | public int getSoLinger() { 166 | return SO_LINGER; 167 | } 168 | 169 | public boolean isAllowHalfClosure() { 170 | return ALLOW_HALF_CLOSURE; 171 | } 172 | 173 | public static Integer getRandomPort() { 174 | return randomPort; 175 | } 176 | 177 | public int getReaderIdleTimeSeconds() { 178 | return READER_IDLE_TIME_SECONDS; 179 | } 180 | 181 | public int getWriterIdleTimeSeconds() { 182 | return WRITER_IDLE_TIME_SECONDS; 183 | } 184 | 185 | public int getAllIdleTimeSeconds() { 186 | return ALL_IDLE_TIME_SECONDS; 187 | } 188 | 189 | public int getmaxFramePayloadLength() { 190 | return MAX_FRAME_PAYLOAD_LENGTH; 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/standard/ServerEndpointExporter.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.standard; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.ServerEndpoint; 4 | import com.chachae.webrtc.netty.socket.exception.DeploymentException; 5 | import com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer; 6 | import com.chachae.webrtc.netty.socket.pojo.PojoMethodMapping; 7 | import java.net.InetSocketAddress; 8 | import java.util.HashMap; 9 | import java.util.LinkedHashSet; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import java.util.StringJoiner; 13 | import org.springframework.beans.TypeConverter; 14 | import org.springframework.beans.TypeMismatchException; 15 | import org.springframework.beans.factory.BeanFactory; 16 | import org.springframework.beans.factory.BeanFactoryAware; 17 | import org.springframework.beans.factory.SmartInitializingSingleton; 18 | import org.springframework.beans.factory.annotation.Autowired; 19 | import org.springframework.beans.factory.config.BeanExpressionContext; 20 | import org.springframework.beans.factory.config.BeanExpressionResolver; 21 | import org.springframework.beans.factory.support.AbstractBeanFactory; 22 | import org.springframework.context.ApplicationContext; 23 | import org.springframework.context.support.ApplicationObjectSupport; 24 | import org.springframework.core.annotation.AnnotatedElementUtils; 25 | import org.springframework.core.env.Environment; 26 | 27 | /** 28 | * @author Yeauty 29 | * @version 1.0 30 | */ 31 | public class ServerEndpointExporter extends ApplicationObjectSupport implements SmartInitializingSingleton, BeanFactoryAware { 32 | 33 | @Autowired 34 | Environment environment; 35 | 36 | private AbstractBeanFactory beanFactory; 37 | 38 | private final Map addressWebsocketServerMap = new HashMap<>(); 39 | 40 | @Override 41 | public void afterSingletonsInstantiated() { 42 | registerEndpoints(); 43 | } 44 | 45 | @Override 46 | public void setBeanFactory(BeanFactory beanFactory) { 47 | if (!(beanFactory instanceof AbstractBeanFactory)) { 48 | throw new IllegalArgumentException( 49 | "AutowiredAnnotationBeanPostProcessor requires a AbstractBeanFactory: " + beanFactory); 50 | } 51 | this.beanFactory = (AbstractBeanFactory) beanFactory; 52 | } 53 | 54 | protected void registerEndpoints() { 55 | Set> endpointClasses = new LinkedHashSet<>(); 56 | 57 | ApplicationContext context = getApplicationContext(); 58 | if (context != null) { 59 | String[] endpointBeanNames = context.getBeanNamesForAnnotation(ServerEndpoint.class); 60 | for (String beanName : endpointBeanNames) { 61 | endpointClasses.add(context.getType(beanName)); 62 | } 63 | } 64 | 65 | for (Class endpointClass : endpointClasses) { 66 | registerEndpoint(endpointClass); 67 | } 68 | 69 | init(); 70 | } 71 | 72 | private void init() { 73 | for (Map.Entry entry : addressWebsocketServerMap.entrySet()) { 74 | WebsocketServer websocketServer = entry.getValue(); 75 | try { 76 | websocketServer.init(); 77 | PojoEndpointServer pojoEndpointServer = websocketServer.getPojoEndpointServer(); 78 | StringJoiner stringJoiner = new StringJoiner(","); 79 | pojoEndpointServer.getPathMatcherSet().forEach(pathMatcher -> stringJoiner.add("'" + pathMatcher.getPattern() + "'")); 80 | logger.info(String.format("\033[34mNetty WebSocket started on port: %s with context path(s): %s .\033[0m", pojoEndpointServer.getPort(), stringJoiner.toString())); 81 | } catch (InterruptedException e) { 82 | logger.error(String.format("websocket [%s] init fail", entry.getKey()), e); 83 | } 84 | } 85 | } 86 | 87 | private void registerEndpoint(Class endpointClass) { 88 | ServerEndpoint annotation = AnnotatedElementUtils.findMergedAnnotation(endpointClass, ServerEndpoint.class); 89 | if (annotation == null) { 90 | throw new IllegalStateException("missingAnnotation ServerEndpoint"); 91 | } 92 | ServerEndpointConfig serverEndpointConfig = buildConfig(annotation); 93 | 94 | ApplicationContext context = getApplicationContext(); 95 | PojoMethodMapping pojoMethodMapping; 96 | try { 97 | pojoMethodMapping = new PojoMethodMapping(endpointClass, context, beanFactory); 98 | } catch (DeploymentException e) { 99 | throw new IllegalStateException("Failed to register ServerEndpointConfig: " + serverEndpointConfig, e); 100 | } 101 | 102 | InetSocketAddress inetSocketAddress = new InetSocketAddress(serverEndpointConfig.getHost(), serverEndpointConfig.getPort()); 103 | String path = resolveAnnotationValue(annotation.value(), String.class, "path"); 104 | 105 | WebsocketServer websocketServer = addressWebsocketServerMap.get(inetSocketAddress); 106 | if (websocketServer == null) { 107 | PojoEndpointServer pojoEndpointServer = new PojoEndpointServer(pojoMethodMapping, serverEndpointConfig, path); 108 | websocketServer = new WebsocketServer(pojoEndpointServer, serverEndpointConfig); 109 | addressWebsocketServerMap.put(inetSocketAddress, websocketServer); 110 | } else { 111 | websocketServer.getPojoEndpointServer().addPathPojoMethodMapping(path, pojoMethodMapping); 112 | } 113 | } 114 | 115 | private ServerEndpointConfig buildConfig(ServerEndpoint annotation) { 116 | String host = resolveAnnotationValue(annotation.host(), String.class, "host"); 117 | int port = resolveAnnotationValue(annotation.port(), Integer.class, "port"); 118 | String path = resolveAnnotationValue(annotation.value(), String.class, "value"); 119 | int bossLoopGroupThreads = resolveAnnotationValue(annotation.bossLoopGroupThreads(), Integer.class, "bossLoopGroupThreads"); 120 | int workerLoopGroupThreads = resolveAnnotationValue(annotation.workerLoopGroupThreads(), Integer.class, "workerLoopGroupThreads"); 121 | boolean useCompressionHandler = resolveAnnotationValue(annotation.useCompressionHandler(), Boolean.class, "useCompressionHandler"); 122 | 123 | int optionConnectTimeoutMillis = resolveAnnotationValue(annotation.optionConnectTimeoutMillis(), Integer.class, "optionConnectTimeoutMillis"); 124 | int optionSoBacklog = resolveAnnotationValue(annotation.optionSoBacklog(), Integer.class, "optionSoBacklog"); 125 | 126 | int childOptionWriteSpinCount = resolveAnnotationValue(annotation.childOptionWriteSpinCount(), Integer.class, "childOptionWriteSpinCount"); 127 | int childOptionWriteBufferHighWaterMark = resolveAnnotationValue(annotation.childOptionWriteBufferHighWaterMark(), Integer.class, "childOptionWriteBufferHighWaterMark"); 128 | int childOptionWriteBufferLowWaterMark = resolveAnnotationValue(annotation.childOptionWriteBufferLowWaterMark(), Integer.class, "childOptionWriteBufferLowWaterMark"); 129 | int childOptionSoRcvbuf = resolveAnnotationValue(annotation.childOptionSoRcvbuf(), Integer.class, "childOptionSoRcvbuf"); 130 | int childOptionSoSndbuf = resolveAnnotationValue(annotation.childOptionSoSndbuf(), Integer.class, "childOptionSoSndbuf"); 131 | boolean childOptionTcpNodelay = resolveAnnotationValue(annotation.childOptionTcpNodelay(), Boolean.class, "childOptionTcpNodelay"); 132 | boolean childOptionSoKeepalive = resolveAnnotationValue(annotation.childOptionSoKeepalive(), Boolean.class, "childOptionSoKeepalive"); 133 | int childOptionSoLinger = resolveAnnotationValue(annotation.childOptionSoLinger(), Integer.class, "childOptionSoLinger"); 134 | boolean childOptionAllowHalfClosure = resolveAnnotationValue(annotation.childOptionAllowHalfClosure(), Boolean.class, "childOptionAllowHalfClosure"); 135 | 136 | int readerIdleTimeSeconds = resolveAnnotationValue(annotation.readerIdleTimeSeconds(), Integer.class, "readerIdleTimeSeconds"); 137 | int writerIdleTimeSeconds = resolveAnnotationValue(annotation.writerIdleTimeSeconds(), Integer.class, "writerIdleTimeSeconds"); 138 | int allIdleTimeSeconds = resolveAnnotationValue(annotation.allIdleTimeSeconds(), Integer.class, "allIdleTimeSeconds"); 139 | 140 | int maxFramePayloadLength = resolveAnnotationValue(annotation.maxFramePayloadLength(), Integer.class, "maxFramePayloadLength"); 141 | 142 | return new ServerEndpointConfig(host, port, path, bossLoopGroupThreads, workerLoopGroupThreads, useCompressionHandler, optionConnectTimeoutMillis, optionSoBacklog, childOptionWriteSpinCount, 143 | childOptionWriteBufferHighWaterMark, childOptionWriteBufferLowWaterMark, childOptionSoRcvbuf, childOptionSoSndbuf, childOptionTcpNodelay, childOptionSoKeepalive, childOptionSoLinger, 144 | childOptionAllowHalfClosure, readerIdleTimeSeconds, writerIdleTimeSeconds, allIdleTimeSeconds, maxFramePayloadLength); 145 | } 146 | 147 | private T resolveAnnotationValue(Object value, Class requiredType, String paramName) { 148 | if (value == null) { 149 | return null; 150 | } 151 | TypeConverter typeConverter = beanFactory.getTypeConverter(); 152 | 153 | if (value instanceof String) { 154 | String strVal = beanFactory.resolveEmbeddedValue((String) value); 155 | BeanExpressionResolver beanExpressionResolver = beanFactory.getBeanExpressionResolver(); 156 | if (beanExpressionResolver != null) { 157 | value = beanExpressionResolver.evaluate(strVal, new BeanExpressionContext(beanFactory, null)); 158 | } else { 159 | value = strVal; 160 | } 161 | } 162 | try { 163 | return typeConverter.convertIfNecessary(value, requiredType); 164 | } catch (TypeMismatchException e) { 165 | throw new IllegalArgumentException("Failed to convert value of parameter '" + paramName + "' to required type '" + requiredType.getName() + "'"); 166 | } 167 | } 168 | 169 | } 170 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/standard/WebSocketServerHandler.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.standard; 2 | 3 | import io.netty.channel.ChannelFutureListener; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.channel.SimpleChannelInboundHandler; 6 | import io.netty.handler.codec.http.websocketx.*; 7 | import com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer; 8 | 9 | class WebSocketServerHandler extends SimpleChannelInboundHandler { 10 | 11 | private final PojoEndpointServer pojoEndpointServer; 12 | 13 | public WebSocketServerHandler(PojoEndpointServer pojoEndpointServer) { 14 | this.pojoEndpointServer = pojoEndpointServer; 15 | } 16 | 17 | @Override 18 | protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame msg) throws Exception { 19 | handleWebSocketFrame(ctx, msg); 20 | } 21 | 22 | @Override 23 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 24 | pojoEndpointServer.doOnError(ctx.channel(), cause); 25 | } 26 | 27 | @Override 28 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 29 | pojoEndpointServer.doOnClose(ctx.channel()); 30 | } 31 | 32 | @Override 33 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 34 | pojoEndpointServer.doOnEvent(ctx.channel(), evt); 35 | } 36 | 37 | private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { 38 | if (frame instanceof TextWebSocketFrame) { 39 | pojoEndpointServer.doOnMessage(ctx.channel(), frame); 40 | return; 41 | } 42 | if (frame instanceof PingWebSocketFrame) { 43 | ctx.writeAndFlush(new PongWebSocketFrame(frame.content().retain())); 44 | return; 45 | } 46 | if (frame instanceof CloseWebSocketFrame) { 47 | ctx.writeAndFlush(frame.retainedDuplicate()).addListener(ChannelFutureListener.CLOSE); 48 | return; 49 | } 50 | if (frame instanceof BinaryWebSocketFrame) { 51 | pojoEndpointServer.doOnBinary(ctx.channel(), frame); 52 | return; 53 | } 54 | if (frame instanceof PongWebSocketFrame) { 55 | return; 56 | } 57 | } 58 | 59 | } -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/standard/WebsocketServer.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.standard; 2 | 3 | import com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer; 4 | import io.netty.bootstrap.ServerBootstrap; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelInitializer; 7 | import io.netty.channel.ChannelOption; 8 | import io.netty.channel.ChannelPipeline; 9 | import io.netty.channel.EventLoopGroup; 10 | import io.netty.channel.WriteBufferWaterMark; 11 | import io.netty.channel.nio.NioEventLoopGroup; 12 | import io.netty.channel.socket.nio.NioServerSocketChannel; 13 | import io.netty.channel.socket.nio.NioSocketChannel; 14 | import io.netty.handler.codec.http.HttpObjectAggregator; 15 | import io.netty.handler.codec.http.HttpServerCodec; 16 | import io.netty.handler.logging.LogLevel; 17 | import io.netty.handler.logging.LoggingHandler; 18 | import java.net.InetAddress; 19 | import java.net.InetSocketAddress; 20 | import java.net.UnknownHostException; 21 | 22 | /** 23 | * @author Yeauty 24 | * @version 1.0 25 | */ 26 | public class WebsocketServer { 27 | 28 | private final PojoEndpointServer pojoEndpointServer; 29 | 30 | private final ServerEndpointConfig config; 31 | 32 | public WebsocketServer(PojoEndpointServer webSocketServerHandler, ServerEndpointConfig serverEndpointConfig) { 33 | this.pojoEndpointServer = webSocketServerHandler; 34 | this.config = serverEndpointConfig; 35 | } 36 | 37 | public void init() throws InterruptedException { 38 | EventLoopGroup boss = new NioEventLoopGroup(config.getBossLoopGroupThreads()); 39 | EventLoopGroup worker = new NioEventLoopGroup(config.getWorkerLoopGroupThreads()); 40 | ServerBootstrap bootstrap = new ServerBootstrap(); 41 | bootstrap.group(boss, worker) 42 | .channel(NioServerSocketChannel.class) 43 | .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, config.getConnectTimeoutMillis()) 44 | .option(ChannelOption.SO_BACKLOG, config.getSoBacklog()) 45 | .childOption(ChannelOption.WRITE_SPIN_COUNT, config.getWriteSpinCount()) 46 | .childOption(ChannelOption.WRITE_BUFFER_WATER_MARK, new WriteBufferWaterMark(config.getWriteBufferLowWaterMark(), config.getWriteBufferHighWaterMark())) 47 | .childOption(ChannelOption.TCP_NODELAY, config.isTcpNodelay()) 48 | .childOption(ChannelOption.SO_KEEPALIVE, config.isSoKeepalive()) 49 | .childOption(ChannelOption.SO_LINGER, config.getSoLinger()) 50 | .childOption(ChannelOption.ALLOW_HALF_CLOSURE, config.isAllowHalfClosure()) 51 | .handler(new LoggingHandler(LogLevel.DEBUG)) 52 | .childHandler(new ChannelInitializer() { 53 | @Override 54 | protected void initChannel(NioSocketChannel ch) throws Exception { 55 | ChannelPipeline pipeline = ch.pipeline(); 56 | pipeline.addLast(new HttpServerCodec()); 57 | pipeline.addLast(new HttpObjectAggregator(65536)); 58 | pipeline.addLast(new HttpServerHandler(pojoEndpointServer, config)); 59 | } 60 | }); 61 | 62 | if (config.getSoRcvbuf() != -1) { 63 | bootstrap.childOption(ChannelOption.SO_RCVBUF, config.getSoRcvbuf()); 64 | } 65 | 66 | if (config.getSoSndbuf() != -1) { 67 | bootstrap.childOption(ChannelOption.SO_SNDBUF, config.getSoSndbuf()); 68 | } 69 | 70 | ChannelFuture channelFuture; 71 | if ("0.0.0.0".equals(config.getHost())) { 72 | channelFuture = bootstrap.bind(config.getPort()); 73 | } else { 74 | try { 75 | channelFuture = bootstrap.bind(new InetSocketAddress(InetAddress.getByName(config.getHost()), config.getPort())); 76 | } catch (UnknownHostException e) { 77 | channelFuture = bootstrap.bind(config.getHost(), config.getPort()); 78 | e.printStackTrace(); 79 | } 80 | } 81 | 82 | channelFuture.addListener(future -> { 83 | if (!future.isSuccess()) { 84 | future.cause().printStackTrace(); 85 | } 86 | }); 87 | 88 | Runtime.getRuntime().addShutdownHook(new Thread(() -> { 89 | boss.shutdownGracefully().syncUninterruptibly(); 90 | worker.shutdownGracefully().syncUninterruptibly(); 91 | })); 92 | } 93 | 94 | public PojoEndpointServer getPojoEndpointServer() { 95 | return pojoEndpointServer; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/AntPathMatcherWraaper.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import static com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer.URI_TEMPLATE; 4 | 5 | import io.netty.channel.Channel; 6 | import io.netty.handler.codec.http.QueryStringDecoder; 7 | import java.util.LinkedHashMap; 8 | import java.util.Map; 9 | import org.springframework.util.AntPathMatcher; 10 | 11 | public class AntPathMatcherWraaper extends AntPathMatcher implements WsPathMatcher { 12 | 13 | private final String pattern; 14 | 15 | public AntPathMatcherWraaper(String pattern) { 16 | this.pattern = pattern; 17 | } 18 | 19 | @Override 20 | public String getPattern() { 21 | return this.pattern; 22 | } 23 | 24 | @Override 25 | public boolean matchAndExtract(QueryStringDecoder decoder, Channel channel) { 26 | Map variables = new LinkedHashMap<>(); 27 | boolean result = doMatch(pattern, decoder.path(), true, variables); 28 | if (result) { 29 | channel.attr(URI_TEMPLATE).set(variables); 30 | return true; 31 | } 32 | return false; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/ByteMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.OnBinary; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.channel.Channel; 6 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 7 | import org.springframework.core.MethodParameter; 8 | 9 | public class ByteMethodArgumentResolver implements MethodArgumentResolver { 10 | 11 | @Override 12 | public boolean supportsParameter(MethodParameter parameter) { 13 | return parameter.getMethod().isAnnotationPresent(OnBinary.class) && 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 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/DefaultPathMatcher.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | if (!pattern.equals(decoder.path())) { 23 | return false; 24 | } 25 | return true; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/EventMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.OnEvent; 4 | import io.netty.channel.Channel; 5 | import java.util.Objects; 6 | import org.springframework.beans.TypeConverter; 7 | import org.springframework.beans.factory.support.AbstractBeanFactory; 8 | import org.springframework.core.MethodParameter; 9 | 10 | public class EventMethodArgumentResolver implements MethodArgumentResolver { 11 | 12 | private final AbstractBeanFactory beanFactory; 13 | 14 | public EventMethodArgumentResolver(AbstractBeanFactory beanFactory) { 15 | this.beanFactory = beanFactory; 16 | } 17 | 18 | 19 | @Override 20 | public boolean supportsParameter(MethodParameter parameter) { 21 | return Objects.requireNonNull(parameter.getMethod()).isAnnotationPresent(OnEvent.class); 22 | } 23 | 24 | @Override 25 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 26 | if (object == null) { 27 | return null; 28 | } 29 | TypeConverter typeConverter = beanFactory.getTypeConverter(); 30 | return typeConverter.convertIfNecessary(object, parameter.getParameterType()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/HttpHeadersMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | 10 | @Override 11 | public boolean supportsParameter(MethodParameter parameter) { 12 | return HttpHeaders.class.isAssignableFrom(parameter.getParameterType()); 13 | } 14 | 15 | @Override 16 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 17 | return ((FullHttpRequest) object).headers(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/MethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 supported by this resolver. 12 | * 13 | * @param parameter the method parameter to check 14 | * @return {@code true} if this resolver supports the supplied parameter; {@code false} otherwise 15 | */ 16 | boolean supportsParameter(MethodParameter parameter); 17 | 18 | 19 | @Nullable 20 | Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/PathVariableMapMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import static com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer.URI_TEMPLATE; 4 | 5 | import com.chachae.webrtc.netty.socket.annotation.PathVariable; 6 | import io.netty.channel.Channel; 7 | import java.util.Collections; 8 | import java.util.Map; 9 | import org.springframework.core.MethodParameter; 10 | import org.springframework.util.CollectionUtils; 11 | import org.springframework.util.StringUtils; 12 | 13 | public class PathVariableMapMethodArgumentResolver implements MethodArgumentResolver { 14 | 15 | @Override 16 | public boolean supportsParameter(MethodParameter parameter) { 17 | PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); 18 | return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) && 19 | !StringUtils.hasText(ann.value())); 20 | } 21 | 22 | @Override 23 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 24 | PathVariable ann = parameter.getParameterAnnotation(PathVariable.class); 25 | String name = ann.name(); 26 | if (name.isEmpty()) { 27 | name = parameter.getParameterName(); 28 | if (name == null) { 29 | throw new IllegalArgumentException( 30 | "Name for argument type [" + parameter.getNestedParameterType().getName() + 31 | "] not available, and parameter name information not found in class file either."); 32 | } 33 | } 34 | Map uriTemplateVars = channel.attr(URI_TEMPLATE).get(); 35 | if (!CollectionUtils.isEmpty(uriTemplateVars)) { 36 | return uriTemplateVars; 37 | } else { 38 | return Collections.emptyMap(); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/PathVariableMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import static com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer.URI_TEMPLATE; 4 | 5 | import com.chachae.webrtc.netty.socket.annotation.PathVariable; 6 | import io.netty.channel.Channel; 7 | import java.util.Map; 8 | import org.springframework.beans.TypeConverter; 9 | import org.springframework.beans.factory.support.AbstractBeanFactory; 10 | import org.springframework.core.MethodParameter; 11 | 12 | public class PathVariableMethodArgumentResolver implements MethodArgumentResolver { 13 | 14 | private AbstractBeanFactory beanFactory; 15 | 16 | public PathVariableMethodArgumentResolver(AbstractBeanFactory beanFactory) { 17 | this.beanFactory = beanFactory; 18 | } 19 | 20 | @Override 21 | public boolean supportsParameter(MethodParameter parameter) { 22 | return parameter.hasParameterAnnotation(PathVariable.class); 23 | } 24 | 25 | @Override 26 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 27 | PathVariable ann = parameter.getParameterAnnotation(PathVariable.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 | Map uriTemplateVars = channel.attr(URI_TEMPLATE).get(); 38 | Object arg = (uriTemplateVars != null ? uriTemplateVars.get(name) : null); 39 | TypeConverter typeConverter = beanFactory.getTypeConverter(); 40 | return typeConverter.convertIfNecessary(arg, parameter.getParameterType()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/RequestParamMapMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import static com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer.REQUEST_PARAM; 4 | 5 | import com.chachae.webrtc.netty.socket.annotation.RequestParam; 6 | import io.netty.channel.Channel; 7 | import io.netty.handler.codec.http.FullHttpRequest; 8 | import io.netty.handler.codec.http.QueryStringDecoder; 9 | import java.util.List; 10 | import java.util.Map; 11 | import org.springframework.core.MethodParameter; 12 | import org.springframework.util.LinkedMultiValueMap; 13 | import org.springframework.util.MultiValueMap; 14 | import org.springframework.util.StringUtils; 15 | 16 | public class RequestParamMapMethodArgumentResolver implements MethodArgumentResolver { 17 | 18 | @Override 19 | public boolean supportsParameter(MethodParameter parameter) { 20 | RequestParam requestParam = parameter.getParameterAnnotation(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 | 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(REQUEST_PARAM)) { 39 | QueryStringDecoder decoder = new QueryStringDecoder(((FullHttpRequest) object).uri()); 40 | channel.attr(REQUEST_PARAM).set(decoder.parameters()); 41 | } 42 | 43 | Map> requestParams = channel.attr(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 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/RequestParamMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import static com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer.REQUEST_PARAM; 4 | 5 | import com.chachae.webrtc.netty.socket.annotation.RequestParam; 6 | import io.netty.channel.Channel; 7 | import io.netty.handler.codec.http.FullHttpRequest; 8 | import io.netty.handler.codec.http.QueryStringDecoder; 9 | import java.util.List; 10 | import java.util.Map; 11 | import org.springframework.beans.TypeConverter; 12 | import org.springframework.beans.factory.support.AbstractBeanFactory; 13 | import org.springframework.core.MethodParameter; 14 | 15 | public class RequestParamMethodArgumentResolver implements MethodArgumentResolver { 16 | 17 | private AbstractBeanFactory beanFactory; 18 | 19 | public RequestParamMethodArgumentResolver(AbstractBeanFactory beanFactory) { 20 | this.beanFactory = beanFactory; 21 | } 22 | 23 | @Override 24 | public boolean supportsParameter(MethodParameter parameter) { 25 | return parameter.hasParameterAnnotation(RequestParam.class); 26 | } 27 | 28 | @Override 29 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 30 | RequestParam ann = parameter.getParameterAnnotation(RequestParam.class); 31 | String name = ann.name(); 32 | if (name.isEmpty()) { 33 | name = parameter.getParameterName(); 34 | if (name == null) { 35 | throw new IllegalArgumentException( 36 | "Name for argument type [" + parameter.getNestedParameterType().getName() + 37 | "] not available, and parameter name information not found in class file either."); 38 | } 39 | } 40 | 41 | if (!channel.hasAttr(REQUEST_PARAM)) { 42 | QueryStringDecoder decoder = new QueryStringDecoder(((FullHttpRequest) object).uri()); 43 | channel.attr(REQUEST_PARAM).set(decoder.parameters()); 44 | } 45 | 46 | Map> requestParams = channel.attr(REQUEST_PARAM).get(); 47 | List arg = (requestParams != null ? requestParams.get(name) : null); 48 | TypeConverter typeConverter = beanFactory.getTypeConverter(); 49 | if (arg == null) { 50 | if ("\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n".equals(ann.defaultValue())) { 51 | return null; 52 | } else { 53 | return typeConverter.convertIfNecessary(ann.defaultValue(), parameter.getParameterType()); 54 | } 55 | } 56 | if (List.class.isAssignableFrom(parameter.getParameterType())) { 57 | return typeConverter.convertIfNecessary(arg, parameter.getParameterType()); 58 | } else { 59 | return typeConverter.convertIfNecessary(arg.get(0), parameter.getParameterType()); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/SessionMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import io.netty.channel.Channel; 4 | import org.springframework.core.MethodParameter; 5 | import com.chachae.webrtc.netty.socket.pojo.Session; 6 | 7 | import static com.chachae.webrtc.netty.socket.pojo.PojoEndpointServer.SESSION_KEY; 8 | 9 | public class SessionMethodArgumentResolver implements MethodArgumentResolver { 10 | 11 | @Override 12 | public boolean supportsParameter(MethodParameter parameter) { 13 | return Session.class.isAssignableFrom(parameter.getParameterType()); 14 | } 15 | 16 | @Override 17 | public Object resolveArgument(MethodParameter parameter, Channel channel, Object object) throws Exception { 18 | return channel.attr(SESSION_KEY).get(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/TextMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.OnMessage; 4 | import io.netty.channel.Channel; 5 | import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; 6 | import org.springframework.core.MethodParameter; 7 | 8 | public class TextMethodArgumentResolver implements MethodArgumentResolver { 9 | 10 | @Override 11 | public boolean supportsParameter(MethodParameter parameter) { 12 | return parameter.getMethod().isAnnotationPresent(OnMessage.class) && 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 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/ThrowableMethodArgumentResolver.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.support; 2 | 3 | import com.chachae.webrtc.netty.socket.annotation.OnError; 4 | import io.netty.channel.Channel; 5 | import org.springframework.core.MethodParameter; 6 | 7 | public class ThrowableMethodArgumentResolver implements MethodArgumentResolver { 8 | 9 | @Override 10 | public boolean supportsParameter(MethodParameter parameter) { 11 | return parameter.getMethod().isAnnotationPresent(OnError.class) && 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 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/java/com/chachae/webrtc/netty/socket/support/WsPathMatcher.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.socket.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 | -------------------------------------------------------------------------------- /Netty-WebSocket-Starter/src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ 2 | com.chachae.webrtc.netty.socket.autoconfigure.NettyWebSocketAutoConfigure -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## WebRTC-One-Way-Monitor 2 | 3 | ![https://img.shields.io/badge/license-Apache%202.0-blue.svg?longCache=true&style=flat-square](https://img.shields.io/badge/license-Apache%202.0-blue.svg?longCache=true&style=flat-square) 4 | ![https://tokei.rs/b1/github/chachae/WebRTC-One-Way-Monitor?category=lines](https://tokei.rs/b1/github/chachae/WebRTC-One-Way-Monitor?category=lines) 5 | 6 | 本案例已完成基于 WebSocket & Netty 和 WebRTC 实现发送一对一文本消息、群组消息、单向视频监控与屏幕共享案例,使用 SpringBoot作为应用基本框架,前端使用 Vue.js 实现。可用于局域网和公网(已配置turn服务器的情况下)完成一系列基于 WebRTC 实现的流媒体传输功能,本案例以在线考试反作弊系统为基点,构建目的意旨实现摄像头和屏幕共享的多对一单向监控功能,有此类需求的朋友可以通过本案例获得相关思路和灵感,而像即时通讯服务、面对面视频聊天等实现 GitHub 上的高星开源项目繁多,大家可移步相关项目学习。 7 | 8 | ### 相关介绍 9 | 10 | WebRTC是一个支持网页浏览器进行实时语音对话或视频对话的API。Google 在 2011年6月1日收购了GIPS,并将webRTC开源。随后webRTC 在Google、Mozilla、Opera支持下被纳入W3C推荐标准。相关的学习文章非常多,建议先仔细阅读文章内容,领略一下 WebRTC 的精髓,往后进行开发才不会因为各种各样的概念而搞混了,因为本人在开发过程中遇到的坑也是挺多的,所以这里简单推荐几篇别人写的文章,我也是从中收获蛮多有用信息的,到此看不懂没关系,先预备着。 11 | 12 | [WebRTC学习总结](https://juejin.im/post/6844903624561147918) 13 | 14 | [CentOS搭建coturn服务器](https://blog.csdn.net/hello123yy/article/details/84976086) 15 | 16 | [WebRTC中的信令和内网穿透技术 STUN / TURN](https://blog.csdn.net/shaosunrise/article/details/83627828?utm_medium=distribute.wap_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase&depth_1-utm_source=distribute.wap_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-3.nonecase) 17 | 18 | [解决WebRTC视频通话,NAT穿透时,局域网有效而4G网无效的问题](https://blog.csdn.net/u013342752/article/details/103857499) 19 | 20 | ### 相关解决方案 21 | 22 | 集群环境下的消息传递解决方案:首先本段文字只是本人的一个思路,解决方案并不唯一。本案例 socket 消息通过 Redis 队列实现消息广播,由于 WebSocketSession 并不能通过序列化来实现某些(如:Redis 缓存)持久化行为,大环境下通过静态域保存以完成会话存储就需要考虑集群环境下会话不在同一个服务节点上的问题,所以通过广播的方式实现分布式或者集群环境下消息传递的可靠性,如有需求,可以替换为其它消息中间件来实现这一消息广播行为。 23 | 24 | 单向监控解决方案:如上所述本案例基于开发背景是为了实现单向监控,不同于IM服务。通阅读上述文章我们不难知晓 WebRTC 要求 createOffer 方一定要有Media stream,即视频音频流,可以使摄像头也可以是屏幕共享,而正常的用户操作应该是主动请求到对方的视频信息,因此我想到的是用被监控者的客户端来创建 CreateOffer。整体思路是监控者 A 去发一个命令让被监控者 B 创建 createOffer。而 B 正好是被监控方,保证的 Media stream 是存在的1。 25 | 26 | ### 配置和启动 27 | 28 | 1. **[必选]** 在宿主机上安装 Redis 数据库,已安装可略过 29 | 30 | 2. 克隆本项目,git clone https://github.com/chachae/WebRTC-One-Way-Monitor.git 31 | 32 | 3. WebRTC-WebSocket 和 WebRTC-Netty 二选一 33 | 34 | 4. **[可选]** 根据实际需求编辑类路径内 application.yml 相关配置 35 | 36 | 5. **[可选]** 根据实际需求编辑 monitor.html 内 openLocalMedia方法,更改监控目标。 37 | 38 | ```javascript 39 | // 摄像头(二选一) 40 | // navigator.mediaDevices.getUserMedia(this.mediaConstraints) 41 | // 屏幕共享(二选一) 42 | navigator.mediaDevices.getDisplayMedia(this.screenConstraints) 43 | ``` 44 | 45 | 6. 启动 Application 主启动器,监控方访问根目录,被监控方访问:http://domain/monitor 46 | 47 | ### 截图 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 |
监控方
被监控方
63 | 64 | ### License 65 | 66 | ```reStructuredText 67 | Copyright [2020] [chachae] 68 | 69 | Licensed under the Apache License, Version 2.0 (the "License"); 70 | you may not use this file except in compliance with the License. 71 | You may obtain a copy of the License at 72 | 73 | http://www.apache.org/licenses/LICENSE-2.0 74 | 75 | Unless required by applicable law or agreed to in writing, software 76 | distributed under the License is distributed on an "AS IS" BASIS, 77 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 78 | See the License for the specific language governing permissions and 79 | limitations under the License. 80 | ``` 81 | -------------------------------------------------------------------------------- /WebRTC-Common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | WebRTC-One-Way-Monitor 7 | com.chachae.webrtc 8 | 1.0 9 | 10 | 11 | 4.0.0 12 | 13 | WebRTC-Common 14 | 15 | 16 | 17 | 18 | 19 | org.projectlombok 20 | lombok 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-thymeleaf 27 | 28 | 29 | 30 | 31 | org.springframework.boot 32 | spring-boot-starter-web 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-validation 39 | 40 | 41 | 42 | 43 | org.apache.commons 44 | commons-text 45 | ${commons-text-version} 46 | 47 | 48 | 49 | 50 | com.alibaba 51 | fastjson 52 | ${fastjson-version} 53 | 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-data-redis 59 | 60 | 61 | 62 | 63 | org.apache.commons 64 | commons-pool2 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /WebRTC-Common/src/main/java/com/chachae/webrtc/common/AbstractRedisCache.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.common; 2 | 3 | /** 4 | * @author chachae 5 | * @version v1.0 6 | * @date 2020/8/13 20:52 7 | */ 8 | public abstract class AbstractRedisCache { 9 | 10 | public T get(Object key) { 11 | return null; 12 | } 13 | 14 | public void put(Object key, T data) { 15 | } 16 | 17 | public void put(Object key, T data, Long seconds) { 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /WebRTC-Common/src/main/java/com/chachae/webrtc/common/ApiException.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.common; 2 | 3 | /** 4 | * @author chachae 5 | * @version v1.0 6 | * @date 2020/8/13 22:28 7 | */ 8 | public class ApiException extends RuntimeException { 9 | 10 | private static final long serialVersionUID = -6916154417432027437L; 11 | 12 | public ApiException(String message) { 13 | super(message); 14 | } 15 | } -------------------------------------------------------------------------------- /WebRTC-Common/src/main/java/com/chachae/webrtc/common/R.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.common; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import java.io.Serializable; 5 | import lombok.Data; 6 | 7 | /** 8 | * @author chachae 9 | * @version v1.0 10 | * @date 2020/8/13 22:10 11 | */ 12 | 13 | @Data 14 | @JsonInclude(JsonInclude.Include.NON_NULL) 15 | public class R implements Serializable { 16 | 17 | private static final long serialVersionUID = -8713837163942965721L; 18 | 19 | private String msg; 20 | private transient T data; 21 | 22 | private R(String msg, T data) { 23 | this.msg = msg; 24 | this.data = data; 25 | } 26 | 27 | public static R ok() { 28 | return R.ok(null); 29 | } 30 | 31 | public static R ok(T data) { 32 | return new R<>("ok", data); 33 | } 34 | 35 | public static R fail() { 36 | return new R<>("fail", null); 37 | } 38 | 39 | public static R fail(String msg) { 40 | return new R<>(msg, null); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /WebRTC-Common/src/main/java/com/chachae/webrtc/common/RedisService.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.common; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import java.util.concurrent.TimeUnit; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.data.redis.core.RedisTemplate; 10 | 11 | /** 12 | * @author chachae 13 | * @version v1.0 14 | * @date 2020/8/13 20:38 15 | */ 16 | @SuppressWarnings("all") 17 | public class RedisService { 18 | 19 | @Autowired 20 | private RedisTemplate redisTemplate; 21 | 22 | /** 23 | * 指定缓存失效时间 24 | * 25 | * @param key 键 26 | * @param time 时间(秒) 27 | * @return Boolean 28 | */ 29 | public Boolean expire(String key, Long time) { 30 | try { 31 | if (time > 0) { 32 | redisTemplate.expire(key, time, TimeUnit.SECONDS); 33 | } 34 | return true; 35 | } catch (Exception e) { 36 | e.printStackTrace(); 37 | return false; 38 | } 39 | } 40 | 41 | /** 42 | * 根据key获取过期时间 43 | * 44 | * @param key 键 不能为 null 45 | * @return 时间(秒) 返回 0代表为永久有效 46 | */ 47 | public Long getExpire(String key) { 48 | return redisTemplate.getExpire(key, TimeUnit.SECONDS); 49 | } 50 | 51 | /** 52 | * 判断 key是否存在 53 | * 54 | * @param key 键 55 | * @return true 存在 false不存在 56 | */ 57 | public Boolean hasKey(String key) { 58 | try { 59 | return redisTemplate.hasKey(key); 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | return false; 63 | } 64 | } 65 | 66 | /** 67 | * 删除缓存 68 | * 69 | * @param key 可以传一个值 或多个 70 | */ 71 | public void del(String... key) { 72 | if (key != null && key.length > 0) { 73 | if (key.length == 1) { 74 | redisTemplate.delete(key[0]); 75 | } else { 76 | redisTemplate.delete(Arrays.asList(key)); 77 | } 78 | } 79 | } 80 | 81 | /** 82 | * 普通缓存获取 83 | * 84 | * @param key 键 85 | * @return 值 86 | */ 87 | public Object get(String key) { 88 | return key == null ? null : redisTemplate.opsForValue().get(key); 89 | } 90 | 91 | /** 92 | * 普通缓存放入 93 | * 94 | * @param key 键 95 | * @param value 值 96 | * @return true成功 false失败 97 | */ 98 | public Boolean set(String key, Object value) { 99 | try { 100 | redisTemplate.opsForValue().set(key, value); 101 | return true; 102 | } catch (Exception e) { 103 | e.printStackTrace(); 104 | return false; 105 | } 106 | } 107 | 108 | /** 109 | * 普通缓存放入并设置时间 110 | * 111 | * @param key 键 112 | * @param value 值 113 | * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 114 | * @return true成功 false 失败 115 | */ 116 | public Boolean set(String key, Object value, Long time) { 117 | return set(key, value, time, TimeUnit.SECONDS); 118 | } 119 | 120 | /** 121 | * 普通缓存放入并设置时间和时间单位 122 | * 123 | * @param key 键 124 | * @param value 值 125 | * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期 126 | * @return true成功 false 失败 127 | */ 128 | public Boolean set(String key, Object value, Long time, TimeUnit timeUnit) { 129 | try { 130 | if (time > 0) { 131 | redisTemplate.opsForValue().set(key, value, time, timeUnit); 132 | } else { 133 | set(key, value); 134 | } 135 | return true; 136 | } catch (Exception e) { 137 | e.printStackTrace(); 138 | return false; 139 | } 140 | } 141 | 142 | /** 143 | * 递增 144 | * 145 | * @param key 键 146 | * @param delta 要增加几(大于0) 147 | * @return Long 148 | */ 149 | public Long incr(String key, Long delta) { 150 | if (delta < 0) { 151 | throw new RuntimeException("递增因子必须大于0"); 152 | } 153 | return redisTemplate.opsForValue().increment(key, delta); 154 | } 155 | 156 | /** 157 | * 递减 158 | * 159 | * @param key 键 160 | * @param delta 要减少几 161 | * @return Long 162 | */ 163 | public Long decr(String key, Long delta) { 164 | if (delta < 0) { 165 | throw new RuntimeException("递减因子必须大于0"); 166 | } 167 | return redisTemplate.opsForValue().increment(key, -delta); 168 | } 169 | 170 | /** 171 | * HashGet 172 | * 173 | * @param key 键 不能为 null 174 | * @param item 项 不能为 null 175 | * @return 值 176 | */ 177 | public Object hget(String key, String item) { 178 | return redisTemplate.opsForHash().get(key, item); 179 | } 180 | 181 | /** 182 | * 获取 hashKey对应的所有键值 183 | * 184 | * @param key 键 185 | * @return 对应的多个键值 186 | */ 187 | public Map hmget(String key) { 188 | return redisTemplate.opsForHash().entries(key); 189 | } 190 | 191 | /** 192 | * HashSet 193 | * 194 | * @param key 键 195 | * @param map 对应多个键值 196 | * @return true 成功 false 失败 197 | */ 198 | public Boolean hmset(String key, Map map) { 199 | try { 200 | redisTemplate.opsForHash().putAll(key, map); 201 | return true; 202 | } catch (Exception e) { 203 | e.printStackTrace(); 204 | return false; 205 | } 206 | } 207 | 208 | /** 209 | * HashSet 并设置时间 210 | * 211 | * @param key 键 212 | * @param map 对应多个键值 213 | * @param time 时间(秒) 214 | * @return true成功 false失败 215 | */ 216 | public Boolean hmset(String key, Map map, Long time) { 217 | try { 218 | redisTemplate.opsForHash().putAll(key, map); 219 | if (time > 0) { 220 | expire(key, time); 221 | } 222 | return true; 223 | } catch (Exception e) { 224 | e.printStackTrace(); 225 | return false; 226 | } 227 | } 228 | 229 | /** 230 | * 向一张hash表中放入数据,如果不存在将创建 231 | * 232 | * @param key 键 233 | * @param item 项 234 | * @param value 值 235 | * @return true 成功 false失败 236 | */ 237 | public Boolean hset(String key, String item, Object value) { 238 | try { 239 | redisTemplate.opsForHash().put(key, item, value); 240 | return true; 241 | } catch (Exception e) { 242 | e.printStackTrace(); 243 | return false; 244 | } 245 | } 246 | 247 | /** 248 | * 向一张hash表中放入数据,如果不存在将创建 249 | * 250 | * @param key 键 251 | * @param item 项 252 | * @param value 值 253 | * @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间 254 | * @return true 成功 false失败 255 | */ 256 | public Boolean hset(String key, String item, Object value, Long time) { 257 | try { 258 | redisTemplate.opsForHash().put(key, item, value); 259 | if (time > 0) { 260 | expire(key, time); 261 | } 262 | return true; 263 | } catch (Exception e) { 264 | e.printStackTrace(); 265 | return false; 266 | } 267 | } 268 | 269 | /** 270 | * 删除hash表中的值 271 | * 272 | * @param key 键 不能为 null 273 | * @param item 项 可以使多个不能为 null 274 | */ 275 | public void hdel(String key, Object... item) { 276 | redisTemplate.opsForHash().delete(key, item); 277 | } 278 | 279 | /** 280 | * 判断hash表中是否有该项的值 281 | * 282 | * @param key 键 不能为 null 283 | * @param item 项 不能为 null 284 | * @return true 存在 false不存在 285 | */ 286 | public Boolean hHasKey(String key, String item) { 287 | return redisTemplate.opsForHash().hasKey(key, item); 288 | } 289 | 290 | /** 291 | * hash递增 如果不存在,就会创建一个 并把新增后的值返回 292 | * 293 | * @param key 键 294 | * @param item 项 295 | * @param by 要增加几(大于0) 296 | * @return Double 297 | */ 298 | public Double hincr(String key, String item, Double by) { 299 | return redisTemplate.opsForHash().increment(key, item, by); 300 | } 301 | 302 | /** 303 | * hash递减 304 | * 305 | * @param key 键 306 | * @param item 项 307 | * @param by 要减少记(小于0) 308 | * @return Double 309 | */ 310 | public Double hdecr(String key, String item, Double by) { 311 | return redisTemplate.opsForHash().increment(key, item, -by); 312 | } 313 | 314 | /** 315 | * 根据 key获取 Set中的所有值 316 | * 317 | * @param key 键 318 | * @return Set 319 | */ 320 | public Set sGet(String key) { 321 | try { 322 | return redisTemplate.opsForSet().members(key); 323 | } catch (Exception e) { 324 | e.printStackTrace(); 325 | return null; 326 | } 327 | } 328 | 329 | /** 330 | * 根据value从一个set中查询,是否存在 331 | * 332 | * @param key 键 333 | * @param value 值 334 | * @return true 存在 false不存在 335 | */ 336 | public Boolean sHasKey(String key, Object value) { 337 | try { 338 | return redisTemplate.opsForSet().isMember(key, value); 339 | } catch (Exception e) { 340 | e.printStackTrace(); 341 | return false; 342 | } 343 | } 344 | 345 | /** 346 | * 将数据放入set缓存 347 | * 348 | * @param key 键 349 | * @param values 值 可以是多个 350 | * @return 成功个数 351 | */ 352 | public Long sSet(String key, Object... values) { 353 | try { 354 | return redisTemplate.opsForSet().add(key, values); 355 | } catch (Exception e) { 356 | e.printStackTrace(); 357 | return 0L; 358 | } 359 | } 360 | 361 | /** 362 | * 将set数据放入缓存 363 | * 364 | * @param key 键 365 | * @param time 时间(秒) 366 | * @param values 值 可以是多个 367 | * @return 成功个数 368 | */ 369 | public Long sSetAndTime(String key, Long time, Object... values) { 370 | try { 371 | Long count = redisTemplate.opsForSet().add(key, values); 372 | if (time > 0) { 373 | expire(key, time); 374 | } 375 | return count; 376 | } catch (Exception e) { 377 | e.printStackTrace(); 378 | return 0L; 379 | } 380 | } 381 | 382 | /** 383 | * 获取set缓存的长度 384 | * 385 | * @param key 键 386 | * @return Long 387 | */ 388 | public Long sGetSetSize(String key) { 389 | try { 390 | return redisTemplate.opsForSet().size(key); 391 | } catch (Exception e) { 392 | e.printStackTrace(); 393 | return 0L; 394 | } 395 | } 396 | 397 | /** 398 | * 移除值为value的 399 | * 400 | * @param key 键 401 | * @param values 值 可以是多个 402 | * @return 移除的个数 403 | */ 404 | public Long setRemove(String key, Object... values) { 405 | try { 406 | return redisTemplate.opsForSet().remove(key, values); 407 | } catch (Exception e) { 408 | e.printStackTrace(); 409 | return 0L; 410 | } 411 | } 412 | 413 | /** 414 | * 获取list缓存的内容 415 | * 416 | * @param key 键 417 | * @param start 开始 418 | * @param end 结束 0 到 -1代表所有值 419 | * @return List 420 | */ 421 | public List lGet(String key, Long start, Long end) { 422 | try { 423 | return redisTemplate.opsForList().range(key, start, end); 424 | } catch (Exception e) { 425 | e.printStackTrace(); 426 | return null; 427 | } 428 | } 429 | 430 | /** 431 | * 获取list缓存的长度 432 | * 433 | * @param key 键 434 | * @return Long 435 | */ 436 | public Long lGetListSize(String key) { 437 | try { 438 | return redisTemplate.opsForList().size(key); 439 | } catch (Exception e) { 440 | e.printStackTrace(); 441 | return 0L; 442 | } 443 | } 444 | 445 | /** 446 | * 通过索引 获取list中的值 447 | * 448 | * @param key 键 449 | * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推; index<0时,-1,表尾,-2倒数第二个元素,依次类推 450 | * @return Object 451 | */ 452 | public Object lGetIndex(String key, Long index) { 453 | try { 454 | return redisTemplate.opsForList().index(key, index); 455 | } catch (Exception e) { 456 | e.printStackTrace(); 457 | return null; 458 | } 459 | } 460 | 461 | /** 462 | * 将list放入缓存 463 | * 464 | * @param key 键 465 | * @param value 值 466 | * @return Boolean 467 | */ 468 | public Boolean lSet(String key, Object value) { 469 | try { 470 | redisTemplate.opsForList().rightPush(key, value); 471 | return true; 472 | } catch (Exception e) { 473 | e.printStackTrace(); 474 | return false; 475 | } 476 | } 477 | 478 | /** 479 | * 将list放入缓存 480 | * 481 | * @param key 键 482 | * @param value 值 483 | * @param time 时间(秒) 484 | * @return Boolean 485 | */ 486 | public Boolean lSet(String key, Object value, Long time) { 487 | try { 488 | redisTemplate.opsForList().rightPush(key, value); 489 | if (time > 0) { 490 | expire(key, time); 491 | } 492 | return true; 493 | } catch (Exception e) { 494 | e.printStackTrace(); 495 | return false; 496 | } 497 | } 498 | 499 | /** 500 | * 将list放入缓存 501 | * 502 | * @param key 键 503 | * @param value 值 504 | * @return Boolean 505 | */ 506 | public Boolean lSet(String key, List value) { 507 | try { 508 | redisTemplate.opsForList().rightPushAll(key, value); 509 | return true; 510 | } catch (Exception e) { 511 | e.printStackTrace(); 512 | return false; 513 | } 514 | } 515 | 516 | /** 517 | * 将list放入缓存 518 | * 519 | * @param key 键 520 | * @param value 值 521 | * @param time 时间(秒) 522 | * @return Boolean 523 | */ 524 | public Boolean lSet(String key, List value, Long time) { 525 | try { 526 | redisTemplate.opsForList().rightPushAll(key, value); 527 | if (time > 0) { 528 | expire(key, time); 529 | } 530 | return true; 531 | } catch (Exception e) { 532 | e.printStackTrace(); 533 | return false; 534 | } 535 | } 536 | 537 | /** 538 | * 根据索引修改list中的某条数据 539 | * 540 | * @param key 键 541 | * @param index 索引 542 | * @param value 值 543 | * @return Boolean 544 | */ 545 | public Boolean lUpdateIndex(String key, Long index, Object value) { 546 | try { 547 | redisTemplate.opsForList().set(key, index, value); 548 | return true; 549 | } catch (Exception e) { 550 | e.printStackTrace(); 551 | return false; 552 | } 553 | } 554 | 555 | /** 556 | * 移除N个值为value 557 | * 558 | * @param key 键 559 | * @param count 移除多少个 560 | * @param value 值 561 | * @return 移除的个数 562 | */ 563 | public Long lRemove(String key, Long count, Object value) { 564 | try { 565 | return redisTemplate.opsForList().remove(key, count, value); 566 | } catch (Exception e) { 567 | e.printStackTrace(); 568 | return 0L; 569 | } 570 | } 571 | } 572 | -------------------------------------------------------------------------------- /WebRTC-Netty/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | WebRTC-One-Way-Monitor 7 | com.chachae.webrtc 8 | 1.0 9 | 10 | 4.0.0 11 | 12 | WebRTC-Netty 13 | 14 | 15 | 16 | 17 | 18 | com.chachae.webrtc 19 | WebRTC-Common 20 | ${project.version} 21 | 22 | 23 | 24 | 25 | com.chachae.webrtc 26 | Netty-WebSocket-Starter 27 | ${project.version} 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | org.springframework.boot 36 | spring-boot-maven-plugin 37 | 38 | com.chachae.webrtc.netty.WebrtcNettyApplication 39 | 40 | 41 | 42 | 43 | repackage 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/WebrtcNettyApplication.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty; 2 | 3 | import org.springframework.boot.Banner.Mode; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.builder.SpringApplicationBuilder; 6 | import org.springframework.boot.web.servlet.ServletComponentScan; 7 | 8 | /** 9 | * @author chachae 10 | * @version v1.0 11 | * @date 2020/9/9 14:55 12 | */ 13 | @ServletComponentScan 14 | @SpringBootApplication 15 | public class WebrtcNettyApplication { 16 | 17 | public static void main(String[] args) { 18 | new SpringApplicationBuilder(WebrtcNettyApplication.class) 19 | .bannerMode(Mode.OFF) 20 | .run(args); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/config/RedisPublishConfig.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.config; 2 | 3 | import com.chachae.webrtc.netty.constant.SocketConfigConstant; 4 | import com.chachae.webrtc.netty.service.RedisMessage; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.redis.connection.RedisConnectionFactory; 8 | import org.springframework.data.redis.listener.PatternTopic; 9 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; 10 | import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; 11 | 12 | /** 13 | * @author chachae 14 | * @version v1.0 15 | * @date 2020/8/14 11:19 16 | */ 17 | @Configuration 18 | public class RedisPublishConfig { 19 | 20 | @Bean 21 | RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, 22 | MessageListenerAdapter listenerAdapter) { 23 | RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 24 | container.setConnectionFactory(connectionFactory); 25 | container.addMessageListener(listenerAdapter, new PatternTopic(SocketConfigConstant.MESSAGE_TOPIC)); 26 | return container; 27 | } 28 | 29 | @Bean 30 | MessageListenerAdapter listenerAdapter(RedisMessage receiver) { 31 | // 这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用receiveMessage 32 | return new MessageListenerAdapter(receiver, "receiveMessage"); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/config/SessionConfig.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.config; 2 | 3 | import com.chachae.webrtc.netty.socket.pojo.Session; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | /** 8 | * @author chachae 9 | * @version v1.0 10 | * @date 2020/9/10 10:51 11 | */ 12 | public interface SessionConfig { 13 | 14 | Map sessionMap = new ConcurrentHashMap<>(); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/constant/MsgTypeConstant.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.constant; 2 | 3 | /** 4 | * @author chachae 5 | * @version v1.0 6 | * @date 2020/9/11 14:01 7 | */ 8 | public interface MsgTypeConstant { 9 | 10 | String CMD = "cmd"; 11 | String OFFER = "offer"; 12 | String ANSWER = "answer"; 13 | String CANDIDATE = "candidate"; 14 | String HEART = "heart"; 15 | } 16 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/constant/SocketConfigConstant.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.constant; 2 | 3 | /** 4 | * @author chachae 5 | * @version v1.0 6 | * @date 2020/9/11 15:04 7 | */ 8 | public interface SocketConfigConstant { 9 | 10 | String MESSAGE_TOPIC = "netty-channel"; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/controller/SocketMessageController.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.controller; 2 | 3 | import com.chachae.webrtc.netty.constant.SocketConfigConstant; 4 | import javax.validation.constraints.NotBlank; 5 | import lombok.RequiredArgsConstructor; 6 | import org.springframework.data.redis.core.RedisTemplate; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | /** 12 | * @author chachae 13 | * @version v1.0 14 | * @date 2020/9/11 14:47 15 | */ 16 | @RequiredArgsConstructor 17 | @RestController 18 | @RequestMapping("message") 19 | public class SocketMessageController { 20 | 21 | private final RedisTemplate stringRedisTemplate; 22 | 23 | @PostMapping("1t1") 24 | public void oneToOne(@NotBlank(message = "{required}") String message) { 25 | stringRedisTemplate.convertAndSend(SocketConfigConstant.MESSAGE_TOPIC, message); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/controller/ViewController.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.controller; 2 | 3 | import org.springframework.stereotype.Controller; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | 6 | /** 7 | * @author chachae 8 | * @version v1.0 9 | * @date 2020/9/11 13:58 10 | */ 11 | 12 | @Controller 13 | public class ViewController { 14 | 15 | @GetMapping("monitor") 16 | public String toMonitor() { 17 | return "monitor"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/entity/Message.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.entity; 2 | 3 | import java.io.Serializable; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author chachae 8 | * @version v1.0 9 | * @date 2020/9/9 21:15 10 | */ 11 | @Data 12 | public class Message implements Serializable { 13 | 14 | /** 15 | * 发送方id 16 | */ 17 | private String fromId; 18 | 19 | /** 20 | * 接收方id 21 | */ 22 | private String toId; 23 | 24 | /** 25 | * 发送文本 26 | */ 27 | private String content; 28 | 29 | /** 30 | * 消息类型 31 | */ 32 | private String command; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/handle/SysWebSocket.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.handle; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.chachae.webrtc.netty.config.SessionConfig; 5 | import com.chachae.webrtc.netty.constant.MsgTypeConstant; 6 | import com.chachae.webrtc.netty.entity.Message; 7 | import com.chachae.webrtc.netty.service.RedisMessage; 8 | import com.chachae.webrtc.netty.socket.annotation.BeforeHandshake; 9 | import com.chachae.webrtc.netty.socket.annotation.OnBinary; 10 | import com.chachae.webrtc.netty.socket.annotation.OnClose; 11 | import com.chachae.webrtc.netty.socket.annotation.OnError; 12 | import com.chachae.webrtc.netty.socket.annotation.OnEvent; 13 | import com.chachae.webrtc.netty.socket.annotation.OnMessage; 14 | import com.chachae.webrtc.netty.socket.annotation.OnOpen; 15 | import com.chachae.webrtc.netty.socket.annotation.PathVariable; 16 | import com.chachae.webrtc.netty.socket.annotation.ServerEndpoint; 17 | import com.chachae.webrtc.netty.socket.pojo.Session; 18 | import io.netty.handler.codec.http.HttpHeaders; 19 | import io.netty.handler.timeout.IdleStateEvent; 20 | 21 | /** 22 | * @author chachae 23 | * @version v1.0 24 | * @date 2020/9/9 20:00 25 | */ 26 | @ServerEndpoint(path = "/ws/{id}", port = "${ws.port}") 27 | public class SysWebSocket implements RedisMessage { 28 | 29 | @BeforeHandshake 30 | public void handshake(Session session, HttpHeaders headers, @PathVariable String id) { 31 | session.setSubprotocols("stomp"); 32 | session.setAttribute("id", id); 33 | if (id == null) { 34 | session.close(); 35 | } 36 | } 37 | 38 | @OnOpen 39 | public void onOpen(Session session, HttpHeaders headers, @PathVariable String id) { 40 | // 实际业务,key=用户id,value=session 41 | SessionConfig.sessionMap.putIfAbsent(id, session); 42 | } 43 | 44 | @OnClose 45 | public void onClose(Session session) { 46 | Object id = session.getAttribute("id"); 47 | if (id instanceof String) { 48 | SessionConfig.sessionMap.remove(id); 49 | } 50 | } 51 | 52 | @OnError 53 | public void onError(Session session, Throwable throwable) { 54 | throwable.printStackTrace(); 55 | } 56 | 57 | @OnMessage 58 | public void onMessage(Session session, String message) { 59 | System.out.println(message); 60 | Message msg = JSON.parseObject(message, Message.class); 61 | if (message != null && SessionConfig.sessionMap.get(msg.getToId()) != null) { 62 | // SessionConfig.sessionMap.get(msg.getToId()).sendText(msg.getText()); 63 | receiveMessage(message); 64 | } 65 | } 66 | 67 | @OnBinary 68 | public void onBinary(Session session, byte[] bytes) { 69 | for (byte b : bytes) { 70 | System.out.println(b); 71 | } 72 | session.sendBinary(bytes); 73 | } 74 | 75 | @OnEvent 76 | public void onEvent(Session session, Object evt) { 77 | if (evt instanceof IdleStateEvent) { 78 | IdleStateEvent idleStateEvent = (IdleStateEvent) evt; 79 | switch (idleStateEvent.state()) { 80 | case READER_IDLE: 81 | System.out.println("read idle"); 82 | break; 83 | case WRITER_IDLE: 84 | System.out.println("write idle"); 85 | break; 86 | case ALL_IDLE: 87 | System.out.println("all idle"); 88 | break; 89 | default: 90 | break; 91 | } 92 | } 93 | } 94 | 95 | /** 96 | * 接收广播消息 97 | * 98 | * @param message 消息 JSON 99 | */ 100 | @Override 101 | public void receiveMessage(String message) { 102 | Message msg = JSON.parseObject(message, Message.class); 103 | // 此处判断 channel 是否存在该实例 104 | if (msg != null && SessionConfig.sessionMap.get(msg.getToId()) != null) { 105 | handlerMessage(msg, SessionConfig.sessionMap.get(msg.getToId())); 106 | } 107 | } 108 | 109 | private void handlerMessage(Message message, Session session) { 110 | switch (message.getCommand()) { 111 | case MsgTypeConstant.CMD: 112 | case MsgTypeConstant.ANSWER: 113 | case MsgTypeConstant.OFFER: 114 | case MsgTypeConstant.CANDIDATE: 115 | handleCmd(message, session); 116 | break; 117 | case MsgTypeConstant.HEART: 118 | handleHeart(message, session); 119 | default: 120 | break; 121 | } 122 | } 123 | 124 | private void handleHeart(Message message, Session session) { 125 | if (!message.getContent().equals("ok")) { 126 | this.onClose(session); 127 | } 128 | } 129 | 130 | private void handleCmd(Message message, Session session) { 131 | String fromId = message.getFromId(); 132 | message.setFromId(message.getToId()); 133 | message.setToId(fromId); 134 | session.sendText(JSON.toJSONString(message)); 135 | } 136 | 137 | } 138 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/service/RedisMessage.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.service; 2 | 3 | public interface RedisMessage { 4 | 5 | /** 6 | * 接受信息 7 | * 8 | * @param message 消息 JSON 9 | */ 10 | void receiveMessage(String message); 11 | } 12 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/java/com/chachae/webrtc/netty/util/IdUtil.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.netty.util; 2 | 3 | import java.util.UUID; 4 | 5 | /** 6 | * @author chachae 7 | * @version v1.0 8 | * @date 2020/9/10 10:59 9 | */ 10 | public class IdUtil { 11 | 12 | private IdUtil() { 13 | } 14 | 15 | private static String getUUID(){ 16 | return UUID.randomUUID().toString(); 17 | } 18 | 19 | public static String uuid() { 20 | return getUUID(); 21 | } 22 | 23 | public static String fastUUID() { 24 | return getUUID().replace("-", ""); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | required=\u4E0D\u80FD\u4E3A\u7A7A 2 | range=\u6709\u6548\u957f\u5ea6{min}\u5230{max}\u4e2a\u5b57\u7b26 3 | email=\u90ae\u7bb1\u683c\u5f0f\u4e0d\u5408\u6cd5 4 | mobile=\u624b\u673a\u53f7\u4e0d\u5408\u6cd5 5 | noMoreThan=\u957f\u5ea6\u4e0d\u80fd\u8d85\u8fc7{max}\u4e2a\u5b57\u7b26 6 | invalid=\u503c\u4e0d\u5408\u6cd5 -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: ${server.port} 3 | 4 | # WebSocket 端口 5 | ws: 6 | port: 10086 7 | 8 | spring: 9 | jackson: 10 | date-format: yyyy-MM-dd HH:mm:ss 11 | time-zone: GMT+8 12 | redis: 13 | database: 10 14 | host: ${redis.url:127.0.0.1} 15 | port: ${redis.port:6379} 16 | lettuce: 17 | pool: 18 | min-idle: 8 19 | max-idle: 500 20 | max-active: 2000 21 | max-wait: 10000 22 | timeout: 5000 -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebSocket 连接 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 提交 24 | 25 | 26 | 27 |

用户 {{ monitorForm.userId }} 的屏幕共享

28 | 30 |
31 | 32 | 260 | -------------------------------------------------------------------------------- /WebRTC-Netty/src/main/resources/templates/monitor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 摄像头监控与屏幕共享 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 26 |
27 |
28 | 29 | 224 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | WebRTC-One-Way-Monitor 7 | com.chachae.webrtc 8 | 1.0 9 | 10 | 4.0.0 11 | 12 | WebRTC-WebSocket 13 | 14 | 15 | 16 | 17 | 18 | com.chachae.webrtc 19 | WebRTC-Common 20 | ${project.version} 21 | 22 | 23 | 24 | 25 | org.springframework.boot 26 | spring-boot-starter-websocket 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/WebrtcApplication.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc; 2 | 3 | import org.springframework.boot.Banner.Mode; 4 | import org.springframework.boot.WebApplicationType; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.boot.builder.SpringApplicationBuilder; 7 | import org.springframework.boot.web.servlet.ServletComponentScan; 8 | import org.springframework.scheduling.annotation.EnableScheduling; 9 | 10 | @EnableScheduling 11 | @ServletComponentScan 12 | @SpringBootApplication 13 | public class WebrtcApplication { 14 | 15 | public static void main(String[] args) { 16 | new SpringApplicationBuilder(WebrtcApplication.class) 17 | .bannerMode(Mode.OFF) 18 | .run(args); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/cache/RoomCache.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.cache; 2 | 3 | import com.chachae.webrtc.common.AbstractRedisCache; 4 | import com.chachae.webrtc.common.RedisService; 5 | import com.chachae.webrtc.entity.ConnectionSystemUser; 6 | import java.util.Set; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * @author chachae 12 | * @version v1.0 13 | * @date 2020/8/13 21:25 14 | */ 15 | @Service 16 | @RequiredArgsConstructor 17 | public class RoomCache extends AbstractRedisCache> { 18 | 19 | private final RedisService redisService; 20 | private static final String CACHE_PREFIX = "webrtc:room:"; 21 | 22 | @Override 23 | @SuppressWarnings("unchecked") 24 | public Set get(Object key) { 25 | Set set = redisService.sGet(CACHE_PREFIX + key); 26 | return set != null ? (Set) set : null; 27 | } 28 | 29 | public long count(String roomId) { 30 | return redisService.sGetSetSize(CACHE_PREFIX + roomId); 31 | } 32 | 33 | public void remove(String roomId, String userId) { 34 | Set set = get(roomId); 35 | for (ConnectionSystemUser cur : set) { 36 | if (cur.getUserId().equals(userId)) { 37 | redisService.setRemove(CACHE_PREFIX + roomId, cur); 38 | break; 39 | } 40 | } 41 | } 42 | 43 | public void add(String roomId, ConnectionSystemUser user) { 44 | redisService.sSetAndTime(CACHE_PREFIX + roomId, 7200L, user); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.config; 2 | 3 | import com.chachae.webrtc.common.RedisService; 4 | import com.fasterxml.jackson.annotation.JsonAutoDetect; 5 | import com.fasterxml.jackson.annotation.PropertyAccessor; 6 | import com.fasterxml.jackson.databind.ObjectMapper; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.data.redis.connection.RedisConnectionFactory; 11 | import org.springframework.data.redis.core.RedisTemplate; 12 | import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer; 13 | import org.springframework.data.redis.serializer.StringRedisSerializer; 14 | 15 | /** 16 | * @author chachae 17 | * @version v1.0 18 | * @date 2020/8/13 20:38 19 | */ 20 | @Configuration 21 | public class RedisConfig { 22 | 23 | @Bean(name = "redisTemplate") 24 | public RedisTemplate redisTemplate(RedisConnectionFactory factory) { 25 | RedisTemplate template = new RedisTemplate<>(); 26 | template.setConnectionFactory(factory); 27 | Jackson2JsonRedisSerializer serializer = new Jackson2JsonRedisSerializer<>(Object.class); 28 | ObjectMapper objectMapper = new ObjectMapper(); 29 | objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 30 | objectMapper.activateDefaultTyping(objectMapper.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL); 31 | serializer.setObjectMapper(objectMapper); 32 | StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); 33 | template.setKeySerializer(stringRedisSerializer); 34 | template.setHashKeySerializer(stringRedisSerializer); 35 | template.setValueSerializer(serializer); 36 | template.setHashValueSerializer(serializer); 37 | template.afterPropertiesSet(); 38 | return template; 39 | } 40 | 41 | @Bean 42 | @ConditionalOnBean(name = "redisTemplate") 43 | public RedisService redisService() { 44 | return new RedisService(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/config/RedisPublishConfig.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.config; 2 | 3 | import com.chachae.webrtc.service.RedisMessage; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.data.redis.connection.RedisConnectionFactory; 8 | import org.springframework.data.redis.listener.PatternTopic; 9 | import org.springframework.data.redis.listener.RedisMessageListenerContainer; 10 | import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; 11 | 12 | /** 13 | * @author chachae 14 | * @version v1.0 15 | * @date 2020/8/14 11:19 16 | */ 17 | @Configuration 18 | public class RedisPublishConfig { 19 | 20 | @Bean 21 | RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory, 22 | MessageListenerAdapter listenerAdapter) { 23 | RedisMessageListenerContainer container = new RedisMessageListenerContainer(); 24 | container.setConnectionFactory(connectionFactory); 25 | container.addMessageListener(listenerAdapter, new PatternTopic("chart")); 26 | return container; 27 | } 28 | 29 | @Bean 30 | MessageListenerAdapter listenerAdapter(RedisMessage receiver) { 31 | // 这个地方 是给messageListenerAdapter 传入一个消息接受的处理器,利用反射的方法调用receiveMessage 32 | return new MessageListenerAdapter(receiver, "receiveMessage"); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.config; 2 | 3 | import com.chachae.webrtc.interceptor.WebSocketInterceptor; 4 | import com.chachae.webrtc.handler.WebSocketMessageHandler; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.socket.config.annotation.EnableWebSocket; 7 | import org.springframework.web.socket.config.annotation.WebSocketConfigurer; 8 | import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry; 9 | 10 | /** 11 | * @author chachae 12 | * @version v1.0 13 | * @date 2020/8/13 19:04 14 | */ 15 | 16 | @Configuration 17 | @EnableWebSocket 18 | public class WebSocketConfig implements WebSocketConfigurer { 19 | 20 | private static final String MAPPING = "/websocket/{roomId}/{userId}"; 21 | 22 | @Override 23 | public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { 24 | 25 | registry 26 | .addHandler(new WebSocketMessageHandler(), MAPPING) 27 | .setAllowedOrigins("*") 28 | .addInterceptors(new WebSocketInterceptor()); 29 | } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/constant/MessageConstant.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.constant; 2 | 3 | /** 4 | * @author chachae 5 | * @version v1.0 6 | * @date 2020/8/14 13:15 7 | */ 8 | public interface MessageConstant { 9 | 10 | String COMMAND_SEND_GROUP = "send::message::group"; 11 | String COMMAND_SEND_ONE = "send::message::one"; 12 | String COMMAND_SEND_CMD = "cmd"; 13 | String TYPE_COMMAND_HEART = "heart"; 14 | String TYPE_COMMAND_OFFER = "offer"; 15 | String TYPE_COMMAND_ANSWER = "answer"; 16 | String TYPE_COMMAND_CANDIDATE = "candidate"; 17 | } 18 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/controller/MessageController.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.chachae.webrtc.constant.MessageConstant; 5 | import com.chachae.webrtc.entity.Message; 6 | import lombok.RequiredArgsConstructor; 7 | import org.springframework.data.redis.core.RedisTemplate; 8 | import org.springframework.scheduling.annotation.Scheduled; 9 | import org.springframework.validation.annotation.Validated; 10 | import org.springframework.web.bind.annotation.PostMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | import org.springframework.web.bind.annotation.RestController; 13 | 14 | /** 15 | * @author chachae 16 | * @version v1.0 17 | * @date 2020/8/14 15:33 18 | */ 19 | @Validated 20 | @RestController 21 | @RequestMapping("message") 22 | @RequiredArgsConstructor 23 | public class MessageController { 24 | 25 | private final RedisTemplate stringStringRedisTemplate; 26 | 27 | @PostMapping("/1t1") 28 | public void topicMsg(String message) { 29 | //广播消息到各个订阅者 30 | stringStringRedisTemplate.convertAndSend("chart", message); 31 | } 32 | 33 | @PostMapping("/group") 34 | public void groupMsg(String message) { 35 | //广播消息到各个订阅者 36 | stringStringRedisTemplate.convertAndSend("chart", message); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/controller/PageController.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.controller; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | 7 | /** 8 | * @author chachae 9 | * @version v1.0 10 | * @date 2020/8/13 20:36 11 | */ 12 | 13 | @Slf4j 14 | @Controller 15 | public class PageController { 16 | 17 | @GetMapping({"/", "index"}) 18 | public String goIndex() { 19 | return "index"; 20 | } 21 | 22 | @GetMapping("monitor") 23 | public String goMonitor() { 24 | return "monitor"; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/entity/ConnectionSystemUser.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import java.io.Serializable; 5 | import javax.validation.constraints.NotNull; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Data; 8 | import lombok.NoArgsConstructor; 9 | 10 | /** 11 | * websocket 连接用户 12 | * 13 | * @author chachae 14 | * @version v1.0 15 | * @date 2020/8/13 21:28 16 | */ 17 | @Data 18 | @NoArgsConstructor 19 | @AllArgsConstructor 20 | @JsonInclude(JsonInclude.Include.NON_NULL) 21 | public class ConnectionSystemUser implements Serializable { 22 | 23 | private static final long serialVersionUID = 1890550997179325107L; 24 | 25 | @NotNull(message = "{required}") 26 | private String userId; 27 | private String roomId; 28 | 29 | } 30 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/entity/Message.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.entity; 2 | 3 | import java.io.Serializable; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author chachae 8 | * @version v1.0 9 | * @date 2020/8/13 23:05 10 | */ 11 | @Data 12 | public class Message implements Serializable { 13 | 14 | private static final long serialVersionUID = -5457063706371166926L; 15 | 16 | // 房间内发送消息(不包含自己) 17 | public static final String COMMAND_SEND_MESSAGE_EVERYONE_EXCLUDE = "send::message::1"; 18 | public static final String TYPE_COMMAND_ROOM_LIST = "roomList"; 19 | public static final String TYPE_COMMAND_DIALOGUE = "dialogue"; 20 | public static final String TYPE_COMMAND_READY = "ready"; 21 | public static final String TYPE_COMMAND_OFFER = "offer"; 22 | public static final String TYPE_COMMAND_ANSWER = "answer"; 23 | public static final String TYPE_COMMAND_CANDIDATE = "candidate"; 24 | 25 | private String command; 26 | private String userId; 27 | private String receiverId; 28 | private String receiverRoomId; 29 | private String roomId; 30 | private String content; 31 | 32 | public Message() { 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/entity/Room.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.entity; 2 | 3 | import java.io.Serializable; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author chachae 8 | * @version v1.0 9 | * @date 2020/8/13 21:20 10 | */ 11 | @Data 12 | public class Room implements Serializable { 13 | 14 | private static final long serialVersionUID = 5897321139282629928L; 15 | 16 | private Long roomId; 17 | private String roomName; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/entity/SystemUser.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.entity; 2 | 3 | import java.io.Serializable; 4 | import lombok.Data; 5 | 6 | /** 7 | * @author chachae 8 | * @version v1.0 9 | * @date 2020/8/13 21:16 10 | */ 11 | @Data 12 | public class SystemUser implements Serializable { 13 | 14 | private static final long serialVersionUID = 4703015886585800107L; 15 | 16 | private Long userId; 17 | private String username; 18 | private String password; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/handler/BaseExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.handler; 2 | 3 | import com.chachae.webrtc.common.ApiException; 4 | import com.chachae.webrtc.common.R; 5 | import java.util.List; 6 | import java.util.Set; 7 | import javax.validation.ConstraintViolation; 8 | import javax.validation.ConstraintViolationException; 9 | import javax.validation.Path; 10 | import lombok.extern.slf4j.Slf4j; 11 | import org.apache.commons.lang3.StringUtils; 12 | import org.springframework.http.HttpStatus; 13 | import org.springframework.validation.BindException; 14 | import org.springframework.validation.FieldError; 15 | import org.springframework.web.HttpMediaTypeNotSupportedException; 16 | import org.springframework.web.HttpRequestMethodNotSupportedException; 17 | import org.springframework.web.bind.MethodArgumentNotValidException; 18 | import org.springframework.web.bind.annotation.ExceptionHandler; 19 | import org.springframework.web.bind.annotation.ResponseStatus; 20 | import org.springframework.web.bind.annotation.RestControllerAdvice; 21 | 22 | /** 23 | * @author chachae 24 | * @version v1.0 25 | * @date 2020/8/13 22:28 26 | */ 27 | @Slf4j 28 | @RestControllerAdvice 29 | public class BaseExceptionHandler { 30 | 31 | @ExceptionHandler(value = Exception.class) 32 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 33 | protected R handleException(Exception e) { 34 | log.error("系统内部异常,异常信息", e); 35 | return R.fail("系统内部异常"); 36 | } 37 | 38 | @ExceptionHandler(value = ApiException.class) 39 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 40 | protected R handleApiException(ApiException e) { 41 | log.error("系统错误", e); 42 | return R.fail(e.getMessage()); 43 | } 44 | 45 | /** 46 | * 统一处理请求参数校验(实体对象传参) 47 | * 48 | * @param e BindException 49 | * @return R 50 | */ 51 | @ExceptionHandler(BindException.class) 52 | @ResponseStatus(HttpStatus.BAD_REQUEST) 53 | protected R handleBindException(BindException e) { 54 | StringBuilder message = new StringBuilder(); 55 | List fieldErrors = e.getBindingResult().getFieldErrors(); 56 | for (FieldError error : fieldErrors) { 57 | message.append(error.getField()).append(error.getDefaultMessage()).append(","); 58 | } 59 | message = new StringBuilder(message.substring(0, message.length() - 1)); 60 | return R.fail(message.toString()); 61 | } 62 | 63 | /** 64 | * 统一处理请求参数校验(普通传参) 65 | * 66 | * @param e ConstraintViolationException 67 | * @return R 68 | */ 69 | @ExceptionHandler(value = ConstraintViolationException.class) 70 | @ResponseStatus(HttpStatus.BAD_REQUEST) 71 | protected R handleConstraintViolationException(ConstraintViolationException e) { 72 | StringBuilder message = new StringBuilder(); 73 | Set> violations = e.getConstraintViolations(); 74 | for (ConstraintViolation violation : violations) { 75 | Path path = violation.getPropertyPath(); 76 | String[] pathArr = StringUtils.split(path.toString(), '.'); 77 | message.append(pathArr[1]).append(violation.getMessage()).append(','); 78 | } 79 | message = new StringBuilder(message.substring(0, message.length() - 1)); 80 | return R.fail(message.toString()); 81 | } 82 | 83 | /** 84 | * 统一处理请求参数校验(json) 85 | * 86 | * @param e ConstraintViolationException 87 | * @return R 88 | */ 89 | @ExceptionHandler(MethodArgumentNotValidException.class) 90 | @ResponseStatus(HttpStatus.BAD_REQUEST) 91 | protected R handlerMethodArgumentNotValidException(MethodArgumentNotValidException e) { 92 | StringBuilder message = new StringBuilder(); 93 | for (FieldError error : e.getBindingResult().getFieldErrors()) { 94 | message.append(error.getField()).append(error.getDefaultMessage()).append(","); 95 | } 96 | message = new StringBuilder(message.substring(0, message.length() - 1)); 97 | log.error(message.toString(), e); 98 | return R.fail(message.toString()); 99 | } 100 | 101 | @ExceptionHandler(value = HttpMediaTypeNotSupportedException.class) 102 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 103 | protected R handleHttpMediaTypeNotSupportedException( 104 | HttpMediaTypeNotSupportedException e) { 105 | return R.fail("该方法不支持" + StringUtils.substringBetween(e.getMessage(), "'", "'") + "媒体类型"); 106 | } 107 | 108 | @ExceptionHandler(value = HttpRequestMethodNotSupportedException.class) 109 | @ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR) 110 | protected R handleHttpRequestMethodNotSupportedException( 111 | HttpRequestMethodNotSupportedException e) { 112 | return R.fail("该方法不支持" + StringUtils.substringBetween(e.getMessage(), "'", "'") + "请求方法"); 113 | } 114 | 115 | } -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/handler/WebSocketMessageHandler.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.handler; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.chachae.webrtc.constant.MessageConstant; 5 | import com.chachae.webrtc.entity.ConnectionSystemUser; 6 | import com.chachae.webrtc.entity.Message; 7 | import com.chachae.webrtc.service.IMessageForwardService; 8 | import com.chachae.webrtc.service.IRoomService; 9 | import com.chachae.webrtc.service.RedisMessage; 10 | import java.util.Map; 11 | import java.util.Set; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | import lombok.extern.slf4j.Slf4j; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Component; 16 | import org.springframework.web.socket.CloseStatus; 17 | import org.springframework.web.socket.WebSocketHandler; 18 | import org.springframework.web.socket.WebSocketMessage; 19 | import org.springframework.web.socket.WebSocketSession; 20 | 21 | /** 22 | * @author chachae 23 | * @version v1.0 24 | * @date 2020/8/13 22:42 25 | */ 26 | @Slf4j 27 | @Component 28 | public class WebSocketMessageHandler implements WebSocketHandler, RedisMessage { 29 | 30 | private static IRoomService roomService; 31 | private static IMessageForwardService messageForwardService; 32 | 33 | @Autowired 34 | private void setRoomService(IRoomService roomService) { 35 | WebSocketMessageHandler.roomService = roomService; 36 | } 37 | 38 | @Autowired 39 | private void setMessageForwardService(IMessageForwardService messageForwardService) { 40 | WebSocketMessageHandler.messageForwardService = messageForwardService; 41 | } 42 | 43 | private static final Map socketMap = new ConcurrentHashMap<>(); 44 | 45 | @Override 46 | public void afterConnectionEstablished(WebSocketSession session) { 47 | //获取用户信息 48 | String userId = getSessionUserId(session); 49 | String roomId = getSessionRoomId(session); 50 | socketMap.put(userId, session); 51 | roomService.enterRoom(roomId, new ConnectionSystemUser(userId, roomId)); 52 | } 53 | 54 | /** 55 | * 消息广播 56 | */ 57 | @Override 58 | public void handleMessage(WebSocketSession session, WebSocketMessage webSocketMessage) { 59 | Message message = JSON.parseObject(webSocketMessage.getPayload().toString(), Message.class); 60 | sendMessage(message); 61 | } 62 | 63 | @Override 64 | public void handleTransportError(WebSocketSession session, Throwable throwable) { 65 | log.error("用户 {} 连接错误,异常信息如下:{}", getSessionUserId(session), throwable); 66 | } 67 | 68 | @Override 69 | public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { 70 | // 即使连接错误,回调了onError方法,最终也会回调onClose方法 71 | String userId = getSessionUserId(session); 72 | roomService.countUser(getSessionRoomId(session)); 73 | socketMap.remove(userId); 74 | session.close(); 75 | } 76 | 77 | @Override 78 | public boolean supportsPartialMessages() { 79 | return false; 80 | } 81 | 82 | /** 83 | * 接收广播消息 84 | * 85 | * @param message 消息 JSON 86 | */ 87 | @Override 88 | public void receiveMessage(String message) { 89 | sendMessage(JSON.parseObject(message, Message.class)); 90 | } 91 | 92 | /** 93 | * 处理消息类型 94 | * 95 | * @param message 消息对象 96 | */ 97 | public boolean sendMessage(Message message) { 98 | // 获取广播消息指令 99 | switch (message.getCommand()) { 100 | // 1对1发送消息 101 | case MessageConstant.TYPE_COMMAND_OFFER: 102 | case MessageConstant.TYPE_COMMAND_ANSWER: 103 | case MessageConstant.TYPE_COMMAND_CANDIDATE: 104 | case MessageConstant.COMMAND_SEND_ONE: 105 | case MessageConstant.COMMAND_SEND_CMD: 106 | return messageForwardService.sendMessageToOne(message, socketMap.get(message.getReceiverId())); 107 | // 群组内群发 108 | case MessageConstant.COMMAND_SEND_GROUP: 109 | Set users = roomService.listUser(message.getReceiverRoomId()); 110 | return messageForwardService.sendMessageGroup(message, users, socketMap); 111 | // 发送心跳 112 | case MessageConstant.TYPE_COMMAND_HEART: 113 | return messageForwardService.sendMessageToOne(message, socketMap.get(message.getUserId())); 114 | default: 115 | return true; 116 | } 117 | } 118 | 119 | private String getSessionUserId(WebSocketSession session) { 120 | return (String) session.getAttributes().get("userId"); 121 | } 122 | 123 | private String getSessionRoomId(WebSocketSession session) { 124 | return (String) session.getAttributes().get("roomId"); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/interceptor/WebSocketInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.interceptor; 2 | 3 | import java.util.Map; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.server.ServerHttpRequest; 6 | import org.springframework.http.server.ServerHttpResponse; 7 | import org.springframework.web.socket.WebSocketHandler; 8 | import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor; 9 | 10 | /** 11 | * @author chachae 12 | * @version v1.0 13 | * @date 2020/8/14 11:31 14 | */ 15 | @Slf4j 16 | public class WebSocketInterceptor extends HttpSessionHandshakeInterceptor { 17 | 18 | @Override 19 | public boolean beforeHandshake(ServerHttpRequest serverHttpRequest, ServerHttpResponse seHttpResponse, 20 | WebSocketHandler wsHandler, Map attributes) throws Exception { 21 | String roomId = serverHttpRequest.getURI().toString().split("/")[4]; 22 | String userId = serverHttpRequest.getURI().toString().split("/")[5]; 23 | attributes.put("roomId", roomId); 24 | attributes.put("userId", userId); 25 | log.info("握手之前"); 26 | //从request里面获取对象,存放attributes 27 | return super.beforeHandshake(serverHttpRequest, seHttpResponse, wsHandler, attributes); 28 | } 29 | 30 | @Override 31 | public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, 32 | Exception ex) { 33 | log.info("握手之后"); 34 | super.afterHandshake(request, response, wsHandler, ex); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/service/IMessageForwardService.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.service; 2 | 3 | import com.chachae.webrtc.entity.ConnectionSystemUser; 4 | import com.chachae.webrtc.entity.Message; 5 | import java.util.Map; 6 | import java.util.Set; 7 | import org.springframework.web.socket.WebSocketSession; 8 | 9 | /** 10 | * @author chachae 11 | * @version v1.0 12 | * @date 2020/8/13 23:16 13 | */ 14 | public interface IMessageForwardService { 15 | 16 | /** 17 | * 发送给某个人 18 | * 19 | * @param message 消息 JSON 20 | */ 21 | boolean sendMessageToOne(Message message, WebSocketSession session); 22 | 23 | /** 24 | * 发送给某个群组 25 | * 26 | * @param message 消息 JSON 27 | */ 28 | boolean sendMessageGroup(Message message, Set sessions, Map socketMap); 29 | } 30 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/service/IRoomService.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.service; 2 | 3 | import com.chachae.webrtc.entity.ConnectionSystemUser; 4 | import java.util.Set; 5 | 6 | /** 7 | * @author chachae 8 | * @version v1.0 9 | * @date 2020/8/13 21:15 10 | */ 11 | public interface IRoomService { 12 | 13 | void enterRoom(String roomId, ConnectionSystemUser user); 14 | 15 | void createRoom(String roomId); 16 | 17 | Set listUser(String roomId); 18 | 19 | Long countUser(String roomId); 20 | 21 | void removeUser(String roomId, String userId); 22 | } 23 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/service/RedisMessage.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.service; 2 | 3 | /** 4 | * @author chachae 5 | * @version v1.0 6 | * @date 2020/8/14 11:24 7 | */ 8 | public interface RedisMessage { 9 | 10 | /** 11 | * 接受信息 12 | * 13 | * @param message 消息 JSON 14 | */ 15 | void receiveMessage(String message); 16 | } 17 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/service/impl/MessageForwardServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.service.impl; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.chachae.webrtc.entity.ConnectionSystemUser; 5 | import com.chachae.webrtc.entity.Message; 6 | import com.chachae.webrtc.service.IMessageForwardService; 7 | import java.io.IOException; 8 | import java.util.Map; 9 | import java.util.Set; 10 | import org.springframework.stereotype.Service; 11 | import org.springframework.web.socket.TextMessage; 12 | import org.springframework.web.socket.WebSocketSession; 13 | 14 | /** 15 | * @author chachae 16 | * @version v1.0 17 | * @date 2020/8/13 23:16 18 | */ 19 | @Service 20 | public class MessageForwardServiceImpl implements IMessageForwardService { 21 | 22 | @Override 23 | public boolean sendMessageToOne(Message message, WebSocketSession session) { 24 | if (session == null || !session.isOpen()) { 25 | return false; 26 | } 27 | try { 28 | session.sendMessage(new TextMessage(JSON.toJSONString(message))); 29 | return true; 30 | } catch (IOException e) { 31 | e.printStackTrace(); 32 | return false; 33 | } 34 | } 35 | 36 | @Override 37 | public boolean sendMessageGroup(Message message, Set users, Map socketMap) { 38 | for (ConnectionSystemUser user : users) { 39 | // 默认不发送给本人 40 | if (!message.getUserId().equals(user.getUserId())) { 41 | this.sendMessageToOne(message, socketMap.get(user.getUserId())); 42 | } 43 | } 44 | return true; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/java/com/chachae/webrtc/service/impl/RoomServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.chachae.webrtc.service.impl; 2 | 3 | import com.chachae.webrtc.cache.RoomCache; 4 | import com.chachae.webrtc.entity.ConnectionSystemUser; 5 | import com.chachae.webrtc.service.IRoomService; 6 | import java.util.Set; 7 | import lombok.RequiredArgsConstructor; 8 | import org.springframework.stereotype.Service; 9 | 10 | /** 11 | * @author chachae 12 | * @version v1.0 13 | * @date 2020/8/13 21:15 14 | */ 15 | @Service 16 | @RequiredArgsConstructor 17 | public class RoomServiceImpl implements IRoomService { 18 | 19 | private final RoomCache roomCache; 20 | 21 | @Override 22 | public synchronized void enterRoom(String roomId, ConnectionSystemUser user) { 23 | user.setRoomId(roomId); 24 | roomCache.add(roomId, user); 25 | } 26 | 27 | @Override 28 | public void createRoom(String roomId) { 29 | roomCache.put(roomId, null); 30 | } 31 | 32 | @Override 33 | public Set listUser(String roomId) { 34 | return roomCache.get(roomId); 35 | } 36 | 37 | @Override 38 | public Long countUser(String roomId) { 39 | return roomCache.count(roomId); 40 | } 41 | 42 | @Override 43 | public void removeUser(String roomId, String userId) { 44 | roomCache.remove(roomId, userId); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/resources/ValidationMessages.properties: -------------------------------------------------------------------------------- 1 | required=\u4E0D\u80FD\u4E3A\u7A7A 2 | range=\u6709\u6548\u957f\u5ea6{min}\u5230{max}\u4e2a\u5b57\u7b26 3 | email=\u90ae\u7bb1\u683c\u5f0f\u4e0d\u5408\u6cd5 4 | mobile=\u624b\u673a\u53f7\u4e0d\u5408\u6cd5 5 | noMoreThan=\u957f\u5ea6\u4e0d\u80fd\u8d85\u8fc7{max}\u4e2a\u5b57\u7b26 6 | invalid=\u503c\u4e0d\u5408\u6cd5 -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9000 3 | 4 | spring: 5 | jackson: 6 | date-format: yyyy-MM-dd HH:mm:ss 7 | time-zone: GMT+8 8 | redis: 9 | database: 10 10 | host: ${redis.url:127.0.0.1} 11 | port: ${redis.port:6379} 12 | lettuce: 13 | pool: 14 | min-idle: 8 15 | max-idle: 500 16 | max-active: 2000 17 | max-wait: 10000 18 | timeout: 5000 -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/resources/server.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chachae/WebRTC-With-Socket/4cc7897a2c5570d730457b3068d11efbed892bb6/WebRTC-WebSocket/src/main/resources/server.keystore -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | WebSocket 连接 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 21 | 22 | 23 | 24 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 提交 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 提交 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 提交 65 | 66 | 67 | 68 | 69 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 提交 91 | 92 | 93 | 94 |

用户 {{ monitorForm.userId }} 的屏幕共享

95 | 97 |
98 | 99 | 392 | -------------------------------------------------------------------------------- /WebRTC-WebSocket/src/main/resources/templates/monitor.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 摄像头监控与屏幕共享 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 提交 34 | 35 | 36 | 37 |
38 | 40 |
41 |
42 | 43 | 256 | -------------------------------------------------------------------------------- /images/index-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chachae/WebRTC-With-Socket/4cc7897a2c5570d730457b3068d11efbed892bb6/images/index-1.png -------------------------------------------------------------------------------- /images/monitor-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chachae/WebRTC-With-Socket/4cc7897a2c5570d730457b3068d11efbed892bb6/images/monitor-2.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | pom 7 | 8 | 9 | WebRTC-WebSocket 10 | WebRTC-Netty 11 | WebRTC-Common 12 | Netty-WebSocket-Starter 13 | 14 | 15 | 16 | org.springframework.boot 17 | spring-boot-starter-parent 18 | 2.3.2.RELEASE 19 | 20 | 21 | 22 | com.chachae.webrtc 23 | WebRTC-One-Way-Monitor 24 | 1.0 25 | webrtc-application 26 | webrtc with websocket application 27 | 28 | 29 | 1.8 30 | 5.3.7 31 | 1.2.73 32 | 1.9 33 | 34 | 35 | 36 | 37 | 38 | 39 | org.springframework.boot 40 | spring-boot-devtools 41 | true 42 | 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-test 47 | test 48 | 49 | 50 | org.junit.vintage 51 | junit-vintage-engine 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | org.apache.maven.plugins 61 | maven-compiler-plugin 62 | 63 | ${java.version} 64 | ${java.version} 65 | 66 | 67 | 68 | org.apache.maven.plugins 69 | maven-surefire-plugin 70 | 71 | 72 | true 73 | 74 | 75 | 76 | 77 | 78 | 79 | --------------------------------------------------------------------------------