001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.http;
019
020import java.io.IOException;
021import java.util.Random;
022import java.util.concurrent.ThreadLocalRandom;
023import javax.servlet.Filter;
024import javax.servlet.FilterChain;
025import javax.servlet.FilterConfig;
026import javax.servlet.ServletException;
027import javax.servlet.ServletRequest;
028import javax.servlet.ServletResponse;
029import javax.servlet.http.HttpServletRequest;
030import org.apache.hadoop.conf.Configuration;
031import org.apache.hadoop.hbase.HBaseClassTestRule;
032import org.apache.hadoop.hbase.testclassification.MiscTests;
033import org.apache.hadoop.hbase.testclassification.SmallTests;
034import org.apache.hadoop.net.NetUtils;
035import org.apache.hadoop.util.StringUtils;
036import org.junit.Assert;
037import org.junit.ClassRule;
038import org.junit.Ignore;
039import org.junit.Test;
040import org.junit.experimental.categories.Category;
041import org.slf4j.Logger;
042import org.slf4j.LoggerFactory;
043
044@Category({ MiscTests.class, SmallTests.class })
045public class TestServletFilter extends HttpServerFunctionalTest {
046  @ClassRule
047  public static final HBaseClassTestRule CLASS_RULE =
048    HBaseClassTestRule.forClass(TestServletFilter.class);
049
050  private static final Logger LOG = LoggerFactory.getLogger(HttpServer.class);
051  private static volatile String uri = null;
052
053  /** A very simple filter which record the uri filtered. */
054  static public class SimpleFilter implements Filter {
055    private FilterConfig filterConfig = null;
056
057    @Override
058    public void init(FilterConfig filterConfig) throws ServletException {
059      this.filterConfig = filterConfig;
060    }
061
062    @Override
063    public void destroy() {
064      this.filterConfig = null;
065    }
066
067    @Override
068    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
069      throws IOException, ServletException {
070      if (filterConfig == null) {
071        return;
072      }
073
074      uri = ((HttpServletRequest) request).getRequestURI();
075      LOG.info("filtering " + uri);
076      chain.doFilter(request, response);
077    }
078
079    /** Configuration for the filter */
080    static public class Initializer extends FilterInitializer {
081      public Initializer() {
082      }
083
084      @Override
085      public void initFilter(FilterContainer container, Configuration conf) {
086        container.addFilter("simple", SimpleFilter.class.getName(), null);
087      }
088    }
089  }
090
091  private static void assertExceptionContains(String string, Throwable t) {
092    String msg = t.getMessage();
093    Assert.assertTrue("Expected to find '" + string + "' but got unexpected exception:"
094      + StringUtils.stringifyException(t), msg.contains(string));
095  }
096
097  @Test
098  @Ignore
099  // From stack
100  // Its a 'foreign' test, one that came in from hadoop when we copy/pasted http
101  // It's second class. Could comment it out if only failing test (as per @nkeywal – sort of)
102  public void testServletFilter() throws Exception {
103    Configuration conf = new Configuration();
104
105    // start an http server with CountingFilter
106    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, SimpleFilter.Initializer.class.getName());
107    HttpServer http = createTestServer(conf);
108    http.start();
109
110    final String fsckURL = "/fsck";
111    final String stacksURL = "/stacks";
112    final String ajspURL = "/a.jsp";
113    final String logURL = "/logs/a.log";
114    final String hadooplogoURL = "/static/hadoop-logo.jpg";
115
116    final String[] urls = { fsckURL, stacksURL, ajspURL, logURL, hadooplogoURL };
117    final Random rand = ThreadLocalRandom.current();
118    final int[] sequence = new int[50];
119
120    // generate a random sequence and update counts
121    for (int i = 0; i < sequence.length; i++) {
122      sequence[i] = rand.nextInt(urls.length);
123    }
124
125    // access the urls as the sequence
126    final String prefix = "http://" + NetUtils.getHostPortString(http.getConnectorAddress(0));
127    try {
128      for (int aSequence : sequence) {
129        access(prefix + urls[aSequence]);
130
131        // make sure everything except fsck get filtered
132        if (aSequence == 0) {
133          assertNull(uri);
134        } else {
135          assertEquals(urls[aSequence], uri);
136          uri = null;
137        }
138      }
139    } finally {
140      http.stop();
141    }
142  }
143
144  static public class ErrorFilter extends SimpleFilter {
145    @Override
146    public void init(FilterConfig arg0) throws ServletException {
147      throw new ServletException("Throwing the exception from Filter init");
148    }
149
150    /** Configuration for the filter */
151    static public class Initializer extends FilterInitializer {
152      public Initializer() {
153      }
154
155      @Override
156      public void initFilter(FilterContainer container, Configuration conf) {
157        container.addFilter("simple", ErrorFilter.class.getName(), null);
158      }
159    }
160  }
161
162  @Test
163  public void testServletFilterWhenInitThrowsException() throws Exception {
164    Configuration conf = new Configuration();
165    // start an http server with ErrorFilter
166    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY, ErrorFilter.Initializer.class.getName());
167    HttpServer http = createTestServer(conf);
168    try {
169      http.start();
170      fail("expecting exception");
171    } catch (IOException e) {
172      assertExceptionContains("Problem starting http server", e);
173    }
174  }
175
176  /**
177   * Similar to the above test case, except that it uses a different API to add the filter.
178   * Regression test for HADOOP-8786.
179   */
180  @Test
181  public void testContextSpecificServletFilterWhenInitThrowsException() throws Exception {
182    Configuration conf = new Configuration();
183    HttpServer http = createTestServer(conf);
184    HttpServer.defineFilter(http.webAppContext, "ErrorFilter", ErrorFilter.class.getName(), null,
185      null);
186    try {
187      http.start();
188      fail("expecting exception");
189    } catch (IOException e) {
190      assertExceptionContains("Unable to initialize WebAppContext", e);
191    }
192  }
193}