Package org.snova.framework.proxy.range

Source Code of org.snova.framework.proxy.range.MultiRangeFetchTask

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

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.arch.buffer.Buffer;
import org.arch.common.KeyValuePair;
import org.arch.event.http.HTTPRequestEvent;
import org.arch.event.http.HTTPResponseEvent;
import org.arch.util.StringHelper;
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.util.SharedObjectHelper;

/**
* @author wqy
*
*/
public class MultiRangeFetchTask
{
  protected static Logger       logger                    = LoggerFactory
                                                                .getLogger(MultiRangeFetchTask.class);
  public static final int       STATE_WAIT_NORMAL_RES    = 0;
  public static final int       STATE_WAIT_HEAD_RES    = 1;
  public static final int       STATE_WAIT_RANGE_GET_RES  = 2;
 
  public int                   fetchLimit                = 256 * 1024;
  public int                   fetchWorkerNum            = 1;
  public int                   sessionID;
 
  private int                   rangeState                = STATE_WAIT_NORMAL_RES;
 
  private HTTPRequestEvent   req;
  private HTTPResponseEvent   res;
  private RangeHeaderValue   originRangeHader;
  private AtomicInteger       rangeWorker            = new AtomicInteger(0);
  private int                   contentEnd;
  private int                   contentBegin;
  private int                   rangePos;
  private int                   expectedRangePos;
  private Map<Integer, Buffer>  chunks                = new ConcurrentHashMap<Integer, Buffer>();
  private RangeCallback       cb;
 
  private boolean               closed;
 
  public void setRangeState(int rangeState)
  {
    this.rangeState = rangeState;
  }
 
  public void setRangeCallback(RangeCallback cb)
  {
    this.cb = cb;
  }
 
  public boolean processAsyncResponse(HTTPResponseEvent res)
  {
    if (closed)
    {
      logger.warn(String.format("Session[%d]closed", this.sessionID));
      return false;
    }
    if (this.rangeState == STATE_WAIT_RANGE_GET_RES
            && res.statusCode == 302)
    {
      String location = res.getHeader("Location");
      String xrange = res.getHeader("X-Range");
      if (!StringHelper.isEmptyString(location)
              && !StringHelper.isEmptyString(xrange))
      {
        HTTPRequestEvent freq = cloneRequest(req);
        freq.url = location;
        freq.setHeader("X-Snova-HCE", "1");
        freq.setHeader("Range", xrange);
        cb.writeHttpReq(freq);
        return true;
      }
    }
    switch (rangeState)
    {
      case STATE_WAIT_NORMAL_RES:
      {
        cb.onHttpResponse(res);
        return true;
      }
      case STATE_WAIT_HEAD_RES:
      {
        if (res.statusCode != 206)
        {
          cb.onHttpResponse(res);
          return true;
        }
        this.res = res;
        String contentRangeHeader = res.getHeader("Content-Range");
        int length = res.content.readableBytes();
        if (!StringHelper.isEmptyString(contentRangeHeader))
        {
          ContentRangeHeaderValue v = new ContentRangeHeaderValue(
                  contentRangeHeader);
          length = (int) v.getInstanceLength();
        }
        if (this.contentEnd == -1)
        {
          this.contentEnd = length - 1;
        }
        if (null != originRangeHader)
        {
          res.statusCode = 206;
          res.setHeader("Content-Range", String.format(
                  "bytes %d-%d/%d", this.contentBegin,
                  this.contentEnd, length));
        }
        else
        {
          res.statusCode = 200;
          res.removeHeader("Content-Range");
        }
        res.setHeader("Content-Length", ""
                + (this.contentEnd - this.contentBegin + 1));
       
        int n = res.content.readableBytes();
        this.expectedRangePos += n;
        this.rangePos += n;
        cb.onHttpResponse(res);
        rangeState = STATE_WAIT_RANGE_GET_RES;
        break;
      }
      case STATE_WAIT_RANGE_GET_RES:
      {
        if (res.statusCode != 206)
        {
          logger.error("Expected 206 response, but got "
                  + res.statusCode);
          if(res.getHeader("X-Range") != null)
          {
            fetchWorkerNum = 1;
            final HTTPRequestEvent freq = cloneRequest(req);
            freq.setHeader("Range", res.getHeader("X-Range"));
            SharedObjectHelper.getGlobalTimer().schedule(new Runnable()
            {
              public void run()
              {
                cb.writeHttpReq(freq)
              }
            }, 1, TimeUnit.SECONDS);
            return true;
          }
          return false;
        }
        rangeWorker.decrementAndGet();
        String contentRangeHeader = res.getHeader("Content-Range");
        ContentRangeHeaderValue v = new ContentRangeHeaderValue(
                contentRangeHeader);
        logger.info(String.format("Session[%d]Recv range chunk:%s",
                this.sessionID, contentRangeHeader));
        chunks.put((int) v.getFirstBytePos(), res.content);
        while (chunks.containsKey(expectedRangePos))
        {
          Buffer chunk = chunks.remove(expectedRangePos);
          expectedRangePos += chunk.readableBytes();
          cb.onRangeChunk(chunk);
        }
        if (expectedRangePos < this.contentEnd)
        {
          logger.info(String.format(
                  "Session[%d]Expect range chunk:%d", this.sessionID,
                  this.expectedRangePos));
        }
        else
        {
          logger.info(String.format(
                  "Session[%d]Range task finished.", this.sessionID));
        }
        break;
      }
      default:
      {
        return false;
      }
    }
    while (!this.closed
            && this.res.statusCode < 300
            && rangeWorker.get() < fetchWorkerNum
            && rangePos < contentEnd
            && (rangePos - expectedRangePos) < fetchLimit * fetchWorkerNum
                    * 2)
    {
      synchronized (this)
      {
        int begin = this.rangePos;
        int end = this.rangePos + fetchLimit - 1;
        if (end > this.contentEnd)
        {
          end = this.contentEnd;
        }
        this.rangePos = end + 1;
        rangeWorker.incrementAndGet();
        String rangeHeader = "bytes=" + begin + "-" + end;
        logger.info(String.format("Session[%d]Fetch range:%s",
                this.sessionID, rangeHeader));
        HTTPRequestEvent freq = cloneRequest(req);
        freq.setHeader("Range", rangeHeader);
        freq.setHeader("X-Snova-HCE", "1");
        cb.writeHttpReq(freq);
      }
    }
    return true;
  }
 
