You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@knox.apache.org by mo...@apache.org on 2017/09/27 14:08:59 UTC

[4/4] knox git commit: Merge branch 'master' into KNOX-998-Package_Restructuring

Merge branch 'master' into KNOX-998-Package_Restructuring


Project: http://git-wip-us.apache.org/repos/asf/knox/repo
Commit: http://git-wip-us.apache.org/repos/asf/knox/commit/51348f83
Tree: http://git-wip-us.apache.org/repos/asf/knox/tree/51348f83
Diff: http://git-wip-us.apache.org/repos/asf/knox/diff/51348f83

Branch: refs/heads/KNOX-998-Package_Restructuring
Commit: 51348f838106241efbf34534168c478d77a35d6e
Parents: 7815945 10b3473
Author: Sandeep More <mo...@apache.org>
Authored: Wed Sep 27 10:07:08 2017 -0400
Committer: Sandeep More <mo...@apache.org>
Committed: Wed Sep 27 10:07:08 2017 -0400

----------------------------------------------------------------------
 .../knox/gateway/dispatch/AbstractGatewayDispatch.java      | 8 ++++++++
 .../org/apache/knox/gateway/dispatch/DefaultDispatch.java   | 9 +++++++++
 .../java/org/apache/knox/gateway/dispatch/Dispatch.java     | 6 ++++++
 .../apache/knox/gateway/dispatch/GatewayDispatchFilter.java | 8 ++++++++
 4 files changed, 31 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/knox/blob/51348f83/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/AbstractGatewayDispatch.java
