View Javadoc
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 }