org.mockito
mockito-core
diff --git a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
index 1e4adeeff300..5ec38267770b 100644
--- a/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
+++ b/hbase-http/src/main/java/org/apache/hadoop/hbase/http/HttpServer.java
@@ -77,6 +77,7 @@
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.server.handler.RequestLogHandler;
+import org.eclipse.jetty.server.handler.gzip.GzipHandler;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.servlet.FilterMapping;
@@ -541,6 +542,7 @@ private HttpServer(final Builder b) throws IOException {
this.findPort = b.findPort;
this.authenticationEnabled = b.securityEnabled;
initializeWebServer(b.name, b.hostName, b.conf, b.pathSpecs, b);
+ this.webServer.setHandler(buildGzipHandler(this.webServer.getHandler()));
}
private void initializeWebServer(String name, String hostName,
@@ -628,6 +630,23 @@ private static WebAppContext createWebAppContext(String name,
return ctx;
}
+ /**
+ * Construct and configure an instance of {@link GzipHandler}. With complex
+ * multi-{@link WebAppContext} configurations, it's easiest to apply this handler directly to the
+ * instance of {@link Server} near the end of its configuration, something like
+ *
+ * Server server = new Server();
+ * //...
+ * server.setHandler(buildGzipHandler(server.getHandler()));
+ * server.start();
+ *
+ */
+ public static GzipHandler buildGzipHandler(final Handler wrapped) {
+ final GzipHandler gzipHandler = new GzipHandler();
+ gzipHandler.setHandler(wrapped);
+ return gzipHandler;
+ }
+
private static void addNoCacheFilter(WebAppContext ctxt) {
defineFilter(ctxt, NO_CACHE_FILTER, NoCacheFilter.class.getName(),
Collections. emptyMap(), new String[] { "/*" });
diff --git a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java
index cf24ceb8c155..1082bcba77bd 100644
--- a/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java
+++ b/hbase-http/src/test/java/org/apache/hadoop/hbase/http/TestHttpServer.java
@@ -1,4 +1,4 @@
-/**
+/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
@@ -17,11 +17,16 @@
*/
package org.apache.hadoop.hbase.http;
+import static org.hamcrest.Matchers.greaterThan;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
+import java.nio.CharBuffer;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
@@ -55,8 +60,15 @@
import org.apache.hadoop.security.ShellBasedUnixGroupsMapping;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.security.authorize.AccessControlList;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpHeaders;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ajax.JSON;
+import org.hamcrest.MatcherAssert;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.BeforeClass;
@@ -275,6 +287,60 @@ public void testContentTypes() throws Exception {
// assertEquals("text/html; charset=utf-8", conn.getContentType());
}
+ @Test
+ public void testNegotiatesEncodingGzip() throws IOException {
+ final InputStream stream = ClassLoader.getSystemResourceAsStream("webapps/static/test.css");
+ assertNotNull(stream);
+ final String sourceContent = readFully(stream);
+
+ try (final CloseableHttpClient client = HttpClients.createMinimal()) {
+ final HttpGet request = new HttpGet(new URL(baseUrl, "/static/test.css").toString());
+
+ request.setHeader(HttpHeaders.ACCEPT_ENCODING, null);
+ final long unencodedContentLength;
+ try (final CloseableHttpResponse response = client.execute(request)) {
+ final HttpEntity entity = response.getEntity();
+ assertNotNull(entity);
+ assertNull(entity.getContentEncoding());
+ unencodedContentLength = entity.getContentLength();
+ MatcherAssert.assertThat(unencodedContentLength, greaterThan(0L));
+ final String unencodedEntityBody = readFully(entity.getContent());
+ assertEquals(sourceContent, unencodedEntityBody);
+ }
+
+ request.setHeader(HttpHeaders.ACCEPT_ENCODING, "gzip");
+ final long encodedContentLength;
+ try (final CloseableHttpResponse response = client.execute(request)) {
+ final HttpEntity entity = response.getEntity();
+ assertNotNull(entity);
+ assertNotNull(entity.getContentEncoding());
+ assertEquals("gzip", entity.getContentEncoding().getValue());
+ encodedContentLength = entity.getContentLength();
+ MatcherAssert.assertThat(encodedContentLength, greaterThan(0L));
+ final String encodedEntityBody = readFully(entity.getContent());
+ // the encoding/decoding process, as implemented in this specific combination of dependency
+ // versions, does not perfectly preserve trailing whitespace. thus, `trim()`.
+ assertEquals(sourceContent.trim(), encodedEntityBody.trim());
+ }
+ MatcherAssert.assertThat(unencodedContentLength, greaterThan(encodedContentLength));
+ }
+ }
+
+ private static String readFully(final InputStream input) throws IOException {
+ // TODO: when the time comes, delete me and replace with a JDK11 IO helper API.
+ try (final BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
+ final StringBuilder sb = new StringBuilder();
+ final CharBuffer buffer = CharBuffer.allocate(1024 * 2);
+ while (reader.read(buffer) > 0) {
+ sb.append(buffer);
+ buffer.clear();
+ }
+ return sb.toString();
+ } finally {
+ input.close();
+ }
+ }
+
/**
* Dummy filter that mimics as an authentication filter. Obtains user identity
* from the request parameter user.name. Wraps around the request so that
diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
index d18822960547..78c982c14c8b 100644
--- a/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
+++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
@@ -91,6 +91,7 @@
import org.apache.hadoop.hbase.executor.ExecutorType;
import org.apache.hadoop.hbase.favored.FavoredNodesManager;
import org.apache.hadoop.hbase.favored.FavoredNodesPromoter;
+import org.apache.hadoop.hbase.http.HttpServer;
import org.apache.hadoop.hbase.http.InfoServer;
import org.apache.hadoop.hbase.ipc.CoprocessorRpcUtils;
import org.apache.hadoop.hbase.ipc.RpcServer;
@@ -619,7 +620,8 @@ private int putUpJettyServer() throws IOException {
if (infoPort < 0 || infoServer == null) {
return -1;
}
- if(infoPort == infoServer.getPort()) {
+ if (infoPort == infoServer.getPort()) {
+ // server is already running
return infoPort;
}
final String addr = conf.get("hbase.master.info.bindAddress", "0.0.0.0");
@@ -641,6 +643,7 @@ private int putUpJettyServer() throws IOException {
connector.setPort(infoPort);
masterJettyServer.addConnector(connector);
masterJettyServer.setStopAtShutdown(true);
+ masterJettyServer.setHandler(HttpServer.buildGzipHandler(masterJettyServer.getHandler()));
final String redirectHostname =
StringUtils.isBlank(useThisHostnameInstead) ? null : useThisHostnameInstead;