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
050  @ClassRule
051  public static final HBaseClassTestRule CLASS_RULE =
052      HBaseClassTestRule.forClass(TestServletFilter.class);
053
054  private static final Logger LOG = LoggerFactory.getLogger(HttpServer.class);
055  static volatile String uri = null;
056
057  /** A very simple filter which record the uri filtered. */
058  static public class SimpleFilter implements Filter {
059    private FilterConfig filterConfig = null;
060
061    @Override
062    public void init(FilterConfig filterConfig) throws ServletException {
063      this.filterConfig = filterConfig;
064    }
065
066    @Override
067    public void destroy() {
068      this.filterConfig = null;
069    }
070
071    @Override
072    public void doFilter(ServletRequest request, ServletResponse response,
073        FilterChain chain) throws IOException, ServletException {
074      if (filterConfig == null)
075         return;
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  public 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  /** access a url, ignoring some IOException such as the page does not exist */
101  static void access(String urlstring) throws IOException {
102    LOG.warn("access " + urlstring);
103    URL url = new URL(urlstring);
104    URLConnection connection = url.openConnection();
105    connection.connect();
106
107    try {
108      BufferedReader in = new BufferedReader(new InputStreamReader(
109          connection.getInputStream()));
110      try {
111        for(; in.readLine() != null; );
112      } finally {
113        in.close();
114      }
115    } catch(IOException ioe) {
116      LOG.warn("urlstring=" + urlstring, ioe);
117    }
118  }
119
120  @Test
121  @Ignore
122  //From stack
123  // Its a 'foreign' test, one that came in from hadoop when we copy/pasted http
124  // It's second class. Could comment it out if only failing test (as per @nkeywal – sort of)
125  public void testServletFilter() throws Exception {
126    Configuration conf = new Configuration();
127
128    //start an http server with CountingFilter
129    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY,
130        SimpleFilter.Initializer.class.getName());
131    HttpServer http = createTestServer(conf);
132    http.start();
133
134    final String fsckURL = "/fsck";
135    final String stacksURL = "/stacks";
136    final String ajspURL = "/a.jsp";
137    final String logURL = "/logs/a.log";
138    final String hadooplogoURL = "/static/hadoop-logo.jpg";
139
140    final String[] urls = {fsckURL, stacksURL, ajspURL, logURL, hadooplogoURL};
141    final Random ran = new Random();
142    final int[] sequence = new int[50];
143
144    //generate a random sequence and update counts
145    for(int i = 0; i < sequence.length; i++) {
146      sequence[i] = ran.nextInt(urls.length);
147    }
148
149    //access the urls as the sequence
150    final String prefix = "http://"
151        + NetUtils.getHostPortString(http.getConnectorAddress(0));
152    try {
153      for(int i = 0; i < sequence.length; i++) {
154        access(prefix + urls[sequence[i]]);
155
156        //make sure everything except fsck get filtered
157        if (sequence[i] == 0) {
158          assertEquals(null, uri);
159        } else {
160          assertEquals(urls[sequence[i]], uri);
161          uri = null;
162        }
163      }
164    } finally {
165      http.stop();
166    }
167  }
168
169  static public class ErrorFilter extends SimpleFilter {
170    @Override
171    public void init(FilterConfig arg0) throws ServletException {
172      throw new ServletException("Throwing the exception from Filter init");
173    }
174
175    /** Configuration for the filter */
176    static public class Initializer extends FilterInitializer {
177      public Initializer() {
178      }
179
180      @Override
181      public void initFilter(FilterContainer container, Configuration conf) {
182        container.addFilter("simple", ErrorFilter.class.getName(), null);
183      }
184    }
185  }
186
187  @Test
188  public void testServletFilterWhenInitThrowsException() throws Exception {
189    Configuration conf = new Configuration();
190    // start an http server with ErrorFilter
191    conf.set(HttpServer.FILTER_INITIALIZERS_PROPERTY,
192        ErrorFilter.Initializer.class.getName());
193    HttpServer http = createTestServer(conf);
194    try {
195      http.start();
196      fail("expecting exception");
197    } catch (IOException e) {
198      assertExceptionContains("Problem starting http server", e);
199    }
200  }
201
202  /**
203   * Similar to the above test case, except that it uses a different API to add the
204   * filter. Regression test for HADOOP-8786.
205   */
206  @Test
207  public void testContextSpecificServletFilterWhenInitThrowsException()
208      throws Exception {
209    Configuration conf = new Configuration();
210    HttpServer http = createTestServer(conf);
211    HttpServer.defineFilter(http.webAppContext,
212        "ErrorFilter", ErrorFilter.class.getName(),
213        null, null);
214    try {
215      http.start();
216      fail("expecting exception");
217    } catch (IOException e) {
218      assertExceptionContains("Unable to initialize WebAppContext", e);
219    }
220  }
221
222}