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.BufferedReader;
021import java.io.IOException;
022import java.io.InputStreamReader;
023import java.net.URL;
024import java.net.URLConnection;
025import java.util.Random;
026import javax.servlet.Filter;
027import javax.servlet.FilterChain;
028import javax.servlet.FilterConfig;
029import javax.servlet.ServletException;
030import javax.servlet.ServletRequest;
031import javax.servlet.ServletResponse;
032import javax.servlet.http.HttpServletRequest;
033import org.apache.hadoop.conf.Configuration;
034import org.apache.hadoop.hbase.HBaseClassTestRule;
035import org.apache.hadoop.hbase.testclassification.MiscTests;
036import org.apache.hadoop.hbase.testclassification.SmallTests;
037import org.apache.hadoop.net.NetUtils;
038import org.apache.hadoop.util.StringUtils;
039import org.junit.Assert;
040import org.junit.ClassRule;
041import org.junit.Ignore;
042import org.junit.Test;
043import org.junit.experimental.categories.Category;
044import org.slf4j.Logger;
045import org.slf4j.LoggerFactory;
046
047@Category({MiscTests.class, SmallTests.class})
048public class TestServletFilter extends HttpServerFunctionalTest {
049  @ClassRule
050  public static final HBaseClassTestRule CLASS_RULE =
051      HBaseClassTestRule.forClass(TestServletFilter.class);
052
053  private static final Logger LOG = LoggerFactory.getLogger(HttpServer.class);
054  private static volatile String uri = null;
055
056  /** A very simple filter which record the uri filtered. */
057  static public class SimpleFilter implements Filter {
058    private FilterConfig filterConfig = null;
059
060    @Override
061    public void init(FilterConfig filterConfig) throws ServletException {
062      this.filterConfig = filterConfig;
063    }
064
065    @Override
066    public void destroy() {
067      this.filterConfig = null;
068    }
069
070    @Override
071    public void doFilter(ServletRequest request, ServletResponse response,
072        FilterChain chain) throws IOException, ServletException {
073      if (filterConfig == null) {
074        return;
075      }
076
077      uri = ((HttpServletRequest)request).getRequestURI();
078      LOG.info("filtering " + uri);
079      chain.doFilter(request, response);
080    }
081
082    /** Configuration for the filter */
083    static public class Initializer extends FilterInitializer {
084      public Initializer() {}
085
086      @Override
087      public void initFilter(FilterContainer container, Configuration conf) {
088        container.addFilter("simple", SimpleFilter.class.getName(), null);
089      }
090    }
091  }
092
093  private static void assertExceptionContains(String string, Throwable t) {
094    String msg = t.getMessage();
095    Assert.assertTrue(
096        "Expected to find '" + string + "' but got unexpected exception:"
097        + StringUtils.stringifyException(t), msg.contains(string));
098  }
099
100  /**
101   * access a url, ignoring some IOException such as the page does not exist
102   */
103  private static void access(String urlstring) throws IOException {
104    LOG.warn("access " + urlstring);
105    URL url = new URL(urlstring);
106    URLConnection connection = url.openConnection();
107    connection.connect();
108
109    try {
110      try (BufferedReader in = new BufferedReader(new InputStreamReader(
111              connection.getInputStream(), "UTF-8"))) {
112        for (; in.readLine() != null; ) {
113          // Ignoring the content of the URLs. Only checking if something is there.
114        }
115      }
116    } catch(IOException ioe) {
117      LOG.warn("urlstring=" + urlstring, ioe);
118    }
119  }
120
121  @Test
122  @Ignore
123  //From stack
124  // Its a 'foreign' test, one that came in from hadoop when we copy/pasted http
125  // It's second class. Could comment it out if only failing test (as per @nkeywal – sort of)
126  public void testServletFilter() throws Exception {
127    Configuration conf = new Configuration();
128
129    //start a http server with CountingFilter
130    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY,
131        SimpleFilter.Initializer.class.getName());
132    HttpServer http = createTestServer(conf);
133    http.start();
134
135    final String fsckURL = "/fsck";
136    final String stacksURL = "/stacks";
137    final String ajspURL = "/a.jsp";
138    final String logURL = "/logs/a.log";
139    final String hadooplogoURL = "/static/hadoop-logo.jpg";
140
141    final String[] urls = {fsckURL, stacksURL, ajspURL, logURL, hadooplogoURL};
142    final Random ran = new Random();
143    final int[] sequence = new int[50];
144
145    //generate a random sequence and update counts
146    for(int i = 0; i < sequence.length; i++) {
147      sequence[i] = ran.nextInt(urls.length);
148    }
149
150    //access the urls as the sequence
151    final String prefix = "http://"
152        + NetUtils.getHostPortString(http.getConnectorAddress(0));
153    try {
154      for (int aSequence : sequence) {
155        access(prefix + urls[aSequence]);
156
157        //make sure everything except fsck get filtered
158        if (aSequence == 0) {
159          assertNull(uri);
160        } else {
161          assertEquals(urls[aSequence], uri);
162          uri = null;
163        }
164      }
165    } finally {
166      http.stop();
167    }
168  }
169
170  static public class ErrorFilter extends SimpleFilter {
171    @Override
172    public void init(FilterConfig arg0) throws ServletException {
173      throw new ServletException("Throwing the exception from Filter init");
174    }
175
176    /** Configuration for the filter */
177    static public class Initializer extends FilterInitializer {
178      public Initializer() {
179      }
180
181      @Override
182      public void initFilter(FilterContainer container, Configuration conf) {
183        container.addFilter("simple", ErrorFilter.class.getName(), null);
184      }
185    }
186  }
187
188  @Test
189  public void testServletFilterWhenInitThrowsException() throws Exception {
190    Configuration conf = new Configuration();
191    // start a http server with ErrorFilter
192    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY,
193        ErrorFilter.Initializer.class.getName());
194    HttpServer http = createTestServer(conf);
195    try {
196      http.start();
197      fail("expecting exception");
198    } catch (IOException e) {
199      assertExceptionContains("Problem starting http server", e);
200    }
201  }
202
203  /**
204   * Similar to the above test case, except that it uses a different API to add the
205   * filter. Regression test for HADOOP-8786.
206   */
207  @Test
208  public void testContextSpecificServletFilterWhenInitThrowsException()
209      throws Exception {
210    Configuration conf = new Configuration();
211    HttpServer http = createTestServer(conf);
212    HttpServer.defineFilter(http.webAppContext,
213        "ErrorFilter", ErrorFilter.class.getName(),
214        null, null);
215    try {
216      http.start();
217      fail("expecting exception");
218    } catch (IOException e) {
219      assertExceptionContains("Unable to initialize WebAppContext", e);
220    }
221  }
222}