├── .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 extends Annotation> 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 | 
4 | 
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 |