src/java.net.http/share/classes/jdk/internal/net/http/websocket/FrameConsumer.java
branchhttp-client-branch
changeset 56092 fd85b2bf2b0d
parent 56089 42208b2f224e
equal deleted inserted replaced
56091:aedd6133e7a0 56092:fd85b2bf2b0d
       
     1 /*
       
     2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
       
     3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
       
     4  *
       
     5  * This code is free software; you can redistribute it and/or modify it
       
     6  * under the terms of the GNU General Public License version 2 only, as
       
     7  * published by the Free Software Foundation.  Oracle designates this
       
     8  * particular file as subject to the "Classpath" exception as provided
       
     9  * by Oracle in the LICENSE file that accompanied this code.
       
    10  *
       
    11  * This code is distributed in the hope that it will be useful, but WITHOUT
       
    12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
       
    13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
       
    14  * version 2 for more details (a copy is included in the LICENSE file that
       
    15  * accompanied this code).
       
    16  *
       
    17  * You should have received a copy of the GNU General Public License version
       
    18  * 2 along with this work; if not, write to the Free Software Foundation,
       
    19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
       
    20  *
       
    21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
       
    22  * or visit www.oracle.com if you need additional information or have any
       
    23  * questions.
       
    24  */
       
    25 
       
    26 package jdk.internal.net.http.websocket;
       
    27 
       
    28 import java.net.http.WebSocket.MessagePart;
       
    29 import jdk.internal.net.http.websocket.Frame.Opcode;
       
    30 
       
    31 import java.nio.ByteBuffer;
       
    32 import java.nio.CharBuffer;
       
    33 import java.nio.charset.CharacterCodingException;
       
    34 
       
    35 import static java.lang.String.format;
       
    36 import static java.nio.charset.StandardCharsets.UTF_8;
       
    37 import static java.util.Objects.requireNonNull;
       
    38 import static jdk.internal.net.http.common.Utils.dump;
       
    39 import static jdk.internal.net.http.websocket.StatusCodes.NO_STATUS_CODE;
       
    40 import static jdk.internal.net.http.websocket.StatusCodes.isLegalToReceiveFromServer;
       
    41 
       
    42 /*
       
    43  * Consumes frame parts and notifies a message consumer, when there is
       
    44  * sufficient data to produce a message, or part thereof.
       
    45  *
       
    46  * Data consumed but not yet translated is accumulated until it's sufficient to
       
    47  * form a message.
       
    48  */
       
    49 /* Non-final for testing purposes only */
       
    50 class FrameConsumer implements Frame.Consumer {
       
    51 
       
    52     private final static boolean DEBUG = false;
       
    53     private final MessageStreamConsumer output;
       
    54     private final UTF8AccumulatingDecoder decoder = new UTF8AccumulatingDecoder();
       
    55     private boolean fin;
       
    56     private Opcode opcode, originatingOpcode;
       
    57     private MessagePart part = MessagePart.WHOLE;
       
    58     private long payloadLen;
       
    59     private long unconsumedPayloadLen;
       
    60     private ByteBuffer binaryData;
       
    61 
       
    62     FrameConsumer(MessageStreamConsumer output) {
       
    63         this.output = requireNonNull(output);
       
    64     }
       
    65 
       
    66     /* Exposed for testing purposes only */
       
    67     MessageStreamConsumer getOutput() {
       
    68         return output;
       
    69     }
       
    70 
       
    71     @Override
       
    72     public void fin(boolean value) {
       
    73         if (DEBUG) {
       
    74             System.out.printf("Reading fin: %s%n", value);
       
    75         }
       
    76         fin = value;
       
    77     }
       
    78 
       
    79     @Override
       
    80     public void rsv1(boolean value) {
       
    81         if (DEBUG) {
       
    82             System.out.printf("Reading rsv1: %s%n", value);
       
    83         }
       
    84         if (value) {
       
    85             throw new FailWebSocketException("Unexpected rsv1 bit");
       
    86         }
       
    87     }
       
    88 
       
    89     @Override
       
    90     public void rsv2(boolean value) {
       
    91         if (DEBUG) {
       
    92             System.out.printf("Reading rsv2: %s%n", value);
       
    93         }
       
    94         if (value) {
       
    95             throw new FailWebSocketException("Unexpected rsv2 bit");
       
    96         }
       
    97     }
       
    98 
       
    99     @Override
       
   100     public void rsv3(boolean value) {
       
   101         if (DEBUG) {
       
   102             System.out.printf("Reading rsv3: %s%n", value);
       
   103         }
       
   104         if (value) {
       
   105             throw new FailWebSocketException("Unexpected rsv3 bit");
       
   106         }
       
   107     }
       
   108 
       
   109     @Override
       
   110     public void opcode(Opcode v) {
       
   111         if (DEBUG) {
       
   112             System.out.printf("Reading opcode: %s%n", v);
       
   113         }
       
   114         if (v == Opcode.PING || v == Opcode.PONG || v == Opcode.CLOSE) {
       
   115             if (!fin) {
       
   116                 throw new FailWebSocketException("Fragmented control frame  " + v);
       
   117             }
       
   118             opcode = v;
       
   119         } else if (v == Opcode.TEXT || v == Opcode.BINARY) {
       
   120             if (originatingOpcode != null) {
       
   121                 throw new FailWebSocketException(
       
   122                         format("Unexpected frame %s (fin=%s)", v, fin));
       
   123             }
       
   124             opcode = v;
       
   125             if (!fin) {
       
   126                 originatingOpcode = v;
       
   127             }
       
   128         } else if (v == Opcode.CONTINUATION) {
       
   129             if (originatingOpcode == null) {
       
   130                 throw new FailWebSocketException(
       
   131                         format("Unexpected frame %s (fin=%s)", v, fin));
       
   132             }
       
   133             opcode = v;
       
   134         } else {
       
   135             throw new FailWebSocketException("Unexpected opcode " + v);
       
   136         }
       
   137     }
       
   138 
       
   139     @Override
       
   140     public void mask(boolean value) {
       
   141         if (DEBUG) {
       
   142             System.out.printf("Reading mask: %s%n", value);
       
   143         }
       
   144         if (value) {
       
   145             throw new FailWebSocketException("Masked frame received");
       
   146         }
       
   147     }
       
   148 
       
   149     @Override
       
   150     public void payloadLen(long value) {
       
   151         if (DEBUG) {
       
   152             System.out.printf("Reading payloadLen: %s%n", value);
       
   153         }
       
   154         if (opcode.isControl()) {
       
   155             if (value > 125) {
       
   156                 throw new FailWebSocketException(
       
   157                         format("%s's payload length %s", opcode, value));
       
   158             }
       
   159             assert Opcode.CLOSE.isControl();
       
   160             if (opcode == Opcode.CLOSE && value == 1) {
       
   161                 throw new FailWebSocketException("Incomplete status code");
       
   162             }
       
   163         }
       
   164         payloadLen = value;
       
   165         unconsumedPayloadLen = value;
       
   166     }
       
   167 
       
   168     @Override
       
   169     public void maskingKey(int value) {
       
   170         // `FrameConsumer.mask(boolean)` is where a masked frame is detected and
       
   171         // reported on; `FrameConsumer.mask(boolean)` MUST be invoked before
       
   172         // this method;
       
   173         // So this method (`maskingKey`) is not supposed to be invoked while
       
   174         // reading a frame that has came from the server. If this method is
       
   175         // invoked, then it's an error in implementation, thus InternalError
       
   176         throw new InternalError();
       
   177     }
       
   178 
       
   179     @Override
       
   180     public void payloadData(ByteBuffer data) {
       
   181         if (DEBUG) {
       
   182             System.out.printf("Reading payloadData: %s%n", data);
       
   183         }
       
   184         unconsumedPayloadLen -= data.remaining();
       
   185         boolean isLast = unconsumedPayloadLen == 0;
       
   186         if (opcode.isControl()) {
       
   187             if (binaryData != null) { // An intermediate or the last chunk
       
   188                 binaryData.put(data);
       
   189             } else if (!isLast) { // The first chunk
       
   190                 int remaining = data.remaining();
       
   191                 // It shouldn't be 125, otherwise the next chunk will be of size
       
   192                 // 0, which is not what Reader promises to deliver (eager
       
   193                 // reading)
       
   194                 assert remaining < 125 : dump(remaining);
       
   195                 binaryData = ByteBuffer.allocate(125).put(data);
       
   196             } else { // The only chunk
       
   197                 binaryData = ByteBuffer.allocate(data.remaining()).put(data);
       
   198             }
       
   199         } else {
       
   200             part = determinePart(isLast);
       
   201             boolean text = opcode == Opcode.TEXT || originatingOpcode == Opcode.TEXT;
       
   202             if (!text) {
       
   203                 output.onBinary(data.slice(), part);
       
   204                 data.position(data.limit()); // Consume
       
   205             } else {
       
   206                 boolean binaryNonEmpty = data.hasRemaining();
       
   207                 CharBuffer textData;
       
   208                 try {
       
   209                     textData = decoder.decode(data, part == MessagePart.WHOLE || part == MessagePart.LAST);
       
   210                 } catch (CharacterCodingException e) {
       
   211                     throw new FailWebSocketException(
       
   212                             "Invalid UTF-8 in frame " + opcode, StatusCodes.NOT_CONSISTENT)
       
   213                             .initCause(e);
       
   214                 }
       
   215                 if (!(binaryNonEmpty && !textData.hasRemaining())) {
       
   216                     // If there's a binary data, that result in no text, then we
       
   217                     // don't deliver anything
       
   218                     output.onText(textData, part);
       
   219                 }
       
   220             }
       
   221         }
       
   222     }
       
   223 
       
   224     @Override
       
   225     public void endFrame() {
       
   226         if (DEBUG) {
       
   227             System.out.println("End frame");
       
   228         }
       
   229         if (opcode.isControl()) {
       
   230             binaryData.flip();
       
   231         }
       
   232         switch (opcode) {
       
   233             case CLOSE:
       
   234                 char statusCode = NO_STATUS_CODE;
       
   235                 String reason = "";
       
   236                 if (payloadLen != 0) {
       
   237                     int len = binaryData.remaining();
       
   238                     assert 2 <= len && len <= 125 : dump(len, payloadLen);
       
   239                     statusCode = binaryData.getChar();
       
   240                     if (!isLegalToReceiveFromServer(statusCode)) {
       
   241                         throw new FailWebSocketException(
       
   242                                 "Illegal status code: " + statusCode);
       
   243                     }
       
   244                     try {
       
   245                         reason = UTF_8.newDecoder().decode(binaryData).toString();
       
   246                     } catch (CharacterCodingException e) {
       
   247                         throw new FailWebSocketException("Illegal close reason")
       
   248                                 .initCause(e);
       
   249                     }
       
   250                 }
       
   251                 output.onClose(statusCode, reason);
       
   252                 break;
       
   253             case PING:
       
   254                 output.onPing(binaryData);
       
   255                 binaryData = null;
       
   256                 break;
       
   257             case PONG:
       
   258                 output.onPong(binaryData);
       
   259                 binaryData = null;
       
   260                 break;
       
   261             default:
       
   262                 assert opcode == Opcode.TEXT || opcode == Opcode.BINARY
       
   263                         || opcode == Opcode.CONTINUATION : dump(opcode);
       
   264                 if (fin) {
       
   265                     // It is always the last chunk:
       
   266                     // either TEXT(FIN=TRUE)/BINARY(FIN=TRUE) or CONT(FIN=TRUE)
       
   267                     originatingOpcode = null;
       
   268                 }
       
   269                 break;
       
   270         }
       
   271         payloadLen = 0;
       
   272         opcode = null;
       
   273     }
       
   274 
       
   275     private MessagePart determinePart(boolean isLast) {
       
   276         boolean lastChunk = fin && isLast;
       
   277         switch (part) {
       
   278             case LAST:
       
   279             case WHOLE:
       
   280                 return lastChunk ? MessagePart.WHOLE : MessagePart.FIRST;
       
   281             case FIRST:
       
   282             case PART:
       
   283                 return lastChunk ? MessagePart.LAST : MessagePart.PART;
       
   284             default:
       
   285                 throw new InternalError(String.valueOf(part));
       
   286         }
       
   287     }
       
   288 }