Package com.flipkart.phantom.runtime.impl.server.netty.handler.http

Source Code of com.flipkart.phantom.runtime.impl.server.netty.handler.http.RoutingHttpChannelHandler

/*
* Copyright 2012-2015, the original author or authors.
*
* Licensed 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 com.flipkart.phantom.runtime.impl.server.netty.handler.http;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelEvent;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.ExceptionEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelHandler;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.channel.group.ChannelGroup;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;

import com.flipkart.phantom.event.ServiceProxyEvent;
import com.flipkart.phantom.event.ServiceProxyEventProducer;
import com.flipkart.phantom.http.impl.HttpProxy;
import com.flipkart.phantom.http.impl.HttpRequestWrapper;
import com.flipkart.phantom.task.spi.Executor;
import com.flipkart.phantom.task.spi.repository.ExecutorRepository;

/**
* <code>RoutingHttpChannelHandler</code> is a sub-type of {@link SimpleChannelHandler} that routes Http requests to one or more {@link HttpProxy} instances.
*
* @author Regunath B
* @version 1.0, 6 Sep 2013
*/

public abstract class RoutingHttpChannelHandler extends SimpleChannelUpstreamHandler implements InitializingBean {

    /** Logger for this class*/
    private static final Logger LOGGER = LoggerFactory.getLogger(RoutingHttpChannelHandler.class);

    /** The empty routing key which is default*/
    public static final String ALL_ROUTES = "";

    /** Set of Http headers that we want to remove */
    public static final Set<String> REMOVE_HEADERS = new HashSet<String>();
    static {
      RoutingHttpChannelHandler.REMOVE_HEADERS.add(HTTP.TRANSFER_ENCODING);     
      RoutingHttpChannelHandler.REMOVE_HEADERS.add(HTTP.CONN_DIRECTIVE);
      RoutingHttpChannelHandler.REMOVE_HEADERS.add(HTTP.CONN_KEEP_ALIVE);
      RoutingHttpChannelHandler.REMOVE_HEADERS.add(HTTP.TARGET_HOST);
      RoutingHttpChannelHandler.REMOVE_HEADERS.add("Proxy-Authenticate");
      RoutingHttpChannelHandler.REMOVE_HEADERS.add("TE");
      RoutingHttpChannelHandler.REMOVE_HEADERS.add("Trailers");
      RoutingHttpChannelHandler.REMOVE_HEADERS.add("Upgrade");
    }
   
    /** The default channel group*/
    private ChannelGroup defaultChannelGroup;

    /** The HttpProxyRepository to lookup HttpProxy from */
    private ExecutorRepository<HttpResponse, HttpProxy> repository;

    /** The HTTP proxy handler map*/
    private Map<String, String> proxyMap = new HashMap<String, String>();

    /** The default HTTP proxy handler */
    private String defaultProxy;

  /** The publisher used to broadcast events to Service Proxy Subscribers */
  private ServiceProxyEventProducer eventProducer;

    /** Event Type for publishing all events which are generated here */
    private final static String HTTP_HANDLER = "HTTP_HANDLER";

