You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by am...@apache.org on 2012/05/18 18:58:28 UTC

git commit: TS-475: Support for efficient processing of single range requests.

Updated Branches:
  refs/heads/master 9c5bac58a -> e80215f4e


TS-475: Support for efficient processing of single range requests.


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

Branch: refs/heads/master
Commit: e80215f4e4ab4e3505b74652c31025708ce0e076
Parents: 9c5bac5
Author: Alan M. Carroll <am...@network-geographics.com>
Authored: Fri May 18 11:56:06 2012 -0500
Committer: Alan M. Carroll <am...@network-geographics.com>
Committed: Fri May 18 11:56:06 2012 -0500

----------------------------------------------------------------------
 CHANGES                        |    3 +
 iocore/cache/Cache.cc          |   12 ++
 iocore/cache/CacheRead.cc      |   90 ++++++++++---
 iocore/cache/CacheWrite.cc     |   33 ++++-
 iocore/cache/P_CacheInternal.h |   17 +++-
 proxy/Transform.cc             |  249 ++++-------------------------------
 proxy/Transform.h              |   37 +++++-
 proxy/TransformInternal.h      |   32 +----
 proxy/http/HttpSM.cc           |  234 +++++++++++++++++++++++++++++---
 proxy/http/HttpSM.h            |    7 +-
 proxy/http/HttpTransact.cc     |  112 +++++++++++++---
 proxy/http/HttpTransact.h      |   34 ++++--
 proxy/http/HttpTunnel.cc       |   25 +++-
 13 files changed, 546 insertions(+), 339 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/CHANGES
----------------------------------------------------------------------
diff --git a/CHANGES b/CHANGES
index b4371e9..27893f1 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,5 +1,8 @@
                                                          -*- coding: utf-8 -*-
 Changes with Apache Traffic Server 3.1.4
+  *) [TS-475] Accelerated single range requests.
+     Based on initial work by jumby, with help from bwyatt.
+
   *) [TS-1236] HTTP Accept filters do not work on Illumos.
 
   *) [TS-1075] Workarounds for linux auto-port issues in transparent

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/iocore/cache/Cache.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/Cache.cc b/iocore/cache/Cache.cc
index 587f749..864918a 100644
--- a/iocore/cache/Cache.cc
+++ b/iocore/cache/Cache.cc
@@ -1874,6 +1874,18 @@ CacheVC::handleReadDone(int event, Event *e) {
     }
 #endif
     Doc *doc = (Doc *) buf->data();
