Package org.snova.framework.proxy.gae

Source Code of org.snova.framework.proxy.gae.GAERemoteHandler$GAEFutureCallback

/**
*
*/
package org.snova.framework.proxy.gae;

import java.net.InetSocketAddress;
import java.net.URL;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

import org.arch.buffer.Buffer;
import org.arch.common.KeyValuePair;
import org.arch.config.IniProperties;
import org.arch.event.Event;
import org.arch.event.EventHandler;
import org.arch.event.EventHeader;
import org.arch.event.http.HTTPErrorEvent;
import org.arch.event.http.HTTPEventContants;
import org.arch.event.http.HTTPRequestEvent;
import org.arch.event.http.HTTPResponseEvent;
import org.arch.event.misc.CompressEvent;
import org.arch.event.misc.CompressorType;
import org.arch.event.misc.EncryptEvent;
import org.arch.event.misc.EncryptType;
import org.arch.util.StringHelper;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.DefaultChannelFuture;
import org.jboss.netty.channel.socket.SocketChannel;
import org.jboss.netty.handler.codec.http.DefaultHttpChunk;
import org.jboss.netty.handler.codec.http.DefaultHttpRequest;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpMethod;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.jboss.netty.handler.ssl.SslHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.snova.framework.common.http.ContentRangeHeaderValue;
import org.snova.framework.common.http.RangeHeaderValue;
import org.snova.framework.common.http.SetCookieHeaderValue;
import org.snova.framework.config.SnovaConfiguration;
import org.snova.framework.event.EventHeaderTags;
import org.snova.framework.event.EventHelper;
import org.snova.framework.proxy.LocalProxyHandler;
import org.snova.framework.proxy.RemoteProxyHandler;
import org.snova.framework.proxy.common.RangeChunk;
import org.snova.framework.proxy.common.RangeFetchStatus;
import org.snova.framework.proxy.hosts.HostsService;
import org.snova.framework.proxy.range.MultiRangeFetchTask;
import org.snova.framework.proxy.range.RangeCallback;
import org.snova.framework.util.SharedObjectHelper;
import org.snova.framework.util.SslCertificateHelper;
import org.snova.http.client.Connector;
import org.snova.http.client.FutureCallback;
import org.snova.http.client.HttpClient;
import org.snova.http.client.HttpClientException;
import org.snova.http.client.HttpClientHandler;
import org.snova.http.client.HttpClientHelper;
import org.snova.http.client.Options;
import org.snova.http.client.ProxyCallback;
import org.snova.http.client.common.SimpleSocketAddress;