----------------------------------------------------------------------
diff --cc gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/AbstractGatewayDispatch.java
index fbc86e7,0000000..aad7d4c
mode 100644,000000..100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/AbstractGatewayDispatch.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/AbstractGatewayDispatch.java
@@@ -1,144 -1,0 +1,152 @@@
 +/**
 + * 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.knox.gateway.filter.GatewayResponse;
 +import org.apache.hadoop.io.IOUtils;
 +import org.apache.http.client.HttpClient;
 +import org.apache.http.client.methods.HttpUriRequest;
 +
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.io.OutputStream;
 +import java.net.URI;
 +import java.net.URISyntaxException;
 +import java.util.Enumeration;
 +import java.util.HashSet;
 +import java.util.Set;
 +
 +public abstract class AbstractGatewayDispatch implements Dispatch {
 +
 +  private static final int STREAM_COPY_BUFFER_SIZE = 4096;
 +  private static final Set<String> REQUEST_EXCLUDE_HEADERS = new HashSet<>();
 +  
 +  static {
 +      REQUEST_EXCLUDE_HEADERS.add("Host");
 +      REQUEST_EXCLUDE_HEADERS.add("Authorization");
 +      REQUEST_EXCLUDE_HEADERS.add("Content-Length");
 +      REQUEST_EXCLUDE_HEADERS.add("Transfer-Encoding");
 +  }
 +  
 +  protected  HttpClient client;
 +
 +  @Override
 +  public void init() {
 +  }
 +
 +  protected void writeResponse(HttpServletRequest request, HttpServletResponse response, InputStream stream )
 +      throws IOException {
 +//    ResponseStreamer streamer =
 +//        (ResponseStreamer)request.getAttribute( RESPONSE_STREAMER_ATTRIBUTE_NAME );
 +//    if( streamer != null ) {
 +//      streamer.streamResponse( stream, response.getOutputStream() );
 +//    } else {
 +      if( response instanceof GatewayResponse ) {
 +        ((GatewayResponse)response).streamResponse( stream );
 +      } else {
 +        OutputStream output = response.getOutputStream();
 +        IOUtils.copyBytes( stream, output, STREAM_COPY_BUFFER_SIZE );
 +        //KNOX-685: output.flush();
 +        output.close();
 +      }
 +//    }
 +  }
 +
 +  @Override
 +  public HttpClient getHttpClient() {
 +    return client;
 +  }
 +
 +  @Override
 +  public void setHttpClient(HttpClient client) {
 +    this.client = client;
 +  }
 +
 +  @Override
 +  public URI getDispatchUrl(HttpServletRequest request) {
 +    StringBuffer str = request.getRequestURL();
 +    String query = request.getQueryString();
 +    if ( query != null ) {
 +      str.append('?');
 +      str.append(query);
 +    }
 +    URI url = URI.create(str.toString());
 +    return url;
 +  }
 +
 +  public void doGet( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, URISyntaxException {
 +    response.sendError( HttpServletResponse.SC_METHOD_NOT_ALLOWED );
 +  }
 +
 +  public void doPost( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, URISyntaxException {
 +    response.sendError( HttpServletResponse.SC_METHOD_NOT_ALLOWED );
 +  }
 +
 +  public void doPut( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, URISyntaxException {
 +    response.sendError( HttpServletResponse.SC_METHOD_NOT_ALLOWED );
 +  }
 +
 +  public void doDelete( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, URISyntaxException {
 +    response.sendError( HttpServletResponse.SC_METHOD_NOT_ALLOWED );
 +  }
 +
 +  public void doOptions( URI url, HttpServletRequest request, HttpServletResponse response )
++      throws IOException, URISyntaxException {
++    response.sendError( HttpServletResponse.SC_METHOD_NOT_ALLOWED );
++  }
++
++  /**
++   * @sine 0.14.0
++   */
++  public void doHead( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, URISyntaxException {
 +    response.sendError( HttpServletResponse.SC_METHOD_NOT_ALLOWED );
 +  }
 +  
 +  public void copyRequestHeaderFields(HttpUriRequest outboundRequest,
 +      HttpServletRequest inboundRequest) {
 +    Enumeration<String> headerNames = inboundRequest.getHeaderNames();
 +    while( headerNames.hasMoreElements() ) {
 +      String name = headerNames.nextElement();
 +      if ( !outboundRequest.containsHeader( name )
 +          && !getOutboundRequestExcludeHeaders().contains( name ) ) {
 +        String value = inboundRequest.getHeader( name );
 +        outboundRequest.addHeader( name, value );
 +      }
 +    }
 +  }
 +
 +  public Set<String> getOutboundRequestExcludeHeaders() {
 +    return REQUEST_EXCLUDE_HEADERS;
 +  }
 +
 +  protected void encodeUnwiseCharacters(StringBuffer str) {
 +    int pipe = str.indexOf("|");
 +    while (pipe > -1) {
 +      str.replace(pipe, pipe+1, "%7C");
 +      pipe = str.indexOf("|", pipe+1);
 +    }
 +  }
 +
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/51348f83/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
----------------------------------------------------------------------
diff --cc gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
index 94f64ee,0000000..aeb33b2
mode 100644,000000..100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/DefaultDispatch.java
@@@ -1,319 -1,0 +1,328 @@@
 +/**
 + * 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.knox.gateway.SpiGatewayMessages;
 +import org.apache.knox.gateway.SpiGatewayResources;
 +import org.apache.knox.gateway.audit.api.Action;
 +import org.apache.knox.gateway.audit.api.ActionOutcome;
 +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.Configure;
 +import org.apache.knox.gateway.config.Default;
 +import org.apache.knox.gateway.config.GatewayConfig;
 +import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 +import org.apache.knox.gateway.i18n.resources.ResourcesFactory;
 +import org.apache.knox.gateway.util.MimeTypes;
 +import org.apache.http.Header;
 +import org.apache.http.HttpEntity;
 +import org.apache.http.HttpResponse;
 +import org.apache.http.client.methods.HttpDelete;
 +import org.apache.http.client.methods.HttpGet;
++import org.apache.http.client.methods.HttpHead;
 +import org.apache.http.client.methods.HttpOptions;
 +import org.apache.http.client.methods.HttpPost;
 +import org.apache.http.client.methods.HttpPut;
 +import org.apache.http.client.methods.HttpUriRequest;
 +import org.apache.http.entity.ContentType;
 +
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +import java.io.Closeable;
 +import java.io.IOException;
 +import java.io.InputStream;
 +import java.net.URI;
 +import java.net.URISyntaxException;
 +import java.util.HashSet;
 +import java.util.Set;
 +
 +/**
 + *
 + */
 +public class DefaultDispatch extends AbstractGatewayDispatch {
 +
 +  protected static final String SET_COOKIE = "SET-COOKIE";
 +  protected static final String WWW_AUTHENTICATE = "WWW-AUTHENTICATE";
 +
 +  protected static SpiGatewayMessages LOG = MessagesFactory.get(SpiGatewayMessages.class);
 +  protected static SpiGatewayResources RES = ResourcesFactory.get(SpiGatewayResources.class);
 +  protected static Auditor auditor = AuditServiceFactory.getAuditService().getAuditor(AuditConstants.DEFAULT_AUDITOR_NAME,
 +      AuditConstants.KNOX_SERVICE_NAME, AuditConstants.KNOX_COMPONENT_NAME);
 +
 +  private Set<String> outboundResponseExcludeHeaders;
 +
 +  //Buffer size in bytes
 +  private int replayBufferSize = -1;
 +
 +  @Override
 +  public void init() {
 +    super.init();
 +    outboundResponseExcludeHeaders = new HashSet<>();
 +    outboundResponseExcludeHeaders.add(SET_COOKIE);
 +    outboundResponseExcludeHeaders.add(WWW_AUTHENTICATE);
 +  }
 +
 +  @Override
 +  public void destroy() {
 +
 +  }
 +
 +  protected int getReplayBufferSize() {
 +    if (replayBufferSize > 0) {
 +      return Math.abs(replayBufferSize/1024);
 +    }
 +    return replayBufferSize;
 +  }
 +
 +  @Configure
 +  protected void setReplayBufferSize(@Default("-1")int size) {
 +    setReplayBufferSizeInBytes(size);
 +  }
 +
 +  protected int getReplayBufferSizeInBytes() {
 +    return replayBufferSize;
 +  }
 +
 +  protected void setReplayBufferSizeInBytes(int size) {
 +    if (size > 0) {
 +      size *= 1024;
 +    }
 +    replayBufferSize = size;
 +  }
 +
 +
 +  protected void executeRequest(
 +         HttpUriRequest outboundRequest,
 +         HttpServletRequest inboundRequest,
 +         HttpServletResponse outboundResponse)
 +         throws IOException {
 +      HttpResponse inboundResponse = executeOutboundRequest(outboundRequest);
 +      writeOutboundResponse(outboundRequest, inboundRequest, outboundResponse, inboundResponse);
 +   }
 +
 +  protected HttpResponse executeOutboundRequest( HttpUriRequest outboundRequest ) throws IOException {
 +    LOG.dispatchRequest( outboundRequest.getMethod(), outboundRequest.getURI() );
 +    HttpResponse inboundResponse;
 +
 +    try {
 +      auditor.audit( Action.DISPATCH, outboundRequest.getURI().toString(), ResourceType.URI, ActionOutcome.UNAVAILABLE, RES.requestMethod( outboundRequest.getMethod() ) );
 +      if( !"true".equals( System.getProperty( GatewayConfig.HADOOP_KERBEROS_SECURED ) ) ) {
 +        // Hadoop cluster not Kerberos enabled
 +        addCredentialsToRequest( outboundRequest );
 +      }
 +      inboundResponse = client.execute( outboundRequest );
 +
 +      int statusCode = inboundResponse.getStatusLine().getStatusCode();
 +      if( statusCode != 201 ) {
 +        LOG.dispatchResponseStatusCode( statusCode );
 +      } else {
 +        Header location = inboundResponse.getFirstHeader( "Location" );
 +        if( location == null ) {
 +          LOG.dispatchResponseStatusCode( statusCode );
 +        } else {
 +          LOG.dispatchResponseCreatedStatusCode( statusCode, location.getValue() );
 +        }
 +      }
 +      auditor.audit( Action.DISPATCH, outboundRequest.getURI().toString(), ResourceType.URI, ActionOutcome.SUCCESS, RES.responseStatus( statusCode ) );
 +    } catch( Exception e ) {
 +      // We do not want to expose back end host. port end points to clients, see JIRA KNOX-58
 +      auditor.audit( Action.DISPATCH, outboundRequest.getURI().toString(), ResourceType.URI, ActionOutcome.FAILURE );
 +      LOG.dispatchServiceConnectionException( outboundRequest.getURI(), e );
 +      throw new IOException( RES.dispatchConnectionError() );
 +    }
 +    return inboundResponse;
 +  }
 +
 +  protected void writeOutboundResponse(HttpUriRequest outboundRequest, HttpServletRequest inboundRequest, HttpServletResponse outboundResponse, HttpResponse inboundResponse) throws IOException {
 +    // Copy the client respond header to the server respond.
 +    outboundResponse.setStatus(inboundResponse.getStatusLine().getStatusCode());
 +    Header[] headers = inboundResponse.getAllHeaders();
 +    Set<String> excludeHeaders = getOutboundResponseExcludeHeaders();
 +    boolean hasExcludeHeaders = false;
 +    if ((excludeHeaders != null) && !(excludeHeaders.isEmpty())) {
 +      hasExcludeHeaders = true;
 +    }
 +    for ( Header header : headers ) {
 +      String name = header.getName();
 +      if (hasExcludeHeaders && excludeHeaders.contains(name.toUpperCase())) {
 +        continue;
 +      }
 +      String value = header.getValue();
 +      outboundResponse.addHeader(name, value);
 +    }
 +
 +    HttpEntity entity = inboundResponse.getEntity();
 +    if( entity != null ) {
 +      outboundResponse.setContentType( getInboundResponseContentType( entity ) );
 +      //KM[ If this is set here it ends up setting the content length to the content returned from the server.
 +      // This length might not match if the the content is rewritten.
 +      //      long contentLength = entity.getContentLength();
 +      //      if( contentLength <= Integer.MAX_VALUE ) {
 +      //        outboundResponse.setContentLength( (int)contentLength );
 +      //      }
 +      //]
 +      InputStream stream = entity.getContent();
 +      try {
 +        writeResponse( inboundRequest, outboundResponse, stream );
 +      } finally {
 +        closeInboundResponse( inboundResponse, stream );
 +      }
 +    }
 +  }
 +
 +  private String getInboundResponseContentType( final HttpEntity entity ) {
 +    String fullContentType = null;
 +    if( entity != null ) {
 +      ContentType entityContentType = ContentType.get( entity );
 +      if( entityContentType != null ) {
 +        if( entityContentType.getCharset() == null ) {
 +          final String entityMimeType = entityContentType.getMimeType();
 +          final String defaultCharset = MimeTypes.getDefaultCharsetForMimeType( entityMimeType );
 +          if( defaultCharset != null ) {
 +            LOG.usingDefaultCharsetForEntity( entityMimeType, defaultCharset );
 +            entityContentType = entityContentType.withCharset( defaultCharset );
 +          }
 +        } else {
 +          LOG.usingExplicitCharsetForEntity( entityContentType.getMimeType(), entityContentType.getCharset() );
 +        }
 +        fullContentType = entityContentType.toString();
 +      }
 +    }
 +    if( fullContentType == null ) {
 +      LOG.unknownResponseEntityContentType();
 +    } else {
 +      LOG.inboundResponseEntityContentType( fullContentType );
 +    }
 +    return fullContentType;
 +  }
 +
 +  protected void closeInboundResponse( HttpResponse response, InputStream stream ) throws IOException {
 +    try {
 +      stream.close();
 +    } finally {
 +      if( response instanceof Closeable ) {
 +        ( (Closeable)response).close();
 +      }
 +    }
 +  }
 +
 +   /**
 +    * This method provides a hook for specialized credential propagation
 +    * in subclasses.
 +    *
 +    * @param outboundRequest
 +    */
 +   protected void addCredentialsToRequest(HttpUriRequest outboundRequest) {
 +   }
 +
 +
 +   protected HttpEntity createRequestEntity(HttpServletRequest request)
 +         throws IOException {
 +
 +      String contentType = request.getContentType();
 +      int contentLength = request.getContentLength();
 +      InputStream contentStream = request.getInputStream();
 +
 +      HttpEntity entity;
 +      if (contentType == null) {
 +         entity = new InputStreamEntity(contentStream, contentLength);
 +      } else {
 +         entity = new InputStreamEntity(contentStream, contentLength, ContentType.parse(contentType));
 +      }
 +      GatewayConfig config =
 +         (GatewayConfig)request.getServletContext().getAttribute( GatewayConfig.GATEWAY_CONFIG_ATTRIBUTE );
 +      if( config != null && config.isHadoopKerberosSecured() ) {
 +        //Check if delegation token is supplied in the request
 +        boolean delegationTokenPresent = false;
 +        String queryString = request.getQueryString();
 +        if (queryString != null) {
 +          delegationTokenPresent = queryString.startsWith("delegation=") || queryString.contains("&delegation=");
 +        }
 +        if (replayBufferSize < 0) {
 +          replayBufferSize = config.getHttpServerRequestBuffer();
 +        }
 +        if (!delegationTokenPresent && replayBufferSize > 0 ) {
 +          entity = new PartiallyRepeatableHttpEntity(entity, replayBufferSize);
 +        }
 +      }
 +
 +      return entity;
 +   }
 +
 +   @Override
 +   public void doGet(URI url, HttpServletRequest request, HttpServletResponse response)
 +         throws IOException, URISyntaxException {
 +      HttpGet method = new HttpGet(url);
 +      // https://issues.apache.org/jira/browse/KNOX-107 - Service URLs not rewritten for WebHDFS GET redirects
 +      // This is now taken care of in DefaultHttpClientFactory.createHttpClient
 +      // and setting params here causes configuration setup there to be ignored there.
 +      // method.getParams().setBooleanParameter("http.protocol.handle-redirects", false);
 +      copyRequestHeaderFields(method, request);
 +      executeRequest(method, request, response);
 +   }
 +
 +   @Override
 +   public void doOptions(URI url, HttpServletRequest request, HttpServletResponse response)
 +         throws IOException, URISyntaxException {
 +      HttpOptions method = new HttpOptions(url);
 +      executeRequest(method, request, response);
 +   }
 +
 +   @Override
 +   public void doPut(URI url, HttpServletRequest request, HttpServletResponse response)
 +         throws IOException, URISyntaxException {
 +      HttpPut method = new HttpPut(url);
 +      HttpEntity entity = createRequestEntity(request);
 +      method.setEntity(entity);
 +      copyRequestHeaderFields(method, request);
 +      executeRequest(method, request, response);
 +   }
 +
 +   @Override
 +   public void doPost(URI url, HttpServletRequest request, HttpServletResponse response)
 +         throws IOException, URISyntaxException {
 +      HttpPost method = new HttpPost(url);
 +      HttpEntity entity = createRequestEntity(request);
 +      method.setEntity(entity);
 +      copyRequestHeaderFields(method, request);
 +      executeRequest(method, request, response);
 +   }
 +
 +   @Override
 +   public void doDelete(URI url, HttpServletRequest request, HttpServletResponse response)
 +         throws IOException, URISyntaxException {
 +      HttpDelete method = new HttpDelete(url);
 +      copyRequestHeaderFields(method, request);
 +      executeRequest(method, request, response);
 +   }
 +
++  @Override
++  public void doHead(URI url, HttpServletRequest request, HttpServletResponse response)
++      throws IOException, URISyntaxException {
++    final HttpHead method = new HttpHead(url);
++    copyRequestHeaderFields(method, request);
++    executeRequest(method, request, response);
++  }
++
 +  public Set<String> getOutboundResponseExcludeHeaders() {
 +    return outboundResponseExcludeHeaders;
 +  }
 +
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/51348f83/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/Dispatch.java
----------------------------------------------------------------------
diff --cc gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/Dispatch.java
index ffd472f,0000000..30220b2
mode 100644,000000..100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/Dispatch.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/Dispatch.java
@@@ -1,56 -1,0 +1,62 @@@
 +/**
 + * 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 javax.servlet.ServletException;
 +import javax.servlet.http.HttpServletRequest;
 +import javax.servlet.http.HttpServletResponse;
 +import java.io.IOException;
 +import java.net.URI;
 +import java.net.URISyntaxException;
 +
 +public interface Dispatch {
 +
 +  void init();
 +
 +  void destroy();
 +
 +  HttpClient getHttpClient();
 +
 +  void setHttpClient(HttpClient httpClient);
 +
 +  URI getDispatchUrl( HttpServletRequest request );
 +
 +  void doGet( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, ServletException, URISyntaxException;
 +
 +  void doPost( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, ServletException, URISyntaxException;
 +
 +  void doPut( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, ServletException, URISyntaxException;
 +
 +  void doDelete( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, ServletException, URISyntaxException;
 +
 +  void doOptions( URI url, HttpServletRequest request, HttpServletResponse response )
 +      throws IOException, ServletException, URISyntaxException;
 +
++  /**
++   * @since 0.14.0
++   */
++  void doHead( URI url, HttpServletRequest request, HttpServletResponse response )
++      throws IOException, ServletException, URISyntaxException;
++
 +}

http://git-wip-us.apache.org/repos/asf/knox/blob/51348f83/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/GatewayDispatchFilter.java
----------------------------------------------------------------------
diff --cc gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/GatewayDispatchFilter.java
index eef3718,0000000..7b887d1
mode 100644,000000..100644
--- a/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/GatewayDispatchFilter.java
+++ b/gateway-spi/src/main/java/org/apache/knox/gateway/dispatch/GatewayDispatchFilter.java
@@@ -1,169 -1,0 +1,177 @@@
 +/**
 + * 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.knox.gateway.SpiGatewayMessages;
 +import org.apache.knox.gateway.filter.AbstractGatewayFilter;
 +import org.apache.knox.gateway.config.ConfigurationInjectorBuilder;
 +import org.apache.knox.gateway.i18n.messages.MessagesFactory;
 +import org.apache.http.client.HttpClient;
 +import org.apache.http.impl.client.CloseableHttpClient;
 +
 +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.net.URISyntaxException;
 +import java.util.Collections;
 +import java.util.HashMap;
 +import java.util.Map;
 +
 +public class GatewayDispatchFilter extends AbstractGatewayFilter {
 +
 +  private static Map<String, Adapter> METHOD_ADAPTERS = createMethodAdapters();
 +
 +  protected static SpiGatewayMessages LOG = MessagesFactory.get(SpiGatewayMessages.class);
 +
 +  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("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);
 +    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() {
 +    dispatch.destroy();
 +    try {
 +      if (httpClient instanceof  CloseableHttpClient) {
 +        ((CloseableHttpClient) httpClient).close();
 +      }
 +    } catch ( IOException e ) {
 +      LOG.errorClosingHttpClient(e);
 +    }
 +  }
 +
 +  public Dispatch getDispatch() {
 +    return dispatch;
 +  }
 +
 +  public void setDispatch(Dispatch dispatch) {
 +    this.dispatch = dispatch;
 +  }
 +
 +  @Override
 +  protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
 +    String method = request.getMethod().toUpperCase();
 +    Adapter adapter = METHOD_ADAPTERS.get(method);
 +    if ( adapter != null ) {
 +      try {
 +        adapter.doMethod(dispatch, request, response);
 +      } catch ( URISyntaxException e ) {
 +        throw new ServletException(e);
 +      }
 +    } else {
 +      response.sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
 +    }
 +  }
 +
 +  private interface Adapter {
 +    public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
 +        throws IOException, ServletException, URISyntaxException;
 +  }
 +
 +  private static class GetAdapter implements Adapter {
 +    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 {
 +    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 {
 +    public void doMethod(Dispatch dispatch, HttpServletRequest request, HttpServletResponse response)
 +        throws IOException, ServletException, URISyntaxException {
 +      dispatch.doPut( dispatch.getDispatchUrl(request), request, response);
 +    }
 +  }
 +
 +  private static class DeleteAdapter implements Adapter {
 +    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 {
 +    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 {
++    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);
 +  }
 +}