+
+    if (is_debug_tag_set("cache_read")) {
+      char xt[33];
+      Debug("cache_read"
+            , "Read fragment %s Length %d/%d/%"PRId64"[pre=%d] vc=%s doc=%s %d frags"
+            , doc->key.toHexStr(xt), doc->data_len(), doc->len, doc->total_len, doc->prefix_len()
+            , f.single_fragment ? "single" : "multi"
+            , doc->single_fragment() ? "single" : "multi"
+            , doc->nfrags()
+        );
+    }
+
     // put into ram cache?
     if (io.ok() &&
         ((doc->first_key == *read_key) || (doc->key == *read_key) || STORE_COLLISION) && doc->magic == DOC_MAGIC) {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/iocore/cache/CacheRead.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/CacheRead.cc b/iocore/cache/CacheRead.cc
index d10f851..b23a13c 100644
--- a/iocore/cache/CacheRead.cc
+++ b/iocore/cache/CacheRead.cc
@@ -624,43 +624,81 @@ CacheVC::openReadMain(int event, Event * e)
   int64_t bytes = doc->len - doc_pos;
   IOBufferBlock *b = NULL;
   if (seek_to) { // handle do_io_pread
-    if (seek_to >= (int64_t)doc_len) {
+    if (seek_to >= doc_len) {
       vio.ndone = doc_len;
       return calluser(VC_EVENT_EOS);
     }
     Doc *first_doc = (Doc*)first_buf->data();
-    Frag *first_frag = first_doc->frags();
-    if (!f.single_fragment) {
-      // find the target fragment
-      int i = 0;
-      for (; i < (int)first_doc->nfrags(); i++)
-        if (seek_to < (int64_t)first_frag[i].offset) break;
-      if (i >= (int)first_doc->nfrags()) {
-        Warning("bad fragment header");
-        return calluser(VC_EVENT_ERROR);
+    Frag *frags = first_doc->frags();
+    if (is_debug_tag_set("cache_seek")) {
+      char b[33], c[33];
+      Debug("cache_seek", "Seek @ %"PRId64" in %s from #%d @ %"PRId64"/%d:%s",
+            seek_to, first_key.toHexStr(b), fragment, doc_pos, doc->len, doc->key.toHexStr(c));
+    }
+    /* Because single fragment objects can migrate to hang off an alt vector
+       they can appear to the VC as multi-fragment when they are not really.
+       The essential difference is the existence of a fragment table. All
+       fragments past the header fragment have the same value for this check
+       and it's consistent with the existence of a frag table in first_doc.
+       f.single_fragment is not (it can be false when this check is true).
+    */
+    if (!doc->single_fragment()) {
+      int target = 0;
+      uint64_t next_off = frags[target].offset;
+      int lfi = static_cast<int>(first_doc->nfrags()) - 1;
+      ink_debug_assert(lfi >= 0); // because it's not a single frag doc.
+
+      /* Note: frag[i].offset is the offset of the first byte past the
+         i'th fragment. So frag[0].offset is the offset of the first
+         byte of fragment 1. In addition the # of fragments is one
+         more than the fragment table length, the start of the last
+         fragment being the last offset in the table.
+      */
+      if (fragment == 0 ||
+          seek_to < frags[fragment-1].offset ||
+          (fragment <= lfi && frags[fragment].offset <= seek_to)
+        ) {
+        // search from frag 0 on to find the proper frag
+        while (seek_to >= next_off && target < lfi) {
+          next_off = frags[++target].offset;
+        }
+        if (target == lfi && seek_to >= next_off) ++target;
+      } else { // shortcut if we are in the fragment already
+        target = fragment;
       }
-      // fragment is the current fragment
-      // key is the next key (fragment + 1)
-      if (i != fragment) {
-        i--; // read will increment the key and fragment
-        while (i > fragment) {
+      if (target != fragment) {
+        // Lread will read the next fragment always, so if that
+        // is the one we want, we don't need to do anything
+        int cfi = fragment;
+        --target;
+        while (target > fragment) {
           next_CacheKey(&key, &key);
-          fragment++;
+          ++fragment;
         }
-        while (i < fragment) {
+        while (target < fragment) {
           prev_CacheKey(&key, &key);
-          fragment--;
+          --fragment;
+        }
+
+        if (is_debug_tag_set("cache_seek")) {
+          char target_key_str[33];
+          key.toHexStr(target_key_str);
+          Debug("cache_seek", "Seek #%d @ %"PRId64" -> #%d @ %"PRId64":%s", cfi, doc_pos, target, seek_to, target_key_str);
         }
         goto Lread;
       }
     }
-    if (fragment)
-      doc_pos = doc->prefix_len() + seek_to - (int64_t)first_frag[fragment-1].offset;
-    else
-      doc_pos = doc->prefix_len() + seek_to; 
+    doc_pos = doc->prefix_len() + seek_to;
+    if (fragment) doc_pos -= static_cast<int64_t>(frags[fragment-1].offset);
     vio.ndone = 0;
     seek_to = 0;
     ntodo = vio.ntodo();
+    bytes = doc->len - doc_pos;
+    if (is_debug_tag_set("cache_seek")) {
+      char target_key_str[33];
+      key.toHexStr(target_key_str);
+      Debug("cache_seek", "Read # %d @ %"PRId64"/%d for %"PRId64, fragment, doc_pos, doc->len, bytes);
+    }
   }
   if (ntodo <= 0)
     return EVENT_CONT;
@@ -1055,6 +1093,14 @@ CacheVC::openReadStartHead(int event, Event * e)
       doc_pos = doc->prefix_len();
       doc_len = doc->total_len;
     }
+
+    if (is_debug_tag_set("cache_read")) { // amc debug
+      char xt[33],yt[33];
+      Debug("cache_rad", "CacheReadStartHead - read %s target %s - %s %d of %"PRId64" bytes, %d fragments",
+            doc->key.toHexStr(xt), key.toHexStr(yt),
+            f.single_fragment ? "single" : "multi",
+            doc->len, doc->total_len, doc->nfrags());
+    }
     // the first fragment might have been gc'ed. Make sure the first
     // fragment is there before returning CACHE_EVENT_OPEN_READ
     if (!f.single_fragment)

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/iocore/cache/CacheWrite.cc
----------------------------------------------------------------------
diff --git a/iocore/cache/CacheWrite.cc b/iocore/cache/CacheWrite.cc
index df18b6c..a75b5fb 100644
--- a/iocore/cache/CacheWrite.cc
+++ b/iocore/cache/CacheWrite.cc
@@ -179,10 +179,24 @@ CacheVC::handleWrite(int event, Event *e)
 
   // plain write case
   ink_assert(!trigger);
-  if (f.use_first_key && fragment) {
-    frag_len = (fragment-1) * sizeof(Frag);
-  } else
-    frag_len = 0;
+  frag_len = 0;
+  if (f.use_first_key) { // Writing the header fragment.
+    if (fragment) { // Multi-fragment from cache fill.
+      frag_len = (fragment-1) * sizeof(Frag);
+    } else if (first_buf) {
+      Doc* old_header = reinterpret_cast<Doc*>(first_buf->data());
+      if (!frag && old_header->flen && !f.single_fragment) {
+        // There's a fragment table we need to preserve.
+        frag_len = old_header->flen;
+        if (is_debug_tag_set("cache_update")) {
+          char x[33];
+          Debug("cache_update", "Preserving fragment table (%d in %d bytes) for %s",
+                old_header->nfrags(), frag_len, first_key.toHexStr(x));
+        }
+      }
+    }
+  }
+
   set_agg_write_in_progress();
   POP_HANDLER;
   agg_len = vol->round_to_approx_size(write_len + header_len + frag_len + sizeofDoc);
@@ -784,8 +798,13 @@ agg_copy(char *p, CacheVC *vc)
       doc->key = vc->key;
       dir_set_head(&vc->dir, !vc->fragment);
     }
-    if (doc->flen)
-      memcpy(doc->frags(), &vc->frag[0], doc->flen);
+    if (doc->flen) {
+      // There's a fragment table to write.
+      // If this was a cache fill, the frag table is internal.
+      // If it's a header update (e.g. 304 NOT MODIFIED return on
+      // stale object) then the frag table is in the first_buf.
+      memcpy(doc->frags(), vc->get_frag_table(), doc->flen);
+    }
 
 #ifdef HTTP_CACHE
     if (vc->f.rewrite_resident_alt) {
@@ -1507,7 +1526,7 @@ CacheVC::openWriteStartDone(int event, Event *e)
       }
 
       /* INKqa07123.
-         A directory entry which is nolonger valid may have been overwritten.
+         A directory entry which is no longer valid may have been overwritten.
          We need to start afresh from the beginning by setting last_collision
          to NULL.
        */

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/iocore/cache/P_CacheInternal.h
----------------------------------------------------------------------
diff --git a/iocore/cache/P_CacheInternal.h b/iocore/cache/P_CacheInternal.h
index 260edea..69e298f 100644
--- a/iocore/cache/P_CacheInternal.h
+++ b/iocore/cache/P_CacheInternal.h
@@ -361,6 +361,12 @@ struct CacheVC: public CacheVConnection
   virtual bool set_disk_io_priority(int priority);
   virtual int get_disk_io_priority();
 
+  /** Get the fragment table.
+      @return The address of the start of the fragment table,
+      or @c NULL if there is no fragment table.
+  */
+  virtual Frag* get_frag_table();
+
   // offsets from the base stat
 #define CACHE_STAT_ACTIVE  0
 #define CACHE_STAT_SUCCESS 1
@@ -436,7 +442,7 @@ struct CacheVC: public CacheVConnection
   int base_stat;
   int recursive;
   int closed;
-  int64_t seek_to;                // pread offset
+  uint64_t seek_to;               // pread offset
   int64_t offset;                 // offset into 'blocks' of data to write
   int64_t writer_offset;          // offset of the writer for reading from a writer
   int64_t length;                 // length of data available to write
@@ -740,6 +746,15 @@ CacheVC::writer_done()
   return false;
 }
 
+TS_INLINE Frag*
+CacheVC::get_frag_table() {
+  if (frag)
+    return frag;
+  else if (first_buf)
+    return reinterpret_cast<Doc*>(first_buf->data())->frags();
+  return 0;
+}
+
 TS_INLINE int
 Vol::close_write(CacheVC *cont)
 {

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/Transform.cc
----------------------------------------------------------------------
diff --git a/proxy/Transform.cc b/proxy/Transform.cc
index 34f819e..8a71878 100644
--- a/proxy/Transform.cc
+++ b/proxy/Transform.cc
@@ -115,17 +115,9 @@ TransformProcessor::null_transform(ProxyMutex *mutex)
   -------------------------------------------------------------------------*/
 
 INKVConnInternal *
-TransformProcessor::range_transform(ProxyMutex *mut, MIMEField *range_field, HTTPInfo *cache_obj, HTTPHdr *transform_resp, bool & b)
+TransformProcessor::range_transform(ProxyMutex *mut, RangeRecord *ranges, bool unsatisfiable, int num_fields, HTTPHdr *transform_resp, const char * content_type, int content_type_len, int64_t content_length)
 {
-  RangeTransform *range_transform = NEW(new RangeTransform(mut, range_field, cache_obj, transform_resp));
-
-  b = range_transform->is_range_unsatisfiable();
-
-  if (b || range_transform->is_this_range_not_handled()) {
-    delete range_transform;
-    return NULL;
-  }
-
+  RangeTransform *range_transform = NEW(new RangeTransform(mut, ranges, unsatisfiable, num_fields, transform_resp, content_type, content_type_len, content_length));
   return range_transform;
 }
 
@@ -735,56 +727,24 @@ TransformTest::run()
 
 
 
-///////////////////////////////////////////////////////////////////
-/// RangeTransform implementation
-/// handling Range requests from clients
-///////////////////////////////////////////////////////////////////
-
 /*-------------------------------------------------------------------------
   -------------------------------------------------------------------------*/
 
-inline static int
-num_chars_for_int(int64_t i)
-{
-  int k = 1;
-
-  if (i < 0)
-    return 0;
-
-  while ((i /= 10) != 0)
-    ++k;
-
-  return k;
-}
-
-
-/*-------------------------------------------------------------------------
-  -------------------------------------------------------------------------*/
-
-RangeTransform::RangeTransform(ProxyMutex *mut, MIMEField *range_field, HTTPInfo *cache_obj, HTTPHdr *transform_resp)
+RangeTransform::RangeTransform(ProxyMutex *mut, RangeRecord *ranges,bool unsatisfiable, int num_fields, HTTPHdr * transform_resp, const char * content_type, int content_type_len, int64_t content_length)
   : INKVConnInternal(NULL, reinterpret_cast<TSMutex>(mut)),
-    m_output_buf(NULL),
-    m_output_reader(NULL),
-    m_range_field(range_field),
-    m_transform_resp(transform_resp),
-    m_output_vio(NULL),
-    m_unsatisfiable_range(true),
-    m_not_handle_range(false),
-    m_num_range_fields(0),
-    m_current_range(0), m_content_type(NULL), m_content_type_len(0), m_ranges(NULL), m_output_cl(0), m_done(0)
+  m_output_buf(NULL),
+  m_output_reader(NULL),
+  m_transform_resp(transform_resp),
+  m_output_vio(NULL),
+  m_unsatisfiable_range(unsatisfiable),
+  m_range_content_length(0),
+  m_num_range_fields(num_fields),
+  m_current_range(0), m_content_type(content_type), m_content_type_len(content_type_len), m_ranges(ranges), m_output_cl(content_length), m_done(0)
 {
   SET_HANDLER(&RangeTransform::handle_event);
 
-  m_content_type = cache_obj->
-    response_get()->value_get(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, &m_content_type_len);
-
-  m_content_length = cache_obj->object_size_get();
-  m_num_chars_for_cl = num_chars_for_int(m_content_length);
-
-  parse_range_and_compare();
-  calculate_output_cl();
-
-  Debug("transform_range", "RangeTransform creation finishes");
+  m_num_chars_for_cl = num_chars_for_int(m_range_content_length);
+  Debug("http_trans", "RangeTransform creation finishes");
 }
 
 
@@ -793,8 +753,6 @@ RangeTransform::RangeTransform(ProxyMutex *mut, MIMEField *range_field, HTTPInfo
 
 RangeTransform::~RangeTransform()
 {
-  if (m_ranges)
-    delete[]m_ranges;
   if (m_output_buf)
     free_MIOBuffer(m_output_buf);
 }
@@ -803,119 +761,6 @@ RangeTransform::~RangeTransform()
 /*-------------------------------------------------------------------------
   -------------------------------------------------------------------------*/
 
-void
-RangeTransform::parse_range_and_compare()
-{
-  // note: unsatisfiable_range is initialized to true in constructor
-  int prev_good_range, i;
-  const char *value;
-  int value_len;
-  HdrCsvIter csv;
-  const char *s, *e;
-
-  if (m_content_length <= 0)
-    return;
-
-  ink_assert(m_range_field != NULL);
-
-  m_num_range_fields = 0;
-  value = csv.get_first(m_range_field, &value_len);
-
-  while (value) {
-    m_num_range_fields++;
-    value = csv.get_next(&value_len);
-  }
-
-  if (m_num_range_fields <= 0)
-    return;
-
-  m_ranges = NEW(new RangeRecord[m_num_range_fields]);
-
-  value = csv.get_first(m_range_field, &value_len);
-
-  i = 0;
-  prev_good_range = -1;
-  // Currently HTTP/1.1 only defines bytes Range
-  if (ptr_len_ncmp(value, value_len, "bytes=", 6) == 0) {
-    while (value) {
-      // If delimiter '-' is missing
-      if (!(e = (const char *) memchr(value, '-', value_len))) {
-        m_unsatisfiable_range = true;
-        return;
-      }
-      
-      if ( memcmp(value,"bytes=",6) == 0 ) {
-        s = value + 6;
-      }
-      else {
-        s = value;
-      }
-      
-      m_ranges[i]._start = ((s==e)?-1:mime_parse_int64(s, e));
-
-      e++;
-      s = e;
-      e = value + value_len;
-      if ( e && *(e-1) == '-') { //open-ended Range: bytes=10-\r\n\r\n should be supported
-        m_ranges[i]._end = -1;
-      }
-      else {
-        m_ranges[i]._end = mime_parse_int64(s, e);
-      }
-
-      // check and change if necessary whether this is a right entry
-      // the last _end bytes are required
-      if (m_ranges[i]._start == -1 && m_ranges[i]._end > 0) {
-        if (m_ranges[i]._end > m_content_length)
-          m_ranges[i]._end = m_content_length;
-
-        m_ranges[i]._start = m_content_length - m_ranges[i]._end;
-        m_ranges[i]._end = m_content_length - 1;
-      }
-      // open start
-      else if (m_ranges[i]._start >= 0 && m_ranges[i]._end == -1) {
-        if (m_ranges[i]._start >= m_content_length)
-          m_ranges[i]._start = -1;
-        else
-          m_ranges[i]._end = m_content_length - 1;
-      }
-      // "normal" Range - could be wrong if _end<_start
-      else if (m_ranges[i]._start >= 0 && m_ranges[i]._end >= 0) {
-        if (m_ranges[i]._start > m_ranges[i]._end || m_ranges[i]._start >= m_content_length)
-          m_ranges[i]._start = m_ranges[i]._end = -1;
-        else if (m_ranges[i]._end >= m_content_length)
-          m_ranges[i]._end = m_content_length - 1;
-      }
-
-      else
-        m_ranges[i]._start = m_ranges[i]._end = -1;
-
-      // this is a good Range entry
-      if (m_ranges[i]._start != -1) {
-        if (m_unsatisfiable_range) {
-          m_unsatisfiable_range = false;
-          // initialize m_current_range to the first good Range
-          m_current_range = i;
-        }
-        // currently we don't handle out-of-order Range entry
-        else if (prev_good_range >= 0 && m_ranges[i]._start <= m_ranges[prev_good_range]._end) {
-          m_not_handle_range = true;
-          break;
-        }
-
-        prev_good_range = i;
-      }
-
-      value = csv.get_next(&value_len);
-      i++;
-    }
-  }
-}
-
-
-/*-------------------------------------------------------------------------
-  -------------------------------------------------------------------------*/
-
 int
 RangeTransform::handle_event(int event, void *edata)
 {
@@ -923,7 +768,7 @@ RangeTransform::handle_event(int event, void *edata)
 
   if (m_closed) {
     if (m_deletable) {
-      Debug("transform_range", "RangeTransform destroy: %p", this);
+      Debug("http_trans", "RangeTransform destroy: %p ndone=%"PRId64, this, m_output_vio ? m_output_vio->ndone : 0);
       delete this;
     }
   } else {
@@ -1035,7 +880,7 @@ RangeTransform::transform_to_range()
           add_boundary(true);
         }
 
-        Debug("transform_range", "total bytes of Range response body is %"PRId64"", m_done);
+        Debug("http_trans", "total bytes of Range response body is %"PRId64, m_done);
         m_output_vio->nbytes = m_done;
         m_output_vio->reenable();
 
@@ -1093,8 +938,6 @@ static char bound[] = "RANGE_SEPARATOR";
 static char range_type[] = "multipart/byteranges; boundary=RANGE_SEPARATOR";
 static char cont_type[] = "Content-type: ";
 static char cont_range[] = "Content-range: bytes ";
-static int sub_header_size = sizeof(cont_type) - 1 + 2 + sizeof(cont_range) - 1 + 4;
-static int boundary_size = 2 + sizeof(bound) - 1 + 2;
 
 void
 RangeTransform::add_boundary(bool end)
@@ -1127,7 +970,7 @@ RangeTransform::add_sub_header(int index)
   m_done += m_output_buf->write("\r\n", 2);
   m_done += m_output_buf->write(cont_range, sizeof(cont_range) - 1);
 
-  snprintf(numbers, sizeof(numbers), "%" PRId64 "d-%" PRId64 "d/%" PRId64 "d", m_ranges[index]._start, m_ranges[index]._end, m_content_length);
+  snprintf(numbers, sizeof(numbers), "%" PRId64 "d-%" PRId64 "d/%" PRId64 "d", m_ranges[index]._start, m_ranges[index]._end, m_output_cl);
   len = strlen(numbers);
   if (len < RANGE_NUMBERS_LENGTH)
     m_done += m_output_buf->write(numbers, len);
@@ -1148,8 +991,9 @@ RangeTransform::change_response_header()
   MIMEField *field;
   char *reason_phrase;
   HTTPStatus status_code;
-
-  ink_assert(m_transform_resp->field_find(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE) == NULL);
+  
+  ink_release_assert(m_transform_resp);
+  ink_release_assert(m_transform_resp->field_find(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE) == NULL);
 
   status_code = HTTP_STATUS_PARTIAL_CONTENT;
   m_transform_resp->status_set(status_code);
@@ -1157,62 +1001,19 @@ RangeTransform::change_response_header()
   m_transform_resp->reason_set(reason_phrase, strlen(reason_phrase));
 
   // set the right Content-Type for multiple entry Range
-  if (m_num_range_fields > 1) {
-    field = m_transform_resp->field_find(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+  field = m_transform_resp->field_find(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
 
-    if (field != NULL)
-      m_transform_resp->field_delete(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+  if (field != NULL)
+    m_transform_resp->field_delete(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
 
 
-    field = m_transform_resp->field_create(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
-    field->value_append(m_transform_resp->m_heap, m_transform_resp->m_mime, range_type, sizeof(range_type) - 1);
+  field = m_transform_resp->field_create(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+  field->value_append(m_transform_resp->m_heap, m_transform_resp->m_mime, range_type, sizeof(range_type) - 1);
 
-    m_transform_resp->field_attach(field);
-  }
+  m_transform_resp->field_attach(field);
 
-  else {
-    char numbers[RANGE_NUMBERS_LENGTH];
-
-    field = m_transform_resp->field_create(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE);
-    snprintf(numbers, sizeof(numbers), "bytes %" PRId64 "-%" PRId64 "/%" PRId64 "", m_ranges[0]._start, m_ranges[0]._end, m_content_length);
-    field->value_append(m_transform_resp->m_heap, m_transform_resp->m_mime, numbers, strlen(numbers));
-    m_transform_resp->field_attach(field);
-  }
 }
 
 #undef RANGE_NUMBERS_LENGTH
 
-
-/*-------------------------------------------------------------------------
-  -------------------------------------------------------------------------*/
-
-void
-RangeTransform::calculate_output_cl()
-{
-  int i;
-
-  if (m_unsatisfiable_range)
-    return;
-
-  if (m_num_range_fields == 1)
-    m_output_cl = m_ranges[0]._end - m_ranges[0]._start + 1;
-
-  else {
-    for (i = 0; i < m_num_range_fields; i++) {
-      if (m_ranges[i]._start >= 0) {
-        m_output_cl += boundary_size;
-        m_output_cl += sub_header_size + m_content_type_len;
-        m_output_cl += num_chars_for_int(m_ranges[i]._start)
-          + num_chars_for_int(m_ranges[i]._end) + m_num_chars_for_cl + 2;
-        m_output_cl += m_ranges[i]._end - m_ranges[i]._start + 1;
-        m_output_cl += 2;
-      }
-    }
-
-    m_output_cl += boundary_size + 2;
-  }
-
-  Debug("transform_range", "Pre-calculated Content-Length for Range response is %"PRId64"", m_output_cl);
-}
-
 #endif // TS_NO_TRANSFORM

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/Transform.h
----------------------------------------------------------------------
diff --git a/proxy/Transform.h b/proxy/Transform.h
index a9e95c7..af27870 100644
--- a/proxy/Transform.h
+++ b/proxy/Transform.h
@@ -25,13 +25,21 @@
 #define __TRANSFORM_H__
 
 #include "P_EventSystem.h"
-
 #include "HTTP.h"
 #include "InkAPIInternal.h"
 
-
 #define TRANSFORM_READ_READY   (TRANSFORM_EVENTS_START + 0)
 
+typedef struct _RangeRecord
+{
+  _RangeRecord() :
+  _start(-1), _end(-1), _done_byte(-1)
+  { }
+
+  int64_t _start;
+  int64_t _end;
+  int64_t _done_byte;
+} RangeRecord;
 
 class TransformProcessor
 {
@@ -41,11 +49,9 @@ public:
 public:
   VConnection * open(Continuation * cont, APIHook * hooks);
   INKVConnInternal *null_transform(ProxyMutex * mutex);
-  INKVConnInternal *range_transform(ProxyMutex * mutex, MIMEField * range_field, HTTPInfo * cache_obj,
-                                    HTTPHdr * transform_resp, bool & b);
+  INKVConnInternal *range_transform(ProxyMutex * mutex, RangeRecord * ranges, bool, int, HTTPHdr *, const char * content_type, int content_type_len, int64_t content_length);
 };
 
-
 #ifdef TS_HAS_TESTS
 class TransformTest
 {
@@ -54,6 +60,27 @@ public:
 };
 #endif
 
+///////////////////////////////////////////////////////////////////
+/// RangeTransform implementation
+/// handling Range requests from clients
+///////////////////////////////////////////////////////////////////
+
+/*-------------------------------------------------------------------------
+  -------------------------------------------------------------------------*/
+
+inline static int
+num_chars_for_int(int64_t i)
+{
+  int k = 1;
+
+  if (i < 0)
+    return 0;
+
+  while ((i /= 10) != 0)
+    ++k;
+
+  return k;
+}
 
 extern TransformProcessor transformProcessor;
 

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/TransformInternal.h
----------------------------------------------------------------------
diff --git a/proxy/TransformInternal.h b/proxy/TransformInternal.h
index 641868d..45781a7 100644
--- a/proxy/TransformInternal.h
+++ b/proxy/TransformInternal.h
@@ -115,10 +115,10 @@ public:
 class RangeTransform:public INKVConnInternal
 {
 public:
-  RangeTransform(ProxyMutex * mutex, MIMEField * range_field, HTTPInfo * cache_obj, HTTPHdr * transform_resp);
+  RangeTransform(ProxyMutex * mutex, RangeRecord * ranges, bool, int, HTTPHdr *transform_resp, const char * content_type, int content_type_len, int64_t content_length);
   ~RangeTransform();
 
-  void parse_range_and_compare();
+  // void parse_range_and_compare();
   int handle_event(int event, void *edata);
 
   void transform_to_range();
@@ -126,35 +126,18 @@ public:
   void add_sub_header(int index);
   void change_response_header();
   void calculate_output_cl();
-  bool is_this_range_not_handled()
-  {
-    return m_not_handle_range;
-  }
-  bool is_range_unsatisfiable()
-  {
-    return m_unsatisfiable_range;
-  }
-
-  typedef struct _RangeRecord
-  {
-  _RangeRecord() :
-    _start(-1), _end(-1), _done_byte(-1)
-    { }
-
-    int64_t _start;
-    int64_t _end;
-    int64_t _done_byte;
-  } RangeRecord;
 
 public:
   MIOBuffer * m_output_buf;
   IOBufferReader *m_output_reader;
-  MIMEField *m_range_field;
+  
+  
+  // MIMEField *m_range_field;
   HTTPHdr *m_transform_resp;
   VIO *m_output_vio;
   bool m_unsatisfiable_range;
-  bool m_not_handle_range;
-  int64_t m_content_length;
+  // bool m_not_handle_range;
+  int64_t m_range_content_length;
   int m_num_chars_for_cl;
   int m_num_range_fields;
   int m_current_range;
@@ -163,6 +146,7 @@ public:
   RangeRecord *m_ranges;
   int64_t m_output_cl;
   int64_t m_done;
+  
 };
 
 #define PREFETCH

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/http/HttpSM.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpSM.cc b/proxy/http/HttpSM.cc
index f456181..18b2aab 100644
--- a/proxy/http/HttpSM.cc
+++ b/proxy/http/HttpSM.cc
@@ -37,6 +37,7 @@
 #include "PluginVC.h"
 #include "ReverseProxy.h"
 #include "RemapProcessor.h"
+#include "Transform.h"
 
 #include "HttpPages.h"
 
@@ -87,6 +88,12 @@ static uint32_t val[MAX_SCATTER_LEN];
 static uint16_t to[MAX_SCATTER_LEN];
 static int scat_count = 0;
 
+static char bound[] = "RANGE_SEPARATOR";
+//
+static char cont_type[] = "Content-type: ";
+static char cont_range[] = "Content-range: bytes ";
+static int sub_header_size = sizeof(cont_type) - 1 + 2 + sizeof(cont_range) - 1 + 4;
+static int boundary_size = 2 + sizeof(bound) - 1 + 2;
 
 /**
  * Takes two milestones and returns the difference.
@@ -3024,7 +3031,7 @@ HttpSM::tunnel_handler_ua(int event, HttpTunnelConsumer * c)
   //
   HttpTransact::Source_t original_source = t_state.source;
   if (HttpTransact::SOURCE_TRANSFORM == original_source &&
-      t_state.range_setup == HttpTransact::RANGE_TRANSFORM) {
+      t_state.range_setup != HttpTransact::RANGE_NONE) {
     original_source = t_state.pre_transform_source;
   }
 
@@ -3571,7 +3578,7 @@ HttpSM::tunnel_handler_transform_write(int event, HttpTunnelConsumer * c)
   // all other transforms are plugin driven and the difference between
   // source data and final data should represent the transformation delta
   //
-  if (t_state.range_setup != HttpTransact::RANGE_TRANSFORM) {
+  if (t_state.range_setup == HttpTransact::RANGE_NONE) {
     switch (t_state.pre_transform_source) {
     case HttpTransact::SOURCE_HTTP_ORIGIN_SERVER:
       server_response_body_bytes = client_response_body_bytes;
@@ -3878,37 +3885,220 @@ HttpSM::do_hostdb_update_if_necessary()
   return;
 }
 
+void
+HttpSM::parse_range_and_compare(MIMEField *field, int64_t content_length)
+{
+  // note: unsatisfiable_range is initialized to true in constructor
+  int prev_good_range, i;
+  const char *value;
+  int value_len;
+  HdrCsvIter csv;
+  const char *s, *e;
+
+  if (content_length <= 0)
+    return;
 
-// this function first checks if cached response has Accept-Ranges and
-// Content-Length header and is HTTP/1.1. && There is no other plugins
-// hooked to TS_HTTP_RESPONSE_TRANSFORM_HOOK.
-// Then setup Range transformation if necessary
+  ink_assert(field != NULL);
+
+  t_state.num_range_fields = 0;
+  value = csv.get_first(field, &value_len);
+
+  while (value) {
+    t_state.num_range_fields++;
+    value = csv.get_next(&value_len);
+  }
+
+  if (t_state.num_range_fields <= 0)
+    return;
+
+  t_state.ranges = NEW(new RangeRecord[t_state.num_range_fields]);
+
+  value = csv.get_first(field, &value_len);
+
+  i = 0;
+  prev_good_range = -1;
+  // Currently HTTP/1.1 only defines bytes Range
+  if (ptr_len_ncmp(value, value_len, "bytes=", 6) == 0) {
+    while (value) {
+      // If delimiter '-' is missing
+      if (!(e = (const char *) memchr(value, '-', value_len))) {
+        t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE;
+        t_state.num_range_fields = -1;
+        return;
+      }
+
+      if ( memcmp(value,"bytes=",6) == 0 ) {
+        s = value + 6;
+      }
+      else {
+        s = value;
+      }
+      t_state.ranges[i]._start = ((s==e)?-1:mime_parse_int64(s, e));
+
+      e++;
+      s = e;
+      e = value + value_len;
+      if ( e && *(e-1) == '-') { //open-ended Range: bytes=10-\r\n\r\n should be supported
+        t_state.ranges[i]._end = -1;
+      }
+      else {
+        t_state.ranges[i]._end = mime_parse_int64(s, e);
+      }
+
+      // check and change if necessary whether this is a right entry
+      // the last _end bytes are required
+      if (t_state.ranges[i]._start == -1 && t_state.ranges[i]._end > 0) {
+        if (t_state.ranges[i]._end > content_length)
+          t_state.ranges[i]._end = content_length;
+
+        t_state.ranges[i]._start = content_length - t_state.ranges[i]._end;
+        t_state.ranges[i]._end = content_length - 1;
+      }
+      // open start
+      else if (t_state.ranges[i]._start >= 0 && t_state.ranges[i]._end == -1) {
+        if (t_state.ranges[i]._start >= content_length)
+          t_state.ranges[i]._start = -1;
+        else
+          t_state.ranges[i]._end = content_length - 1;
+      }
+      // "normal" Range - could be wrong if _end<_start
+      else if (t_state.ranges[i]._start >= 0 && t_state.ranges[i]._end >= 0) {
+        if (t_state.ranges[i]._start > t_state.ranges[i]._end || t_state.ranges[i]._start >= content_length)
+          t_state.ranges[i]._start = t_state.ranges[i]._end = -1;
+        else if (t_state.ranges[i]._end >= content_length)
+          t_state.ranges[i]._end = content_length - 1;
+      }
+
+      else
+        t_state.ranges[i]._start = t_state.ranges[i]._end = -1;
+
+      // this is a good Range entry
+      if (t_state.ranges[i]._start != -1) {
+        if (t_state.unsatisfiable_range) {
+          t_state.unsatisfiable_range = false;
+          // initialize t_state.current_range to the first good Range
+          t_state.current_range = i;
+        }
+        // currently we don't handle out-of-order Range entry
+        else if (prev_good_range >= 0 && t_state.ranges[i]._start <= t_state.ranges[prev_good_range]._end) {
+          t_state.not_handle_range = true;
+          break;
+        }
+
+        prev_good_range = i;
+      }
+
+      value = csv.get_next(&value_len);
+      i++;
+    }
+  }
+}
+
+void
+HttpSM::calculate_output_cl(int64_t content_length, int64_t num_chars)
+{
+  int i;
+
+  if (t_state.unsatisfiable_range)
+    return;
+
+  if (t_state.num_range_fields == 1) {
+    t_state.range_output_cl = t_state.ranges[0]._end - t_state.ranges[0]._start + 1;
+  }
+  else {
+    for (i = 0; i < t_state.num_range_fields; i++) {
+      if (t_state.ranges[i]._start >= 0) {
+        t_state.range_output_cl += boundary_size;
+        t_state.range_output_cl += sub_header_size + content_length;
+        t_state.range_output_cl += num_chars_for_int(t_state.ranges[i]._start)
+          + num_chars_for_int(t_state.ranges[i]._end) + num_chars + 2;
+        t_state.range_output_cl += t_state.ranges[i]._end - t_state.ranges[i]._start + 1;
+        t_state.range_output_cl += 2;
+      }
+    }
+
+    t_state.range_output_cl += boundary_size + 2;
+  }
+
+  Debug("http_range", "Pre-calculated Content-Length for Range response is %"PRId64, t_state.range_output_cl);
+}
+
+void
+HttpSM::do_range_parse(MIMEField *range_field)
+{
+
+  //bool res = false;
+  
+  int64_t content_length   = t_state.cache_info.object_read->object_size_get();
+  int64_t num_chars_for_cl = num_chars_for_int(content_length);
+  
+  parse_range_and_compare(range_field, content_length);
+  calculate_output_cl(content_length, num_chars_for_cl);
+  
+  if (t_state.unsatisfiable_range) {
+    t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE;
+    return;
+  }
+
+  if (t_state.not_handle_range) {
+    t_state.range_setup = HttpTransact::RANGE_NOT_HANDLED;
+    return;
+  }
+
+  t_state.range_setup = HttpTransact::RANGE_REQUESTED;
+}
+
+// this function looks for any Range: headers, parses them and either
+// sets up a transform processor to handle the request OR defers to the 
+// HttpTunnel
 void
 HttpSM::do_range_setup_if_necessary()
 {
   MIMEField *field;
   INKVConnInternal *range_trans;
-  bool res = false;
-
+  int field_content_type_len = -1;
+  const char * content_type;
+  
   ink_assert(t_state.cache_info.object_read != NULL);
-
+  
   field = t_state.hdr_info.client_request.field_find(MIME_FIELD_RANGE, MIME_LEN_RANGE);
   ink_assert(field != NULL);
-
+  
   t_state.range_setup = HttpTransact::RANGE_NONE;
   if (t_state.method == HTTP_WKSIDX_GET && t_state.hdr_info.client_request.version_get() == HTTPVersion(1, 1)) {
-    if (api_hooks.get(TS_HTTP_RESPONSE_TRANSFORM_HOOK) == NULL) {
-      // We may still not do Range if it is out of order Range.
-      range_trans = transformProcessor.range_transform(mutex, field,
-                                                       t_state.cache_info.object_read,
-                                                       &t_state.hdr_info.transform_response, res);
-      if (range_trans != NULL) {
-        api_hooks.append(TS_HTTP_RESPONSE_TRANSFORM_HOOK, range_trans);
-        t_state.range_setup = HttpTransact::RANGE_TRANSFORM;
-      } else if (res)
-        t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE;
-      else
-        t_state.range_setup = HttpTransact::RANGE_NOT_HANDLED;
+    do_range_parse(field);
+    
+    if (t_state.range_setup == HttpTransact::RANGE_REQUESTED && 
+        t_state.num_range_fields > 1 &&
+        api_hooks.get(TS_HTTP_RESPONSE_TRANSFORM_HOOK) == NULL
+       ) 
+    {
+          Debug("http_trans", "Handling multiple Range: requests");
+          content_type = t_state.cache_info.object_read->response_get()->value_get(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE, &field_content_type_len);
+          //create a Range: transform processor for requests of type Range: bytes=1-2,4-5,10-100 (eg. multiple ranges)
+          range_trans = transformProcessor.range_transform(mutex, 
+                  t_state.ranges,
+                  t_state.unsatisfiable_range, 
+                  t_state.num_range_fields, 
+                  &t_state.hdr_info.transform_response,
+                  content_type,
+                  field_content_type_len,
+                  t_state.cache_info.object_read->object_size_get()
+                  );
+          if (range_trans != NULL) {
+            api_hooks.append(TS_HTTP_RESPONSE_TRANSFORM_HOOK, range_trans);
+            t_state.range_setup = HttpTransact::RANGE_REQUESTED;
+          }
+          else { //we couldnt append the transform to our API hooks so bailing
+            t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE;
+          } 
+    }
+    else if (t_state.range_setup == HttpTransact::RANGE_REQUESTED && t_state.num_range_fields == 1) {
+      Debug("http_trans", "Handling single Range: request");
+      //no op, we will handle this later in the HttpTunnel
+    }
+    else {
+      t_state.range_setup = HttpTransact::RANGE_NOT_SATISFIABLE;
     }
   }
 }

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/http/HttpSM.h
----------------------------------------------------------------------
diff --git a/proxy/http/HttpSM.h b/proxy/http/HttpSM.h
index d2eb74c..07aeaad 100644
--- a/proxy/http/HttpSM.h
+++ b/proxy/http/HttpSM.h
@@ -41,6 +41,7 @@
 #include "InkAPIInternal.h"
 #include "StatSystem.h"
 #include "HttpClientSession.h"
+#include "HdrUtils.h"
 //#include "AuthHttpAdapter.h"
 
 /* Enable LAZY_BUF_ALLOC to delay allocation of buffers until they
@@ -218,7 +219,11 @@ public:
   // setup Range transfomration if so.
   // return true when the Range is unsatisfiable
   void do_range_setup_if_necessary();
-
+  
+  void do_range_parse(MIMEField *range_field);
+  void calculate_output_cl(int64_t, int64_t);
+  void parse_range_and_compare(MIMEField*, int64_t);
+  
   // Called by transact to prevent reset problems
   //  failed PUSH requests
   void set_ua_half_close_flag();

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/http/HttpTransact.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpTransact.cc b/proxy/http/HttpTransact.cc
index 5d60c6c..68e7540 100644
--- a/proxy/http/HttpTransact.cc
+++ b/proxy/http/HttpTransact.cc
@@ -50,6 +50,8 @@
 #include "IPAllow.h"
 
 static const char *URL_MSG = "Unable to process requested URL.\n";
+static char range_type[] = "multipart/byteranges; boundary=RANGE_SEPARATOR";
+#define RANGE_NUMBERS_LENGTH 60
 
 #define HTTP_INCREMENT_TRANS_STAT(X) update_stat(s, X, 1);
 #define HTTP_SUM_TRANS_STAT(X,S) update_stat(s, X, (ink_statval_t) S);
@@ -2447,7 +2449,8 @@ HttpTransact::HandleCacheOpenReadHit(State* s)
   }
 
   ink_assert(s->cache_lookup_result == CACHE_LOOKUP_HIT_FRESH ||
-             s->cache_lookup_result == CACHE_LOOKUP_HIT_WARNING || s->cache_lookup_result == CACHE_LOOKUP_HIT_STALE);
+             s->cache_lookup_result == CACHE_LOOKUP_HIT_WARNING ||
+             s->cache_lookup_result == CACHE_LOOKUP_HIT_STALE);
   if (s->cache_lookup_result == CACHE_LOOKUP_HIT_STALE &&
       s->api_update_cached_object != HttpTransact::UPDATE_CACHED_OBJECT_CONTINUE) {
     needs_revalidate = true;
@@ -2481,9 +2484,6 @@ HttpTransact::HandleCacheOpenReadHit(State* s)
     s->www_auth_content = send_revalidate ? CACHE_AUTH_STALE : CACHE_AUTH_FRESH;
     send_revalidate = true;
   }
-  if (send_revalidate && s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE)) {
-    s->range_setup = RANGE_REVALIDATE;
-  }
 
   DebugTxn("http_trans", "CacheOpenRead --- needs_auth          = %d", needs_authenticate);
   DebugTxn("http_trans", "CacheOpenRead --- needs_revalidate    = %d", needs_revalidate);
@@ -2564,9 +2564,29 @@ HttpTransact::HandleCacheOpenReadHit(State* s)
           return;
         }
       }
+
       build_request(s, &s->hdr_info.client_request, &s->hdr_info.server_request, s->current.server->http_version);
 
-      issue_revalidate(s);
+      /* We can't handle revalidate requests with ranges. If it comes
+         back unmodified that's fine but if not the OS will either
+         provide the entire object which is difficult to handle and
+         potentially very inefficient, or we'll get back just the
+         range and we can't cache that anyway. So just forward the
+         request.
+
+         Note: We're here only if the cache is stale so we don't want
+         to serve it.
+      */
+      if (s->hdr_info.client_request.presence(MIME_PRESENCE_RANGE)) {
+        Debug("http_trans", "[CacheOpenReadHit] Forwarding range request for stale cache object.");
+        s->cache_info.action = CACHE_DO_NO_ACTION;
+        s->cache_info.object_read = NULL;
+      } else {
+        // If it's not a range tweak the request to be a revalidate.
+        // Note this doesn't actually issue the request, it simply
+        // adjusts the headers for later.
+        issue_revalidate(s);
+      }
 
       // this can not be anything but a simple origin server connection.
       // in other words, we would not have looked up the cache for a
@@ -2706,16 +2726,16 @@ HttpTransact::build_response_from_cache(State* s, HTTPWarningCode warning_code)
       // only if the cached response is a 200 OK
       if (client_response_code == HTTP_STATUS_OK && client_request->presence(MIME_PRESENCE_RANGE)) {
         s->state_machine->do_range_setup_if_necessary();
-        if (s->range_setup == RANGE_NOT_SATISFIABLE && s->http_config_param->reverse_proxy_enabled) {
+        if (s->range_setup == RANGE_NOT_SATISFIABLE) {
           build_error_response(s, HTTP_STATUS_RANGE_NOT_SATISFIABLE, "Requested Range Not Satisfiable","","");
           s->cache_info.action = CACHE_DO_NO_ACTION;
           s->next_action = PROXY_INTERNAL_CACHE_NOOP;
           break;
-        } else if (s->range_setup == RANGE_NOT_SATISFIABLE || s->range_setup == RANGE_NOT_HANDLED) {
-          // we switch to tunneing for Range requests either
-          // 1. we need to revalidate or
-          // 2. out-of-order Range requests
-          DebugTxn("http_seq", "[HttpTransact::HandleCacheOpenReadHit] Out-oforder Range request - tunneling");
+        } else if (s->range_setup == RANGE_NOT_HANDLED) {
+          // we switch to tunneling for Range requests if it is out of order.
+          // In that case we fetch the entire source so it's OK to switch
+          // this late.
+          DebugTxn("http_seq", "[HttpTransact::HandleCacheOpenReadHit] Out-of-order Range request - tunneling");
           s->cache_info.action = CACHE_DO_NO_ACTION;
           if (s->force_dns) {
             HandleCacheOpenReadMiss(s); // DNS is already completed no need of doing DNS
@@ -4528,6 +4548,11 @@ HttpTransact::handle_no_cache_operation_on_forward_server_response(State* s)
       s->next_action = how_to_open_connection(s);
     }
     return;
+  case HTTP_STATUS_PARTIAL_CONTENT:
+    // If we get this back we should be just passing it through.
+    ink_debug_assert(s->cache_info.action == CACHE_DO_NO_ACTION);
+    s->next_action = SERVER_READ;
+    break;
   default:
     DebugTxn("http_trans", "[hncoofsr] server sent back something other than 100,304,200");
     /* Default behavior is to pass-through response to the client */
@@ -6085,7 +6110,7 @@ HttpTransact::is_response_cacheable(State* s, HTTPHdr* request, HTTPHdr* respons
   int req_method = request->method_get_wksidx();
   if (!(HttpTransactHeaders::is_method_cacheable(req_method)) && s->api_req_cacheable == false) {
     DebugTxn("http_trans", "[is_response_cacheable] " "only GET, and some HEAD and POST are cachable");
-    return (false);
+    return false;
   }
   // DebugTxn("http_trans", "[is_response_cacheable] method is cacheable");
   // If the request was not looked up in the cache, the response
@@ -6097,7 +6122,7 @@ HttpTransact::is_response_cacheable(State* s, HTTPHdr* request, HTTPHdr* respons
   }
   // already has a fresh copy in the cache
   if (s->range_setup == RANGE_NOT_HANDLED)
-    return (false);
+    return false;
 
   // Check whether the response is cachable based on its cookie
   // If there are cookies in response but a ttl is set, allow caching
@@ -6121,7 +6146,7 @@ HttpTransact::is_response_cacheable(State* s, HTTPHdr* request, HTTPHdr* respons
   }
   // DebugTxn("http_trans", "[is_response_cacheable] server permits storing");
 
-  // does config explicitly forbit storing?
+  // does config explicitly forbid storing?
   // ttl overides other config parameters
   if ((!s->cache_info.directives.does_config_permit_storing &&
        !s->cache_control.ignore_server_no_cache &&
@@ -6132,7 +6157,7 @@ HttpTransact::is_response_cacheable(State* s, HTTPHdr* request, HTTPHdr* respons
   }
   // DebugTxn("http_trans", "[is_response_cacheable] config permits storing");
 
-  // does client explicitly forbit storing?
+  // does client explicitly forbid storing?
   if (!s->cache_info.directives.does_client_permit_storing && !s->cache_control.ignore_client_no_cache) {
     DebugTxn("http_trans", "[is_response_cacheable] client does not permit storing, "
           "and cache control does not say to ignore client no-cache");
@@ -6687,13 +6712,21 @@ HttpTransact::handle_content_length_header(State* s, HTTPHdr* header, HTTPHdr* b
 
         switch (s->source) {
         case SOURCE_CACHE:
+          
+          // if we are doing a single Range: request, calculate the new
+          // C-L: header
+          if (s->range_setup == HttpTransact::RANGE_REQUESTED && s->num_range_fields == 1) {
+            Debug("http_trans", "Partial content requested, re-calculating content-length");
+            change_response_header_because_of_range_request(s,header);
+            s->hdr_info.trust_response_cl = true;
+          }
           ////////////////////////////////////////////////
           //  Make sure that the cache's object size    //
           //   agrees with the Content-Length           //
           //   Otherwise, set the state's machine view  //
           //   of c-l to undefined to turn off K-A      //
           ////////////////////////////////////////////////
-          if ((int64_t) s->cache_info.object_read->object_size_get() == cl) {
+          else if ((int64_t) s->cache_info.object_read->object_size_get() == cl) {
             s->hdr_info.trust_response_cl = true;
           } else {
             DebugTxn("http_trans", "Content Length header and cache object size mismatch." "Disabling keep-alive");
@@ -6719,6 +6752,7 @@ HttpTransact::handle_content_length_header(State* s, HTTPHdr* header, HTTPHdr* b
         header->field_delete(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH);
         s->hdr_info.trust_response_cl = false;
       }
+      Debug("http_trans", "[handle_content_length_header] RESPONSE cont len in hdr is %"PRId64, header->get_content_length());
     } else {
       // No content length header
       if (s->source == SOURCE_CACHE) {
@@ -6732,7 +6766,15 @@ HttpTransact::handle_content_length_header(State* s, HTTPHdr* header, HTTPHdr* b
           header->field_delete(MIME_FIELD_CONTENT_LENGTH, MIME_LEN_CONTENT_LENGTH);
           s->hdr_info.trust_response_cl = false;
           s->hdr_info.request_content_length = HTTP_UNDEFINED_CL;
-        } else {
+        } 
+        else if (s->range_setup == HttpTransact::RANGE_REQUESTED && s->num_range_fields == 1) {
+          // if we are doing a single Range: request, calculate the new
+          // C-L: header
+          Debug("http_trans", "Partial content requested, re-calculating content-length");
+          change_response_header_because_of_range_request(s,header);
+          s->hdr_info.trust_response_cl = true;
+        }
+        else {
           header->set_content_length(cl);
           s->hdr_info.trust_response_cl = true;
         }
@@ -8852,3 +8894,39 @@ HttpTransact::delete_warning_value(HTTPHdr* to_warn, HTTPWarningCode warning_cod
     }
   }
 }
+
+void
+HttpTransact::change_response_header_because_of_range_request(State *s, HTTPHdr * header)
+{
+  MIMEField *field;
+  char *reason_phrase;
+
+  ink_assert(header->field_find(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE) == NULL);
+
+  header->status_set(HTTP_STATUS_PARTIAL_CONTENT);
+  reason_phrase = (char *) (http_hdr_reason_lookup(HTTP_STATUS_PARTIAL_CONTENT));
+  header->reason_set(reason_phrase, strlen(reason_phrase));
+
+  // set the right Content-Type for multiple entry Range
+  if (s->num_range_fields > 1) {
+    field = header->field_find(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+
+    if (field != NULL)
+      header->field_delete(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+
+    field = header->field_create(MIME_FIELD_CONTENT_TYPE, MIME_LEN_CONTENT_TYPE);
+    field->value_append(header->m_heap, header->m_mime, range_type, sizeof(range_type) - 1);
+
+    header->field_attach(field);
+    header->set_content_length(s->range_output_cl);
+  }
+  else {
+    char numbers[RANGE_NUMBERS_LENGTH];
+
+    field = header->field_create(MIME_FIELD_CONTENT_RANGE, MIME_LEN_CONTENT_RANGE);
+    snprintf(numbers, sizeof(numbers), "bytes %"PRId64"-%"PRId64"/%"PRId64, s->ranges[0]._start, s->ranges[0]._end, s->cache_info.object_read->object_size_get());
+    field->value_append(header->m_heap, header->m_mime, numbers, strlen(numbers));
+    header->field_attach(field);
+    header->set_content_length(s->range_output_cl);
+  }
+}

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/http/HttpTransact.h
----------------------------------------------------------------------
diff --git a/proxy/http/HttpTransact.h b/proxy/http/HttpTransact.h
index 694ac04..e70d1b2 100644
--- a/proxy/http/HttpTransact.h
+++ b/proxy/http/HttpTransact.h
@@ -651,13 +651,12 @@ public:
 
   enum RangeSetup_t
   {
-    RANGE_NONE,
-    RANGE_TRANSFORM,
+    RANGE_NONE = 0,
+    RANGE_REQUESTED,
     RANGE_NOT_SATISFIABLE,
     RANGE_NOT_HANDLED,
-    RANGE_REVALIDATE
   };
-
+  
   enum CacheAuth_t
   {
     CACHE_AUTH_NONE = 0,
@@ -982,9 +981,6 @@ public:
     StatBlock first_stats;
     StatBlock *current_stats;
 
-    // for Range: to avoid write transfomed Range response into cache
-    RangeSetup_t range_setup;
-
     // for negative caching
     bool negative_caching;
 
@@ -1052,7 +1048,16 @@ public:
     URL pristine_url;  // pristine url is the url before remap
     
     bool api_skip_all_remapping;
-
+    
+    // Http Range: related variables
+    RangeSetup_t range_setup;
+    bool unsatisfiable_range;
+    bool not_handle_range;
+    int64_t num_range_fields;
+    int64_t range_output_cl;
+    int64_t current_range;
+    RangeRecord *ranges;
+    
     OverridableHttpConfigParams *txn_conf;
     OverridableHttpConfigParams my_txn_conf; // Storage for plugins, to avoid malloc
     
@@ -1103,7 +1108,6 @@ public:
         state_machine_id(0),
         first_stats(),
         current_stats(NULL),
-        range_setup(RANGE_NONE),
         negative_caching(false),
         www_auth_content(CACHE_AUTH_NONE),
         client_connection_enabled(true),
@@ -1144,6 +1148,13 @@ public:
         reverse_proxy(false), url_remap_success(false), remap_redirect(NULL), filter_mask(0), already_downgraded(false),
         pristine_url(),
         api_skip_all_remapping(false),
+        range_setup(RANGE_NONE),
+        unsatisfiable_range(false),
+        not_handle_range(false),
+        num_range_fields(0),
+        range_output_cl(0),
+        current_range(-1),
+        ranges(NULL),
         txn_conf(NULL)
     {
       int i;
@@ -1232,6 +1243,10 @@ public:
       url_map.clear();
       arena.reset();
       pristine_url.clear();
+
+      delete[] ranges; ranges = NULL;
+      range_setup = RANGE_NONE;
+      unsatisfiable_range = true;
       return;
     }
 
@@ -1366,6 +1381,7 @@ public:
 
   static void build_response_copy(State* s, HTTPHdr* base_response, HTTPHdr* outgoing_response, HTTPVersion outgoing_version);
   static void handle_content_length_header(State* s, HTTPHdr* header, HTTPHdr* base);
+  static void change_response_header_because_of_range_request(State* s, HTTPHdr* header);
 
   static void handle_request_keep_alive_headers(State *s, HTTPVersion ver, HTTPHdr *heads);
   static void handle_response_keep_alive_headers(State *s, HTTPVersion ver, HTTPHdr *heads);

http://git-wip-us.apache.org/repos/asf/trafficserver/blob/e80215f4/proxy/http/HttpTunnel.cc
----------------------------------------------------------------------
diff --git a/proxy/http/HttpTunnel.cc b/proxy/http/HttpTunnel.cc
index f10c84c..d9f1a62 100644
--- a/proxy/http/HttpTunnel.cc
+++ b/proxy/http/HttpTunnel.cc
@@ -784,7 +784,12 @@ HttpTunnel::producer_run(HttpTunnelProducer * p)
     }
   }
 
-  if (p->nbytes >= 0) {
+  int64_t read_start_pos = 0;
+  if (p->vc_type == HT_CACHE_READ && sm->t_state.range_setup == HttpTransact::RANGE_REQUESTED && sm->t_state.num_range_fields == 1) {
+    read_start_pos = sm->t_state.ranges[0]._start;
+    producer_n = (sm->t_state.ranges[0]._end - sm->t_state.ranges[0]._start)+1;
+    consumer_n = (producer_n + sm->client_response_hdr_bytes);
+  } else if (p->nbytes >= 0) {
     consumer_n = p->nbytes;
     producer_n = p->ntodo;
   } else {
@@ -925,16 +930,22 @@ HttpTunnel::producer_run(HttpTunnelProducer * p)
       Debug("http_tunnel", "[%" PRId64 "] [tunnel_run] producer already done", sm->sm_id);
       producer_handler(HTTP_TUNNEL_EVENT_PRECOMPLETE, p);
     } else {
-      p->read_vio = p->vc->do_io_read(this, producer_n, p->read_buffer);
+      if (read_start_pos > 0) {
+        p->read_vio = ((CacheVC*)p->vc)->do_io_pread(this, producer_n, p->read_buffer, read_start_pos);
+      }
+      else {
+        p->read_vio = p->vc->do_io_read(this, producer_n, p->read_buffer);
+      }
     }
-
-    // Now that the tunnel has started, we must remove producer's reader so
-    // that it doesn't act like a buffer guard
-    p->read_buffer->dealloc_reader(p->buffer_start);
-    p->buffer_start = NULL;
   }
+
+  // Now that the tunnel has started, we must remove producer's reader so
+  // that it doesn't act like a buffer guard
+  p->read_buffer->dealloc_reader(p->buffer_start);
+  p->buffer_start = NULL;
 }
 
+
 int
 HttpTunnel::producer_handler_dechunked(int event, HttpTunnelProducer * p)
 {