Package com.linkedin.r2.filter.compression

Source Code of com.linkedin.r2.filter.compression.ServerCompressionFilter

/*
   Copyright (c) 2013 LinkedIn Corp.

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package com.linkedin.r2.filter.compression;

import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.linkedin.r2.filter.Filter;
import com.linkedin.r2.filter.NextFilter;
import com.linkedin.r2.filter.message.rest.RestFilter;
import com.linkedin.r2.message.RequestContext;
import com.linkedin.r2.message.rest.RestRequest;
import com.linkedin.r2.message.rest.RestResponse;
import com.linkedin.r2.message.rest.RestResponseBuilder;
import com.linkedin.r2.transport.http.common.HttpConstants;

/**
*
* Filter class for server to negotiate acceptable compression formats from clients
* and compresses the response with the relevant headers accordingly.
* @author erli
*
*/
public class ServerCompressionFilter implements Filter, RestFilter
{
  private static final Logger LOG = LoggerFactory.getLogger(ServerCompressionFilter.class);

  private final Set<EncodingType> _supportedEncoding;

  /**
   * Instantiates an empty compression filter that does no compression.
   */
  public ServerCompressionFilter()
  {
    this(new EncodingType[0]);
  }

  /** Takes a comma delimited string containing standard
   * HTTP encoding headers and instantiates server compression
   * support for the said encoding types.
   * @param acceptedFilters
   */
  public ServerCompressionFilter(String acceptedFilters)
  {
    this(AcceptEncoding.parseAcceptEncoding(acceptedFilters));
  }

  /** Instantiates a compression filter
   * that supports the compression methods in the given set in argument.
   * @param supportedEncoding
   */
  public ServerCompressionFilter(EncodingType[] supportedEncoding)
  {
    _supportedEncoding = new HashSet<EncodingType>(Arrays.asList(supportedEncoding));
    _supportedEncoding.add(EncodingType.IDENTITY);
    _supportedEncoding.add(EncodingType.ANY);
  }

  /**
   * Handles compression tasks for incoming requests
   */
  @Override
  public void onRestRequest(RestRequest req, RequestContext requestContext,
                            Map<String, String> wireAttrs,
                            NextFilter<RestRequest, RestResponse> nextFilter)
  {
    try
    {
      //Check if the request is compressed, if so, decompress
      String requestCompression = req.getHeader(HttpConstants.CONTENT_ENCODING);

      if (requestCompression != null)
      {
        //This must be a specific compression type other than *
        EncodingType encoding = EncodingType.get(requestCompression.trim().toLowerCase());
        if (encoding == null || encoding == EncodingType.ANY)
        {
          //NOTE: this is going to be thrown up, but this isn't quite the right type of exception
          //Will change to proper type when rest.li supports centralized filter exception handling
          throw new RuntimeException(CompressionConstants.UNSUPPORTED_ENCODING
                                     + requestCompression);
        }

        //Process the correct compression types only
        if (encoding.hasCompressor())
        {
          byte[] decompressedContent = encoding.getCompressor().inflate(req.getEntity().asInputStream());
          req = req.builder().setEntity(decompressedContent).build();
        }
      }

      //Get client support for compression and flag compress if need be
      String responseCompression = req.getHeader(HttpConstants.ACCEPT_ENCODING);
      if (responseCompression == null)
      {
        responseCompression = ""; //Only permit identity
      }

      requestContext.putLocalAttr(HttpConstants.ACCEPT_ENCODING, responseCompression);
    }
    catch (CompressionException e)
    {
      LOG.error(e.getMessage(), e.getCause());
    }

    nextFilter.onRequest(req, requestContext, wireAttrs);
  }

  /**
   * Optionally compresses outgoing response
   * */
  @Override
  public void onRestResponse(RestResponse res, RequestContext requestContext,
                             Map<String, String> wireAttrs,
                             NextFilter<RestRequest, RestResponse> nextFilter)
  {
    try
    {
      if (res.getEntity().length() > 0)
      {
        String responseCompression = (String) requestContext.getLocalAttr(HttpConstants.ACCEPT_ENCODING);
        if (responseCompression == null)
        {
          throw new CompressionException(CompressionConstants.UNKNOWN_ENCODING);
        }

        List<AcceptEncoding> parsedEncodings = AcceptEncoding.parseAcceptEncodingHeader(responseCompression, _supportedEncoding);
        EncodingType selectedEncoding = AcceptEncoding.chooseBest(parsedEncodings);

        //Check if there exists an acceptable encoding
        if (selectedEncoding != null)
        {
          //NOTE: this is sort of problematic and is mirrored in 3 other places.
          //ByteBuffer from res.getEntity() is read only, and it's awkward for
          //compressor to return a sensible value for identity
          if (selectedEncoding.hasCompressor())
          {
            Compressor compressor = selectedEncoding.getCompressor();
            byte[] compressed = compressor.deflate(res.getEntity().asInputStream());

            if (compressed.length < res.getEntity().length())
            {
              RestResponseBuilder resCompress = res.builder();
              resCompress.addHeaderValue(HttpConstants.CONTENT_ENCODING, compressor.getContentEncodingName());
              resCompress.setEntity(compressed);
              res = resCompress.build();
            }
          }
        }
        else
        {
          //Not acceptable encoding status
          res = res.builder().setStatus(HttpConstants.NOT_ACCEPTABLE).setEntity(new byte[0]).build();
        }
      }
    }
    catch (CompressionException e)
    {
      LOG.error(e.getMessage(), e.getCause());
    }

    nextFilter.onResponse(res, requestContext, wireAttrs);
  }


  @Override
  public void onRestError(Throwable ex, RequestContext requestContext,
                          Map<String, String> wireAttrs,
                          NextFilter<RestRequest, RestResponse> nextFilter)
  {
    nextFilter.onError(ex, requestContext, wireAttrs);
  }
}
TOP

Related Classes of com.linkedin.r2.filter.compression.ServerCompressionFilter

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.