  public void close()
  {
    closed = true;
    chunks.clear();
  }
 
  private boolean processRequest(HTTPRequestEvent req)
  {
    if (!req.method.equalsIgnoreCase("GET"))
    {
      return false;
    }
    this.req = req;
    String rangeHeader = req.getHeader("Range");
    this.contentEnd = -1;
    this.contentBegin = 0;
    this.rangePos = 0;
    this.expectedRangePos = 0;
    if (!StringHelper.isEmptyString(rangeHeader))
    {
      this.originRangeHader = new RangeHeaderValue(rangeHeader);
      this.contentBegin = (int) originRangeHader.getFirstBytePos();
      this.contentEnd = (int) originRangeHader.getLastBytePos();
      this.rangePos = this.contentBegin;
      this.expectedRangePos = this.rangePos;
    }
    return true;
  }
 
  private HTTPRequestEvent cloneRequest(HTTPRequestEvent req)
  {
    HTTPRequestEvent clone = new HTTPRequestEvent();
    clone.method = req.method;
    clone.url = req.url;
    clone.setHash(req.getHash());
    for (KeyValuePair<String, String> h : req.headers)
    {
      clone.addHeader(h.getName(), h.getValue());
    }
    return clone;
  }
 
  public void asyncGet(HTTPRequestEvent req, RangeCallback cb)
  {
    this.cb = cb;
    processRequest(req);
    if (null != originRangeHader)
    {
      if (this.contentEnd > 0
              && this.contentEnd - this.contentBegin < this.fetchLimit)
      {
        this.rangeState = STATE_WAIT_NORMAL_RES;
        cb.writeHttpReq(req);
        return;
      }
    }
   
    HTTPRequestEvent freq = cloneRequest(req);
    freq.setHeader("Range", String.format("bytes=%d-%d", this.contentBegin,
            this.contentBegin + this.fetchLimit - 1));
    freq.setHeader("X-Snova-HCE", "1");
    this.rangeState = STATE_WAIT_HEAD_RES;
    cb.writeHttpReq(freq);
  }
}
TOP

Related Classes of org.snova.framework.proxy.range.MultiRangeFetchTask

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.