1 /*
2 * Copyright 2019 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty5.handler.codec.http.websocketx;
17
18 import io.netty5.buffer.api.Buffer;
19 import io.netty5.buffer.api.BufferAllocator;
20 import io.netty5.util.CharsetUtil;
21 import io.netty5.util.internal.StringUtil;
22
23 import java.nio.charset.StandardCharsets;
24
25 /**
26 * Web Socket Frame for closing the connection.
27 */
28 public class CloseWebSocketFrame extends WebSocketFrame {
29 /**
30 * Creates a new empty close frame with closing status code and reason text
31 *
32 * @param allocator {@link BufferAllocator} to use for allocating data.
33 * @param status Status code as per <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
34 * example, <tt>1000</tt> indicates normal closure.
35 */
36 public CloseWebSocketFrame(BufferAllocator allocator, WebSocketCloseStatus status) {
37 this(allocator, requireValidStatusCode(status.code()), status.reasonText());
38 }
39
40 /**
41 * Creates a new empty close frame with closing status code and reason text
42 *
43 * @param allocator {@link BufferAllocator} to use for allocating data.
44 * @param status Status code as per <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For
45 * example, <tt>1000</tt> indicates normal closure.
46 * @param reasonText
47 * Reason text. Set to null if no text.
48 */
49 public CloseWebSocketFrame(BufferAllocator allocator, WebSocketCloseStatus status, String reasonText) {
50 this(allocator, requireValidStatusCode(status.code()), reasonText);
51 }
52
53 /**
54 * Creates a new empty close frame with closing status code and reason text
55 *
56 * @param allocator {@link BufferAllocator} to use for allocating data.
57 * @param statusCode Integer status code as per <a href=
58 * "https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For example, <tt>1000</tt> indicates normal
59 * closure.
60 * @param reasonText Reason text. Set to null if no text.
61 */
62 public CloseWebSocketFrame(BufferAllocator allocator, int statusCode, String reasonText) {
63 this(allocator, true, 0, requireValidStatusCode(statusCode), reasonText);
64 }
65
66 /**
67 * Creates a new close frame with no losing status code and no reason text
68 *
69 * @param allocator {@link BufferAllocator} to use for allocating data.
70 * @param finalFragment flag indicating if this frame is the final fragment
71 * @param rsv reserved bits used for protocol extensions.
72 */
73 public CloseWebSocketFrame(BufferAllocator allocator, boolean finalFragment, int rsv) {
74 this(finalFragment, rsv, allocator.allocate(0));
75 }
76
77 /**
78 * Creates a new close frame with closing status code and reason text
79 *
80 * @param finalFragment flag indicating if this frame is the final fragment
81 * @param rsv reserved bits used for protocol extensions
82 * @param statusCode Integer status code as per <a href=
83 * "https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. For example, <tt>1000</tt> indicates normal
84 * closure.
85 * @param reasonText Reason text. Set to null if no text.
86 */
87 public CloseWebSocketFrame(BufferAllocator allocator, boolean finalFragment, int rsv, int statusCode,
88 String reasonText) {
89 super(finalFragment, rsv, newBinaryData(allocator, requireValidStatusCode(statusCode), reasonText));
90 }
91
92 private static Buffer newBinaryData(BufferAllocator allocator, short statusCode, String reasonText) {
93 if (reasonText == null) {
94 reasonText = StringUtil.EMPTY_STRING;
95 }
96
97 final Buffer binaryData;
98 if (!reasonText.isEmpty()) {
99 byte[] reasonTextBytes = reasonText.getBytes(StandardCharsets.UTF_8);
100 binaryData = allocator.allocate(2 + reasonTextBytes.length);
101 binaryData.writeShort(statusCode);
102 binaryData.writeBytes(reasonTextBytes);
103 } else {
104 binaryData = allocator.allocate(2).writeShort(statusCode);
105 }
106
107 return binaryData;
108 }
109
110 /**
111 * Creates a new close frame
112 *
113 * @param finalFragment flag indicating if this frame is the final fragment
114 * @param rsv reserved bits used for protocol extensions
115 * @param binaryData the content of the frame. Must be 2 byte integer followed by optional UTF-8 encoded string.
116 */
117 public CloseWebSocketFrame(boolean finalFragment, int rsv, Buffer binaryData) {
118 super(finalFragment, rsv, binaryData);
119 }
120
121 private CloseWebSocketFrame(CloseWebSocketFrame copyFrom, Buffer data) {
122 super(copyFrom, data);
123 }
124
125 /**
126 * Returns the closing status code as per <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a>. If
127 * a status code is set, -1 is returned.
128 */
129 public int statusCode() {
130 Buffer binaryData = binaryData();
131 if (binaryData == null || binaryData.readableBytes() < 2) {
132 return -1;
133 }
134
135 return binaryData.getShort(binaryData.readerOffset());
136 }
137
138 /**
139 * Returns the reason text as per <a href="https://tools.ietf.org/html/rfc6455#section-7.4">RFC 6455</a> If a reason
140 * text is not supplied, an empty string is returned.
141 */
142 public String reasonText() {
143 Buffer binaryData = binaryData();
144 if (binaryData == null || binaryData.readableBytes() <= 2) {
145 return "";
146 }
147
148 int base = binaryData.readerOffset();
149 try {
150 binaryData.skipReadableBytes(2);
151 return binaryData.toString(CharsetUtil.UTF_8);
152 } finally {
153 binaryData.readerOffset(base);
154 }
155 }
156
157 @Override
158 protected WebSocketFrame receive(Buffer buf) {
159 return new CloseWebSocketFrame(this, buf);
160 }
161
162 static short requireValidStatusCode(int statusCode) {
163 if (WebSocketCloseStatus.isValidStatusCode(statusCode)) {
164 return (short) statusCode;
165 } else {
166 throw new IllegalArgumentException(
167 "WebSocket close status code does NOT comply with RFC-6455: " + statusCode);
168 }
169 }
170 }