2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
19 package org
.apache
.hadoop
.hbase
.rest
;
21 import com
.fasterxml
.jackson
.jaxrs
.json
.JacksonJaxbJsonProvider
;
22 import java
.lang
.management
.ManagementFactory
;
23 import java
.util
.ArrayList
;
24 import java
.util
.EnumSet
;
25 import java
.util
.List
;
27 import java
.util
.concurrent
.ArrayBlockingQueue
;
28 import javax
.servlet
.DispatcherType
;
29 import org
.apache
.commons
.lang3
.ArrayUtils
;
30 import org
.apache
.commons
.lang3
.StringUtils
;
31 import org
.apache
.hadoop
.conf
.Configuration
;
32 import org
.apache
.hadoop
.hbase
.HBaseConfiguration
;
33 import org
.apache
.hadoop
.hbase
.HBaseInterfaceAudience
;
34 import org
.apache
.hadoop
.hbase
.http
.ClickjackingPreventionFilter
;
35 import org
.apache
.hadoop
.hbase
.http
.HttpServerUtil
;
36 import org
.apache
.hadoop
.hbase
.http
.InfoServer
;
37 import org
.apache
.hadoop
.hbase
.http
.SecurityHeadersFilter
;
38 import org
.apache
.hadoop
.hbase
.log
.HBaseMarkers
;
39 import org
.apache
.hadoop
.hbase
.rest
.filter
.AuthFilter
;
40 import org
.apache
.hadoop
.hbase
.rest
.filter
.GzipFilter
;
41 import org
.apache
.hadoop
.hbase
.rest
.filter
.RestCsrfPreventionFilter
;
42 import org
.apache
.hadoop
.hbase
.security
.UserProvider
;
43 import org
.apache
.hadoop
.hbase
.util
.DNS
;
44 import org
.apache
.hadoop
.hbase
.util
.EnvironmentEdgeManager
;
45 import org
.apache
.hadoop
.hbase
.util
.Pair
;
46 import org
.apache
.hadoop
.hbase
.util
.ReflectionUtils
;
47 import org
.apache
.hadoop
.hbase
.util
.Strings
;
48 import org
.apache
.hadoop
.hbase
.util
.VersionInfo
;
49 import org
.apache
.yetus
.audience
.InterfaceAudience
;
50 import org
.slf4j
.Logger
;
51 import org
.slf4j
.LoggerFactory
;
53 import org
.apache
.hbase
.thirdparty
.com
.google
.common
.base
.Preconditions
;
54 import org
.apache
.hbase
.thirdparty
.org
.apache
.commons
.cli
.CommandLine
;
55 import org
.apache
.hbase
.thirdparty
.org
.apache
.commons
.cli
.HelpFormatter
;
56 import org
.apache
.hbase
.thirdparty
.org
.apache
.commons
.cli
.Options
;
57 import org
.apache
.hbase
.thirdparty
.org
.apache
.commons
.cli
.ParseException
;
58 import org
.apache
.hbase
.thirdparty
.org
.apache
.commons
.cli
.PosixParser
;
59 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.http
.HttpVersion
;
60 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.jmx
.MBeanContainer
;
61 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.server
.HttpConfiguration
;
62 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.server
.HttpConnectionFactory
;
63 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.server
.SecureRequestCustomizer
;
64 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.server
.Server
;
65 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.server
.ServerConnector
;
66 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.server
.SslConnectionFactory
;
67 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.servlet
.FilterHolder
;
68 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.servlet
.ServletContextHandler
;
69 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.servlet
.ServletHolder
;
70 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.util
.ssl
.SslContextFactory
;
71 import org
.apache
.hbase
.thirdparty
.org
.eclipse
.jetty
.util
.thread
.QueuedThreadPool
;
72 import org
.apache
.hbase
.thirdparty
.org
.glassfish
.jersey
.server
.ResourceConfig
;
73 import org
.apache
.hbase
.thirdparty
.org
.glassfish
.jersey
.servlet
.ServletContainer
;
76 * Main class for launching REST gateway as a servlet hosted by Jetty.
78 * The following options are supported:
80 * <li>-p --port : service port</li>
81 * <li>-ro --readonly : server mode</li>
84 @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience
.TOOLS
)
85 public class RESTServer
implements Constants
{
86 static Logger LOG
= LoggerFactory
.getLogger("RESTServer");
88 static final String REST_CSRF_ENABLED_KEY
= "hbase.rest.csrf.enabled";
89 static final boolean REST_CSRF_ENABLED_DEFAULT
= false;
90 boolean restCSRFEnabled
= false;
91 static final String REST_CSRF_CUSTOM_HEADER_KEY
="hbase.rest.csrf.custom.header";
92 static final String REST_CSRF_CUSTOM_HEADER_DEFAULT
= "X-XSRF-HEADER";
93 static final String REST_CSRF_METHODS_TO_IGNORE_KEY
= "hbase.rest.csrf.methods.to.ignore";
94 static final String REST_CSRF_METHODS_TO_IGNORE_DEFAULT
= "GET,OPTIONS,HEAD,TRACE";
95 public static final String SKIP_LOGIN_KEY
= "hbase.rest.skip.login";
96 static final int DEFAULT_HTTP_MAX_HEADER_SIZE
= 64 * 1024; // 64k
98 private static final String PATH_SPEC_ANY
= "/*";
100 static final String REST_HTTP_ALLOW_OPTIONS_METHOD
= "hbase.rest.http.allow.options.method";
101 // HTTP OPTIONS method is commonly used in REST APIs for negotiation. So it is enabled by default.
102 private static boolean REST_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT
= true;
103 static final String REST_CSRF_BROWSER_USERAGENTS_REGEX_KEY
=
104 "hbase.rest-csrf.browser-useragents-regex";
106 // HACK, making this static for AuthFilter to get at our configuration. Necessary for unit tests.
107 @edu.umd
.cs
.findbugs
.annotations
.SuppressWarnings(
108 value
={"ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD", "MS_CANNOT_BE_FINAL"},
109 justification
="For testing")
110 public static Configuration conf
= null;
111 private final UserProvider userProvider
;
112 private Server server
;
113 private InfoServer infoServer
;
115 public RESTServer(Configuration conf
) {
116 RESTServer
.conf
= conf
;
117 this.userProvider
= UserProvider
.instantiate(conf
);
120 private static void printUsageAndExit(Options options
, int exitCode
) {
121 HelpFormatter formatter
= new HelpFormatter();
122 formatter
.printHelp("hbase rest start", "", options
,
123 "\nTo run the REST server as a daemon, execute " +
124 "hbase-daemon.sh start|stop rest [-i <port>] [-p <port>] [-ro]\n", true);
125 System
.exit(exitCode
);
128 void addCSRFFilter(ServletContextHandler ctxHandler
, Configuration conf
) {
129 restCSRFEnabled
= conf
.getBoolean(REST_CSRF_ENABLED_KEY
, REST_CSRF_ENABLED_DEFAULT
);
130 if (restCSRFEnabled
) {
131 Map
<String
, String
> restCsrfParams
= RestCsrfPreventionFilter
132 .getFilterParams(conf
, "hbase.rest-csrf.");
133 FilterHolder holder
= new FilterHolder();
134 holder
.setName("csrf");
135 holder
.setClassName(RestCsrfPreventionFilter
.class.getName());
136 holder
.setInitParameters(restCsrfParams
);
137 ctxHandler
.addFilter(holder
, PATH_SPEC_ANY
, EnumSet
.allOf(DispatcherType
.class));
141 private void addClickjackingPreventionFilter(ServletContextHandler ctxHandler
,
142 Configuration conf
) {
143 FilterHolder holder
= new FilterHolder();
144 holder
.setName("clickjackingprevention");
145 holder
.setClassName(ClickjackingPreventionFilter
.class.getName());
146 holder
.setInitParameters(ClickjackingPreventionFilter
.getDefaultParameters(conf
));
147 ctxHandler
.addFilter(holder
, PATH_SPEC_ANY
, EnumSet
.allOf(DispatcherType
.class));
150 private void addSecurityHeadersFilter(ServletContextHandler ctxHandler
, Configuration conf
) {
151 FilterHolder holder
= new FilterHolder();
152 holder
.setName("securityheaders");
153 holder
.setClassName(SecurityHeadersFilter
.class.getName());
154 holder
.setInitParameters(SecurityHeadersFilter
.getDefaultParameters(conf
));
155 ctxHandler
.addFilter(holder
, PATH_SPEC_ANY
, EnumSet
.allOf(DispatcherType
.class));
158 // login the server principal (if using secure Hadoop)
159 private static Pair
<FilterHolder
, Class
<?
extends ServletContainer
>> loginServerPrincipal(
160 UserProvider userProvider
, Configuration conf
) throws Exception
{
161 Class
<?
extends ServletContainer
> containerClass
= ServletContainer
.class;
162 if (userProvider
.isHadoopSecurityEnabled() && userProvider
.isHBaseSecurityEnabled()) {
163 String machineName
= Strings
.domainNamePointerToHostName(
164 DNS
.getDefaultHost(conf
.get(REST_DNS_INTERFACE
, "default"),
165 conf
.get(REST_DNS_NAMESERVER
, "default")));
166 String keytabFilename
= conf
.get(REST_KEYTAB_FILE
);
167 Preconditions
.checkArgument(keytabFilename
!= null && !keytabFilename
.isEmpty(),
168 REST_KEYTAB_FILE
+ " should be set if security is enabled");
169 String principalConfig
= conf
.get(REST_KERBEROS_PRINCIPAL
);
170 Preconditions
.checkArgument(principalConfig
!= null && !principalConfig
.isEmpty(),
171 REST_KERBEROS_PRINCIPAL
+ " should be set if security is enabled");
172 // Hook for unit tests, this will log out any other user and mess up tests.
173 if (!conf
.getBoolean(SKIP_LOGIN_KEY
, false)) {
174 userProvider
.login(REST_KEYTAB_FILE
, REST_KERBEROS_PRINCIPAL
, machineName
);
176 if (conf
.get(REST_AUTHENTICATION_TYPE
) != null) {
177 containerClass
= RESTServletContainer
.class;
178 FilterHolder authFilter
= new FilterHolder();
179 authFilter
.setClassName(AuthFilter
.class.getName());
180 authFilter
.setName("AuthenticationFilter");
181 return new Pair
<>(authFilter
,containerClass
);
184 return new Pair
<>(null, containerClass
);
187 private static void parseCommandLine(String
[] args
, Configuration conf
) {
188 Options options
= new Options();
189 options
.addOption("p", "port", true, "Port to bind to [default: " + DEFAULT_LISTEN_PORT
+ "]");
190 options
.addOption("ro", "readonly", false, "Respond only to GET HTTP " +
191 "method requests [default: false]");
192 options
.addOption("i", "infoport", true, "Port for WEB UI");
194 CommandLine commandLine
= null;
196 commandLine
= new PosixParser().parse(options
, args
);
197 } catch (ParseException e
) {
198 LOG
.error("Could not parse: ", e
);
199 printUsageAndExit(options
, -1);
202 // check for user-defined port setting, if so override the conf
203 if (commandLine
!= null && commandLine
.hasOption("port")) {
204 String val
= commandLine
.getOptionValue("port");
205 conf
.setInt("hbase.rest.port", Integer
.parseInt(val
));
206 if (LOG
.isDebugEnabled()) {
207 LOG
.debug("port set to " + val
);
211 // check if server should only process GET requests, if so override the conf
212 if (commandLine
!= null && commandLine
.hasOption("readonly")) {
213 conf
.setBoolean("hbase.rest.readonly", true);
214 if (LOG
.isDebugEnabled()) {
215 LOG
.debug("readonly set to true");
219 // check for user-defined info server port setting, if so override the conf
220 if (commandLine
!= null && commandLine
.hasOption("infoport")) {
221 String val
= commandLine
.getOptionValue("infoport");
222 conf
.setInt("hbase.rest.info.port", Integer
.parseInt(val
));
223 if (LOG
.isDebugEnabled()) {
224 LOG
.debug("WEB UI port set to " + val
);
228 if (commandLine
!= null && commandLine
.hasOption("skipLogin")) {
229 conf
.setBoolean(SKIP_LOGIN_KEY
, true);
230 if (LOG
.isDebugEnabled()) {
231 LOG
.debug("Skipping Kerberos login for REST server");
235 List
<String
> remainingArgs
= commandLine
!= null ? commandLine
.getArgList() : new ArrayList
<>();
236 if (remainingArgs
.size() != 1) {
237 printUsageAndExit(options
, 1);
240 String command
= remainingArgs
.get(0);
241 if ("start".equals(command
)) {
242 // continue and start container
243 } else if ("stop".equals(command
)) {
246 printUsageAndExit(options
, 1);
252 * Runs the REST server.
254 public synchronized void run() throws Exception
{
255 Pair
<FilterHolder
, Class
<?
extends ServletContainer
>> pair
= loginServerPrincipal(
257 FilterHolder authFilter
= pair
.getFirst();
258 Class
<?
extends ServletContainer
> containerClass
= pair
.getSecond();
259 RESTServlet servlet
= RESTServlet
.getInstance(conf
, userProvider
);
261 // set up the Jersey servlet container for Jetty
262 ResourceConfig application
= new ResourceConfig().
263 packages("org.apache.hadoop.hbase.rest").register(JacksonJaxbJsonProvider
.class);
264 // Using our custom ServletContainer is tremendously important. This is what makes sure the
265 // UGI.doAs() is done for the remoteUser, and calls are not made as the REST server itself.
266 ServletContainer servletContainer
= ReflectionUtils
.newInstance(containerClass
, application
);
267 ServletHolder sh
= new ServletHolder(servletContainer
);
269 // Set the default max thread number to 100 to limit
270 // the number of concurrent requests so that REST server doesn't OOM easily.
271 // Jetty set the default max thread number to 250, if we don't set it.
273 // Our default min thread number 2 is the same as that used by Jetty.
274 int maxThreads
= servlet
.getConfiguration().getInt(REST_THREAD_POOL_THREADS_MAX
, 100);
275 int minThreads
= servlet
.getConfiguration().getInt(REST_THREAD_POOL_THREADS_MIN
, 2);
276 // Use the default queue (unbounded with Jetty 9.3) if the queue size is negative, otherwise use
277 // bounded {@link ArrayBlockingQueue} with the given size
278 int queueSize
= servlet
.getConfiguration().getInt(REST_THREAD_POOL_TASK_QUEUE_SIZE
, -1);
279 int idleTimeout
= servlet
.getConfiguration().getInt(REST_THREAD_POOL_THREAD_IDLE_TIMEOUT
, 60000);
280 QueuedThreadPool threadPool
= queueSize
> 0 ?
281 new QueuedThreadPool(maxThreads
, minThreads
, idleTimeout
, new ArrayBlockingQueue
<>(queueSize
)) :
282 new QueuedThreadPool(maxThreads
, minThreads
, idleTimeout
);
284 this.server
= new Server(threadPool
);
287 MBeanContainer mbContainer
=new MBeanContainer(ManagementFactory
.getPlatformMBeanServer());
288 server
.addEventListener(mbContainer
);
289 server
.addBean(mbContainer
);
292 String host
= servlet
.getConfiguration().get("hbase.rest.host", "0.0.0.0");
293 int servicePort
= servlet
.getConfiguration().getInt("hbase.rest.port", 8080);
294 HttpConfiguration httpConfig
= new HttpConfiguration();
295 httpConfig
.setSecureScheme("https");
296 httpConfig
.setSecurePort(servicePort
);
297 httpConfig
.setHeaderCacheSize(DEFAULT_HTTP_MAX_HEADER_SIZE
);
298 httpConfig
.setRequestHeaderSize(DEFAULT_HTTP_MAX_HEADER_SIZE
);
299 httpConfig
.setResponseHeaderSize(DEFAULT_HTTP_MAX_HEADER_SIZE
);
300 httpConfig
.setSendServerVersion(false);
301 httpConfig
.setSendDateHeader(false);
303 ServerConnector serverConnector
;
304 if (conf
.getBoolean(REST_SSL_ENABLED
, false)) {
305 HttpConfiguration httpsConfig
= new HttpConfiguration(httpConfig
);
306 httpsConfig
.addCustomizer(new SecureRequestCustomizer());
308 SslContextFactory sslCtxFactory
= new SslContextFactory();
309 String keystore
= conf
.get(REST_SSL_KEYSTORE_STORE
);
310 String keystoreType
= conf
.get(REST_SSL_KEYSTORE_TYPE
);
311 String password
= HBaseConfiguration
.getPassword(conf
,
312 REST_SSL_KEYSTORE_PASSWORD
, null);
313 String keyPassword
= HBaseConfiguration
.getPassword(conf
,
314 REST_SSL_KEYSTORE_KEYPASSWORD
, password
);
315 sslCtxFactory
.setKeyStorePath(keystore
);
316 if(StringUtils
.isNotBlank(keystoreType
)) {
317 sslCtxFactory
.setKeyStoreType(keystoreType
);
319 sslCtxFactory
.setKeyStorePassword(password
);
320 sslCtxFactory
.setKeyManagerPassword(keyPassword
);
322 String trustStore
= conf
.get(REST_SSL_TRUSTSTORE_STORE
);
323 if(StringUtils
.isNotBlank(trustStore
)) {
324 sslCtxFactory
.setTrustStorePath(trustStore
);
326 String trustStorePassword
=
327 HBaseConfiguration
.getPassword(conf
, REST_SSL_TRUSTSTORE_PASSWORD
, null);
328 if(StringUtils
.isNotBlank(trustStorePassword
)) {
329 sslCtxFactory
.setTrustStorePassword(trustStorePassword
);
331 String trustStoreType
= conf
.get(REST_SSL_TRUSTSTORE_TYPE
);
332 if(StringUtils
.isNotBlank(trustStoreType
)) {
333 sslCtxFactory
.setTrustStoreType(trustStoreType
);
336 String
[] excludeCiphers
= servlet
.getConfiguration().getStrings(
337 REST_SSL_EXCLUDE_CIPHER_SUITES
, ArrayUtils
.EMPTY_STRING_ARRAY
);
338 if (excludeCiphers
.length
!= 0) {
339 sslCtxFactory
.setExcludeCipherSuites(excludeCiphers
);
341 String
[] includeCiphers
= servlet
.getConfiguration().getStrings(
342 REST_SSL_INCLUDE_CIPHER_SUITES
, ArrayUtils
.EMPTY_STRING_ARRAY
);
343 if (includeCiphers
.length
!= 0) {
344 sslCtxFactory
.setIncludeCipherSuites(includeCiphers
);
347 String
[] excludeProtocols
= servlet
.getConfiguration().getStrings(
348 REST_SSL_EXCLUDE_PROTOCOLS
, ArrayUtils
.EMPTY_STRING_ARRAY
);
349 if (excludeProtocols
.length
!= 0) {
350 sslCtxFactory
.setExcludeProtocols(excludeProtocols
);
352 String
[] includeProtocols
= servlet
.getConfiguration().getStrings(
353 REST_SSL_INCLUDE_PROTOCOLS
, ArrayUtils
.EMPTY_STRING_ARRAY
);
354 if (includeProtocols
.length
!= 0) {
355 sslCtxFactory
.setIncludeProtocols(includeProtocols
);
358 serverConnector
= new ServerConnector(server
,
359 new SslConnectionFactory(sslCtxFactory
, HttpVersion
.HTTP_1_1
.toString()),
360 new HttpConnectionFactory(httpsConfig
));
362 serverConnector
= new ServerConnector(server
, new HttpConnectionFactory(httpConfig
));
365 int acceptQueueSize
= servlet
.getConfiguration().getInt(REST_CONNECTOR_ACCEPT_QUEUE_SIZE
, -1);
366 if (acceptQueueSize
>= 0) {
367 serverConnector
.setAcceptQueueSize(acceptQueueSize
);
370 serverConnector
.setPort(servicePort
);
371 serverConnector
.setHost(host
);
373 server
.addConnector(serverConnector
);
374 server
.setStopAtShutdown(true);
377 ServletContextHandler ctxHandler
= new ServletContextHandler(server
, "/", ServletContextHandler
.SESSIONS
);
378 ctxHandler
.addServlet(sh
, PATH_SPEC_ANY
);
379 if (authFilter
!= null) {
380 ctxHandler
.addFilter(authFilter
, PATH_SPEC_ANY
, EnumSet
.of(DispatcherType
.REQUEST
));
383 // Load filters from configuration.
384 String
[] filterClasses
= servlet
.getConfiguration().getStrings(FILTER_CLASSES
,
385 GzipFilter
.class.getName());
386 for (String filter
: filterClasses
) {
387 filter
= filter
.trim();
388 ctxHandler
.addFilter(filter
, PATH_SPEC_ANY
, EnumSet
.of(DispatcherType
.REQUEST
));
390 addCSRFFilter(ctxHandler
, conf
);
391 addClickjackingPreventionFilter(ctxHandler
, conf
);
392 addSecurityHeadersFilter(ctxHandler
, conf
);
393 HttpServerUtil
.constrainHttpMethods(ctxHandler
, servlet
.getConfiguration()
394 .getBoolean(REST_HTTP_ALLOW_OPTIONS_METHOD
, REST_HTTP_ALLOW_OPTIONS_METHOD_DEFAULT
));
396 // Put up info server.
397 int port
= conf
.getInt("hbase.rest.info.port", 8085);
399 conf
.setLong("startcode", EnvironmentEdgeManager
.currentTime());
400 String a
= conf
.get("hbase.rest.info.bindAddress", "0.0.0.0");
401 this.infoServer
= new InfoServer("rest", a
, port
, false, conf
);
402 this.infoServer
.setAttribute("hbase.conf", conf
);
403 this.infoServer
.start();
409 public synchronized void join() throws Exception
{
410 if (server
== null) {
411 throw new IllegalStateException("Server is not running");
416 public synchronized void stop() throws Exception
{
417 if (server
== null) {
418 throw new IllegalStateException("Server is not running");
425 public synchronized int getPort() {
426 if (server
== null) {
427 throw new IllegalStateException("Server is not running");
429 return ((ServerConnector
) server
.getConnectors()[0]).getLocalPort();
432 @SuppressWarnings("deprecation")
433 public synchronized int getInfoPort() {
434 if (infoServer
== null) {
435 throw new IllegalStateException("InfoServer is not running");
437 return infoServer
.getPort();
440 public Configuration
getConf() {
445 * The main method for the HBase rest server.
446 * @param args command-line arguments
447 * @throws Exception exception
449 public static void main(String
[] args
) throws Exception
{
450 LOG
.info("***** STARTING service '" + RESTServer
.class.getSimpleName() + "' *****");
451 VersionInfo
.logVersion();
452 final Configuration conf
= HBaseConfiguration
.create();
453 parseCommandLine(args
, conf
);
454 RESTServer server
= new RESTServer(conf
);
459 } catch (Exception e
) {
460 LOG
.error(HBaseMarkers
.FATAL
, "Failed to start server", e
);
464 LOG
.info("***** STOPPING service '" + RESTServer
.class.getSimpleName() + "' *****");