blob: 785bc39736e3d71a2c43b70595b9a43f1580166a [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;
import org.apache.knox.gateway.audit.api.Action;
import org.apache.knox.gateway.audit.api.ActionOutcome;
import org.apache.knox.gateway.audit.api.AuditService;
import org.apache.knox.gateway.audit.api.AuditServiceFactory;
import org.apache.knox.gateway.audit.api.Auditor;
import org.apache.knox.gateway.audit.api.ResourceType;
import org.apache.knox.gateway.audit.log4j.audit.AuditConstants;
import org.apache.knox.gateway.config.GatewayConfig;
import org.apache.knox.gateway.descriptor.GatewayDescriptor;
import org.apache.knox.gateway.descriptor.GatewayDescriptorFactory;
import org.apache.knox.gateway.filter.AbstractGatewayFilter;
import org.apache.knox.gateway.i18n.messages.MessagesFactory;
import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
import org.apache.knox.gateway.services.ServiceType;
import org.apache.knox.gateway.services.GatewayServices;
import org.apache.knox.gateway.services.metrics.MetricsService;
import org.apache.knox.gateway.util.ServletRequestUtils;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.Servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
public class GatewayServlet implements Servlet, Filter {
public static final String GATEWAY_DESCRIPTOR_LOCATION_DEFAULT = "gateway.xml";
public static final String GATEWAY_DESCRIPTOR_LOCATION_PARAM = "gatewayDescriptorLocation";
private static final GatewayResources res = ResourcesFactory.get( GatewayResources.class );
private static final GatewayMessages LOG = MessagesFactory.get( GatewayMessages.class );
private static final AuditService auditService = AuditServiceFactory.getAuditService();
private static final Auditor auditor = AuditServiceFactory.getAuditService().getAuditor(AuditConstants.DEFAULT_AUDITOR_NAME, AuditConstants.KNOX_SERVICE_NAME,
AuditConstants.KNOX_COMPONENT_NAME);
private FilterConfigAdapter filterConfig;
private GatewayFilter filter;
public GatewayServlet( GatewayFilter filter ) {
this.filterConfig = null;
this.filter = filter;
}
public GatewayServlet() {
this( null );
}
public synchronized GatewayFilter getFilter() {
return filter;
}
public synchronized void setFilter( GatewayFilter filter ) throws ServletException {
if( filterConfig != null ) {
filter.init( filterConfig );
}
this.filter = filter;
if( filter != null && filterConfig != null ) {
filter.destroy();
}
}
@Override
public synchronized void init( ServletConfig servletConfig ) throws ServletException {
try {
if( filter == null ) {
filter = createFilter( servletConfig );
}
filterConfig = new FilterConfigAdapter( servletConfig );
if( filter != null ) {
filter.init( filterConfig );
}
} catch( ServletException | RuntimeException e ) {
LOG.failedToInitializeServletInstace( e );
throw e;
}
}
@Override
public void init( FilterConfig filterConfig ) throws ServletException {
try {
if( filter == null ) {
filter = createFilter( filterConfig );
}
if( filter != null ) {
filter.init( filterConfig );
}
} catch( ServletException | RuntimeException e ) {
LOG.failedToInitializeServletInstace( e );
throw e;
}
}
@Override
public ServletConfig getServletConfig() {
return filterConfig.getServletConfig();
}
@Override
public void service( ServletRequest servletRequest, ServletResponse servletResponse ) throws ServletException, IOException {
try {
auditService.createContext();
GatewayFilter f = filter;
if( f != null ) {
try {
f.doFilter( servletRequest, servletResponse, null );
} catch( IOException | RuntimeException | ServletException e ) {
LOG.failedToExecuteFilter( e );
throw e;
}
} else {
((HttpServletResponse)servletResponse).setStatus( HttpServletResponse.SC_SERVICE_UNAVAILABLE );
}
auditLog(servletRequest, servletResponse);
} finally {
auditService.detachContext();
}
}
@Override
public void doFilter( ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain ) throws IOException, ServletException {
try {
auditService.createContext();
GatewayFilter f = filter;
if( f != null ) {
try {
f.doFilter( servletRequest, servletResponse );
/* if response is committed in case of SSO redirect no need to apply further filters */
if(!servletResponse.isCommitted()) {
//TODO: This should really happen naturally somehow as part of being a filter. This way will cause problems eventually.
chain.doFilter( servletRequest, servletResponse );
}
} catch( IOException | RuntimeException | ServletException e ) {
LOG.failedToExecuteFilter( e );
throw e;
}
} else {
((HttpServletResponse)servletResponse).setStatus( HttpServletResponse.SC_SERVICE_UNAVAILABLE );
}
auditLog(servletRequest, servletResponse);
} finally {
auditService.detachContext();
}
}
private void auditLog(ServletRequest servletRequest, ServletResponse servletResponse) {
final int status = ((HttpServletResponse) servletResponse).getStatus();
final String requestUri, actionOutcome;
if (HttpServletResponse.SC_SERVICE_UNAVAILABLE == status) {
requestUri = ServletRequestUtils.getContextPathWithQuery(servletRequest);
actionOutcome = ActionOutcome.UNAVAILABLE;
} else {
requestUri = (String) servletRequest.getAttribute(AbstractGatewayFilter.SOURCE_REQUEST_CONTEXT_URL_ATTRIBUTE_NAME);
actionOutcome = ActionOutcome.SUCCESS;
}
auditor.audit(Action.ACCESS, requestUri, ResourceType.URI, actionOutcome, res.responseStatus(status));
}
@Override
public String getServletInfo() {
return res.gatewayServletInfo();
}
@Override
public synchronized void destroy() {
if( filter != null ) {
filter.destroy();
}
filter = null;
}
private static GatewayFilter createFilter( InputStream stream, ServletContext servletContext ) throws ServletException {
try {
GatewayFilter filter = null;
if( stream != null ) {
try (InputStreamReader reader = new InputStreamReader(stream, StandardCharsets.UTF_8)){
GatewayDescriptor descriptor = GatewayDescriptorFactory.load("xml", reader);
filter = GatewayFactory.create( descriptor );
} finally {
stream.close();
}
}
GatewayConfig gatewayConfig = (GatewayConfig) servletContext.getAttribute(GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE);
if (gatewayConfig.isMetricsEnabled()) {
GatewayServices gatewayServices = (GatewayServices) servletContext.getAttribute(GatewayServices.GATEWAY_SERVICES_ATTRIBUTE);
MetricsService metricsService = gatewayServices.getService(ServiceType.METRICS_SERVICE);
if (metricsService != null) {
GatewayFilter instrumentedFilter = metricsService.getInstrumented(filter);
if (instrumentedFilter != null) {
filter = instrumentedFilter;
}
}
}
return filter;
} catch( IOException | URISyntaxException e ) {
throw new ServletException( e );
}
}
private static GatewayFilter createFilter( FilterConfig filterConfig ) throws ServletException {
GatewayFilter filter;
InputStream stream;
String location = filterConfig.getInitParameter( GATEWAY_DESCRIPTOR_LOCATION_PARAM );
if( location != null ) {
stream = filterConfig.getServletContext().getResourceAsStream( location );
if( stream == null ) {
stream = filterConfig.getServletContext().getResourceAsStream( "/WEB-INF/" + location );
}
} else {
stream = filterConfig.getServletContext().getResourceAsStream( GATEWAY_DESCRIPTOR_LOCATION_DEFAULT );
}
filter = createFilter( stream, filterConfig.getServletContext());
return filter;
}
private static GatewayFilter createFilter( ServletConfig servletConfig ) throws ServletException {
GatewayFilter filter;
InputStream stream;
String location = servletConfig.getInitParameter( GATEWAY_DESCRIPTOR_LOCATION_PARAM );
if( location != null ) {
stream = servletConfig.getServletContext().getResourceAsStream( location );
if( stream == null ) {
stream = servletConfig.getServletContext().getResourceAsStream( "/WEB-INF/" + location );
}
} else {
stream = servletConfig.getServletContext().getResourceAsStream( GATEWAY_DESCRIPTOR_LOCATION_DEFAULT );
}
filter = createFilter( stream, servletConfig.getServletContext());
return filter;
}
private static class FilterConfigAdapter implements FilterConfig {
private ServletConfig config;
FilterConfigAdapter( ServletConfig config ) {
this.config = config;
}
private ServletConfig getServletConfig() {
return config;
}
@Override
public String getFilterName() {
return config.getServletName();
}
@Override
public ServletContext getServletContext() {
return config.getServletContext();
}
@Override
public String getInitParameter( String name ) {
return config.getInitParameter( name );
}
@Override
public Enumeration<String> getInitParameterNames() {
return config.getInitParameterNames();
}
}
}