    /**
     * Interface method implementation. Checks if all mandatory properties have been set
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        Assert.notNull(this.defaultProxy, "The 'defaultProxy' may not be null");
        // add the default proxy for all routes i.e. default
        this.proxyMap.put(RoutingHttpChannelHandler.ALL_ROUTES, defaultProxy);
    }

    /**
     * Overriden superclass method. Adds the newly created Channel to the default channel group and calls the super class {@link #channelOpen(ChannelHandlerContext, ChannelStateEvent)} method
     * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#channelOpen(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelStateEvent)
     */
    public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent event) throws Exception {
        super.channelOpen(ctx, event);
    }

    /**
     * Interface method implementation. Reads and processes Http commands sent to the service proxy. Expects data in the Http protocol.
     * @see org.jboss.netty.channel.SimpleChannelUpstreamHandler#handleUpstream(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ChannelEvent)
     */
    public void messageReceived(ChannelHandlerContext ctx, MessageEvent messageEvent) throws Exception {
        long receiveTime = System.currentTimeMillis();
        HttpRequest request = (HttpRequest) messageEvent.getMessage();
        LOGGER.debug("Request is: " + request.getMethod() + " " + request.getUri());

        this.processRequestHeaders(request);

        ChannelBuffer inputBuffer = request.getContent();
        byte[] requestData = new byte[inputBuffer.readableBytes()];
        inputBuffer.readBytes(requestData, 0, requestData.length);

        // Prepare request Wrapper
        HttpRequestWrapper executorHttpRequest = new HttpRequestWrapper();
        executorHttpRequest.setData(requestData);
        executorHttpRequest.setMethod(request.getMethod().toString());
        executorHttpRequest.setUri(request.getUri());
        executorHttpRequest.setHeaders(request.getHeaders());
        executorHttpRequest.setProtocol(request.getProtocolVersion().getProtocolName());
        executorHttpRequest.setMajorVersion(request.getProtocolVersion().getMajorVersion());
        executorHttpRequest.setMinorVersion(request.getProtocolVersion().getMinorVersion());

        // executor
        String proxy = this.proxyMap.get(this.getRoutingKey(request));
        if (proxy == null) {
            proxy = this.proxyMap.get(RoutingHttpChannelHandler.ALL_ROUTES);
            LOGGER.info("Routing key for : " + request.getUri() + " returned null. Using default proxy instead.");
        }
        Executor<HttpResponse> executor = this.repository.getExecutor(proxy, proxy, executorHttpRequest);

        // execute
        HttpResponse response = null;
        try {
            response = (HttpResponse) executor.execute();
        } catch (Exception e) {
            throw new RuntimeException("Error in executing HTTP request:" + proxy + " URI:" + request.getUri(), e);
        } finally {
            if (eventProducer != null) {
                // Publishes event both in case of success and failure.
                ServiceProxyEvent.Builder eventBuilder;
                if (executor == null)
                    eventBuilder = new ServiceProxyEvent.Builder(request.getUri(), HTTP_HANDLER).withEventSource(getClass().getName());
                else {
                    eventBuilder = executor.getEventBuilder().withCommandData(executor).withEventSource(executor.getClass().getName());
                }
                eventBuilder.withRequestReceiveTime(receiveTime);
                eventProducer.publishEvent(eventBuilder.build());
            } else
                LOGGER.debug("eventProducer not set, not publishing event");
        }

        // send response
        writeCommandExecutionResponse(ctx, messageEvent, response);
    }

    /**
     * Interface method implementation. Closes the underlying channel after logging a warning message
     * @see org.jboss.netty.channel.SimpleChannelHandler#exceptionCaught(org.jboss.netty.channel.ChannelHandlerContext, org.jboss.netty.channel.ExceptionEvent)
     */
    public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent event) throws Exception {
        LOGGER.warn("Exception {} thrown on Channel {}. Disconnect initiated", event, event.getChannel());
        event.getCause().printStackTrace();
        event.getChannel().close();
    }

    /**
     * Returns the routing key to use for proxy selection. Sub-types may use the passed-in request data attributes to determine routing
   * @param request the HttpRequest object
     * @return the routing key to identify the proxy
     */
    protected abstract String getRoutingKey(HttpRequest request);

    /**
     * Writes the specified TaskResult data to the channel output. Only the raw output data is written and rest of the TaskResult fields are ignored
     * @param ctx the ChannelHandlerContext
     * @param event the ChannelEvent
     * @throws Exception in case of any errors
     */
    private void writeCommandExecutionResponse(ChannelHandlerContext ctx, ChannelEvent event, HttpResponse response) throws Exception {
        // Don't write anything if the response is null
        if (response == null) {
            // write empty response
            event.getChannel().write(new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.NO_CONTENT)).addListener(ChannelFutureListener.CLOSE);
            return;
        }
        org.jboss.netty.handler.codec.http.HttpResponse httpResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.valueOf(response.getStatusLine().getStatusCode()));
        // write headers
        for (Header header : response.getAllHeaders()) {
            if (!RoutingHttpChannelHandler.REMOVE_HEADERS.contains(header.getName())) {
                httpResponse.setHeader(header.getName(),header.getValue());
            }
        }
       
        // write entity
        HttpEntity responseEntity = response.getEntity();
        byte[] responseData = EntityUtils.toByteArray(responseEntity);
        httpResponse.setContent(ChannelBuffers.copiedBuffer(responseData));
        // write response
        event.getChannel().write(httpResponse).addListener(ChannelFutureListener.CLOSE);
    }
   
    /**
     * Helper method to remove or otherwise modify Http request headers that we dont want to propagate. This implementation removes all headers specified
     * under {@link RoutingHttpChannelHandler#REMOVE_HEADERS}. Sub-types may override this method to change this behavior
     * @param request the HttpRequest that needs to be processed for remove headers
     */
    protected void processRequestHeaders(HttpRequest request) {
        for (String header : RoutingHttpChannelHandler.REMOVE_HEADERS) {
          request.removeHeader(header);
        }     
    }

    /** Start Getter/Setter methods */
    public ChannelGroup getDefaultChannelGroup() {
        return this.defaultChannelGroup;
    }
    public void setDefaultChannelGroup(ChannelGroup defaultChannelGroup) {
        this.defaultChannelGroup = defaultChannelGroup;
    }
    public ExecutorRepository<HttpResponse, HttpProxy> getRepository() {
        return this.repository;
    }
    public void setRepository(ExecutorRepository<HttpResponse, HttpProxy> repository) {
        this.repository = repository;
    }
    public Map<String, String> getProxyMap() {
        return this.proxyMap;
    }
    public void setProxyMap(Map<String, String> proxyMap) {
        this.proxyMap = proxyMap;
    }
    public String getDefaultProxy() {
        return this.defaultProxy;
    }
    public void setDefaultProxy(String defaultProxy) {
        this.defaultProxy = defaultProxy;
    }

  public void setEventProducer(ServiceProxyEventProducer eventProducer) {
    this.eventProducer = eventProducer;

  }
    /** End Getter/Setter methods */

TOP

Related Classes of com.flipkart.phantom.runtime.impl.server.netty.handler.http.RoutingHttpChannelHandler

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.