com.signalfx.shaded.jetty.websocket.common.extensions.compress.PerMessageDeflateExtension Maven / Gradle / Ivy
Show all versions of signalfx-java Show documentation
//
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package com.signalfx.shaded.jetty.websocket.common.extensions.compress;
import java.nio.ByteBuffer;
import java.util.zip.DataFormatException;
import com.signalfx.shaded.jetty.util.log.Log;
import com.signalfx.shaded.jetty.util.log.Logger;
import com.signalfx.shaded.jetty.websocket.api.BadPayloadException;
import com.signalfx.shaded.jetty.websocket.api.BatchMode;
import com.signalfx.shaded.jetty.websocket.api.ProtocolException;
import com.signalfx.shaded.jetty.websocket.api.WriteCallback;
import com.signalfx.shaded.jetty.websocket.api.extensions.ExtensionConfig;
import com.signalfx.shaded.jetty.websocket.api.extensions.Frame;
import com.signalfx.shaded.jetty.websocket.common.OpCode;
/**
* Per Message Deflate Compression extension for WebSocket.
*
* Attempts to follow Compression Extensions for WebSocket
*/
public class PerMessageDeflateExtension extends CompressExtension
{
private static final Logger LOG = Log.getLogger(PerMessageDeflateExtension.class);
private ExtensionConfig configRequested;
private ExtensionConfig configNegotiated;
private boolean incomingContextTakeover = true;
private boolean outgoingContextTakeover = true;
private boolean incomingCompressed;
@Override
public String getName()
{
return "permessage-deflate";
}
@Override
public void incomingFrame(Frame frame)
{
// Incoming frames are always non concurrent because
// they are read and parsed with a single thread, and
// therefore there is no need for synchronization.
// This extension requires the RSV1 bit set only in the first frame.
// Subsequent continuation frames don't have RSV1 set, but are compressed.
if (frame.getType().isData())
{
incomingCompressed = frame.isRsv1();
}
if (OpCode.isControlFrame(frame.getOpCode()) || !incomingCompressed)
{
nextIncomingFrame(frame);
return;
}
if (frame.getOpCode() == OpCode.CONTINUATION && frame.isRsv1())
{
// Per RFC7692 we MUST Fail the websocket connection
throw new ProtocolException("Invalid RSV1 set on permessage-deflate CONTINUATION frame");
}
try (ByteAccumulator accumulator = newByteAccumulator())
{
ByteBuffer payload = frame.getPayload();
decompress(accumulator, payload);
if (frame.isFin())
{
decompress(accumulator, TAIL_BYTES_BUF.slice());
}
forwardIncoming(frame, accumulator);
}
catch (DataFormatException e)
{
throw new BadPayloadException(e);
}
if (frame.isFin())
incomingCompressed = false;
}
@Override
protected void nextIncomingFrame(Frame frame)
{
if (frame.isFin() && !incomingContextTakeover)
{
if (LOG.isDebugEnabled())
LOG.debug("Incoming Context Reset");
decompressCount.set(0);
getInflater().reset();
}
super.nextIncomingFrame(frame);
}
@Override
protected void nextOutgoingFrame(Frame frame, WriteCallback callback, BatchMode batchMode)
{
if (frame.isFin() && !outgoingContextTakeover)
{
if (LOG.isDebugEnabled())
LOG.debug("Outgoing Context Reset");
getDeflater().reset();
}
super.nextOutgoingFrame(frame, callback, batchMode);
}
@Override
int getRsvUseMode()
{
return RSV_USE_ONLY_FIRST;
}
@Override
int getTailDropMode()
{
return TAIL_DROP_FIN_ONLY;
}
@Override
public void setConfig(final ExtensionConfig config)
{
configRequested = new ExtensionConfig(config);
configNegotiated = new ExtensionConfig(config.getName());
for (String key : config.getParameterKeys())
{
key = key.trim();
switch (key)
{
case "client_max_window_bits":
case "server_max_window_bits":
{
// Not supported by Jetty
// Don't negotiate these parameters
break;
}
case "client_no_context_takeover":
{
configNegotiated.setParameter("client_no_context_takeover");
switch (getPolicy().getBehavior())
{
case CLIENT:
incomingContextTakeover = false;
break;
case SERVER:
outgoingContextTakeover = false;
break;
}
break;
}
case "server_no_context_takeover":
{
configNegotiated.setParameter("server_no_context_takeover");
switch (getPolicy().getBehavior())
{
case CLIENT:
outgoingContextTakeover = false;
break;
case SERVER:
incomingContextTakeover = false;
break;
}
break;
}
default:
{
throw new IllegalArgumentException();
}
}
}
if (LOG.isDebugEnabled())
LOG.debug("config: outgoingContextTakeover={}, incomingContextTakeover={} : {}", outgoingContextTakeover, incomingContextTakeover, this);
super.setConfig(configNegotiated);
}
@Override
public String toString()
{
return String.format("%s[requested=\"%s\", negotiated=\"%s\"]",
getClass().getSimpleName(),
configRequested.getParameterizedName(),
configNegotiated.getParameterizedName());
}
}