├── .gitignore
├── LICENSE
├── README.md
├── README_zh.md
├── pom.xml
└── src
└── main
├── java
└── org
│ └── yeauty
│ ├── 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
│ ├── EndpointClassPathScanner.java
│ ├── HttpServerHandler.java
│ ├── ServerEndpointConfig.java
│ ├── ServerEndpointExporter.java
│ ├── WebSocketServerHandler.java
│ └── WebsocketServer.java
│ ├── support
│ ├── AntPathMatcherWrapper.java
│ ├── ByteMethodArgumentResolver.java
│ ├── DefaultPathMatcher.java
│ ├── EventMethodArgumentResolver.java
│ ├── HttpHeadersMethodArgumentResolver.java
│ ├── MethodArgumentResolver.java
│ ├── PathVariableMapMethodArgumentResolver.java
│ ├── PathVariableMethodArgumentResolver.java
│ ├── RequestParamMapMethodArgumentResolver.java
│ ├── RequestParamMethodArgumentResolver.java
│ ├── SessionMethodArgumentResolver.java
│ ├── TextMethodArgumentResolver.java
│ ├── ThrowableMethodArgumentResolver.java
│ └── WsPathMatcher.java
│ └── util
│ └── SslUtils.java
└── resources
└── META-INF
└── spring.factories
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled class file
2 | *.class
3 |
4 | # Log file
5 | *.log
6 |
7 | # BlueJ files
8 | *.ctxt
9 |
10 | # Mobile Tools for Java (J2ME)
11 | .mtj.tmp/
12 |
13 | # Package Files #
14 | *.jar
15 | *.war
16 | *.nar
17 | *.ear
18 | *.zip
19 | *.tar.gz
20 | *.rar
21 |
22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
23 | hs_err_pid*
24 | .idea
25 | *.iml
26 | target/
27 | .DS_Store
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | netty-websocket-spring-boot-starter [](http://www.apache.org/licenses/LICENSE-2.0.html)
2 | ===================================
3 |
4 | [中文文档](https://github.com/YeautyYE/netty-websocket-spring-boot-starter/blob/master/README_zh.md) (Chinese Docs)
5 |
6 | ### About
7 | netty-websocket-spring-boot-starter will help you develop WebSocket server by using Netty in spring-boot,it is easy to develop by using annotation like spring-websocket
8 |
9 | ### Requirement
10 | - jdk version 17
11 |
12 |
13 | ### Quick Start
14 |
15 | - add Dependencies:
16 |
17 | ```xml
18 |
19 | org.yeauty
20 | netty-websocket-spring-boot-starter
21 | 0.13.0
22 |
23 | ```
24 |
25 | - annotate `@ServerEndpoint` on endpoint class,and annotate `@BeforeHandshake`,`@OnOpen`,`@OnClose`,`@OnError`,`@OnMessage`,`@OnBinary`,`@OnEvent` on the method. e.g.
26 |
27 | ```java
28 | @ServerEndpoint(path = "/ws/{arg}")
29 | public class MyWebSocket {
30 |
31 | @BeforeHandshake
32 | public void handshake(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){
33 | session.setSubprotocols("stomp");
34 | if (!"ok".equals(req)){
35 | System.out.println("Authentication failed!");
36 | session.close();
37 | }
38 | }
39 |
40 | @OnOpen
41 | public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){
42 | System.out.println("new connection");
43 | System.out.println(req);
44 | }
45 |
46 | @OnClose
47 | public void onClose(Session session) throws IOException {
48 | System.out.println("one connection closed");
49 | }
50 |
51 | @OnError
52 | public void onError(Session session, Throwable throwable) {
53 | throwable.printStackTrace();
54 | }
55 |
56 | @OnMessage
57 | public void onMessage(Session session, String message) {
58 | System.out.println(message);
59 | session.sendText("Hello Netty!");
60 | }
61 |
62 | @OnBinary
63 | public void onBinary(Session session, byte[] bytes) {
64 | for (byte b : bytes) {
65 | System.out.println(b);
66 | }
67 | session.sendBinary(bytes);
68 | }
69 |
70 | @OnEvent
71 | public void onEvent(Session session, Object evt) {
72 | if (evt instanceof IdleStateEvent) {
73 | IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
74 | switch (idleStateEvent.state()) {
75 | case READER_IDLE:
76 | System.out.println("read idle");
77 | break;
78 | case WRITER_IDLE:
79 | System.out.println("write idle");
80 | break;
81 | case ALL_IDLE:
82 | System.out.println("all idle");
83 | break;
84 | default:
85 | break;
86 | }
87 | }
88 | }
89 |
90 | }
91 | ```
92 |
93 | - use Websocket client to connect `ws://127.0.0.1:80/ws/xxx`
94 |
95 |
96 | ### Annotation
97 | ###### @ServerEndpoint
98 | > declaring `ServerEndpointExporter` in Spring configuration,it will scan for WebSocket endpoints that be annotated with `ServerEndpoint` .
99 | > beans that be annotated with `ServerEndpoint` will be registered as a WebSocket endpoint.
100 | > all [configurations](#configuration) are inside this annotation ( e.g. `@ServerEndpoint("/ws")` )
101 |
102 | ###### @BeforeHandshake
103 | > when there is a connection accepted,the method annotated with `@BeforeHandshake` will be called
104 | > classes which be injected to the method are:Session,HttpHeaders...
105 |
106 | ###### @OnOpen
107 | > when there is a WebSocket connection completed,the method annotated with `@OnOpen` will be called
108 | > classes which be injected to the method are:Session,HttpHeaders...
109 |
110 | ###### @OnClose
111 | > when a WebSocket connection closed,the method annotated with `@OnClose` will be called
112 | > classes which be injected to the method are:Session
113 |
114 | ###### @OnError
115 | > when a WebSocket connection throw Throwable, the method annotated with `@OnError` will be called
116 | > classes which be injected to the method are:Session,Throwable
117 |
118 | ###### @OnMessage
119 | > when a WebSocket connection received a message,the method annotated with `@OnMessage` will be called
120 | > classes which be injected to the method are:Session,String
121 |
122 | ###### @OnBinary
123 | > when a WebSocket connection received the binary,the method annotated with `@OnBinary` will be called
124 | > classes which be injected to the method are:Session,byte[]
125 |
126 | ###### @OnEvent
127 | > when a WebSocket connection received the event of Netty,the method annotated with `@OnEvent` will be called
128 | > classes which be injected to the method are:Session,Object
129 |
130 | ### Configuration
131 | > all configurations are configured in `@ServerEndpoint`'s property
132 |
133 | | property | default | description
134 | |---|---|---
135 | |path|"/"|path of WebSocket can be aliased for `value`
136 | |host|"0.0.0.0"|host of WebSocket.`"0.0.0.0"` means all of local addresses
137 | |port|80|port of WebSocket。if the port equals to 0,it will use a random and available port(to get the port [Multi-Endpoint](#multi-endpoint))
138 | |bossLoopGroupThreads|0|num of threads in bossEventLoopGroup
139 | |workerLoopGroupThreads|0|num of threads in workerEventLoopGroup
140 | |useCompressionHandler|false|whether add WebSocketServerCompressionHandler to pipeline
141 | |optionConnectTimeoutMillis|30000|the same as `ChannelOption.CONNECT_TIMEOUT_MILLIS` in Netty
142 | |optionSoBacklog|128|the same as `ChannelOption.SO_BACKLOG` in Netty
143 | |childOptionWriteSpinCount|16|the same as `ChannelOption.WRITE_SPIN_COUNT` in Netty
144 | |childOptionWriteBufferHighWaterMark|64*1024|the same as `ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK` in Netty,but use `ChannelOption.WRITE_BUFFER_WATER_MARK` in fact.
145 | |childOptionWriteBufferLowWaterMark|32*1024|the same as `ChannelOption.WRITE_BUFFER_LOW_WATER_MARK` in Netty,but use `ChannelOption.WRITE_BUFFER_WATER_MARK` in fact.
146 | |childOptionSoRcvbuf|-1(mean not set)|the same as `ChannelOption.SO_RCVBUF` in Netty
147 | |childOptionSoSndbuf|-1(mean not set)|the same as `ChannelOption.SO_SNDBUF` in Netty
148 | |childOptionTcpNodelay|true|the same as `ChannelOption.TCP_NODELAY` in Netty
149 | |childOptionSoKeepalive|false|the same as `ChannelOption.SO_KEEPALIVE` in Netty
150 | |childOptionSoLinger|-1|the same as `ChannelOption.SO_LINGER` in Netty
151 | |childOptionAllowHalfClosure|false|the same as `ChannelOption.ALLOW_HALF_CLOSURE` in Netty
152 | |readerIdleTimeSeconds|0|the same as `readerIdleTimeSeconds` in `IdleStateHandler` and add `IdleStateHandler` to `pipeline` when it is not 0
153 | |writerIdleTimeSeconds|0|the same as `writerIdleTimeSeconds` in `IdleStateHandler` and add `IdleStateHandler` to `pipeline` when it is not 0
154 | |allIdleTimeSeconds|0|the same as `allIdleTimeSeconds` in `IdleStateHandler` and add `IdleStateHandler` to `pipeline` when it is not 0
155 | |maxFramePayloadLength|65536|Maximum allowable frame payload length.
156 | |useEventExecutorGroup|true|Whether to use another thread pool to perform time-consuming synchronous business logic
157 | |eventExecutorGroupThreads|16|num of threads in bossEventLoopGroup
158 | |sslKeyPassword|""(mean not set)|the same as `server.ssl.key-password` in spring-boot
159 | |sslKeyStore|""(mean not set)|the same as `server.ssl.key-store` in spring-boot
160 | |sslKeyStorePassword|""(mean not set)|the same as `server.ssl.key-store-password` in spring-boot
161 | |sslKeyStoreType|""(mean not set)|the same as `server.ssl.key-store-type` in spring-boot
162 | |sslTrustStore|""(mean not set)|the same as `server.ssl.trust-store` in spring-boot
163 | |sslTrustStorePassword|""(mean not set)|the same as `server.ssl.trust-store-password` in spring-boot
164 | |sslTrustStoreType|""(mean not set)|the same as `server.ssl.trust-store-type` in spring-boot
165 | |corsOrigins|{}(mean not set)|the same as `@CrossOrigin#origins` in spring-boot
166 | |corsAllowCredentials|""(mean not set)|the same as `@CrossOrigin#allowCredentials` in spring-boot
167 |
168 | ### Configuration by application.properties
169 | > You can get the configurate of `application.properties` by using `${...}` placeholders. for example:
170 |
171 | - first,use `${...}` in `@ServerEndpoint`
172 | ```java
173 | @ServerEndpoint(host = "${ws.host}",port = "${ws.port}")
174 | public class MyWebSocket {
175 | ...
176 | }
177 | ```
178 | - then configurate in `application.properties`
179 | ```
180 | ws.host=0.0.0.0
181 | ws.port=80
182 | ```
183 |
184 | ### Custom Favicon
185 | The way of configure favicon is the same as spring-boot.If `favicon.ico` is presented in the root of the classpath,it will be automatically used as the favicon of the application.the example is following:
186 | ```
187 | src/
188 | +- main/
189 | +- java/
190 | | +
191 | +- resources/
192 | +- favicon.ico
193 | ```
194 |
195 | ### Custom Error Pages
196 | The way of configure favicon is the same as spring-boot.you can add a file to an `/public/error`
197 | folder.The name of the error page should be the exact status code or a series mask.the example is following:
198 | ```
199 | src/
200 | +- main/
201 | +- java/
202 | | +
203 | +- resources/
204 | +- public/
205 | +- error/
206 | | +- 404.html
207 | | +- 5xx.html
208 | +-
209 | ```
210 |
211 | ### Multi Endpoint
212 | - base on [Quick-Start](#quick-start),use annotation `@ServerEndpoint` and `@Component` in classes which hope to become a endpoint.
213 | - you can get all socket addresses in `ServerEndpointExporter.getInetSocketAddressSet()`.
214 | - when there are different addresses(different host or different port) in WebSocket,they will use different `ServerBootstrap` instance.
215 | - when the addresses are the same,but path is different,they will use the same `ServerBootstrap` instance.
216 | - when multiple port of endpoint is 0 ,they will use the same random port
217 | - when multiple port of endpoint is the same as the path,host can't be set as "0.0.0.0",because it means it binds all of the addresses
218 |
219 | ---
220 | ### Change Log
221 |
222 | #### 0.8.0
223 |
224 | - Auto-Configuration
225 |
226 | #### 0.9.0
227 |
228 | - Support RESTful by `@PathVariable`
229 | - Get param by`@RequestParam` from query
230 | - Remove `ParameterMap` ,instead of `@RequestParam MultiValueMap`
231 | - Add `@BeforeHandshake` annotation,you can close the connect before handshake
232 | - Set sub-protocol in `@BeforeHandshake` event
233 | - Remove the `@Component` on endpoint class
234 | - Update `Netty` version to `4.1.44.Final`
235 |
236 | #### 0.9.1
237 |
238 | - Bug fixed : it was null when using `@RequestParam MultiValueMap` to get value
239 | - Update `Netty` version to `4.1.45.Final`
240 |
241 | #### 0.9.2
242 |
243 | - There are compatibility version under 0.8.0 that can configure the `ServerEndpointExporter` manully
244 |
245 | #### 0.9.3
246 |
247 | - Bug fixed :when there is no `@BeforeHandshake` , NullPointerException will appear
248 |
249 | #### 0.9.4
250 |
251 | - Bug fixed :when there is no `@BeforeHandshake` , `Session` in `OnOpen` is null
252 |
253 | #### 0.9.5
254 |
255 | - Bug fixed :`Throwable` in `OnError` event is null
256 |
257 | #### 0.10.0
258 |
259 | - Modified the default value of `bossLoopGroupThreads` to 1
260 | - Supports configuring `useEventExecutorGroup` to run synchronous and time-consuming business logic in EventExecutorGroup, so that the I/O thread is not blocked by a time-consuming task
261 | - SSL supported
262 | - CORS supported
263 | - Update `Netty` version to `4.1.49.Final`
264 |
265 | #### 0.11.0
266 |
267 | - When the `ServerEndpoint` class is proxied by CGLIB (as with AOP enhancement), it still works
268 |
269 | #### 0.12.0
270 |
271 | - `@enableWebSocket` adds the `scanBasePackages` attribute
272 | - `@serverEndpoint` no longer depends on `@Component`
273 | - Update `Netty` version to `4.1.67.Final`
274 |
275 | #### 0.13.0
276 |
277 | - Fixed the issue where WebSocket compression was not working.
278 | - Upgraded support for Spring Boot 3.
279 | - Included the client's `Sec-WebSocket-Protocol` in the response header.
280 | - When closing the connection, sends a `bye` command first instead of directly closing.
281 | - Updated `Netty` version to `4.1.118.Final`.
--------------------------------------------------------------------------------
/README_zh.md:
--------------------------------------------------------------------------------
1 | netty-websocket-spring-boot-starter [](http://www.apache.org/licenses/LICENSE-2.0.html)
2 | ===================================
3 |
4 | [English Docs](https://github.com/YeautyYE/netty-websocket-spring-boot-starter/blob/master/README.md)
5 |
6 | ### 简介
7 | 本项目帮助你在spring-boot中使用Netty来开发WebSocket服务器,并像spring-websocket的注解开发一样简单
8 |
9 | ### 要求
10 | - jdk版本为17
11 |
12 |
13 | ### 快速开始
14 |
15 | - 添加依赖:
16 |
17 | ```xml
18 |
19 | org.yeauty
20 | netty-websocket-spring-boot-starter
21 | 0.13.0
22 |
23 | ```
24 |
25 | - 在端点类上加上`@ServerEndpoint`注解,并在相应的方法上加上`@BeforeHandshake`、`@OnOpen`、`@OnClose`、`@OnError`、`@OnMessage`、`@OnBinary`、`@OnEvent`注解,样例如下:
26 |
27 | ```java
28 | @ServerEndpoint(path = "/ws/{arg}")
29 | public class MyWebSocket {
30 |
31 | @BeforeHandshake
32 | public void handshake(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){
33 | session.setSubprotocols("stomp");
34 | if (!"ok".equals(req)){
35 | System.out.println("Authentication failed!");
36 | session.close();
37 | }
38 | }
39 |
40 | @OnOpen
41 | public void onOpen(Session session, HttpHeaders headers, @RequestParam String req, @RequestParam MultiValueMap reqMap, @PathVariable String arg, @PathVariable Map pathMap){
42 | System.out.println("new connection");
43 | System.out.println(req);
44 | }
45 |
46 | @OnClose
47 | public void onClose(Session session) throws IOException {
48 | System.out.println("one connection closed");
49 | }
50 |
51 | @OnError
52 | public void onError(Session session, Throwable throwable) {
53 | throwable.printStackTrace();
54 | }
55 |
56 | @OnMessage
57 | public void onMessage(Session session, String message) {
58 | System.out.println(message);
59 | session.sendText("Hello Netty!");
60 | }
61 |
62 | @OnBinary
63 | public void onBinary(Session session, byte[] bytes) {
64 | for (byte b : bytes) {
65 | System.out.println(b);
66 | }
67 | session.sendBinary(bytes);
68 | }
69 |
70 | @OnEvent
71 | public void onEvent(Session session, Object evt) {
72 | if (evt instanceof IdleStateEvent) {
73 | IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
74 | switch (idleStateEvent.state()) {
75 | case READER_IDLE:
76 | System.out.println("read idle");
77 | break;
78 | case WRITER_IDLE:
79 | System.out.println("write idle");
80 | break;
81 | case ALL_IDLE:
82 | System.out.println("all idle");
83 | break;
84 | default:
85 | break;
86 | }
87 | }
88 | }
89 |
90 | }
91 | ```
92 |
93 | - 打开WebSocket客户端,连接到`ws://127.0.0.1:80/ws/xxx`
94 |
95 |
96 | ### 注解
97 | ###### @ServerEndpoint
98 | > 当ServerEndpointExporter类通过Spring配置进行声明并被使用,它将会去扫描带有@ServerEndpoint注解的类
99 | > 被注解的类将被注册成为一个WebSocket端点
100 | > 所有的[配置项](#%E9%85%8D%E7%BD%AE)都在这个注解的属性中 ( 如:`@ServerEndpoint("/ws")` )
101 |
102 | ###### @BeforeHandshake
103 | > 当有新的连接进入时,对该方法进行回调
104 | > 注入参数的类型:Session、HttpHeaders...
105 |
106 | ###### @OnOpen
107 | > 当有新的WebSocket连接完成时,对该方法进行回调
108 | > 注入参数的类型:Session、HttpHeaders...
109 |
110 | ###### @OnClose
111 | > 当有WebSocket连接关闭时,对该方法进行回调
112 | > 注入参数的类型:Session
113 |
114 | ###### @OnError
115 | > 当有WebSocket抛出异常时,对该方法进行回调
116 | > 注入参数的类型:Session、Throwable
117 |
118 | ###### @OnMessage
119 | > 当接收到字符串消息时,对该方法进行回调
120 | > 注入参数的类型:Session、String
121 |
122 | ###### @OnBinary
123 | > 当接收到二进制消息时,对该方法进行回调
124 | > 注入参数的类型:Session、byte[]
125 |
126 | ###### @OnEvent
127 | > 当接收到Netty的事件时,对该方法进行回调
128 | > 注入参数的类型:Session、Object
129 |
130 | ### 配置
131 | > 所有的配置项都在这个注解的属性中
132 |
133 | | 属性 | 默认值 | 说明
134 | |---|---|---
135 | |path|"/"|WebSocket的path,也可以用`value`来设置
136 | |host|"0.0.0.0"|WebSocket的host,`"0.0.0.0"`即是所有本地地址
137 | |port|80|WebSocket绑定端口号。如果为0,则使用随机端口(端口获取可见 [多端点服务](#%E5%A4%9A%E7%AB%AF%E7%82%B9%E6%9C%8D%E5%8A%A1))
138 | |bossLoopGroupThreads|0|bossEventLoopGroup的线程数
139 | |workerLoopGroupThreads|0|workerEventLoopGroup的线程数
140 | |useCompressionHandler|false|是否添加WebSocketServerCompressionHandler到pipeline
141 | |optionConnectTimeoutMillis|30000|与Netty的`ChannelOption.CONNECT_TIMEOUT_MILLIS`一致
142 | |optionSoBacklog|128|与Netty的`ChannelOption.SO_BACKLOG`一致
143 | |childOptionWriteSpinCount|16|与Netty的`ChannelOption.WRITE_SPIN_COUNT`一致
144 | |childOptionWriteBufferHighWaterMark|64*1024|与Netty的`ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK`一致,但实际上是使用`ChannelOption.WRITE_BUFFER_WATER_MARK`
145 | |childOptionWriteBufferLowWaterMark|32*1024|与Netty的`ChannelOption.WRITE_BUFFER_LOW_WATER_MARK`一致,但实际上是使用 `ChannelOption.WRITE_BUFFER_WATER_MARK`
146 | |childOptionSoRcvbuf|-1(即未设置)|与Netty的`ChannelOption.SO_RCVBUF`一致
147 | |childOptionSoSndbuf|-1(即未设置)|与Netty的`ChannelOption.SO_SNDBUF`一致
148 | |childOptionTcpNodelay|true|与Netty的`ChannelOption.TCP_NODELAY`一致
149 | |childOptionSoKeepalive|false|与Netty的`ChannelOption.SO_KEEPALIVE`一致
150 | |childOptionSoLinger|-1|与Netty的`ChannelOption.SO_LINGER`一致
151 | |childOptionAllowHalfClosure|false|与Netty的`ChannelOption.ALLOW_HALF_CLOSURE`一致
152 | |readerIdleTimeSeconds|0|与`IdleStateHandler`中的`readerIdleTimeSeconds`一致,并且当它不为0时,将在`pipeline`中添加`IdleStateHandler`
153 | |writerIdleTimeSeconds|0|与`IdleStateHandler`中的`writerIdleTimeSeconds`一致,并且当它不为0时,将在`pipeline`中添加`IdleStateHandler`
154 | |allIdleTimeSeconds|0|与`IdleStateHandler`中的`allIdleTimeSeconds`一致,并且当它不为0时,将在`pipeline`中添加`IdleStateHandler`
155 | |maxFramePayloadLength|65536|最大允许帧载荷长度
156 | |useEventExecutorGroup|true|是否使用另一个线程池来执行耗时的同步业务逻辑
157 | |eventExecutorGroupThreads|16|eventExecutorGroup的线程数
158 | |sslKeyPassword|""(即未设置)|与spring-boot的`server.ssl.key-password`一致
159 | |sslKeyStore|""(即未设置)|与spring-boot的`server.ssl.key-store`一致
160 | |sslKeyStorePassword|""(即未设置)|与spring-boot的`server.ssl.key-store-password`一致
161 | |sslKeyStoreType|""(即未设置)|与spring-boot的`server.ssl.key-store-type`一致
162 | |sslTrustStore|""(即未设置)|与spring-boot的`server.ssl.trust-store`一致
163 | |sslTrustStorePassword|""(即未设置)|与spring-boot的`server.ssl.trust-store-password`一致
164 | |sslTrustStoreType|""(即未设置)|与spring-boot的`server.ssl.trust-store-type`一致
165 | |corsOrigins|{}(即未设置)|与spring-boot的`@CrossOrigin#origins`一致
166 | |corsAllowCredentials|""(即未设置)|与spring-boot的`@CrossOrigin#allowCredentials`一致
167 |
168 | ### 通过application.properties进行配置
169 | > 所有参数皆可使用`${...}`占位符获取`application.properties`中的配置。如下:
170 |
171 | - 首先在`@ServerEndpoint`注解的属性中使用`${...}`占位符
172 | ```java
173 | @ServerEndpoint(host = "${ws.host}",port = "${ws.port}")
174 | public class MyWebSocket {
175 | ...
176 | }
177 | ```
178 | - 接下来即可在`application.properties`中配置
179 | ```
180 | ws.host=0.0.0.0
181 | ws.port=80
182 | ```
183 |
184 | ### 自定义Favicon
185 | 配置favicon的方式与spring-boot中完全一致。只需将`favicon.ico`文件放到classpath的根目录下即可。如下:
186 | ```
187 | src/
188 | +- main/
189 | +- java/
190 | | +
191 | +- resources/
192 | +- favicon.ico
193 | ```
194 |
195 | ### 自定义错误页面
196 | 配置自定义错误页面的方式与spring-boot中完全一致。你可以添加一个 `/public/error` 目录,错误页面将会是该目录下的静态页面,错误页面的文件名必须是准确的错误状态或者是一串掩码,如下:
197 | ```
198 | src/
199 | +- main/
200 | +- java/
201 | | +
202 | +- resources/
203 | +- public/
204 | +- error/
205 | | +- 404.html
206 | | +- 5xx.html
207 | +-
208 | ```
209 |
210 | ### 多端点服务
211 | - 在[快速启动](#%E5%BF%AB%E9%80%9F%E5%BC%80%E5%A7%8B)的基础上,在多个需要成为端点的类上使用`@ServerEndpoint`、`@Component`注解即可
212 | - 可通过`ServerEndpointExporter.getInetSocketAddressSet()`获取所有端点的地址
213 | - 当地址不同时(即host不同或port不同),使用不同的`ServerBootstrap`实例
214 | - 当地址相同,路径(path)不同时,使用同一个`ServerBootstrap`实例
215 | - 当多个端点服务的port为0时,将使用同一个随机的端口号
216 | - 当多个端点的port和path相同时,host不能设为`"0.0.0.0"`,因为`"0.0.0.0"`意味着绑定所有的host
217 |
218 | ---
219 | ### 更新日志
220 |
221 | #### 0.8.0
222 |
223 | - 自动装配
224 |
225 | #### 0.9.0
226 |
227 | - 通过`@PathVariable`支持RESTful风格中获取参数
228 | - 通过`@RequestParam`实现请求中query的获取参数
229 | - 移除原来的ParameterMap,用`@RequestParam MultiValueMap`代替
230 | - 新增 `@BeforeHandshake` 注解,可在握手之前对连接进行关闭
231 | - 在`@BeforeHandshake`事件中可设置子协议
232 | - 去掉配置端点类上的 `@Component`
233 | - 更新`Netty`版本到 `4.1.44.Final`
234 |
235 | #### 0.9.1
236 |
237 | - 修复bug:当使用`@RequestParam MultiValueMap`时获取的对象为null
238 | - 更新`Netty`版本到 `4.1.45.Final`
239 |
240 | #### 0.9.2
241 |
242 | - 兼容 0.8.0 以下版本,可以手动装配`ServerEndpointExporter`对象
243 |
244 | #### 0.9.3
245 |
246 | - 修复bug:当没有 `@BeforeHandshake`时会出现空指针异常
247 |
248 | #### 0.9.4
249 |
250 | - 修复bug:当没有 `@BeforeHandshake`时 `OnOpen`中的`Session`为null.
251 |
252 | #### 0.9.5
253 |
254 | - 修复bug:`OnError`事件中的`Throwable`为null.
255 |
256 | #### 0.10.0
257 |
258 | - 修改`bossLoopGroupThreads`默认值为1
259 | - 支持通过配置`useEventExecutorGroup`让同步且耗时的业务逻辑在EventExecutorGroup中执行,防止I/O线程被耗时的任务阻塞
260 | - 支持SSL
261 | - 支持跨域
262 | - 更新`Netty`版本到 `4.1.59.Final`
263 |
264 | #### 0.11.0
265 |
266 | - 当`ServerEndpoint`类被cglib代理时(如aop增强),仍能正常运行
267 |
268 | #### 0.12.0
269 |
270 | - `@EnableWebSocket`增加`scanBasePackages`属性
271 | - `@ServerEndpoint`不再依赖`@Component`
272 | - 更新`Netty`版本到 `4.1.67.Final`
273 |
274 | #### 0.13.0
275 |
276 | - 修复了无法进行WebSocket压缩的问题
277 | - 升级支持spring-boot3
278 | - 响应头带上前端的`Sec-WebSocket-Protocol`
279 | - 关闭连接时,会先发送`bye`命令,而不是直接close
280 | - 更新`Netty`版本到 `4.1.118.Final`
281 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | org.yeauty
8 | netty-websocket-spring-boot-starter
9 | 0.13.0
10 |
11 | netty-websocket-spring-boot-starter
12 |
13 | netty-websocket-spring-boot-starter is a Java WebSocket Framework based on Netty
14 |
15 | http://www.yeauty.org/
16 |
17 |
18 |
19 | The Apache Software License, Version 2.0
20 | http://www.apache.org/licenses/LICENSE-2.0.txt
21 |
22 |
23 |
24 |
25 |
26 | Yeauty
27 | YeautyYE@gmail.com
28 |
29 |
30 |
31 |
32 | scm:git:git://github.com/YeautyYE/netty-websocket-spring-boot-starter.git
33 | scm:git:ssh://github.com/YeautyYE/netty-websocket-spring-boot-starter.git
34 |
35 | https://github.com/YeautyYE/netty-websocket-spring-boot-starter
36 |
37 |
38 |
39 | 4.1.118.Final
40 | 3.3.5
41 |
42 |
43 |
44 |
45 | org.springframework.boot
46 | spring-boot-autoconfigure
47 | ${spring-boot.version}
48 | true
49 |
50 |
51 |
52 | io.netty
53 | netty-codec-http
54 | ${netty.version}
55 |
56 |
57 | io.netty
58 | netty-handler
59 | ${netty.version}
60 |
61 |
62 |
63 |
64 |
65 |
66 | release
67 |
68 |
69 |
70 |
71 | org.apache.maven.plugins
72 | maven-source-plugin
73 | 3.0.1
74 |
75 |
76 |
77 | jar-no-fork
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | org.apache.maven.plugins
86 | maven-javadoc-plugin
87 | 3.0.1
88 |
89 |
90 |
91 | jar
92 |
93 |
94 |
95 |
96 | -Xdoclint:none
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 | org.apache.maven.plugins
106 | maven-gpg-plugin
107 | 1.6
108 |
109 |
110 | verify
111 |
112 | sign
113 |
114 |
115 |
116 |
117 |
118 |
119 | org.sonatype.plugins
120 | nexus-staging-maven-plugin
121 | 1.6.13
122 | true
123 |
124 | ossrh
125 | https://oss.sonatype.org/
126 | true
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 | ossrh
135 | https://oss.sonatype.org/content/repositories/snapshots
136 |
137 |
138 | ossrh
139 | https://oss.sonatype.org/service/local/staging/deploy/maven2
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 | org.apache.maven.plugins
149 | maven-compiler-plugin
150 | 3.10.1
151 |
152 | 17
153 | 17
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/BeforeHandshake.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface BeforeHandshake {
11 | }
12 |
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/EnableWebSocket.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.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 | String[] scanBasePackages() default {};
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/NettyWebSocketSelector.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
4 | import org.springframework.context.annotation.Bean;
5 | import org.springframework.context.annotation.Configuration;
6 | import org.yeauty.standard.ServerEndpointExporter;
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 |
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/OnBinary.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface OnBinary {
11 | }
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/OnClose.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface OnClose {
11 | }
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/OnError.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface OnError {
11 | }
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/OnEvent.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface OnEvent {
11 | }
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/OnMessage.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface OnMessage {
11 | }
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/OnOpen.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.annotation;
2 |
3 | import java.lang.annotation.ElementType;
4 | import java.lang.annotation.Retention;
5 | import java.lang.annotation.RetentionPolicy;
6 | import java.lang.annotation.Target;
7 |
8 | @Retention(RetentionPolicy.RUNTIME)
9 | @Target(ElementType.METHOD)
10 | public @interface OnOpen {
11 | }
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/PathVariable.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.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 |
--------------------------------------------------------------------------------
/src/main/java/org/yeauty/annotation/RequestParam.java:
--------------------------------------------------------------------------------
1 | package org.yeauty.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 | *