/**
* @author wqy
*
*/
public class GAERemoteHandler implements RemoteProxyHandler, EventHandler,
        RangeCallback
{
  protected static Logger logger = LoggerFactory
          .getLogger(GAERemoteHandler.class);
  private static HttpClient client;

  private boolean isHttps;
  private GAEServerAuth gaeAuth;
  private LocalProxyHandler local;
  private HTTPRequestEvent proxyRequest;
  private Set<HttpClientHandler> workingHttpClientHandlers = Collections
          .synchronizedSet(new HashSet<HttpClientHandler>());
  private MultiRangeFetchTask rangeTask = null;
  private boolean closed = false;
  boolean injectRange = false;

  public GAERemoteHandler(GAEServerAuth auth)
  {
    try
    {
      this.gaeAuth = auth;
      initHttpClient();
    }
    catch (Exception e)
    {
      logger.error("Failed to init http client.", e);
    }
  }

  private static void initHttpClient() throws Exception
  {
    if (null != client)
    {
      return;
    }
    final IniProperties cfg = SnovaConfiguration.getInstance()
            .getIniProperties();
    Options options = new Options();
    options.maxIdleConnsPerHost = cfg.getIntProperty("GAE",
            "ConnectionPoolSize", 5);
    String proxy = cfg.getProperty("GAE", "Proxy");
    if (null != proxy)
    {
      final URL proxyUrl = new URL(proxy);
      options.proxyCB = new ProxyCallback()
      {
        @Override
        public URL getProxy(HttpRequest request)
        {
          return proxyUrl;
        }
      };
      options.connector = new Connector()
      {
        @Override
        public ChannelFuture connect(String host, int port)
        {
          String remoteHost = HostsService.getMappingHost(host);
          ChannelFuture future = SharedObjectHelper
                  .getClientBootstrap().connect(
                          new InetSocketAddress(remoteHost, port));
          if (proxyUrl.getProtocol().equalsIgnoreCase("https"))
          {
            SSLContext sslContext = null;
            try
            {
              sslContext = SSLContext.getDefault();
            }
            catch (NoSuchAlgorithmException e)
            {
              logger.error("", e);
            }

            SSLEngine sslEngine = sslContext.createSSLEngine();
            sslEngine.setUseClientMode(true);
            final SslHandler ssl = new SslHandler(sslEngine);
            future.getChannel().getPipeline().addLast("ssl", ssl);
          }
          return future;
        }
      };
    }
    client = new HttpClient(options,
            SharedObjectHelper.getClientBootstrap());
  }

  private HttpResponse buildHttpResponse(HTTPResponseEvent ev)
  {
    int status = ev.statusCode;
    HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1,
            HttpResponseStatus.valueOf(status));

    List<KeyValuePair<String, String>> headers = ev.getHeaders();
    for (KeyValuePair<String, String> header : headers)
    {
      if (header.getName().equalsIgnoreCase(HttpHeaders.Names.SET_COOKIE)
              || header.getName().equalsIgnoreCase(
                      HttpHeaders.Names.SET_COOKIE2))
      {
        List<SetCookieHeaderValue> cookies = SetCookieHeaderValue
                .parse(header.getValue());
        for (SetCookieHeaderValue cookie : cookies)
        {
          response.addHeader(header.getName(), cookie.toString());
        }
      }
      else
      {
        response.addHeader(header.getName(), header.getValue());
      }
    }

    if (null == response.getHeader(HttpHeaders.Names.CONTENT_LENGTH))
    {
      response.setHeader(HttpHeaders.Names.CONTENT_LENGTH, ""
              + ev.content.readableBytes());
    }

    response.removeHeader(HttpHeaders.Names.TRANSFER_ENCODING);
    if (HttpHeaders.getContentLength(response) == ev.content
            .readableBytes())
    {
      ChannelBuffer bufer = ChannelBuffers.wrappedBuffer(
              ev.content.getRawBuffer(), ev.content.getReadIndex(),
              ev.content.readableBytes());
      response.setContent(bufer);
    }
    else
    {
      response.setChunked(true);
      // response.setTransferEncoding(HttpTransferEncoding.STREAMED);
    }
    return response;
  }

  private HTTPRequestEvent buildHttpRequestEvent(HttpRequest request)
  {
    HTTPRequestEvent event = new HTTPRequestEvent();
    event.method = request.getMethod().getName();
    event.url = request.getUri();
    if (!event.url.startsWith("http://")
            && !event.url.startsWith("https://"))
    {
      if (isHttps)
      {
        event.url = "https://" + HttpHeaders.getHost(request)
                + event.url;
      }
      else
      {
        event.url = "http://" + HttpHeaders.getHost(request)
                + event.url;
      }
    }
    event.version = request.getProtocolVersion().getText();
    event.setHash(local.getId());
    event.setAttachment(request);
    ChannelBuffer content = request.getContent();
    if (null != content)
    {
      content.markReaderIndex();
      int buflen = content.readableBytes();
      event.content.ensureWritableBytes(content.readableBytes());
      content.readBytes(event.content.getRawBuffer(),
              event.content.getWriteIndex(), content.readableBytes());
      event.content.advanceWriteIndex(buflen);
    }
    for (String name : request.getHeaderNames())
    {
      for (String value : request.getHeaders(name))
      {
        event.headers
                .add(new KeyValuePair<String, String>(name, value));
      }
    }
    return event;
  }

  private void tryProxyRequest()
  {
    if (null == proxyRequest)
    {
      return;
    }
    if (proxyRequest.getContentLength() <= proxyRequest.content
            .readableBytes())
    {
      requestEvent(proxyRequest, this);
      proxyRequest.content.setReadIndex(0);
    }
  }

  private boolean fillHttpRequestBody(ChannelBuffer buf)
  {
    if (null != proxyRequest)
    {
      return false;
    }
    int len = buf.readableBytes();
    proxyRequest.content.ensureWritableBytes(len);
    buf.readBytes(proxyRequest.content.getRawBuffer(),
            proxyRequest.content.getWriteIndex(), len);
    proxyRequest.content.advanceWriteIndex(len);
    return true;
  }

  private SSLContext prepareSslContext(HttpRequest req)
  {
    String httpshost = HttpHeaders.getHost(req);
    SimpleSocketAddress addr = HttpClientHelper.getHttpRemoteAddress(true,
            httpshost);
    SSLContext sslContext = null;
    try
    {
      sslContext = SslCertificateHelper.getFakeSSLContext(addr.host, ""
              + addr.port);
    }
    catch (Exception e)
    {
      logger.error("Failed to init sslcontext", e);
    }

    return sslContext;
  }

  @Override
  public void handleRequest(final LocalProxyHandler local,
          final HttpRequest req)
  {
    this.local = local;
    if (req.getMethod().equals(HttpMethod.CONNECT))
    {
      isHttps = true;
      HttpResponse establised = new DefaultHttpResponse(
              req.getProtocolVersion(), HttpResponseStatus.OK);
      final SSLContext sslCtx = prepareSslContext(req);
      if (null == sslCtx)
      {
        HttpResponse fail = new DefaultHttpResponse(
                req.getProtocolVersion(),
                HttpResponseStatus.SERVICE_UNAVAILABLE);
        local.handleResponse(this, fail);
        return;
      }
      if (null == local.getLocalChannel())
      {
        close();
        return;
      }
      local.getLocalChannel().write(establised)
              .addListener(new ChannelFutureListener()
              {
                public void operationComplete(ChannelFuture future)
                        throws Exception
                {
                  if (future.isDone())
                  {
                    SocketChannel ch = (SocketChannel) local
                            .getLocalChannel();
                    if (ch.getPipeline().get(SslHandler.class) == null)
                    {
                      InetSocketAddress remote = ch
                              .getRemoteAddress();
                      SSLEngine engine = sslCtx.createSSLEngine(
                              remote.getAddress()
                                      .getHostAddress(), remote
                                      .getPort());
                      engine.setUseClientMode(false);
                      ch.getPipeline().addBefore("decoder",
                              "ssl", new SslHandler(engine));
                    }
                  }
                }
              });
      return;
    }
    rangeTask = null;
    proxyRequest = buildHttpRequestEvent(req);

    if (proxyRequest.method.equalsIgnoreCase("GET"))
    {
      if (StringHelper.containsString(HttpHeaders.getHost(req),
              GAEConfig.injectRange) || injectRange)
      {
        IniProperties cfg = SnovaConfiguration.getInstance()
                .getIniProperties();
        rangeTask = new MultiRangeFetchTask();
        rangeTask.sessionID = local.getId();
        rangeTask.fetchLimit = cfg.getIntProperty("GAE",
                "RangeFetchLimitSize", 256 * 1024);
        rangeTask.fetchWorkerNum = cfg.getIntProperty("GAE",
                "RangeConcurrentFetcher", 3);
        rangeTask.asyncGet(proxyRequest, this);
        return;
      }

    }
    tryProxyRequest();
  }

  @Override
  public void handleChunk(LocalProxyHandler local, HttpChunk chunk)
  {
    fillHttpRequestBody(chunk.getContent());
    tryProxyRequest();
  }

  @Override
  public void handleRawData(LocalProxyHandler local, ChannelBuffer raw)
  {
    logger.error("Unsupported raw data in GAE.");
  }

  @Override
  public void close()
  {
    synchronized (workingHttpClientHandlers)
    {
      for (HttpClientHandler ch : workingHttpClientHandlers)
      {
        ch.closeChannel();
      }
      workingHttpClientHandlers.clear();
    }
    if (null != rangeTask)
    {
      rangeTask.close();
    }
    closed = true;
  }

  private void processProxyResponse(HTTPResponseEvent response)
  {
    if (null != rangeTask)
    {
      if (!rangeTask.processAsyncResponse(response))
      {
        close();
      }
      return;
    }
    else
    {
      String originRange = proxyRequest.getHeader("Range");
      String contentRange = response.getHeader("Content-Range");
      if (response.statusCode == 206
              && !StringHelper.isEmptyString(contentRange)
              && proxyRequest.method.equalsIgnoreCase("GET"))
      {
        ContentRangeHeaderValue cv = new ContentRangeHeaderValue(
                contentRange);
        long length = cv.getInstanceLength();
        if (!StringHelper.isEmptyString(originRange))
        {
          RangeHeaderValue rv = new RangeHeaderValue(originRange);
          if (rv.getLastBytePos() > 0)
          {
            length = rv.getLastBytePos() + 1;
          }
        }
        if (length > cv.getLastBytePos() + 1)
        {
          IniProperties cfg = SnovaConfiguration.getInstance()
                  .getIniProperties();
          rangeTask = new MultiRangeFetchTask();
          rangeTask.sessionID = local.getId();
          rangeTask.fetchLimit = cfg.getIntProperty("GAE",
                  "RangeFetchLimitSize", 256 * 1024);
          rangeTask.fetchWorkerNum = cfg.getIntProperty("GAE",
                  "RangeConcurrentFetcher", 3);
          rangeTask.setRangeCallback(this);
          rangeTask
                  .setRangeState(MultiRangeFetchTask.STATE_WAIT_HEAD_RES);
          rangeTask.processAsyncResponse(response);
          return;
        }
        if (StringHelper.isEmptyString(originRange))
        {
          response.statusCode = 200;
          response.removeHeader("Content-Range");
        }
      }
      writeLocalHttpResponse(response);
    }
  }

  @Override
  public void onEvent(EventHeader header, Event event)
  {
    switch (header.type)
    {
      case HTTPEventContants.HTTP_RESPONSE_EVENT_TYPE:
      {
        HTTPResponseEvent response = (HTTPResponseEvent) event;
        processProxyResponse(response);
        break;
      }
      case HTTPEventContants.HTTP_ERROR_EVENT_TYPE:
      {
        HTTPErrorEvent error = (HTTPErrorEvent) event;
        logger.error("Receive error:" + error.errno + ":" + error.error);
        break;
      }
      default:
      {
        logger.error("Unsupported event type:" + event.getClass());
        break;
      }
    }
  }

  private Event wrapEvent(Event ev)
  {
    IniProperties cfg = SnovaConfiguration.getInstance().getIniProperties();
    EncryptType encType = EncryptType.valueOf(cfg.getProperty("GAE",
            "Encrypter", "SE1").toUpperCase());
    EncryptEvent enc = new EncryptEvent(encType, ev);
    return enc;
  }

  public void requestEvent(Event ev, EventHandler handler)
  {
    if (null == handler)
    {
      handler = this;
    }
    requestEvent(ev, handler, new GAEFutureCallback(handler, ev));
  }

  private void requestEvent(Event ev, EventHandler handler,
          GAEFutureCallback cb)
  {
    try
    {
      GAEServerAuth auth = gaeAuth;
      if (null == auth)
      {
        auth = GAE.servers.select();
      }
      int sid = null == local ? 0 : local.getId();
      IniProperties cfg = SnovaConfiguration.getInstance()
              .getIniProperties();
      HttpRequest request = new DefaultHttpRequest(HttpVersion.HTTP_1_1,
              HttpMethod.POST, "/invoke");
      request.setHeader(HttpHeaders.Names.HOST, auth.appid
              + ".appspot.com");
      request.setHeader(HttpHeaders.Names.CONNECTION, "keep-alive");
      request.setHeader(
              HttpHeaders.Names.USER_AGENT,
              cfg.getProperty("GAE", "UserAgent",
                      "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:15.0) Gecko/20100101 Firefox/15.0.1"));
      request.setHeader(HttpHeaders.Names.CONTENT_TYPE,
              "application/octet-stream");

      EventHeaderTags tags = new EventHeaderTags();
      tags.token = auth.token;
      ev.setHash(sid);
      ev = wrapEvent(ev);
      Buffer buf = EventHelper.encodeEvent(tags, ev);
      request.setHeader(HttpHeaders.Names.CONTENT_LENGTH,
              "" + buf.readableBytes());
      request.setContent(ChannelBuffers.wrappedBuffer(buf.getRawBuffer(),
              buf.getReadIndex(), buf.readableBytes()));
      HttpClientHandler h = client.execute(request, cb);
      workingHttpClientHandlers.add(h);
      cb.httpHandler = h;
    }
    catch (HttpClientException e)
    {
      logger.error("Failed to proxy request.", e);
    }
  }

  class GAEFutureCallback implements FutureCallback
  {
    private int failedCount;
    private Buffer bodyContent = new Buffer(1024);
    private long bodyLength = 0;
    private HTTPRequestEvent backupEvent = null;
    private HttpClientHandler httpHandler = null;

    public GAEFutureCallback(EventHandler handler, Event ev)
    {
      ev = Event.extractEvent(ev);
      if (ev instanceof HTTPRequestEvent)
      {
        backupEvent = (HTTPRequestEvent) ev;
      }
      this.handler = handler;
    }

    void clear()
    {
      bodyLength = 0;
      bodyContent.clear();
      failedCount = 0;
    }

    EventHandler handler;

    private void removeHttpClientHandler()
    {
      if (null != httpHandler)
      {
        workingHttpClientHandlers.remove(httpHandler);
        httpHandler = null;
      }
    }

    private void onError()
    {
      removeHttpClientHandler();
      if (closed)
      {
        return;
      }
      failedCount++;
      if (failedCount < 2 && null != backupEvent && !closed)
      {
        backupEvent.content.setReadIndex(0);
        requestEvent(backupEvent, GAERemoteHandler.this, this);
        bodyContent.clear();
      }
      else
      {
        if (null != local)
        {
          local.close();
        }
      }
    }

    private void fillBodyContent(ChannelBuffer buf)
    {
      int len = buf.readableBytes();
      bodyContent.ensureWritableBytes(len);
      buf.readBytes(bodyContent.getRawBuffer(),
              bodyContent.getWriteIndex(), len);
      bodyContent.advanceWriteIndex(len);
      if (bodyContent.readableBytes() >= bodyLength)
      {
        try
        {
          Event ev = EventHelper.parseEvent(bodyContent);
          ev = Event.extractEvent(ev);
          EventHeader header = new EventHeader(
                  Event.getTypeVersion(ev.getClass()), ev.getHash());
          ev.setAttachment(this);
          handler.onEvent(header, ev);
          removeHttpClientHandler();
        }
        catch (Exception e)
        {
          logger.error("", e);
        }
      }
    }

    @Override
    public void onResponse(HttpResponse res)
    {
      if (res.getStatus().getCode() != 200)
      {
        logger.error("Unexpected response:" + res);
        onError();
      }
      else
      {
        bodyLength = HttpHeaders.getContentLength(res);
        bodyContent.clear();
        fillBodyContent(res.getContent());
      }
    }

    @Override
    public void onBody(HttpChunk chunk)
    {
      fillBodyContent(chunk.getContent());
    }

    @Override
    public void onError(String error)
    {
      logger.warn("Recv error:" + error + " for " + hashCode());
      onError();
    }

    @Override
    public void onComplete(HttpResponse res)
    {
    }
  }

  @Override
  public String getName()
  {
    return "GAE";
  }

  private void writeLocalHttpResponse(HTTPResponseEvent res)
  {
    HttpResponse httpres = buildHttpResponse(res);
    if (!httpres.containsHeader("Connection"))
    {
      httpres.setHeader("Connection", "close");
    }
    local.handleResponse(this, httpres);
    if (httpres.isChunked())
    {
      Buffer content = res.content;
      HttpChunk chunk = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(content.getRawBuffer(),
                      content.getReadIndex(), content.readableBytes()));
      local.handleChunk(this, chunk);
    }
  }

  @Override
  public void onHttpResponse(HTTPResponseEvent res)
  {
    writeLocalHttpResponse(res);
  }

  @Override
  public void onRangeChunk(Buffer buf)
  {
    if (null != local)
    {
      HttpChunk chunk = new DefaultHttpChunk(
              ChannelBuffers.wrappedBuffer(buf.getRawBuffer(),
                      buf.getReadIndex(), buf.readableBytes()));
      local.handleChunk(this, chunk);
    }
    else
    {
      if (null != rangeTask)
      {
        rangeTask.close();
      }
    }

  }

  @Override
  public void writeHttpReq(HTTPRequestEvent req)
  {
    requestEvent(req, this);
  }
}
TOP

Related Classes of org.snova.framework.proxy.gae.GAERemoteHandler$GAEFutureCallback

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.