package test.r2.integ;
import com.linkedin.common.callback.Callback;
import com.linkedin.common.callback.FutureCallback;
import com.linkedin.common.util.None;
import com.linkedin.r2.filter.CompressionConfig;
import com.linkedin.r2.filter.FilterChains;
import com.linkedin.r2.filter.R2Constants;
import com.linkedin.r2.filter.compression.ClientCompressionFilter;
import com.linkedin.r2.filter.compression.EncodingType;
import com.linkedin.r2.filter.compression.ServerCompressionFilter;
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.RestRequestBuilder;
import com.linkedin.r2.message.rest.RestResponse;
import com.linkedin.r2.message.rest.RestResponseBuilder;
import com.linkedin.r2.message.rest.RestStatus;
import com.linkedin.r2.sample.Bootstrap;
import com.linkedin.r2.transport.common.Client;
import com.linkedin.r2.transport.common.RestRequestHandler;
import com.linkedin.r2.transport.common.TransportClientFactory;
import com.linkedin.r2.transport.common.bridge.client.TransportClientAdapter;
import com.linkedin.r2.transport.common.bridge.server.TransportDispatcher;
import com.linkedin.r2.transport.common.bridge.server.TransportDispatcherBuilder;
import com.linkedin.r2.transport.http.client.HttpClientFactory;
import com.linkedin.r2.transport.http.common.HttpProtocolVersion;
import com.linkedin.r2.transport.http.server.HttpJettyServer;
import com.linkedin.r2.transport.http.server.HttpServer;
import com.linkedin.r2.transport.http.server.HttpServerFactory;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* @auther Zhenkai Zhu
*/
public class TestRestCompressionEcho
{
protected static final int PORT = 11938;
private static final int THRESHOLD = 4096;
private static final boolean REST_OVER_STREAM = false;
protected static final long LARGE_BYTES_NUM = THRESHOLD * THRESHOLD;
protected static final long SMALL_BYTES_NUM = THRESHOLD - 1;
private static final URI ECHO_URI = URI.create("/echo");
protected final RestFilter _compressionFilter = new ServerCompressionFilter(EncodingType.values(), new CompressionConfig(THRESHOLD));
private HttpServer _server;
private List<TransportClientFactory> _clientFactories = new ArrayList<TransportClientFactory>();
private List<Client> _clients = new ArrayList<Client>();
private final HttpJettyServer.ServletType _servletType;
@Factory(dataProvider = "configs")
public TestRestCompressionEcho(HttpJettyServer.ServletType servletType)
{
_servletType = servletType;
}
@DataProvider
public static Object[][] configs()
{
return new Object[][] {{HttpJettyServer.ServletType.RAP}, {HttpJettyServer.ServletType.ASYNC_EVENT}};
}
@BeforeClass
public void setup() throws IOException
{
_server = getServerFactory().createH2cServer(PORT, getTransportDispatcher(), REST_OVER_STREAM);
_server.start();
}
@AfterClass
public void tearDown() throws Exception
{
for (Client client : _clients)
{
final FutureCallback<None> clientShutdownCallback = new FutureCallback<None>();
client.shutdown(clientShutdownCallback);
clientShutdownCallback.get();
}
for (TransportClientFactory factory : _clientFactories)
{
final FutureCallback<None> factoryShutdownCallback = new FutureCallback<None>();
factory.shutdown(factoryShutdownCallback);
factoryShutdownCallback.get();
}
if (_server != null) {
_server.stop();
_server.waitForStop();
}
}
protected HttpServerFactory getServerFactory()
{
return new HttpServerFactory(FilterChains.createRestChain(_compressionFilter), _servletType);
}
protected TransportDispatcher getTransportDispatcher()
{
return new TransportDispatcherBuilder(REST_OVER_STREAM)
.addRestHandler(ECHO_URI, new RestEchoHandler())
.build();
}
protected Map<String, String> getHttp1ClientProperties()
{
Map<String, String> clientProperties = new HashMap<String, String>();
clientProperties.put(HttpClientFactory.HTTP_PROTOCOL_VERSION, HttpProtocolVersion.HTTP_1_1.name());
clientProperties.put(HttpClientFactory.HTTP_MAX_RESPONSE_SIZE, String.valueOf(LARGE_BYTES_NUM * 2));
clientProperties.put(HttpClientFactory.HTTP_REQUEST_TIMEOUT, "60000");
return clientProperties;
}
protected Map<String, String> getHttp2ClientProperties()
{
Map<String, String> clientProperties = new HashMap<String, String>();
clientProperties.put(HttpClientFactory.HTTP_PROTOCOL_VERSION, HttpProtocolVersion.HTTP_2.name());
clientProperties.put(HttpClientFactory.HTTP_MAX_RESPONSE_SIZE, String.valueOf(LARGE_BYTES_NUM * 2));
clientProperties.put(HttpClientFactory.HTTP_REQUEST_TIMEOUT, "60000");
return clientProperties;
}
@DataProvider
public Object[][] compressionEchoData()
{
EncodingType[] encodings =
new EncodingType[]{
EncodingType.GZIP,
EncodingType.SNAPPY,
EncodingType.IDENTITY
};
Object[][] args = new Object[4 * encodings.length * encodings.length][2];
int cur = 0;
for (EncodingType requestEncoding : encodings)
{
for (EncodingType acceptEncoding : encodings)
{
RestFilter clientCompressionFilter =
new ClientCompressionFilter(requestEncoding,
new CompressionConfig(THRESHOLD),
new EncodingType[]{acceptEncoding},
new CompressionConfig(THRESHOLD),
Arrays.asList(new String[]{"*"}));
TransportClientFactory factory = new HttpClientFactory.Builder()
.setFilterChain(FilterChains.createRestChain(clientCompressionFilter))
.build();
Client http1Client = new TransportClientAdapter(factory.getClient(getHttp1ClientProperties()), REST_OVER_STREAM);
Client http2Client = new TransportClientAdapter(factory.getClient(getHttp2ClientProperties()), REST_OVER_STREAM);
args[cur][0] = http1Client;
args[cur][1] = LARGE_BYTES_NUM;
args[cur + 1][0] = http2Client;
args[cur + 1][1] = LARGE_BYTES_NUM;
cur += 2;
_clientFactories.add(factory);
_clients.add(http1Client);
_clients.add(http2Client);
}
}
// test data that won't trigger compression
for (EncodingType requestEncoding : encodings)
{
for (EncodingType acceptEncoding : encodings)
{
RestFilter clientCompressionFilter =
new ClientCompressionFilter(requestEncoding,
new CompressionConfig(THRESHOLD),
new EncodingType[]{acceptEncoding},
new CompressionConfig(THRESHOLD),
Arrays.asList(new String[]{"*"}));
TransportClientFactory factory = new HttpClientFactory.Builder()
.setFilterChain(FilterChains.createRestChain(clientCompressionFilter))
.build();
Client http1Client = new TransportClientAdapter(factory.getClient(getHttp1ClientProperties()), REST_OVER_STREAM);
Client http2Client = new TransportClientAdapter(factory.getClient(getHttp2ClientProperties()), REST_OVER_STREAM);
args[cur][0] = http1Client;
args[cur][1] = SMALL_BYTES_NUM;
args[cur + 1][0] = http2Client;
args[cur + 1][1] = SMALL_BYTES_NUM;
cur += 2;
_clientFactories.add(factory);
_clients.add(http1Client);
_clients.add(http2Client);
}
}
return args;
}
@Test(dataProvider = "compressionEchoData")
public void testResponseCompression(Client client, long bytes)
throws InterruptedException, TimeoutException, ExecutionException
{
RestRequestBuilder builder = new RestRequestBuilder((Bootstrap.createHttpURI(PORT, ECHO_URI)));
byte[] content = new byte[(int)bytes];
for (int i = 0; i < bytes; i++)
{
content[i] = (byte) (i % 256);
}
RestRequest request = builder.setEntity(content).build();
final FutureCallback<RestResponse> callback = new FutureCallback<RestResponse>();
RequestContext requestContext = new RequestContext();
// OPERATION is required to enabled response compression
requestContext.putLocalAttr(R2Constants.OPERATION, "get");
client.restRequest(request, requestContext, callback);
final RestResponse response = callback.get(60, TimeUnit.SECONDS);
Assert.assertEquals(response.getStatus(), RestStatus.OK);
Assert.assertEquals(response.getEntity().copyBytes(), content);
}
private static class RestEchoHandler implements RestRequestHandler
{
@Override
public void handleRequest(RestRequest request, RequestContext requestContext, final Callback<RestResponse> callback)
{
RestResponseBuilder builder = new RestResponseBuilder();
callback.onSuccess(builder.setEntity(request.getEntity()).build());
}
}
}