View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.websocket.common.extensions.compress;
20  
21  import java.nio.ByteBuffer;
22  
23  import org.eclipse.jetty.util.log.Log;
24  import org.eclipse.jetty.util.log.Logger;
25  import org.eclipse.jetty.websocket.api.BatchMode;
26  import org.eclipse.jetty.websocket.api.WriteCallback;
27  import org.eclipse.jetty.websocket.api.extensions.ExtensionConfig;
28  import org.eclipse.jetty.websocket.api.extensions.Frame;
29  import org.eclipse.jetty.websocket.common.OpCode;
30  
31  /**
32   * Per Message Deflate Compression extension for WebSocket.
33   * <p/>
34   * Attempts to follow <a href="https://tools.ietf.org/html/draft-ietf-hybi-permessage-compression-12">draft-ietf-hybi-permessage-compression-12</a>
35   */
36  public class PerMessageDeflateExtension extends CompressExtension
37  {
38      private static final Logger LOG = Log.getLogger(PerMessageDeflateExtension.class);
39  
40      private ExtensionConfig configRequested;
41      private ExtensionConfig configNegotiated;
42      private boolean incomingContextTakeover = true;
43      private boolean outgoingContextTakeover = true;
44      private boolean incomingCompressed;
45  
46      @Override
47      public String getName()
48      {
49          return "permessage-deflate";
50      }
51  
52      @Override
53      public void incomingFrame(Frame frame)
54      {
55          // Incoming frames are always non concurrent because
56          // they are read and parsed with a single thread, and
57          // therefore there is no need for synchronization.
58  
59          // This extension requires the RSV1 bit set only in the first frame.
60          // Subsequent continuation frames don't have RSV1 set, but are compressed.
61          if (frame.getType().isData())
62              incomingCompressed = frame.isRsv1();
63  
64          if (OpCode.isControlFrame(frame.getOpCode()) || !frame.hasPayload() || !incomingCompressed)
65          {
66              nextIncomingFrame(frame);
67              return;
68          }
69  
70          boolean appendTail = frame.isFin();
71          ByteBuffer payload = frame.getPayload();
72          int remaining = payload.remaining();
73          byte[] input = new byte[remaining + (appendTail ? TAIL_BYTES.length : 0)];
74          payload.get(input, 0, remaining);
75          if (appendTail)
76              System.arraycopy(TAIL_BYTES, 0, input, remaining, TAIL_BYTES.length);
77  
78          forwardIncoming(frame, decompress(input));
79  
80          if (frame.isFin())
81              incomingCompressed = false;
82      }
83  
84      @Override
85      protected void nextIncomingFrame(Frame frame)
86      {
87          if (frame.isFin() && !incomingContextTakeover)
88          {
89              LOG.debug("Incoming Context Reset");
90              getInflater().reset();
91          }
92          super.nextIncomingFrame(frame);
93      }
94  
95      @Override
96      protected void nextOutgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
97      {
98          if (frame.isFin() && !outgoingContextTakeover)
99          {
100             LOG.debug("Outgoing Context Reset");
101             getDeflater().reset();
102         }
103         super.nextOutgoingFrame(frame, callback, batchMode);
104     }
105     
106     @Override
107     int getRsvUseMode()
108     {
109         return RSV_USE_ONLY_FIRST;
110     }
111     
112     @Override
113     int getTailDropMode()
114     {
115         return TAIL_DROP_FIN_ONLY;
116     }
117 
118     @Override
119     public void setConfig(final ExtensionConfig config)
120     {
121         configRequested = new ExtensionConfig(config);
122         configNegotiated = new ExtensionConfig(config.getName());
123         
124         for (String key : config.getParameterKeys())
125         {
126             key = key.trim();
127             switch (key)
128             {
129                 case "client_max_window_bits":
130                 case "server_max_window_bits":
131                 {
132                     // Not supported by Jetty
133                     // Don't negotiate these parameters
134                     break;
135                 }
136                 case "client_no_context_takeover":
137                 {
138                     configNegotiated.setParameter("client_no_context_takeover");
139                     switch (getPolicy().getBehavior())
140                     {
141                         case CLIENT:
142                             incomingContextTakeover = false;
143                             break;
144                         case SERVER:
145                             outgoingContextTakeover = false;
146                             break;
147                     }
148                     break;
149                 }
150                 case "server_no_context_takeover":
151                 {
152                     configNegotiated.setParameter("server_no_context_takeover");
153                     switch (getPolicy().getBehavior())
154                     {
155                         case CLIENT:
156                             outgoingContextTakeover = false;
157                             break;
158                         case SERVER:
159                             incomingContextTakeover = false;
160                             break;
161                     }
162                     break;
163                 }
164                 default:
165                 {
166                     throw new IllegalArgumentException();
167                 }
168             }
169         }
170 
171         super.setConfig(configNegotiated);
172     }
173 
174     @Override
175     public String toString()
176     {
177         return String.format("%s[requested=%s,negotiated=%s]",
178                 getClass().getSimpleName(),
179                 configRequested.getParameterizedName(),
180                 configNegotiated.getParameterizedName());
181     }
182 }