blob: 044c32cfa12f3e69296c4ae05061c68223509dbf [file] [log] [blame]
/*
* 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
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.knox.gateway.dispatch;
import org.apache.http.client.HttpClient;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.knox.gateway.SpiGatewayMessages;
import org.apache.knox.gateway.config.ConfigurationInjectorBuilder;
import org.apache.knox.gateway.filter.AbstractGatewayFilter;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.util.RegExUtils;
import org.apache.knox.gateway.util.WhitelistUtils;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
public class GatewayDispatchFilter extends AbstractGatewayFilter {
private static final Map<String, Adapter> METHOD_ADAPTERS = createMethodAdapters();
protected static final SpiGatewayMessages LOG = MessagesFactory.get(SpiGatewayMessages.class);
private final Object lock = new Object();
private String whitelist;
private Dispatch dispatch;
private HttpClient httpClient;
private static Map<String, Adapter> createMethodAdapters() {
Map<String, Adapter> map = new HashMap<>();
map.put("GET", new GetAdapter());
map.put("POST", new PostAdapter());
map.put("PUT", new PutAdapter());
map.put("PATCH", new PatchAdapter());
map.put("DELETE", new DeleteAdapter());
map.put("OPTIONS", new OptionsAdapter());
map.put("HEAD", new HeadAdapter());
return Collections.unmodifiableMap(map);
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
super.init(filterConfig);
synchronized(lock) {
if (dispatch == null) {
String dispatchImpl = filterConfig.getInitParameter("dispatch-impl");
dispatch = newInstanceFromName(dispatchImpl);
}
ConfigurationInjectorBuilder.configuration().target(dispatch).source(filterConfig).inject();
HttpClientFactory httpClientFactory;
String httpClientFactoryClass = filterConfig.getInitParameter("httpClientFactory");
if (httpClientFactoryClass != null) {
httpClientFactory = newInstanceFromName(httpClientFactoryClass);
} else {
httpClientFactory = new DefaultHttpClientFactory();
}
httpClient = httpClientFactory.createHttpClient(filterConfig);
dispatch.setHttpClient(httpClient);
dispatch.init();
}
}
@Override
public void destroy() {
synchronized(lock) {
dispatch.destroy();
try {
if (httpClient instanceof CloseableHttpClient) {
((CloseableHttpClient) httpClient).close();
}
} catch ( IOException e ) {
LOG.errorClosingHttpClient(e);
}
}
}
public Dispatch getDispatch() {
synchronized(lock) {
return dispatch;
}
}
public void setDispatch(Dispatch dispatch) {
synchronized(lock) {
this.dispatch = dispatch;
}
}
@Override
protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
String method = request.getMethod().toUpperCase(Locale.ROOT);
Adapter adapter = METHOD_ADAPTERS.get(method);
if (adapter != null) {
if (isDispatchAllowed(request)) {
try {
adapter.doMethod(getDispatch(), request, response);
} catch (URISyntaxException e) {
throw new ServletException(e);
}
} else {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
}
} else {
response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
}
}
private boolean isDispatchAllowed(HttpServletRequest request) {
boolean isAllowed = true;
// Initialize the white list if it has not yet been initialized
if (whitelist == null) {
whitelist = WhitelistUtils.getDispatchWhitelist(request);
}
if (whitelist != null) {
String requestURI = request.getRequestURI();
String decodedURL = null;
try {
decodedURL = URLDecoder.decode(requestURI, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
//
}
isAllowed = RegExUtils.checkWhitelist(whitelist, (decodedURL != null ? decodedURL : requestURI));
if (!isAllowed) {
LOG.dispatchDisallowed(requestURI);
}
}
return isAllowed;
}
private interface Adapter {
void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException;
}
private static class GetAdapter implements Adapter {
@Override
public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException {
dispatch.doGet( dispatch.getDispatchUrl(request), request, response);
}
}
private static class PostAdapter implements Adapter {
@Override
public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException {
dispatch.doPost( dispatch.getDispatchUrl(request), request, response);
}
}
private static class PutAdapter implements Adapter {
@Override
public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException {
dispatch.doPut( dispatch.getDispatchUrl(request), request, response);
}
}
private static class PatchAdapter implements Adapter {
@Override
public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException {
dispatch.doPatch( dispatch.getDispatchUrl(request), request, response);
}
}
private static class DeleteAdapter implements Adapter {
@Override
public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException {
dispatch.doDelete( dispatch.getDispatchUrl(request), request, response);
}
}
private static class OptionsAdapter implements Adapter {
@Override
public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException {
dispatch.doOptions( dispatch.getDispatchUrl(request), request, response);
}
}
private static class HeadAdapter implements Adapter {
@Override
public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException, URISyntaxException {
dispatch.doHead( dispatch.getDispatchUrl(request), request, response);
}
}
private <T> T newInstanceFromName(String dispatchImpl) throws ServletException {
try {
Class<T> clazz = loadClass(dispatchImpl);
return clazz.newInstance();
} catch ( Exception e ) {
throw new ServletException(e);
}
}
private <T> Class<T> loadClass(String className) throws ClassNotFoundException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
if ( loader == null ) {
loader = this.getClass().getClassLoader();
}
return (Class<T>) loader.loadClass(className);
}
}