[squid-dev] [PATCH] Http::StreamContext refactoring

Amos Jeffries squid3 at treenet.co.nz
Thu Jan 14 12:53:51 UTC 2016


This patch updates the Pipeline and ClientSocketContext class
interaction for better HTTP/2 compatibility.

The ClientSocketContext is renamed and shuffled to Http::StreamContext.

The name is chosen to match the HTTP/2 "stream" terminology. Which
defines a stream as a bi-directional transaction, including request,
reply and all related 1xx informational and/or control messages. That
same word "stream" is also used in RFC7230 briefly to describe the same
"transaction" scope and details. But not formalized until RFC7540.

Each StreamContext is assigned a 32-bit ID number from a per-connection
range of IDs on creation. HTTP/1 and FTP Server just incrementally
assign a new ID as requests are receved on a connection. HTTP/2 Server
will be assigning according to RFC7540 algorithms.

Pipeline class is updated to use the ID number to manage its contents
rather than Pointer value matching. It is also updated to drop the
HTTP/1 specific assumptions within the Pipeline implementation. As a
behavioural requirement the sequential flow is now left for the Server
and ClientHttpRequest Jobs to ensure correctness.


The old "client-side" classes are now split along the following scopes:

* ::Server / ConnStateData/ *::Server
 - the AsynJob managing I/O on a client connection.

* ClientHttpRequest
 - the AsyncJob operating transaction internal behaviours and Calls.
Will make use of ConnStateData and Http::StreamContext to perform its
duties (has not yet been refactored to do so cleanly yet).

* Http::StreamContext
 - the state data for a transaction. Refcounted and used by the Server
and ClientHttpRequest Jobs for their mutual data storage.

As noted the ClientHttpRequest part of the refactoring has not been
included in this. It still directly performs much of the tasks scoped
for Server classes and needs a restructure in how it utilizes
StreamContext members and method (or possibly whether its activity is
fully migrated to Http1::Server). That is all left for a followup patch.

Amos
-------------- next part --------------
=== modified file 'src/DelayId.cc'
--- src/DelayId.cc	2016-01-01 00:12:18 +0000
+++ src/DelayId.cc	2016-01-10 19:11:02 +0000
@@ -20,6 +20,7 @@
 #include "DelayId.h"
 #include "DelayPool.h"
 #include "DelayPools.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "SquidConfig.h"
 

=== modified file 'src/FwdState.cc'
--- src/FwdState.cc	2016-01-01 00:12:18 +0000
+++ src/FwdState.cc	2016-01-07 14:33:35 +0000
@@ -31,6 +31,7 @@
 #include "gopher.h"
 #include "hier_code.h"
 #include "http.h"
+#include "http/StreamContext.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "icmp/net_db.h"

=== modified file 'src/HttpHdrRange.cc'
--- src/HttpHdrRange.cc	2016-01-01 00:12:18 +0000
+++ src/HttpHdrRange.cc	2016-01-07 15:30:15 +0000
@@ -10,6 +10,7 @@
 
 #include "squid.h"
 #include "client_side_request.h"
+#include "http/StreamContext.h"
 #include "HttpHeaderRange.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"

=== modified file 'src/HttpHeaderTools.cc'
--- src/HttpHeaderTools.cc	2016-01-01 00:12:18 +0000
+++ src/HttpHeaderTools.cc	2016-01-07 15:38:22 +0000
@@ -20,6 +20,7 @@
 #include "fde.h"
 #include "globals.h"
 #include "http/RegisteredHeaders.h"
+#include "http/StreamContext.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeader.h"
 #include "HttpHeaderFieldInfo.h"

=== modified file 'src/HttpRequest.cc'
--- src/HttpRequest.cc	2016-01-01 00:12:18 +0000
+++ src/HttpRequest.cc	2016-01-07 15:20:17 +0000
@@ -19,6 +19,7 @@
 #include "gopher.h"
 #include "http.h"
 #include "http/one/RequestParser.h"
+#include "http/StreamContext.h"
 #include "HttpHdrCc.h"
 #include "HttpHeaderRange.h"
 #include "HttpRequest.h"

=== modified file 'src/Notes.cc'
--- src/Notes.cc	2016-01-01 00:12:18 +0000
+++ src/Notes.cc	2016-01-07 15:29:13 +0000
@@ -13,6 +13,7 @@
 #include "client_side.h"
 #include "ConfigParser.h"
 #include "globals.h"
+#include "http/StreamContext.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "SquidConfig.h"

=== modified file 'src/Pipeline.cc'
--- src/Pipeline.cc	2016-01-01 00:12:18 +0000
+++ src/Pipeline.cc	2016-01-08 10:27:07 +0000
@@ -13,22 +13,24 @@
 #include "anyp/PortCfg.h"
 #include "client_side.h"
 #include "Debug.h"
+#include "http/StreamContext.h"
 #include "Pipeline.h"
 
 void
-Pipeline::add(const ClientSocketContextPointer &c)
+Pipeline::add(const Http::StreamContextPointer &c)
 {
     requests.push_back(c);
     ++nrequests;
+    ++nactive;
     debugs(33, 3, "Pipeline " << (void*)this << " add request " << nrequests << ' ' << c);
 }
 
-ClientSocketContextPointer
+Http::StreamContextPointer
 Pipeline::front() const
 {
     if (requests.empty()) {
         debugs(33, 3, "Pipeline " << (void*)this << " empty");
-        return ClientSocketContextPointer();
+        return Http::StreamContextPointer();
     }
 
     debugs(33, 3, "Pipeline " << (void*)this << " front " << requests.front());
@@ -39,7 +41,7 @@
 Pipeline::terminateAll(int xerrno)
 {
     while (!requests.empty()) {
-        ClientSocketContextPointer context = requests.front();
+        Http::StreamContextPointer context = requests.front();
         debugs(33, 3, "Pipeline " << (void*)this << " notify(" << xerrno << ") " << context);
         context->noteIoError(xerrno);
         context->finished();  // cleanup and self-deregister
@@ -48,15 +50,24 @@
 }
 
 void
-Pipeline::popMe(const ClientSocketContextPointer &which)
+Pipeline::popById(uint32_t which)
 {
     if (requests.empty())
         return;
 
-    debugs(33, 3, "Pipeline " << (void*)this << " drop " << requests.front());
-    // in reality there may be multiple contexts doing processing in parallel.
-    // XXX: pipeline still assumes HTTP/1 FIFO semantics are obeyed.
-    assert(which == requests.front());
-    requests.pop_front();
+    debugs(33, 3, "Pipeline " << (void*)this << " drop id=" << which);
+
+    // find the context and clear its Pointer
+    for (auto &&i : requests) {
+        if (i->id == which) {
+            i = nullptr;
+            --nactive;
+            break;
+        }
+    }
+
+    // trim closed contexts from the list head (if any)
+    while (!requests.empty() && !requests.front())
+        requests.pop_front();
 }
 

=== modified file 'src/Pipeline.h'
--- src/Pipeline.h	2016-01-01 00:12:18 +0000
+++ src/Pipeline.h	2016-01-08 09:54:15 +0000
@@ -10,12 +10,10 @@
 #define SQUID_SRC_PIPELINE_H
 
 #include "base/RefCount.h"
+#include "http/forward.h"
 
 #include <list>
 
-class ClientSocketContext;
-typedef RefCount<ClientSocketContext> ClientSocketContextPointer;
-
 /**
  * A queue of transactions awaiting completion.
  *
@@ -39,17 +37,17 @@
     Pipeline & operator =(const Pipeline &) = delete;
 
 public:
-    Pipeline() : nrequests(0) {}
+    Pipeline() : nrequests(0), nactive(0) {}
     ~Pipeline() = default;
 
     /// register a new request context to the pipeline
-    void add(const ClientSocketContextPointer &);
+    void add(const Http::StreamContextPointer &);
 
     /// get the first request context in the pipeline
-    ClientSocketContextPointer front() const;
+    Http::StreamContextPointer front() const;
 
     /// how many requests are currently pipelined
-    size_t count() const {return requests.size();}
+    size_t count() const {return nactive;}
 
     /// whether there are none or any requests currently pipelined
     bool empty() const {return requests.empty();}
@@ -57,8 +55,8 @@
     /// tell everybody about the err, and abort all waiting requests
     void terminateAll(const int xerrno);
 
-    /// deregister the front request from the pipeline
-    void popMe(const ClientSocketContextPointer &);
+    /// deregister a request from the pipeline
+    void popById(uint32_t);
 
     /// Number of requests seen in this pipeline (so far).
     /// Includes incomplete transactions.
@@ -66,7 +64,11 @@
 
 private:
     /// requests parsed from the connection but not yet completed.
-    std::list<ClientSocketContextPointer> requests;
+    std::list<Http::StreamContextPointer> requests;
+
+    /// Number of still-active streams in this pipeline (so far).
+    /// Includes incomplete transactions.
+    uint32_t nactive;
 };
 
 #endif /* SQUID_SRC_PIPELINE_H */

=== modified file 'src/acl/AtStep.cc'
--- src/acl/AtStep.cc	2016-01-01 00:12:18 +0000
+++ src/acl/AtStep.cc	2016-01-07 11:26:51 +0000
@@ -14,6 +14,7 @@
 #include "acl/AtStepData.h"
 #include "acl/Checklist.h"
 #include "client_side.h"
+#include "http/StreamContext.h"
 #include "ssl/ServerBump.h"
 
 int

=== modified file 'src/acl/Certificate.cc'
--- src/acl/Certificate.cc	2016-01-01 00:12:18 +0000
+++ src/acl/Certificate.cc	2016-01-07 11:52:43 +0000
@@ -21,6 +21,7 @@
 #include "client_side.h"
 #include "fde.h"
 #include "globals.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 
 int

=== modified file 'src/acl/DestinationIp.cc'
--- src/acl/DestinationIp.cc	2016-01-01 00:12:18 +0000
+++ src/acl/DestinationIp.cc	2016-01-07 11:22:14 +0000
@@ -13,6 +13,7 @@
 #include "acl/FilledChecklist.h"
 #include "client_side.h"
 #include "comm/Connection.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "SquidConfig.h"
 

=== modified file 'src/acl/ExtUser.cc'
--- src/acl/ExtUser.cc	2016-01-01 00:12:18 +0000
+++ src/acl/ExtUser.cc	2016-01-07 11:25:11 +0000
@@ -16,6 +16,7 @@
 #include "acl/FilledChecklist.h"
 #include "acl/RegexData.h"
 #include "acl/UserData.h"
+#include "http/StreamContext.h"
 #include "client_side.h"
 
 ACLExtUser::~ACLExtUser()

=== modified file 'src/acl/FilledChecklist.cc'
--- src/acl/FilledChecklist.cc	2016-01-01 00:12:18 +0000
+++ src/acl/FilledChecklist.cc	2016-01-07 10:48:32 +0000
@@ -12,6 +12,7 @@
 #include "comm/Connection.h"
 #include "comm/forward.h"
 #include "ExternalACLEntry.h"
+#include "http/StreamContext.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "SquidConfig.h"

=== modified file 'src/acl/MyPortName.cc'
--- src/acl/MyPortName.cc	2016-01-01 00:12:18 +0000
+++ src/acl/MyPortName.cc	2016-01-07 11:25:07 +0000
@@ -11,11 +11,10 @@
 #include "acl/MyPortName.h"
 #include "acl/StringData.h"
 #include "anyp/PortCfg.h"
+#include "client_side.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 
-/* for ConnStateData */
-#include "client_side.h"
-
 int
 ACLMyPortNameStrategy::match(ACLData<MatchType> * &data, ACLFilledChecklist *checklist, ACLFlags &)
 {

=== modified file 'src/acl/ServerCertificate.cc'
--- src/acl/ServerCertificate.cc	2016-01-01 00:12:18 +0000
+++ src/acl/ServerCertificate.cc	2016-01-07 11:52:47 +0000
@@ -15,6 +15,7 @@
 #include "acl/ServerCertificate.h"
 #include "client_side.h"
 #include "fde.h"
+#include "http/StreamContext.h"
 #include "ssl/ServerBump.h"
 
 int

=== modified file 'src/acl/ServerName.cc'
--- src/acl/ServerName.cc	2016-01-01 00:12:18 +0000
+++ src/acl/ServerName.cc	2016-01-07 11:20:48 +0000
@@ -15,6 +15,7 @@
 #include "acl/ServerName.h"
 #include "client_side.h"
 #include "fde.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "ipcache.h"
 #include "SquidString.h"

=== modified file 'src/auth/Acl.cc'
--- src/auth/Acl.cc	2016-01-01 00:12:18 +0000
+++ src/auth/Acl.cc	2016-01-07 13:58:55 +0000
@@ -14,6 +14,7 @@
 #include "auth/UserRequest.h"
 #include "client_side.h"
 #include "fatal.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 
 /**

=== modified file 'src/auth/AclProxyAuth.cc'
--- src/auth/AclProxyAuth.cc	2016-01-01 00:12:18 +0000
+++ src/auth/AclProxyAuth.cc	2016-01-07 13:03:01 +0000
@@ -18,6 +18,7 @@
 #include "auth/User.h"
 #include "auth/UserRequest.h"
 #include "client_side.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 
 ACLProxyAuth::~ACLProxyAuth()

=== modified file 'src/auth/Gadgets.cc'
--- src/auth/Gadgets.cc	2016-01-01 00:12:18 +0000
+++ src/auth/Gadgets.cc	2016-01-07 13:52:33 +0000
@@ -28,6 +28,7 @@
 #include "auth/UserRequest.h"
 #include "client_side.h"
 #include "globals.h"
+#include "http/StreamContext.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 

=== modified file 'src/auth/UserRequest.cc'
--- src/auth/UserRequest.cc	2016-01-01 00:12:18 +0000
+++ src/auth/UserRequest.cc	2016-01-07 13:55:24 +0000
@@ -21,6 +21,7 @@
 #include "comm/Connection.h"
 #include "fatal.h"
 #include "format/Format.h"
+#include "http/StreamContext.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 #include "MemBuf.h"

=== modified file 'src/auth/negotiate/Config.cc'
--- src/auth/negotiate/Config.cc	2016-01-01 00:12:18 +0000
+++ src/auth/negotiate/Config.cc	2016-01-07 12:57:14 +0000
@@ -22,6 +22,7 @@
 #include "cache_cf.h"
 #include "client_side.h"
 #include "helper.h"
+#include "http/StreamContext.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"

=== modified file 'src/auth/negotiate/UserRequest.cc'
--- src/auth/negotiate/UserRequest.cc	2016-01-01 00:12:18 +0000
+++ src/auth/negotiate/UserRequest.cc	2016-01-07 12:59:53 +0000
@@ -20,6 +20,7 @@
 #include "globals.h"
 #include "helper.h"
 #include "helper/Reply.h"
+#include "http/StreamContext.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"

=== modified file 'src/auth/ntlm/Config.cc'
--- src/auth/ntlm/Config.cc	2016-01-01 00:12:18 +0000
+++ src/auth/ntlm/Config.cc	2016-01-07 12:58:51 +0000
@@ -22,6 +22,7 @@
 #include "cache_cf.h"
 #include "client_side.h"
 #include "helper.h"
+#include "http/StreamContext.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"

=== modified file 'src/auth/ntlm/UserRequest.cc'
--- src/auth/ntlm/UserRequest.cc	2016-01-01 00:12:18 +0000
+++ src/auth/ntlm/UserRequest.cc	2016-01-07 13:03:45 +0000
@@ -20,6 +20,7 @@
 #include "globals.h"
 #include "helper.h"
 #include "helper/Reply.h"
+#include "http/StreamContext.h"
 #include "HttpMsg.h"
 #include "HttpRequest.h"
 #include "MemBuf.h"

=== modified file 'src/clientStream.cc'
--- src/clientStream.cc	2016-01-01 00:12:18 +0000
+++ src/clientStream.cc	2016-01-07 14:38:30 +0000
@@ -11,6 +11,7 @@
 #include "squid.h"
 #include "client_side_request.h"
 #include "clientStream.h"
+#include "http/StreamContext.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"
 

=== modified file 'src/client_side.cc'
--- src/client_side.cc	2016-01-01 00:12:18 +0000
+++ src/client_side.cc	2016-01-09 14:15:47 +0000
@@ -87,6 +87,7 @@
 #include "http.h"
 #include "http/one/RequestParser.h"
 #include "http/one/TeChunkedParser.h"
+#include "http/StreamContext.h"
 #include "HttpHdrContRange.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
@@ -188,91 +189,10 @@
 static void clientUpdateHierCounters(HierarchyLogEntry *);
 static bool clientPingHasFinished(ping_data const *aPing);
 void prepareLogWithRequestDetails(HttpRequest *, AccessLogEntry::Pointer &);
-static void ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn);
-static void clientUpdateSocketStats(const LogTags &logType, size_t size);
+static void ClientSocketContextPushDeferredIfNeeded(Http::StreamContextPointer deferredRequest, ConnStateData * conn);
 
 char *skipLeadingSpace(char *aString);
 
-clientStreamNode *
-ClientSocketContext::getTail() const
-{
-    if (http->client_stream.tail)
-        return (clientStreamNode *)http->client_stream.tail->data;
-
-    return NULL;
-}
-
-clientStreamNode *
-ClientSocketContext::getClientReplyContext() const
-{
-    return (clientStreamNode *)http->client_stream.tail->prev->data;
-}
-
-ConnStateData *
-ClientSocketContext::getConn() const
-{
-    return http->getConn();
-}
-
-ClientSocketContext::~ClientSocketContext()
-{
-    clientStreamNode *node = getTail();
-
-    if (node) {
-        ClientSocketContext *streamContext = dynamic_cast<ClientSocketContext *> (node->data.getRaw());
-
-        if (streamContext) {
-            /* We are *always* the tail - prevent recursive free */
-            assert(this == streamContext);
-            node->data = NULL;
-        }
-    }
-
-    httpRequestFree(http);
-}
-
-void
-ClientSocketContext::registerWithConn()
-{
-    assert (!connRegistered_);
-    assert (http);
-    assert (http->getConn() != NULL);
-    connRegistered_ = true;
-    http->getConn()->pipeline.add(ClientSocketContext::Pointer(this));
-}
-
-void
-ClientSocketContext::finished()
-{
-    assert (http);
-    assert (http->getConn() != NULL);
-    ConnStateData *conn = http->getConn();
-
-    /* we can't handle any more stream data - detach */
-    clientStreamDetach(getTail(), http);
-
-    assert(connRegistered_);
-    connRegistered_ = false;
-    assert(conn->pipeline.front() == this); // XXX: still assumes HTTP/1 semantics
-    conn->pipeline.popMe(ClientSocketContext::Pointer(this));
-}
-
-ClientSocketContext::ClientSocketContext(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq) :
-    clientConnection(aConn),
-    http(aReq),
-    reply(NULL),
-    writtenToSocket(0),
-    mayUseConnection_ (false),
-    connRegistered_ (false)
-{
-    assert(http != NULL);
-    memset (reqbuf, '\0', sizeof (reqbuf));
-    flags.deferred = 0;
-    flags.parsed_ok = 0;
-    deferredparams.node = NULL;
-    deferredparams.rep = NULL;
-}
-
 #if USE_IDENT
 static void
 clientIdentDone(const char *ident, void *data)
@@ -765,129 +685,20 @@
     return 0;
 }
 
-void
-ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData)
-{
-    debugs(33, 2, "clientSocketRecipient: Deferring request " << http->uri);
-    assert(flags.deferred == 0);
-    flags.deferred = 1;
-    deferredparams.node = node;
-    deferredparams.rep = rep;
-    deferredparams.queuedBuffer = receivedData;
-    return;
-}
-
-bool
-ClientSocketContext::startOfOutput() const
-{
-    return http->out.size == 0;
-}
-
-size_t
-ClientSocketContext::lengthToSend(Range<int64_t> const &available)
-{
-    /*the size of available range can always fit in a size_t type*/
-    size_t maximum = (size_t)available.size();
-
-    if (!http->request->range)
-        return maximum;
-
-    assert (canPackMoreRanges());
-
-    if (http->range_iter.debt() == -1)
-        return maximum;
-
-    assert (http->range_iter.debt() > 0);
-
-    /* TODO this + the last line could be a range intersection calculation */
-    if (available.start < http->range_iter.currentSpec()->offset)
-        return 0;
-
-    return min(http->range_iter.debt(), (int64_t)maximum);
-}
-
-void
-ClientSocketContext::noteSentBodyBytes(size_t bytes)
-{
-    debugs(33, 7, bytes << " body bytes");
-
-    http->out.offset += bytes;
-
-    if (!http->request->range)
-        return;
-
-    if (http->range_iter.debt() != -1) {
-        http->range_iter.debt(http->range_iter.debt() - bytes);
-        assert (http->range_iter.debt() >= 0);
-    }
-
-    /* debt() always stops at -1, below that is a bug */
-    assert (http->range_iter.debt() >= -1);
-}
-
 bool
 ClientHttpRequest::multipartRangeRequest() const
 {
     return request->multipartRangeRequest();
 }
 
-bool
-ClientSocketContext::multipartRangeRequest() const
-{
-    return http->multipartRangeRequest();
-}
-
-void
-ClientSocketContext::sendBody(HttpReply * rep, StoreIOBuffer bodyData)
-{
-    assert(rep == NULL);
-
-    if (!multipartRangeRequest() && !http->request->flags.chunkedReply) {
-        size_t length = lengthToSend(bodyData.range());
-        noteSentBodyBytes (length);
-        getConn()->write(bodyData.data, length);
-        return;
-    }
-
-    MemBuf mb;
-    mb.init();
-    if (multipartRangeRequest())
-        packRange(bodyData, &mb);
-    else
-        packChunk(bodyData, mb);
-
-    if (mb.contentSize())
-        getConn()->write(&mb);
-    else
-        writeComplete(0);
-}
-
-/**
- * Packs bodyData into mb using chunked encoding. Packs the last-chunk
- * if bodyData is empty.
- */
-void
-ClientSocketContext::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb)
-{
-    const uint64_t length =
-        static_cast<uint64_t>(lengthToSend(bodyData.range()));
-    noteSentBodyBytes(length);
-
-    mb.appendf("%" PRIX64 "\r\n", length);
-    mb.append(bodyData.data, length);
-    mb.append("\r\n", 2);
-}
-
-/** put terminating boundary for multiparts */
-static void
-clientPackTermBound(String boundary, MemBuf * mb)
+void
+clientPackTermBound(String boundary, MemBuf *mb)
 {
     mb->appendf("\r\n--" SQUIDSTRINGPH "--\r\n", SQUIDSTRINGPRINT(boundary));
-    debugs(33, 6, "clientPackTermBound: buf offset: " << mb->size);
+    debugs(33, 6, "buf offset: " << mb->size);
 }
 
-/** appends a "part" HTTP header (as in a multi-part/range reply) to the buffer */
-static void
+void
 clientPackRangeHdr(const HttpReply * rep, const HttpHdrRangeSpec * spec, String boundary, MemBuf * mb)
 {
     HttpHeader hdr(hoReply);
@@ -895,7 +706,7 @@
     assert(spec);
 
     /* put boundary */
-    debugs(33, 5, "clientPackRangeHdr: appending boundary: " << boundary);
+    debugs(33, 5, "appending boundary: " << boundary);
     /* rfc2046 requires to _prepend_ boundary with <crlf>! */
     mb->appendf("\r\n--" SQUIDSTRINGPH "\r\n", SQUIDSTRINGPRINT(boundary));
 
@@ -913,90 +724,6 @@
     mb->append("\r\n", 2);
 }
 
-/**
- * extracts a "range" from *buf and appends them to mb, updating
- * all offsets and such.
- */
-void
-ClientSocketContext::packRange(StoreIOBuffer const &source, MemBuf * mb)
-{
-    HttpHdrRangeIter * i = &http->range_iter;
-    Range<int64_t> available (source.range());
-    char const *buf = source.data;
-
-    while (i->currentSpec() && available.size()) {
-        const size_t copy_sz = lengthToSend(available);
-
-        if (copy_sz) {
-            /*
-             * intersection of "have" and "need" ranges must not be empty
-             */
-            assert(http->out.offset < i->currentSpec()->offset + i->currentSpec()->length);
-            assert(http->out.offset + (int64_t)available.size() > i->currentSpec()->offset);
-
-            /*
-             * put boundary and headers at the beginning of a range in a
-             * multi-range
-             */
-
-            if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) {
-                assert(http->memObject());
-                clientPackRangeHdr(
-                    http->memObject()->getReply(),  /* original reply */
-                    i->currentSpec(),       /* current range */
-                    i->boundary,    /* boundary, the same for all */
-                    mb);
-            }
-
-            /*
-             * append content
-             */
-            debugs(33, 3, "clientPackRange: appending " << copy_sz << " bytes");
-
-            noteSentBodyBytes (copy_sz);
-
-            mb->append(buf, copy_sz);
-
-            /*
-             * update offsets
-             */
-            available.start += copy_sz;
-
-            buf += copy_sz;
-
-        }
-
-        if (!canPackMoreRanges()) {
-            debugs(33, 3, "clientPackRange: Returning because !canPackMoreRanges.");
-
-            if (i->debt() == 0)
-                /* put terminating boundary for multiparts */
-                clientPackTermBound(i->boundary, mb);
-
-            return;
-        }
-
-        int64_t nextOffset = getNextRangeOffset();
-
-        assert (nextOffset >= http->out.offset);
-
-        int64_t skip = nextOffset - http->out.offset;
-
-        /* adjust for not to be transmitted bytes */
-        http->out.offset = nextOffset;
-
-        if (available.size() <= (uint64_t)skip)
-            return;
-
-        available.start += skip;
-
-        buf += skip;
-
-        if (copy_sz == 0)
-            return;
-    }
-}
-
 /** returns expected content length for multi-range replies
  * note: assumes that httpHdrRangeCanonize has already been called
  * warning: assumes that HTTP headers for individual ranges at the
@@ -1040,44 +767,6 @@
 }
 
 /**
- * returns true if If-Range specs match reply, false otherwise
- */
-static int
-clientIfRangeMatch(ClientHttpRequest * http, HttpReply * rep)
-{
-    const TimeOrTag spec = http->request->header.getTimeOrTag(Http::HdrType::IF_RANGE);
-    /* check for parsing falure */
-
-    if (!spec.valid)
-        return 0;
-
-    /* got an ETag? */
-    if (spec.tag.str) {
-        ETag rep_tag = rep->header.getETag(Http::HdrType::ETAG);
-        debugs(33, 3, "clientIfRangeMatch: ETags: " << spec.tag.str << " and " <<
-               (rep_tag.str ? rep_tag.str : "<none>"));
-
-        if (!rep_tag.str)
-            return 0;       /* entity has no etag to compare with! */
-
-        if (spec.tag.weak || rep_tag.weak) {
-            debugs(33, DBG_IMPORTANT, "clientIfRangeMatch: Weak ETags are not allowed in If-Range: " << spec.tag.str << " ? " << rep_tag.str);
-            return 0;       /* must use strong validator for sub-range requests */
-        }
-
-        return etagIsStrongEqual(rep_tag, spec.tag);
-    }
-
-    /* got modification time? */
-    if (spec.time >= 0) {
-        return http->storeEntry()->lastmod <= spec.time;
-    }
-
-    assert(0);          /* should not happen */
-    return 0;
-}
-
-/**
  * generates a "unique" boundary string for multipart responses
  * the caller is responsible for cleaning the string */
 String
@@ -1091,165 +780,6 @@
     return b;
 }
 
-/** adds appropriate Range headers if needed */
-void
-ClientSocketContext::buildRangeHeader(HttpReply * rep)
-{
-    HttpHeader *hdr = rep ? &rep->header : 0;
-    const char *range_err = NULL;
-    HttpRequest *request = http->request;
-    assert(request->range);
-    /* check if we still want to do ranges */
-
-    int64_t roffLimit = request->getRangeOffsetLimit();
-
-    if (!rep)
-        range_err = "no [parse-able] reply";
-    else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent))
-        range_err = "wrong status code";
-    else if (hdr->has(Http::HdrType::CONTENT_RANGE))
-        range_err = "origin server does ranges";
-    else if (rep->content_length < 0)
-        range_err = "unknown length";
-    else if (rep->content_length != http->memObject()->getReply()->content_length)
-        range_err = "INCONSISTENT length";  /* a bug? */
-
-    /* hits only - upstream CachePeer determines correct behaviour on misses, and client_side_reply determines
-     * hits candidates
-     */
-    else if (http->logType.isTcpHit() && http->request->header.has(Http::HdrType::IF_RANGE) && !clientIfRangeMatch(http, rep))
-        range_err = "If-Range match failed";
-    else if (!http->request->range->canonize(rep))
-        range_err = "canonization failed";
-    else if (http->request->range->isComplex())
-        range_err = "too complex range header";
-    else if (!http->logType.isTcpHit() && http->request->range->offsetLimitExceeded(roffLimit))
-        range_err = "range outside range_offset_limit";
-
-    /* get rid of our range specs on error */
-    if (range_err) {
-        /* XXX We do this here because we need canonisation etc. However, this current
-         * code will lead to incorrect store offset requests - the store will have the
-         * offset data, but we won't be requesting it.
-         * So, we can either re-request, or generate an error
-         */
-        http->request->ignoreRange(range_err);
-    } else {
-        /* XXX: TODO: Review, this unconditional set may be wrong. */
-        rep->sline.set(rep->sline.version, Http::scPartialContent);
-        // web server responded with a valid, but unexpected range.
-        // will (try-to) forward as-is.
-        //TODO: we should cope with multirange request/responses
-        bool replyMatchRequest = rep->content_range != NULL ?
-                                 request->range->contains(rep->content_range->spec) :
-                                 true;
-        const int spec_count = http->request->range->specs.size();
-        int64_t actual_clen = -1;
-
-        debugs(33, 3, "clientBuildRangeHeader: range spec count: " <<
-               spec_count << " virgin clen: " << rep->content_length);
-        assert(spec_count > 0);
-        /* append appropriate header(s) */
-
-        if (spec_count == 1) {
-            if (!replyMatchRequest) {
-                hdr->delById(Http::HdrType::CONTENT_RANGE);
-                hdr->putContRange(rep->content_range);
-                actual_clen = rep->content_length;
-                //http->range_iter.pos = rep->content_range->spec.begin();
-                (*http->range_iter.pos)->offset = rep->content_range->spec.offset;
-                (*http->range_iter.pos)->length = rep->content_range->spec.length;
-
-            } else {
-                HttpHdrRange::iterator pos = http->request->range->begin();
-                assert(*pos);
-                /* append Content-Range */
-
-                if (!hdr->has(Http::HdrType::CONTENT_RANGE)) {
-                    /* No content range, so this was a full object we are
-                     * sending parts of.
-                     */
-                    httpHeaderAddContRange(hdr, **pos, rep->content_length);
-                }
-
-                /* set new Content-Length to the actual number of bytes
-                 * transmitted in the message-body */
-                actual_clen = (*pos)->length;
-            }
-        } else {
-            /* multipart! */
-            /* generate boundary string */
-            http->range_iter.boundary = http->rangeBoundaryStr();
-            /* delete old Content-Type, add ours */
-            hdr->delById(Http::HdrType::CONTENT_TYPE);
-            httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE,
-                              "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"",
-                              SQUIDSTRINGPRINT(http->range_iter.boundary));
-            /* Content-Length is not required in multipart responses
-             * but it is always nice to have one */
-            actual_clen = http->mRangeCLen();
-            /* http->out needs to start where we want data at */
-            http->out.offset = http->range_iter.currentSpec()->offset;
-        }
-
-        /* replace Content-Length header */
-        assert(actual_clen >= 0);
-
-        hdr->delById(Http::HdrType::CONTENT_LENGTH);
-
-        hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen);
-
-        debugs(33, 3, "clientBuildRangeHeader: actual content length: " << actual_clen);
-
-        /* And start the range iter off */
-        http->range_iter.updateSpec();
-    }
-}
-
-void
-ClientSocketContext::prepareReply(HttpReply * rep)
-{
-    reply = rep;
-
-    if (http->request->range)
-        buildRangeHeader(rep);
-}
-
-void
-ClientSocketContext::sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData)
-{
-    prepareReply(rep);
-    assert (rep);
-    MemBuf *mb = rep->pack();
-
-    // dump now, so we dont output any body.
-    debugs(11, 2, "HTTP Client " << clientConnection);
-    debugs(11, 2, "HTTP Client REPLY:\n---------\n" << mb->buf << "\n----------");
-
-    /* Save length of headers for persistent conn checks */
-    http->out.headers_sz = mb->contentSize();
-#if HEADERS_LOG
-
-    headersLog(0, 0, http->request->method, rep);
-#endif
-
-    if (bodyData.data && bodyData.length) {
-        if (multipartRangeRequest())
-            packRange(bodyData, mb);
-        else if (http->request->flags.chunkedReply) {
-            packChunk(bodyData, *mb);
-        } else {
-            size_t length = lengthToSend(bodyData.range());
-            noteSentBodyBytes (length);
-
-            mb->append(bodyData.data, length);
-        }
-    }
-
-    getConn()->write(mb);
-    delete mb;
-}
-
 /**
  * Write a chunk of data to a client socket. If the reply is present,
  * send the reply headers down the wire too, and clean them up when
@@ -1277,7 +807,7 @@
      */
     assert(cbdataReferenceValid(node));
     assert(node->node.next == NULL);
-    ClientSocketContext::Pointer context = dynamic_cast<ClientSocketContext *>(node->data.getRaw());
+    Http::StreamContextPointer context = dynamic_cast<Http::StreamContext *>(node->data.getRaw());
     assert(context != NULL);
 
     /* TODO: check offset is what we asked for */
@@ -1310,7 +840,7 @@
     /* Set null by ContextFree */
     assert(node->node.next == NULL);
     /* this is the assert discussed above */
-    assert(NULL == dynamic_cast<ClientSocketContext *>(node->data.getRaw()));
+    assert(NULL == dynamic_cast<Http::StreamContext *>(node->data.getRaw()));
     /* We are only called when the client socket shutsdown.
      * Tell the prev pipeline member we're finished
      */
@@ -1336,7 +866,7 @@
 }
 
 static void
-ClientSocketContextPushDeferredIfNeeded(ClientSocketContext::Pointer deferredRequest, ConnStateData * conn)
+ClientSocketContextPushDeferredIfNeeded(Http::StreamContextPointer deferredRequest, ConnStateData * conn)
 {
     debugs(33, 2, HERE << conn->clientConnection << " Sending next");
 
@@ -1425,7 +955,7 @@
      * then look at processing it. If not, simply kickstart
      * another read.
      */
-    ClientSocketContext::Pointer deferredRequest = pipeline.front();
+    Http::StreamContextPointer deferredRequest = pipeline.front();
     if (deferredRequest != nullptr) {
         debugs(33, 3, clientConnection << ": calling PushDeferredIfNeeded");
         ClientSocketContextPushDeferredIfNeeded(deferredRequest, this);
@@ -1439,190 +969,6 @@
 }
 
 void
-clientUpdateSocketStats(const LogTags &logType, size_t size)
-{
-    if (size == 0)
-        return;
-
-    statCounter.client_http.kbytes_out += size;
-
-    if (logType.isTcpHit())
-        statCounter.client_http.hit_kbytes_out += size;
-}
-
-/**
- * increments iterator "i"
- * used by clientPackMoreRanges
- *
- \retval true    there is still data available to pack more ranges
- \retval false
- */
-bool
-ClientSocketContext::canPackMoreRanges() const
-{
-    /** first update iterator "i" if needed */
-
-    if (!http->range_iter.debt()) {
-        debugs(33, 5, HERE << "At end of current range spec for " << clientConnection);
-
-        if (http->range_iter.pos != http->range_iter.end)
-            ++http->range_iter.pos;
-
-        http->range_iter.updateSpec();
-    }
-
-    assert(!http->range_iter.debt() == !http->range_iter.currentSpec());
-
-    /* paranoid sync condition */
-    /* continue condition: need_more_data */
-    debugs(33, 5, "ClientSocketContext::canPackMoreRanges: returning " << (http->range_iter.currentSpec() ? true : false));
-    return http->range_iter.currentSpec() ? true : false;
-}
-
-int64_t
-ClientSocketContext::getNextRangeOffset() const
-{
-    debugs (33, 5, "range: " << http->request->range <<
-            "; http offset " << http->out.offset <<
-            "; reply " << reply);
-
-    // XXX: This method is called from many places, including pullData() which
-    // may be called before prepareReply() [on some Squid-generated errors].
-    // Hence, we may not even know yet whether we should honor/do ranges.
-
-    if (http->request->range) {
-        /* offset in range specs does not count the prefix of an http msg */
-        /* check: reply was parsed and range iterator was initialized */
-        assert(http->range_iter.valid);
-        /* filter out data according to range specs */
-        assert (canPackMoreRanges());
-        {
-            int64_t start;      /* offset of still missing data */
-            assert(http->range_iter.currentSpec());
-            start = http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length - http->range_iter.debt();
-            debugs(33, 3, "clientPackMoreRanges: in:  offset: " << http->out.offset);
-            debugs(33, 3, "clientPackMoreRanges: out:"
-                   " start: " << start <<
-                   " spec[" << http->range_iter.pos - http->request->range->begin() << "]:" <<
-                   " [" << http->range_iter.currentSpec()->offset <<
-                   ", " << http->range_iter.currentSpec()->offset + http->range_iter.currentSpec()->length << "),"
-                   " len: " << http->range_iter.currentSpec()->length <<
-                   " debt: " << http->range_iter.debt());
-            if (http->range_iter.currentSpec()->length != -1)
-                assert(http->out.offset <= start);  /* we did not miss it */
-
-            return start;
-        }
-
-    } else if (reply && reply->content_range) {
-        /* request does not have ranges, but reply does */
-        /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
-         *        becomes HttpHdrRange rather than HttpHdrRangeSpec.
-         */
-        return http->out.offset + reply->content_range->spec.offset;
-    }
-
-    return http->out.offset;
-}
-
-void
-ClientSocketContext::pullData()
-{
-    debugs(33, 5, reply << " written " << http->out.size << " into " << clientConnection);
-
-    /* More data will be coming from the stream. */
-    StoreIOBuffer readBuffer;
-    /* XXX: Next requested byte in the range sequence */
-    /* XXX: length = getmaximumrangelenfgth */
-    readBuffer.offset = getNextRangeOffset();
-    readBuffer.length = HTTP_REQBUF_SZ;
-    readBuffer.data = reqbuf;
-    /* we may note we have reached the end of the wanted ranges */
-    clientStreamRead(getTail(), http, readBuffer);
-}
-
-/** Adapt stream status to account for Range cases
- *
- */
-clientStream_status_t
-ClientSocketContext::socketState()
-{
-    switch (clientStreamStatus(getTail(), http)) {
-
-    case STREAM_NONE:
-        /* check for range support ending */
-
-        if (http->request->range) {
-            /* check: reply was parsed and range iterator was initialized */
-            assert(http->range_iter.valid);
-            /* filter out data according to range specs */
-
-            if (!canPackMoreRanges()) {
-                debugs(33, 5, HERE << "Range request at end of returnable " <<
-                       "range sequence on " << clientConnection);
-                // we got everything we wanted from the store
-                return STREAM_COMPLETE;
-            }
-        } else if (reply && reply->content_range) {
-            /* reply has content-range, but Squid is not managing ranges */
-            const int64_t &bytesSent = http->out.offset;
-            const int64_t &bytesExpected = reply->content_range->spec.length;
-
-            debugs(33, 7, HERE << "body bytes sent vs. expected: " <<
-                   bytesSent << " ? " << bytesExpected << " (+" <<
-                   reply->content_range->spec.offset << ")");
-
-            // did we get at least what we expected, based on range specs?
-
-            if (bytesSent == bytesExpected) // got everything
-                return STREAM_COMPLETE;
-
-            if (bytesSent > bytesExpected) // Error: Sent more than expected
-                return STREAM_UNPLANNED_COMPLETE;
-        }
-
-        return STREAM_NONE;
-
-    case STREAM_COMPLETE:
-        return STREAM_COMPLETE;
-
-    case STREAM_UNPLANNED_COMPLETE:
-        return STREAM_UNPLANNED_COMPLETE;
-
-    case STREAM_FAILED:
-        return STREAM_FAILED;
-    }
-
-    fatal ("unreachable code\n");
-    return STREAM_NONE;
-}
-
-/// remembers the abnormal connection termination for logging purposes
-void
-ClientSocketContext::noteIoError(const int xerrno)
-{
-    if (http) {
-        http->logType.err.timedout = (xerrno == ETIMEDOUT);
-        // aborted even if xerrno is zero (which means read abort/eof)
-        http->logType.err.aborted = (xerrno != ETIMEDOUT);
-    }
-}
-
-void
-ClientSocketContext::doClose()
-{
-    clientConnection->close();
-}
-
-/// called when we encounter a response-related error
-void
-ClientSocketContext::initiateClose(const char *reason)
-{
-    debugs(33, 4, clientConnection << " because " << reason);
-    http->getConn()->stopSending(reason); // closes ASAP
-}
-
-void
 ConnStateData::stopSending(const char *error)
 {
     debugs(33, 4, HERE << "sending error (" << clientConnection << "): " << error <<
@@ -1652,64 +998,23 @@
     if (pipeline.empty())
         return;
 
-    pipeline.front()->writeComplete(size);
-}
-
-// TODO: make this only need size parameter, ConnStateData handles the rest
-void
-ClientSocketContext::writeComplete(size_t size)
-{
-    const StoreEntry *entry = http->storeEntry();
-    debugs(33, 5, clientConnection << ", sz " << size <<
-           ", off " << (http->out.size + size) << ", len " <<
-           (entry ? entry->objectLen() : 0));
-
-    http->out.size += size;
-    clientUpdateSocketStats(http->logType, size);
-
-    if (clientHttpRequestStatus(clientConnection->fd, http)) {
-        initiateClose("failure or true request status");
-        /* Do we leak here ? */
-        return;
-    }
-
-    switch (socketState()) {
-
-    case STREAM_NONE:
-        pullData();
-        break;
-
-    case STREAM_COMPLETE: {
-        debugs(33, 5, clientConnection << " Stream complete, keepalive is " << http->request->flags.proxyKeepalive);
-        ConnStateData *c = http->getConn();
-        if (!http->request->flags.proxyKeepalive)
-            clientConnection->close();
-        finished();
-        c->kick();
-    }
-    return;
-
-    case STREAM_UNPLANNED_COMPLETE:
-        initiateClose("STREAM_UNPLANNED_COMPLETE");
-        return;
-
-    case STREAM_FAILED:
-        initiateClose("STREAM_FAILED");
-        return;
-
-    default:
-        fatal("Hit unreachable code in ClientSocketContext::writeComplete\n");
-    }
-}
-
-ClientSocketContext *
+    auto ctx = pipeline.front();
+    if (size) {
+        statCounter.client_http.kbytes_out += size;
+        if (ctx->http->logType.isTcpHit())
+            statCounter.client_http.hit_kbytes_out += size;
+    }
+    ctx->writeComplete(size);
+}
+
+Http::StreamContext *
 ConnStateData::abortRequestParsing(const char *const uri)
 {
     ClientHttpRequest *http = new ClientHttpRequest(this);
     http->req_sz = inBuf.length();
     http->uri = xstrdup(uri);
     setLogUri (http, uri);
-    ClientSocketContext *context = new ClientSocketContext(clientConnection, http);
+    auto *context = new Http::StreamContext(nextStreamId(), clientConnection, http);
     StoreIOBuffer tempBuffer;
     tempBuffer.data = context->reqbuf;
     tempBuffer.length = HTTP_REQBUF_SZ;
@@ -1985,9 +1290,9 @@
  *          parsing failure
  *  \param[out] http_ver will be set as a side-effect of the parsing
  *  \return NULL on incomplete requests,
- *          a ClientSocketContext structure on success or failure.
+ *          a Http::StreamContext on success or failure.
  */
-ClientSocketContext *
+Http::StreamContext *
 parseHttpRequest(ConnStateData *csd, const Http1::RequestParserPointer &hp)
 {
     /* Attempt to parse the first line; this will define where the method, url, version and header begin */
@@ -2055,7 +1360,7 @@
     ClientHttpRequest *http = new ClientHttpRequest(csd);
 
     http->req_sz = hp->messageHeaderSize();
-    ClientSocketContext *result = new ClientSocketContext(csd->clientConnection, http);
+    Http::StreamContext *result = new Http::StreamContext(csd->nextStreamId(), csd->clientConnection, http);
 
     StoreIOBuffer tempBuffer;
     tempBuffer.data = result->reqbuf;
@@ -2166,7 +1471,7 @@
 }
 
 #if USE_OPENSSL
-bool ConnStateData::serveDelayedError(ClientSocketContext *context)
+bool ConnStateData::serveDelayedError(Http::StreamContext *context)
 {
     ClientHttpRequest *http = context->http;
 
@@ -2256,7 +1561,7 @@
  * or false otherwise
  */
 bool
-clientTunnelOnError(ConnStateData *conn, ClientSocketContext *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
+clientTunnelOnError(ConnStateData *conn, Http::StreamContext *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes)
 {
     if (conn->port->flags.isIntercepted() &&
             Config.accessList.on_unsupported_protocol && conn->pipeline.nrequests <= 1) {
@@ -2272,8 +1577,7 @@
                 // XXX: Either the context is finished() or it should stay queued.
                 // The below may leak client streams BodyPipe objects. BUT, we need
                 // to check if client-streams detatch is safe to do here (finished() will detatch).
-                assert(conn->pipeline.front() == context); // XXX: still assumes HTTP/1 semantics
-                conn->pipeline.popMe(ClientSocketContextPointer(context));
+                conn->pipeline.popById(context->id);
             }
             Comm::SetSelect(conn->clientConnection->fd, COMM_SELECT_READ, NULL, NULL, 0);
             conn->fakeAConnectRequest("unknown-protocol", conn->preservedClientData);
@@ -2314,7 +1618,7 @@
 }
 
 void
-clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, ClientSocketContext *context)
+clientProcessRequest(ConnStateData *conn, const Http1::RequestParserPointer &hp, Http::StreamContext *context)
 {
     ClientHttpRequest *http = context->http;
     bool chunked = false;
@@ -2850,7 +2154,7 @@
         if (needProxyProtocolHeader_ && !parseProxyProtocolHeader())
             break;
 
-        if (ClientSocketContext *context = parseOneRequest()) {
+        if (Http::StreamContext *context = parseOneRequest()) {
             debugs(33, 5, clientConnection << ": done parsing a request");
 
             AsyncCall::Pointer timeoutCall = commCbCall(5, 4, "clientLifetimeTimeout",
@@ -3021,7 +2325,7 @@
     // but if we fail when the server connection is used already, the server may send
     // us its response too, causing various assertions. How to prevent that?
 #if WE_KNOW_HOW_TO_SEND_ERRORS
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     if (context != NULL && !context->http->out.offset) { // output nothing yet
         clientStreamNode *node = context->getClientReplyContext();
         clientReplyContext *repContext = dynamic_cast<clientReplyContext*>(node->data.getRaw());
@@ -3968,7 +3272,7 @@
         transferProtocol = Http::ProtocolVersion();
         // inBuf still has the "CONNECT ..." request data, reset it to SSL hello message
         inBuf.append(rbuf.content(), rbuf.contentSize());
-        ClientSocketContext::Pointer context = pipeline.front();
+        Http::StreamContextPointer context = pipeline.front();
         ClientHttpRequest *http = context->http;
         tunnelStart(http);
     }
@@ -4416,7 +3720,7 @@
         Must(!bodyPipe); // we rely on it being nil after we are done with body
         if (withSuccess) {
             Must(myPipe->bodySizeKnown());
-            ClientSocketContext::Pointer context = pipeline.front();
+            Http::StreamContextPointer context = pipeline.front();
             if (context != NULL && context->http && context->http->request)
                 context->http->request->setContentLength(myPipe->bodySize());
         }

=== modified file 'src/client_side.h'
--- src/client_side.h	2016-01-01 00:12:18 +0000
+++ src/client_side.h	2016-01-09 15:19:01 +0000
@@ -27,130 +27,9 @@
 #include "ssl/support.h"
 #endif
 
-class ConnStateData;
 class ClientHttpRequest;
-class clientStreamNode;
-namespace AnyP
-{
-class PortCfg;
-} // namespace Anyp
-
-/**
- * Badly named.
- * This is in fact the processing context for a single HTTP transaction.
- *
- * A context lifetime extends from directly after a request has been parsed
- * off the client connection buffer, until the last byte of both request
- * and reply payload (if any) have been written.
- *
- * (NOTE: it is not certain yet if an early reply to a POST/PUT is sent by
- * the server whether the context will remain in the pipeline until its
- * request payload has finished being read. It is supposed to, but may not)
- *
- * Contexts self-register with the Pipeline being managed by the Server
- * for the connection on which the request was received.
- *
- * When HTTP/1 pipeline is operating there may be multiple transactions using
- * the clientConnection. Only the back() context may read from the connection,
- * and only the front() context may write to it. A context which needs to read
- * or write to the connection but does not meet those criteria must be shifted
- * to the deferred state.
- *
- * When a context is completed the finished() method needs to be called which
- * will perform all cleanup and deregistration operations. If the reason for
- * finishing is an error, then notifyIoError() needs to be called prior to
- * the finished() method.
- * The caller should follow finished() with a call to ConnStateData::kick()
- * to resume processing of other transactions or I/O on the connection.
- *
- * Alternatively the initiateClose() method can be called to terminate the
- * whole client connection and all other pending contexts.
- *
- * The socket level management is done by a Server which owns us.
- * The scope of this objects control over a socket consists of the data
- * buffer received from the Server with an initially unknown length.
- * When that length is known it sets the end boundary of our access to the
- * buffer.
- *
- * The individual processing actions are done by other Jobs which we
- * kick off as needed.
- *
- * XXX: If an async call ends the ClientHttpRequest job, ClientSocketContext
- * (and ConnStateData) may not know about it, leading to segfaults and
- * assertions. This is difficult to fix
- * because ClientHttpRequest lacks a good way to communicate its ongoing
- * destruction back to the ClientSocketContext which pretends to "own" *http.
- */
-class ClientSocketContext : public RefCountable
-{
-    MEMPROXY_CLASS(ClientSocketContext);
-
-public:
-    typedef RefCount<ClientSocketContext> Pointer;
-    ClientSocketContext(const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq);
-    ~ClientSocketContext();
-    bool startOfOutput() const;
-    void writeComplete(size_t size);
-
-    Comm::ConnectionPointer clientConnection; /// details about the client connection socket.
-    ClientHttpRequest *http;    /* we pretend to own that job */
-    HttpReply *reply;
-    char reqbuf[HTTP_REQBUF_SZ];
-
-    struct {
-
-        unsigned deferred:1; /* This is a pipelined request waiting for the current object to complete */
-
-        unsigned parsed_ok:1; /* Was this parsed correctly? */
-    } flags;
-    bool mayUseConnection() const {return mayUseConnection_;}
-
-    void mayUseConnection(bool aBool) {
-        mayUseConnection_ = aBool;
-        debugs(33,3, HERE << "This " << this << " marked " << aBool);
-    }
-
-    class DeferredParams
-    {
-
-    public:
-        clientStreamNode *node;
-        HttpReply *rep;
-        StoreIOBuffer queuedBuffer;
-    };
-
-    DeferredParams deferredparams;
-    int64_t writtenToSocket;
-    void pullData();
-    int64_t getNextRangeOffset() const;
-    bool canPackMoreRanges() const;
-    clientStream_status_t socketState();
-    void sendBody(HttpReply * rep, StoreIOBuffer bodyData);
-    void sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData);
-    size_t lengthToSend(Range<int64_t> const &available);
-    void noteSentBodyBytes(size_t);
-    void buildRangeHeader(HttpReply * rep);
-    clientStreamNode * getTail() const;
-    clientStreamNode * getClientReplyContext() const;
-    ConnStateData *getConn() const;
-    void finished(); ///< cleanup when the transaction has finished. may destroy 'this'
-    void deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData);
-    bool multipartRangeRequest() const;
-    void registerWithConn();
-    void noteIoError(const int xerrno); ///< update state to reflect I/O error
-    void initiateClose(const char *reason); ///< terminate due to a send/write error (may continue reading)
-
-private:
-    void prepareReply(HttpReply * rep);
-    void packChunk(const StoreIOBuffer &bodyData, MemBuf &mb);
-    void packRange(StoreIOBuffer const &, MemBuf * mb);
-    void doClose();
-
-    bool mayUseConnection_; /* This request may use the connection. Don't read anymore requests for now */
-    bool connRegistered_;
-};
-
-class ConnectionDetail;
+class HttpHdrRangeSpec;
+
 #if USE_OPENSSL
 namespace Ssl
 {
@@ -174,7 +53,7 @@
  * processed.
  *
  * Performs HTTP message processing to kick off the actual HTTP request
- * handling objects (ClientSocketContext, ClientHttpRequest, HttpRequest).
+ * handling objects (Http::StreamContext, ClientHttpRequest, HttpRequest).
  *
  * Performs SSL-Bump processing for switching between HTTP and HTTPS protocols.
  *
@@ -190,6 +69,7 @@
     virtual ~ConnStateData();
 
     /* ::Server API */
+    virtual uint32_t nextStreamId() {return ++nextStreamId_;}
     virtual void receivedFirstByte();
     virtual bool handleReadData();
     virtual void afterClientRead();
@@ -372,7 +252,7 @@
     /// Returns false if no [delayed] error should be written to the client.
     /// Otherwise, writes the error to the client and returns true. Also checks
     /// for SQUID_X509_V_ERR_DOMAIN_MISMATCH on bumped requests.
-    bool serveDelayedError(ClientSocketContext *context);
+    bool serveDelayedError(Http::StreamContext *);
 
     Ssl::BumpMode sslBumpMode; ///< ssl_bump decision (Ssl::bumpEnd if n/a).
 
@@ -388,7 +268,7 @@
     virtual void writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call) = 0;
 
     /// ClientStream calls this to supply response header (once) and data
-    /// for the current ClientSocketContext.
+    /// for the current Http::StreamContext.
     virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData) = 0;
 
     /// remove no longer needed leading bytes from the input buffer
@@ -397,7 +277,7 @@
     /* TODO: Make the methods below (at least) non-public when possible. */
 
     /// stop parsing the request and create context for relaying error info
-    ClientSocketContext *abortRequestParsing(const char *const errUri);
+    Http::StreamContext *abortRequestParsing(const char *const errUri);
 
     /// generate a fake CONNECT request with the given payload
     /// at the beginning of the client I/O buffer
@@ -429,10 +309,10 @@
     /// parse input buffer prefix into a single transfer protocol request
     /// return NULL to request more header bytes (after checking any limits)
     /// use abortRequestParsing() to handle parsing errors w/o creating request
-    virtual ClientSocketContext *parseOneRequest() = 0;
+    virtual Http::StreamContext *parseOneRequest() = 0;
 
     /// start processing a freshly parsed request
-    virtual void processParsedRequest(ClientSocketContext *context) = 0;
+    virtual void processParsedRequest(Http::StreamContext *) = 0;
 
     /// returning N allows a pipeline of 1+N requests (see pipeline_prefetch)
     virtual int pipelinePrefetchMax() const;
@@ -505,6 +385,12 @@
 /// decide whether to expect multiple requests on the corresponding connection
 void clientSetKeepaliveFlag(ClientHttpRequest *http);
 
+/// append a "part" HTTP header (as in a multi-part/range reply) to the buffer
+void clientPackRangeHdr(const HttpReply *, const HttpHdrRangeSpec *, String boundary, MemBuf *);
+
+/// put terminating boundary for multiparts to the buffer
+void clientPackTermBound(String boundary, MemBuf *);
+
 /* misplaced declaratrions of Stream callbacks provided/used by client side */
 SQUIDCEXTERN CSR clientGetMoreData;
 SQUIDCEXTERN CSS clientReplyStatus;
@@ -513,8 +399,8 @@
 CSD clientSocketDetach;
 
 /* TODO: Move to HttpServer. Warning: Move requires large code nonchanges! */
-ClientSocketContext *parseHttpRequest(ConnStateData *, const Http1::RequestParserPointer &);
-void clientProcessRequest(ConnStateData *, const Http1::RequestParserPointer &, ClientSocketContext *);
+Http::StreamContext *parseHttpRequest(ConnStateData *, const Http1::RequestParserPointer &);
+void clientProcessRequest(ConnStateData *, const Http1::RequestParserPointer &, Http::StreamContext *);
 void clientPostHttpsAccept(ConnStateData *);
 
 #endif /* SQUID_CLIENTSIDE_H */

=== modified file 'src/client_side_reply.cc'
--- src/client_side_reply.cc	2016-01-01 00:12:18 +0000
+++ src/client_side_reply.cc	2016-01-07 14:42:33 +0000
@@ -21,6 +21,7 @@
 #include "FwdState.h"
 #include "globals.h"
 #include "globals.h"
+#include "http/StreamContext.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"

=== modified file 'src/client_side_request.cc'
--- src/client_side_request.cc	2016-01-01 00:12:18 +0000
+++ src/client_side_request.cc	2016-01-07 14:38:37 +0000
@@ -37,6 +37,7 @@
 #include "helper.h"
 #include "helper/Reply.h"
 #include "http.h"
+#include "http/StreamContext.h"
 #include "HttpHdrCc.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"

=== modified file 'src/clients/FtpClient.cc'
--- src/clients/FtpClient.cc	2016-01-01 00:12:18 +0000
+++ src/clients/FtpClient.cc	2016-01-07 12:27:48 +0000
@@ -19,6 +19,7 @@
 #include "errorpage.h"
 #include "fd.h"
 #include "ftp/Parsing.h"
+#include "http/StreamContext.h"
 #include "ip/tools.h"
 #include "SquidConfig.h"
 #include "SquidString.h"

=== modified file 'src/clients/FtpRelay.cc'
--- src/clients/FtpRelay.cc	2016-01-01 00:12:18 +0000
+++ src/clients/FtpRelay.cc	2016-01-07 12:29:45 +0000
@@ -15,6 +15,7 @@
 #include "clients/FtpClient.h"
 #include "ftp/Elements.h"
 #include "ftp/Parsing.h"
+#include "http/StreamContext.h"
 #include "HttpHdrCc.h"
 #include "HttpRequest.h"
 #include "SBuf.h"

=== modified file 'src/delay_pools.cc'
--- src/delay_pools.cc	2016-01-01 00:12:18 +0000
+++ src/delay_pools.cc	2016-01-10 17:22:50 +0000
@@ -30,6 +30,7 @@
 #include "DelayUser.h"
 #include "DelayVector.h"
 #include "event.h"
+#include "http/StreamContext.h"
 #include "ip/Address.h"
 #include "MemObject.h"
 #include "mgr/Registration.h"

=== modified file 'src/esi/Context.cc'
--- src/esi/Context.cc	2016-01-01 00:12:18 +0000
+++ src/esi/Context.cc	2016-01-07 14:18:55 +0000
@@ -17,6 +17,7 @@
 
 #include "client_side_request.h"
 #include "esi/Context.h"
+#include "http/StreamContext.h"
 #include "Store.h"
 
 void

=== modified file 'src/esi/Esi.cc'
--- src/esi/Esi.cc	2016-01-01 00:12:18 +0000
+++ src/esi/Esi.cc	2016-01-07 14:34:39 +0000
@@ -30,6 +30,7 @@
 #include "esi/Segment.h"
 #include "esi/VarState.h"
 #include "fatal.h"
+#include "http/StreamContext.h"
 #include "HttpHdrSc.h"
 #include "HttpHdrScTarget.h"
 #include "HttpReply.h"

=== modified file 'src/esi/Include.cc'
--- src/esi/Include.cc	2016-01-01 00:12:18 +0000
+++ src/esi/Include.cc	2016-01-07 14:43:44 +0000
@@ -17,6 +17,7 @@
 #include "esi/Include.h"
 #include "esi/VarState.h"
 #include "fatal.h"
+#include "http/StreamContext.h"
 #include "HttpReply.h"
 #include "log/access_log.h"
 

=== modified file 'src/external_acl.cc'
--- src/external_acl.cc	2016-01-01 00:12:18 +0000
+++ src/external_acl.cc	2016-01-07 14:33:44 +0000
@@ -22,6 +22,7 @@
 #include "format/Token.h"
 #include "helper.h"
 #include "helper/Reply.h"
+#include "http/StreamContext.h"
 #include "HttpHeaderTools.h"
 #include "HttpReply.h"
 #include "HttpRequest.h"

=== modified file 'src/format/Format.cc'
--- src/format/Format.cc	2016-01-01 00:12:18 +0000
+++ src/format/Format.cc	2016-01-07 12:11:18 +0000
@@ -17,6 +17,7 @@
 #include "format/Quoting.h"
 #include "format/Token.h"
 #include "fqdncache.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "MemBuf.h"
 #include "rfc1738.h"

=== modified file 'src/http.cc'
--- src/http.cc	2016-01-01 00:12:18 +0000
+++ src/http.cc	2016-01-07 15:18:05 +0000
@@ -32,6 +32,7 @@
 #include "http.h"
 #include "http/one/ResponseParser.h"
 #include "http/one/TeChunkedParser.h"
+#include "http/StreamContext.h"
 #include "HttpControlMsg.h"
 #include "HttpHdrCc.h"
 #include "HttpHdrContRange.h"

=== modified file 'src/http/Makefile.am'
--- src/http/Makefile.am	2016-01-01 00:12:18 +0000
+++ src/http/Makefile.am	2016-01-09 03:08:25 +0000
@@ -26,7 +26,9 @@
 	StatusCode.cc \
 	StatusCode.h \
 	StatusLine.cc \
-	StatusLine.h
+	StatusLine.h \
+	StreamContext.cc \
+	StreamContext.h
 
 libsquid_http_la_LIBADD= one/libhttp1.la
 

=== added file 'src/http/StreamContext.cc'
--- src/http/StreamContext.cc	1970-01-01 00:00:00 +0000
+++ src/http/StreamContext.cc	2016-01-10 12:47:07 +0000
@@ -0,0 +1,667 @@
+/*
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#include "squid.h"
+#include "client_side_request.h"
+#include "http/StreamContext.h"
+#include "HttpHdrContRange.h"
+#include "HttpHeaderTools.h"
+#include "Store.h"
+#include "TimeOrTag.h"
+
+Http::StreamContext::StreamContext(uint32_t anId, const Comm::ConnectionPointer &aConn, ClientHttpRequest *aReq) :
+    id(anId),
+    clientConnection(aConn),
+    http(aReq),
+    reply(nullptr),
+    writtenToSocket(0),
+    mayUseConnection_(false),
+    connRegistered_(false)
+{
+    assert(http != nullptr);
+    memset(reqbuf, '\0', sizeof (reqbuf));
+    flags.deferred = 0;
+    flags.parsed_ok = 0;
+    deferredparams.node = nullptr;
+    deferredparams.rep = nullptr;
+}
+
+Http::StreamContext::~StreamContext()
+{
+    if (auto node = getTail()) {
+        if (auto ctx = dynamic_cast<Http::StreamContext *>(node->data.getRaw())) {
+            /* We are *always* the tail - prevent recursive free */
+            assert(this == ctx);
+            node->data = nullptr;
+        }
+    }
+    httpRequestFree(http);
+}
+
+void
+Http::StreamContext::registerWithConn()
+{
+    assert(!connRegistered_);
+    assert(getConn());
+    connRegistered_ = true;
+    getConn()->pipeline.add(Http::StreamContextPointer(this));
+}
+
+bool
+Http::StreamContext::startOfOutput() const
+{
+    return http->out.size == 0;
+}
+
+void
+Http::StreamContext::writeComplete(size_t size)
+{
+    const StoreEntry *entry = http->storeEntry();
+    debugs(33, 5, clientConnection << ", sz " << size <<
+           ", off " << (http->out.size + size) << ", len " <<
+           (entry ? entry->objectLen() : 0));
+
+    http->out.size += size;
+
+    if (clientHttpRequestStatus(clientConnection->fd, http)) {
+        initiateClose("failure or true request status");
+        /* Do we leak here ? */
+        return;
+    }
+
+    switch (socketState()) {
+
+    case STREAM_NONE:
+        pullData();
+        break;
+
+    case STREAM_COMPLETE: {
+        debugs(33, 5, clientConnection << " Stream complete, keepalive is " <<
+                      http->request->flags.proxyKeepalive);
+        ConnStateData *c = getConn();
+        if (!http->request->flags.proxyKeepalive)
+            clientConnection->close();
+        finished();
+        c->kick();
+    }
+    return;
+
+    case STREAM_UNPLANNED_COMPLETE:
+        initiateClose("STREAM_UNPLANNED_COMPLETE");
+        return;
+
+    case STREAM_FAILED:
+        initiateClose("STREAM_FAILED");
+        return;
+
+    default:
+        fatal("Hit unreachable code in Http::StreamContext::writeComplete\n");
+    }
+}
+
+void
+Http::StreamContext::pullData()
+{
+    debugs(33, 5, reply << " written " << http->out.size << " into " << clientConnection);
+
+    /* More data will be coming from the stream. */
+    StoreIOBuffer readBuffer;
+    /* XXX: Next requested byte in the range sequence */
+    /* XXX: length = getmaximumrangelenfgth */
+    readBuffer.offset = getNextRangeOffset();
+    readBuffer.length = HTTP_REQBUF_SZ;
+    readBuffer.data = reqbuf;
+    /* we may note we have reached the end of the wanted ranges */
+    clientStreamRead(getTail(), http, readBuffer);
+}
+
+bool
+Http::StreamContext::multipartRangeRequest() const
+{
+    return http->multipartRangeRequest();
+}
+
+int64_t
+Http::StreamContext::getNextRangeOffset() const
+{
+    debugs (33, 5, "range: " << http->request->range <<
+            "; http offset " << http->out.offset <<
+            "; reply " << reply);
+
+    // XXX: This method is called from many places, including pullData() which
+    // may be called before prepareReply() [on some Squid-generated errors].
+    // Hence, we may not even know yet whether we should honor/do ranges.
+
+    if (http->request->range) {
+        /* offset in range specs does not count the prefix of an http msg */
+        /* check: reply was parsed and range iterator was initialized */
+        assert(http->range_iter.valid);
+        /* filter out data according to range specs */
+        assert(canPackMoreRanges());
+        {
+            assert(http->range_iter.currentSpec());
+            /* offset of still missing data */
+            int64_t start = http->range_iter.currentSpec()->offset +
+                            http->range_iter.currentSpec()->length -
+                            http->range_iter.debt();
+            debugs(33, 3, "clientPackMoreRanges: in:  offset: " << http->out.offset);
+            debugs(33, 3, "clientPackMoreRanges: out:"
+                   " start: " << start <<
+                   " spec[" << http->range_iter.pos - http->request->range->begin() << "]:" <<
+                   " [" << http->range_iter.currentSpec()->offset <<
+                   ", " << http->range_iter.currentSpec()->offset +
+                   http->range_iter.currentSpec()->length << "),"
+                   " len: " << http->range_iter.currentSpec()->length <<
+                   " debt: " << http->range_iter.debt());
+            if (http->range_iter.currentSpec()->length != -1)
+                assert(http->out.offset <= start);  /* we did not miss it */
+
+            return start;
+        }
+
+    } else if (reply && reply->content_range) {
+        /* request does not have ranges, but reply does */
+        /** \todo FIXME: should use range_iter_pos on reply, as soon as reply->content_range
+         *        becomes HttpHdrRange rather than HttpHdrRangeSpec.
+         */
+        return http->out.offset + reply->content_range->spec.offset;
+    }
+
+    return http->out.offset;
+}
+
+/**
+ * increments iterator "i"
+ * used by clientPackMoreRanges
+ *
+ * \retval true    there is still data available to pack more ranges
+ * \retval false
+ */
+bool
+Http::StreamContext::canPackMoreRanges() const
+{
+    /** first update iterator "i" if needed */
+    if (!http->range_iter.debt()) {
+        debugs(33, 5, "At end of current range spec for " << clientConnection);
+
+        if (http->range_iter.pos != http->range_iter.end)
+            ++http->range_iter.pos;
+
+        http->range_iter.updateSpec();
+    }
+
+    assert(!http->range_iter.debt() == !http->range_iter.currentSpec());
+
+    /* paranoid sync condition */
+    /* continue condition: need_more_data */
+    debugs(33, 5, "returning " << (http->range_iter.currentSpec() ? true : false));
+    return http->range_iter.currentSpec() ? true : false;
+}
+
+/// Adapt stream status to account for Range cases
+clientStream_status_t
+Http::StreamContext::socketState()
+{
+    switch (clientStreamStatus(getTail(), http)) {
+
+    case STREAM_NONE:
+        /* check for range support ending */
+        if (http->request->range) {
+            /* check: reply was parsed and range iterator was initialized */
+            assert(http->range_iter.valid);
+            /* filter out data according to range specs */
+
+            if (!canPackMoreRanges()) {
+                debugs(33, 5, "Range request at end of returnable " <<
+                       "range sequence on " << clientConnection);
+                // we got everything we wanted from the store
+                return STREAM_COMPLETE;
+            }
+        } else if (reply && reply->content_range) {
+            /* reply has content-range, but Squid is not managing ranges */
+            const int64_t &bytesSent = http->out.offset;
+            const int64_t &bytesExpected = reply->content_range->spec.length;
+
+            debugs(33, 7, "body bytes sent vs. expected: " <<
+                   bytesSent << " ? " << bytesExpected << " (+" <<
+                   reply->content_range->spec.offset << ")");
+
+            // did we get at least what we expected, based on range specs?
+
+            if (bytesSent == bytesExpected) // got everything
+                return STREAM_COMPLETE;
+
+            if (bytesSent > bytesExpected) // Error: Sent more than expected
+                return STREAM_UNPLANNED_COMPLETE;
+        }
+
+        return STREAM_NONE;
+
+    case STREAM_COMPLETE:
+        return STREAM_COMPLETE;
+
+    case STREAM_UNPLANNED_COMPLETE:
+        return STREAM_UNPLANNED_COMPLETE;
+
+    case STREAM_FAILED:
+        return STREAM_FAILED;
+    }
+
+    fatal ("unreachable code\n");
+    return STREAM_NONE;
+}
+
+void
+Http::StreamContext::sendStartOfMessage(HttpReply *rep, StoreIOBuffer bodyData)
+{
+    prepareReply(rep);
+    assert(rep);
+    MemBuf *mb = rep->pack();
+
+    // dump now, so we dont output any body.
+    debugs(11, 2, "HTTP Client " << clientConnection);
+    debugs(11, 2, "HTTP Client REPLY:\n---------\n" << mb->buf << "\n----------");
+
+    /* Save length of headers for persistent conn checks */
+    http->out.headers_sz = mb->contentSize();
+#if HEADERS_LOG
+    headersLog(0, 0, http->request->method, rep);
+#endif
+
+    if (bodyData.data && bodyData.length) {
+        if (multipartRangeRequest())
+            packRange(bodyData, mb);
+        else if (http->request->flags.chunkedReply) {
+            packChunk(bodyData, *mb);
+        } else {
+            size_t length = lengthToSend(bodyData.range());
+            noteSentBodyBytes(length);
+            mb->append(bodyData.data, length);
+        }
+    }
+
+    getConn()->write(mb);
+    delete mb;
+}
+
+void
+Http::StreamContext::sendBody(StoreIOBuffer bodyData)
+{
+    if (!multipartRangeRequest() && !http->request->flags.chunkedReply) {
+        size_t length = lengthToSend(bodyData.range());
+        noteSentBodyBytes(length);
+        getConn()->write(bodyData.data, length);
+        return;
+    }
+
+    MemBuf mb;
+    mb.init();
+    if (multipartRangeRequest())
+        packRange(bodyData, &mb);
+    else
+        packChunk(bodyData, mb);
+
+    if (mb.contentSize())
+        getConn()->write(&mb);
+    else
+        writeComplete(0);
+}
+
+size_t
+Http::StreamContext::lengthToSend(Range<int64_t> const &available) const
+{
+    // the size of available range can always fit into a size_t type
+    size_t maximum = available.size();
+
+    if (!http->request->range)
+        return maximum;
+
+    assert(canPackMoreRanges());
+
+    if (http->range_iter.debt() == -1)
+        return maximum;
+
+    assert(http->range_iter.debt() > 0);
+
+    /* TODO this + the last line could be a range intersection calculation */
+    if (available.start < http->range_iter.currentSpec()->offset)
+        return 0;
+
+    return min(http->range_iter.debt(), static_cast<int64_t>(maximum));
+}
+
+void
+Http::StreamContext::noteSentBodyBytes(size_t bytes)
+{
+    debugs(33, 7, bytes << " body bytes");
+    http->out.offset += bytes;
+
+    if (!http->request->range)
+        return;
+
+    if (http->range_iter.debt() != -1) {
+        http->range_iter.debt(http->range_iter.debt() - bytes);
+        assert (http->range_iter.debt() >= 0);
+    }
+
+    /* debt() always stops at -1, below that is a bug */
+    assert(http->range_iter.debt() >= -1);
+}
+
+/// \return true when If-Range specs match reply, false otherwise
+static bool
+clientIfRangeMatch(ClientHttpRequest * http, HttpReply * rep)
+{
+    const TimeOrTag spec = http->request->header.getTimeOrTag(Http::HdrType::IF_RANGE);
+
+    /* check for parsing falure */
+    if (!spec.valid)
+        return false;
+
+    /* got an ETag? */
+    if (spec.tag.str) {
+        ETag rep_tag = rep->header.getETag(Http::HdrType::ETAG);
+        debugs(33, 3, "ETags: " << spec.tag.str << " and " <<
+               (rep_tag.str ? rep_tag.str : "<none>"));
+
+        if (!rep_tag.str)
+            return false; // entity has no etag to compare with!
+
+        if (spec.tag.weak || rep_tag.weak) {
+            debugs(33, DBG_IMPORTANT, "Weak ETags are not allowed in If-Range: " <<
+                   spec.tag.str << " ? " << rep_tag.str);
+            return false; // must use strong validator for sub-range requests
+        }
+
+        return etagIsStrongEqual(rep_tag, spec.tag);
+    }
+
+    /* got modification time? */
+    if (spec.time >= 0)
+        return http->storeEntry()->lastmod <= spec.time;
+
+    assert(0);          /* should not happen */
+    return false;
+}
+
+// seems to be something better suited to Server logic
+/** adds appropriate Range headers if needed */
+void
+Http::StreamContext::buildRangeHeader(HttpReply *rep)
+{
+    HttpHeader *hdr = rep ? &rep->header : nullptr;
+    const char *range_err = nullptr;
+    HttpRequest *request = http->request;
+    assert(request->range);
+    /* check if we still want to do ranges */
+    int64_t roffLimit = request->getRangeOffsetLimit();
+
+    if (!rep)
+        range_err = "no [parse-able] reply";
+    else if ((rep->sline.status() != Http::scOkay) && (rep->sline.status() != Http::scPartialContent))
+        range_err = "wrong status code";
+    else if (hdr->has(Http::HdrType::CONTENT_RANGE))
+        range_err = "origin server does ranges";
+    else if (rep->content_length < 0)
+        range_err = "unknown length";
+    else if (rep->content_length != http->memObject()->getReply()->content_length)
+        range_err = "INCONSISTENT length";  /* a bug? */
+
+    /* hits only - upstream CachePeer determines correct behaviour on misses,
+     * and client_side_reply determines hits candidates
+     */
+    else if (http->logType.isTcpHit() &&
+             http->request->header.has(Http::HdrType::IF_RANGE) &&
+             !clientIfRangeMatch(http, rep))
+        range_err = "If-Range match failed";
+
+    else if (!http->request->range->canonize(rep))
+        range_err = "canonization failed";
+    else if (http->request->range->isComplex())
+        range_err = "too complex range header";
+    else if (!http->logType.isTcpHit() && http->request->range->offsetLimitExceeded(roffLimit))
+        range_err = "range outside range_offset_limit";
+
+    /* get rid of our range specs on error */
+    if (range_err) {
+        /* XXX We do this here because we need canonisation etc. However, this current
+         * code will lead to incorrect store offset requests - the store will have the
+         * offset data, but we won't be requesting it.
+         * So, we can either re-request, or generate an error
+         */
+        http->request->ignoreRange(range_err);
+    } else {
+        /* XXX: TODO: Review, this unconditional set may be wrong. */
+        rep->sline.set(rep->sline.version, Http::scPartialContent);
+        // web server responded with a valid, but unexpected range.
+        // will (try-to) forward as-is.
+        //TODO: we should cope with multirange request/responses
+        bool replyMatchRequest = rep->content_range != nullptr ?
+                                 request->range->contains(rep->content_range->spec) :
+                                 true;
+        const int spec_count = http->request->range->specs.size();
+        int64_t actual_clen = -1;
+
+        debugs(33, 3, "range spec count: " << spec_count <<
+               " virgin clen: " << rep->content_length);
+        assert(spec_count > 0);
+        /* append appropriate header(s) */
+        if (spec_count == 1) {
+            if (!replyMatchRequest) {
+                hdr->delById(Http::HdrType::CONTENT_RANGE);
+                hdr->putContRange(rep->content_range);
+                actual_clen = rep->content_length;
+                //http->range_iter.pos = rep->content_range->spec.begin();
+                (*http->range_iter.pos)->offset = rep->content_range->spec.offset;
+                (*http->range_iter.pos)->length = rep->content_range->spec.length;
+
+            } else {
+                HttpHdrRange::iterator pos = http->request->range->begin();
+                assert(*pos);
+                /* append Content-Range */
+
+                if (!hdr->has(Http::HdrType::CONTENT_RANGE)) {
+                    /* No content range, so this was a full object we are
+                     * sending parts of.
+                     */
+                    httpHeaderAddContRange(hdr, **pos, rep->content_length);
+                }
+
+                /* set new Content-Length to the actual number of bytes
+                 * transmitted in the message-body */
+                actual_clen = (*pos)->length;
+            }
+        } else {
+            /* multipart! */
+            /* generate boundary string */
+            http->range_iter.boundary = http->rangeBoundaryStr();
+            /* delete old Content-Type, add ours */
+            hdr->delById(Http::HdrType::CONTENT_TYPE);
+            httpHeaderPutStrf(hdr, Http::HdrType::CONTENT_TYPE,
+                              "multipart/byteranges; boundary=\"" SQUIDSTRINGPH "\"",
+                              SQUIDSTRINGPRINT(http->range_iter.boundary));
+            /* Content-Length is not required in multipart responses
+             * but it is always nice to have one */
+            actual_clen = http->mRangeCLen();
+
+            /* http->out needs to start where we want data at */
+            http->out.offset = http->range_iter.currentSpec()->offset;
+        }
+
+        /* replace Content-Length header */
+        assert(actual_clen >= 0);
+        hdr->delById(Http::HdrType::CONTENT_LENGTH);
+        hdr->putInt64(Http::HdrType::CONTENT_LENGTH, actual_clen);
+        debugs(33, 3, "actual content length: " << actual_clen);
+
+        /* And start the range iter off */
+        http->range_iter.updateSpec();
+    }
+}
+
+clientStreamNode *
+Http::StreamContext::getTail() const
+{
+    if (http->client_stream.tail)
+        return static_cast<clientStreamNode *>(http->client_stream.tail->data);
+
+    return nullptr;
+}
+
+clientStreamNode *
+Http::StreamContext::getClientReplyContext() const
+{
+    return static_cast<clientStreamNode *>(http->client_stream.tail->prev->data);
+}
+
+ConnStateData *
+Http::StreamContext::getConn() const
+{
+    assert(http && http->getConn());
+    return http->getConn();
+}
+
+/// remembers the abnormal connection termination for logging purposes
+void
+Http::StreamContext::noteIoError(const int xerrno)
+{
+    if (http) {
+        http->logType.err.timedout = (xerrno == ETIMEDOUT);
+        // aborted even if xerrno is zero (which means read abort/eof)
+        http->logType.err.aborted = (xerrno != ETIMEDOUT);
+    }
+}
+
+void
+Http::StreamContext::finished()
+{
+    ConnStateData *conn = getConn();
+
+    /* we can't handle any more stream data - detach */
+    clientStreamDetach(getTail(), http);
+
+    assert(connRegistered_);
+    connRegistered_ = false;
+    conn->pipeline.popById(id);
+}
+
+/// called when we encounter a response-related error
+void
+Http::StreamContext::initiateClose(const char *reason)
+{
+    debugs(33, 4, clientConnection << " because " << reason);
+    getConn()->stopSending(reason); // closes ASAP
+}
+
+void
+Http::StreamContext::deferRecipientForLater(clientStreamNode *node, HttpReply *rep, StoreIOBuffer receivedData)
+{
+    debugs(33, 2, "Deferring request " << http->uri);
+    assert(flags.deferred == 0);
+    flags.deferred = 1;
+    deferredparams.node = node;
+    deferredparams.rep = rep;
+    deferredparams.queuedBuffer = receivedData;
+}
+
+void
+Http::StreamContext::prepareReply(HttpReply *rep)
+{
+    reply = rep;
+    if (http->request->range)
+        buildRangeHeader(rep);
+}
+
+/**
+ * Packs bodyData into mb using chunked encoding.
+ * Packs the last-chunk if bodyData is empty.
+ */
+void
+Http::StreamContext::packChunk(const StoreIOBuffer &bodyData, MemBuf &mb)
+{
+    const uint64_t length =
+        static_cast<uint64_t>(lengthToSend(bodyData.range()));
+    noteSentBodyBytes(length);
+
+    mb.appendf("%" PRIX64 "\r\n", length);
+    mb.append(bodyData.data, length);
+    mb.append("\r\n", 2);
+}
+
+/**
+ * extracts a "range" from *buf and appends them to mb, updating
+ * all offsets and such.
+ */
+void
+Http::StreamContext::packRange(StoreIOBuffer const &source, MemBuf *mb)
+{
+    HttpHdrRangeIter * i = &http->range_iter;
+    Range<int64_t> available(source.range());
+    char const *buf = source.data;
+
+    while (i->currentSpec() && available.size()) {
+        const size_t copy_sz = lengthToSend(available);
+        if (copy_sz) {
+            // intersection of "have" and "need" ranges must not be empty
+            assert(http->out.offset < i->currentSpec()->offset + i->currentSpec()->length);
+            assert(http->out.offset + (int64_t)available.size() > i->currentSpec()->offset);
+
+            /*
+             * put boundary and headers at the beginning of a range in a
+             * multi-range
+             */
+            if (http->multipartRangeRequest() && i->debt() == i->currentSpec()->length) {
+                assert(http->memObject());
+                clientPackRangeHdr(
+                    http->memObject()->getReply(),  /* original reply */
+                    i->currentSpec(),       /* current range */
+                    i->boundary,    /* boundary, the same for all */
+                    mb);
+            }
+
+            // append content
+            debugs(33, 3, "appending " << copy_sz << " bytes");
+            noteSentBodyBytes(copy_sz);
+            mb->append(buf, copy_sz);
+
+            // update offsets
+            available.start += copy_sz;
+            buf += copy_sz;
+        }
+
+        if (!canPackMoreRanges()) {
+            debugs(33, 3, "Returning because !canPackMoreRanges.");
+            if (i->debt() == 0)
+                // put terminating boundary for multiparts
+                clientPackTermBound(i->boundary, mb);
+            return;
+        }
+
+        int64_t nextOffset = getNextRangeOffset();
+        assert(nextOffset >= http->out.offset);
+        int64_t skip = nextOffset - http->out.offset;
+        /* adjust for not to be transmitted bytes */
+        http->out.offset = nextOffset;
+
+        if (available.size() <= (uint64_t)skip)
+            return;
+
+        available.start += skip;
+        buf += skip;
+
+        if (copy_sz == 0)
+            return;
+    }
+}
+
+void
+Http::StreamContext::doClose()
+{
+    clientConnection->close();
+}
+

=== added file 'src/http/StreamContext.h'
--- src/http/StreamContext.h	1970-01-01 00:00:00 +0000
+++ src/http/StreamContext.h	2016-01-09 13:43:46 +0000
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ *
+ * Squid software is distributed under GPLv2+ license and includes
+ * contributions from numerous individuals and organizations.
+ * Please see the COPYING and CONTRIBUTORS files for details.
+ */
+
+#ifndef SQUID_SRC_HTTP_STREAMCONTEXT_H
+#define SQUID_SRC_HTTP_STREAMCONTEXT_H
+
+#include "http/forward.h"
+#include "mem/forward.h"
+#include "StoreIOBuffer.h"
+
+class clientStreamNode;
+class ClientHttpRequest;
+
+namespace Http
+{
+
+/**
+ * The processing context for a single HTTP transaction (stream).
+ *
+ * A context lifetime extends from directly after a request has been parsed
+ * off the client connection buffer, until the last byte of both request
+ * and reply payload (if any) have been written.
+ *
+ * Contexts self-register with the Http::Server Pipeline being managed by the
+ * Server for the connection on which the request was received.
+ *
+ * The socket level management and I/O is done by a Server which owns us.
+ * The scope of this objects control over a socket consists of the data
+ * buffer received from the Server with an initially unknown length.
+ * When that length is known it sets the end boundary of our access to the
+ * buffer.
+ *
+ * The individual processing actions are done by other Jobs which we start.
+ *
+ * When a context is completed the finished() method needs to be called which
+ * will perform all cleanup and deregistration operations. If the reason for
+ * finishing is an error, then notifyIoError() needs to be called prior to
+ * the finished() method.
+ * The caller should follow finished() with a call to ConnStateData::kick()
+ * to resume processing of other transactions or I/O on the connection.
+ *
+ * Alternatively the initiateClose() method can be called to terminate the
+ * whole client connection and all other pending contexts.
+ *
+ * HTTP/1.x:
+ *
+ * When HTTP/1 pipeline is operating there may be multiple transactions using
+ * the client connection. Only the back() context may read from the connection,
+ * and only the front() context may write to it. A context which needs to read
+ * or write to the connection but does not meet those criteria must be shifted
+ * to the deferred state.
+ *
+ *
+ * XXX: If an async call ends the ClientHttpRequest job, Http::StreamContext
+ * (and ConnStateData) may not know about it, leading to segfaults and
+ * assertions. This is difficult to fix
+ * because ClientHttpRequest lacks a good way to communicate its ongoing
+ * destruction back to the Http::StreamContext which pretends to "own" *http.
+ */
+class StreamContext : public RefCountable
+{
+    MEMPROXY_CLASS(StreamContext);
+
+public:
+    /// construct with HTTP/1.x details
+    StreamContext(uint32_t id, const Comm::ConnectionPointer &, ClientHttpRequest *);
+    ~StreamContext();
+
+    /// register this stream with the Server
+    void registerWithConn();
+
+    /// whether the reply has started being sent
+    bool startOfOutput() const;
+
+    /// update stream state after a write, may initiate more I/O
+    void writeComplete(size_t size);
+
+    /// get more data to send
+    void pullData();
+
+    /// \return true if the HTTP request is for multiple ranges
+    bool multipartRangeRequest() const;
+
+    int64_t getNextRangeOffset() const;
+    bool canPackMoreRanges() const;
+    size_t lengthToSend(Range<int64_t> const &available) const;
+
+    clientStream_status_t socketState();
+
+    /// send an HTTP reply message headers and maybe some initial payload
+    void sendStartOfMessage(HttpReply *, StoreIOBuffer bodyData);
+    /// send some HTTP reply message payload
+    void sendBody(StoreIOBuffer bodyData);
+    /// update stream state when N bytes are being sent.
+    /// NP: Http1Server bytes actually not sent yet, just packed into a MemBuf ready
+    void noteSentBodyBytes(size_t);
+
+    /// add Range headers (if any) to the given HTTP reply message
+    void buildRangeHeader(HttpReply *);
+
+    clientStreamNode * getTail() const;
+    clientStreamNode * getClientReplyContext() const;
+
+    ConnStateData *getConn() const;
+
+    /// update state to reflect I/O error
+    void noteIoError(const int xerrno);
+
+    /// cleanup when the transaction has finished. may destroy 'this'
+    void finished();
+
+    /// terminate due to a send/write error (may continue reading)
+    void initiateClose(const char *reason);
+
+    void deferRecipientForLater(clientStreamNode *, HttpReply *, StoreIOBuffer receivedData);
+
+public:
+    // NP: stream ID is relative to the connection, not global.
+    uint32_t id; ///< stream ID within the client connection.
+
+public: // HTTP/1.x state data
+
+    Comm::ConnectionPointer clientConnection; ///< details about the client connection socket
+    ClientHttpRequest *http;    /* we pretend to own that Job */
+    HttpReply *reply;
+    char reqbuf[HTTP_REQBUF_SZ];
+    struct {
+        unsigned deferred:1; ///< This is a pipelined request waiting for the current object to complete
+        unsigned parsed_ok:1; ///< Was this parsed correctly?
+    } flags;
+
+    bool mayUseConnection() const {return mayUseConnection_;}
+
+    void mayUseConnection(bool aBool) {
+        mayUseConnection_ = aBool;
+        debugs(33, 3, "This " << this << " marked " << aBool);
+    }
+
+    class DeferredParams
+    {
+
+    public:
+        clientStreamNode *node;
+        HttpReply *rep;
+        StoreIOBuffer queuedBuffer;
+    };
+
+    DeferredParams deferredparams;
+    int64_t writtenToSocket;
+
+private:
+    void prepareReply(HttpReply *);
+    void packChunk(const StoreIOBuffer &bodyData, MemBuf &);
+    void packRange(StoreIOBuffer const &, MemBuf *);
+    void doClose();
+
+    bool mayUseConnection_; /* This request may use the connection. Don't read anymore requests for now */
+    bool connRegistered_;
+};
+
+} // namespace Http
+
+#endif /* SQUID_SRC_HTTP_STREAMCONTEXT_H */

=== modified file 'src/http/forward.h'
--- src/http/forward.h	2016-01-01 00:12:18 +0000
+++ src/http/forward.h	2016-01-07 09:53:34 +0000
@@ -11,6 +11,14 @@
 
 #include "http/one/forward.h"
 
+namespace Http
+{
+
+class StreamContext;
+typedef RefCount<Http::StreamContext> StreamContextPointer;
+
+} // namespace Http
+
 // TODO move these classes into Http namespace
 class HttpRequestMethod;
 typedef RefCount<HttpRequestMethod> HttpRequestMethodPointer;

=== modified file 'src/ident/AclIdent.cc'
--- src/ident/AclIdent.cc	2016-01-01 00:12:18 +0000
+++ src/ident/AclIdent.cc	2016-01-07 14:03:46 +0000
@@ -18,6 +18,7 @@
 #include "client_side.h"
 #include "comm/Connection.h"
 #include "globals.h"
+#include "http/StreamContext.h"
 #include "ident/AclIdent.h"
 #include "ident/Ident.h"
 

=== modified file 'src/main.cc'
--- src/main.cc	2016-01-01 00:12:18 +0000
+++ src/main.cc	2016-01-07 15:41:12 +0000
@@ -37,6 +37,7 @@
 #include "FwdState.h"
 #include "globals.h"
 #include "htcp.h"
+#include "http/StreamContext.h"
 #include "HttpHeader.h"
 #include "HttpReply.h"
 #include "icmp/IcmpSquid.h"

=== modified file 'src/peer_select.cc'
--- src/peer_select.cc	2016-01-01 00:12:18 +0000
+++ src/peer_select.cc	2016-01-07 15:23:59 +0000
@@ -20,6 +20,7 @@
 #include "globals.h"
 #include "hier_code.h"
 #include "htcp.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "icmp/net_db.h"
 #include "ICP.h"

=== modified file 'src/redirect.cc'
--- src/redirect.cc	2016-01-01 00:12:18 +0000
+++ src/redirect.cc	2016-01-07 15:23:55 +0000
@@ -20,6 +20,7 @@
 #include "globals.h"
 #include "helper.h"
 #include "helper/Reply.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "mgr/Registration.h"
 #include "redirect.h"

=== modified file 'src/servers/FtpServer.cc'
--- src/servers/FtpServer.cc	2016-01-01 00:12:18 +0000
+++ src/servers/FtpServer.cc	2016-01-08 15:40:48 +0000
@@ -26,6 +26,7 @@
 #include "ftp/Parsing.h"
 #include "globals.h"
 #include "http/one/RequestParser.h"
+#include "http/StreamContext.h"
 #include "HttpHdrCc.h"
 #include "ip/tools.h"
 #include "ipc/FdNotes.h"
@@ -125,7 +126,7 @@
 {
     // zero pipelinePrefetchMax() ensures that there is only parsed request
     Must(pipeline.count() == 1);
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     Must(context != nullptr);
 
     ClientHttpRequest *const http = context->http;
@@ -149,7 +150,7 @@
 }
 
 void
-Ftp::Server::processParsedRequest(ClientSocketContext *)
+Ftp::Server::processParsedRequest(Http::StreamContext *)
 {
     Must(pipeline.count() == 1);
 
@@ -288,7 +289,7 @@
 Ftp::Server::notePeerConnection(Comm::ConnectionPointer conn)
 {
     // find request
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     Must(context != nullptr);
     ClientHttpRequest *const http = context->http;
     Must(http != NULL);
@@ -548,7 +549,7 @@
 }
 
 /// creates a context filled with an error message for a given early error
-ClientSocketContext *
+Http::StreamContext *
 Ftp::Server::earlyError(const EarlyErrorKind eek)
 {
     /* Default values, to be updated by the switch statement below */
@@ -602,7 +603,7 @@
         // no default so that a compiler can check that we have covered all cases
     }
 
-    ClientSocketContext *context = abortRequestParsing(errUri);
+    Http::StreamContext *context = abortRequestParsing(errUri);
     clientStreamNode *node = context->getClientReplyContext();
     Must(node);
     clientReplyContext *repContext = dynamic_cast<clientReplyContext *>(node->data.getRaw());
@@ -616,9 +617,9 @@
 }
 
 /// Parses a single FTP request on the control connection.
-/// Returns a new ClientSocketContext on valid requests and all errors.
+/// Returns a new Http::StreamContext on valid requests and all errors.
 /// Returns NULL on incomplete requests that may still succeed given more data.
-ClientSocketContext *
+Http::StreamContext *
 Ftp::Server::parseOneRequest()
 {
     flags.readMore = false; // common for all but one case below
@@ -696,7 +697,7 @@
 
         // process USER request now because it sets FTP peer host name
         if (cmd == cmdUser()) {
-            if (ClientSocketContext *errCtx = handleUserRequest(cmd, params))
+            if (Http::StreamContext *errCtx = handleUserRequest(cmd, params))
                 return errCtx;
         }
     }
@@ -740,8 +741,8 @@
     http->req_sz = tok.parsedSize();
     http->uri = newUri;
 
-    ClientSocketContext *const result =
-        new ClientSocketContext(clientConnection, http);
+    Http::StreamContext *const result =
+        new Http::StreamContext(nextStreamId(), clientConnection, http);
 
     StoreIOBuffer tempBuffer;
     tempBuffer.data = result->reqbuf;
@@ -761,7 +762,7 @@
 Ftp::Server::handleReply(HttpReply *reply, StoreIOBuffer data)
 {
     // the caller guarantees that we are dealing with the current context only
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     assert(context != nullptr);
 
     if (context->http && context->http->al != NULL &&
@@ -869,7 +870,7 @@
 void
 Ftp::Server::handlePasvReply(const HttpReply *reply, StoreIOBuffer)
 {
-    const ClientSocketContext::Pointer context(pipeline.front());
+    const Http::StreamContextPointer context(pipeline.front());
     assert(context != nullptr);
 
     if (context->http->request->errType != ERR_NONE) {
@@ -1227,7 +1228,7 @@
         return;
     }
 
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     if (context != nullptr && context->http) {
         context->http->out.size += io.size;
         context->http->out.headers_sz += io.size;
@@ -1249,7 +1250,7 @@
         return;
     }
 
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     assert(context->http);
     context->http->out.size += io.size;
     context->http->out.headers_sz += io.size;
@@ -1339,7 +1340,7 @@
 
 /// Called to parse USER command, which is required to create an HTTP request
 /// wrapper. W/o request, the errors are handled by returning earlyError().
-ClientSocketContext *
+Http::StreamContext *
 Ftp::Server::handleUserRequest(const SBuf &, SBuf &params)
 {
     if (params.isEmpty())
@@ -1665,7 +1666,7 @@
         if (params.conn != NULL)
             params.conn->close();
         setReply(425, "Cannot open data connection.");
-        ClientSocketContext::Pointer context = pipeline.front();
+        Http::StreamContextPointer context = pipeline.front();
         Must(context->http);
         Must(context->http->storeEntry() != NULL);
     } else {
@@ -1680,7 +1681,7 @@
 void
 Ftp::Server::setReply(const int code, const char *msg)
 {
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     ClientHttpRequest *const http = context->http;
     assert(http != NULL);
     assert(http->storeEntry() == NULL);

=== modified file 'src/servers/FtpServer.h'
--- src/servers/FtpServer.h	2016-01-01 00:12:18 +0000
+++ src/servers/FtpServer.h	2016-01-07 09:51:48 +0000
@@ -81,8 +81,8 @@
     };
 
     /* ConnStateData API */
-    virtual ClientSocketContext *parseOneRequest();
-    virtual void processParsedRequest(ClientSocketContext *context);
+    virtual Http::StreamContext *parseOneRequest();
+    virtual void processParsedRequest(Http::StreamContext *context);
     virtual void notePeerConnection(Comm::ConnectionPointer conn);
     virtual void clientPinnedConnectionClosed(const CommCloseCbParams &io);
     virtual void handleReply(HttpReply *header, StoreIOBuffer receivedData);
@@ -112,7 +112,7 @@
 
     void calcUri(const SBuf *file);
     void changeState(const Ftp::ServerState newState, const char *reason);
-    ClientSocketContext *handleUserRequest(const SBuf &cmd, SBuf &params);
+    Http::StreamContext *handleUserRequest(const SBuf &cmd, SBuf &params);
     bool checkDataConnPost() const;
     void replyDataWritingCheckpoint();
     void maybeReadUploadData();
@@ -126,7 +126,7 @@
     void writeForwardedReplyAndCall(const HttpReply *reply, AsyncCall::Pointer &call);
     void writeReply(MemBuf &mb);
 
-    ClientSocketContext *earlyError(const EarlyErrorKind eek);
+    Http::StreamContext *earlyError(const EarlyErrorKind eek);
     bool handleRequest(HttpRequest *);
     void setDataCommand();
     bool checkDataConnPre();

=== modified file 'src/servers/Http1Server.cc'
--- src/servers/Http1Server.cc	2016-01-01 00:12:18 +0000
+++ src/servers/Http1Server.cc	2016-01-09 09:36:17 +0000
@@ -15,6 +15,7 @@
 #include "client_side_request.h"
 #include "comm/Write.h"
 #include "http/one/RequestParser.h"
+#include "http/StreamContext.h"
 #include "HttpHeaderTools.h"
 #include "profiler/Profiler.h"
 #include "servers/Http1Server.h"
@@ -70,7 +71,7 @@
     readSomeData();
 }
 
-ClientSocketContext *
+Http::StreamContext *
 Http::One::Server::parseOneRequest()
 {
     PROF_start(HttpServer_parseOneRequest);
@@ -82,17 +83,17 @@
         parser_ = new Http1::RequestParser();
 
     /* Process request */
-    ClientSocketContext *context = parseHttpRequest(this, parser_);
+    Http::StreamContext *context = parseHttpRequest(this, parser_);
 
     PROF_stop(HttpServer_parseOneRequest);
     return context;
 }
 
 void clientProcessRequestFinished(ConnStateData *conn, const HttpRequest::Pointer &request);
-bool clientTunnelOnError(ConnStateData *conn, ClientSocketContext *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes);
+bool clientTunnelOnError(ConnStateData *conn, Http::StreamContext *context, HttpRequest *request, const HttpRequestMethod& method, err_type requestError, Http::StatusCode errStatusCode, const char *requestErrorBytes);
 
 bool
-Http::One::Server::buildHttpRequest(ClientSocketContext *context)
+Http::One::Server::buildHttpRequest(Http::StreamContext *context)
 {
     HttpRequest::Pointer request;
     ClientHttpRequest *http = context->http;
@@ -179,14 +180,14 @@
 }
 
 void
-Http::One::Server::proceedAfterBodyContinuation(ClientSocketContext::Pointer context)
+Http::One::Server::proceedAfterBodyContinuation(Http::StreamContextPointer context)
 {
     debugs(33, 5, "Body Continuation written");
     clientProcessRequest(this, parser_, context.getRaw());
 }
 
 void
-Http::One::Server::processParsedRequest(ClientSocketContext *context)
+Http::One::Server::processParsedRequest(Http::StreamContext *context)
 {
     if (!buildHttpRequest(context))
         return;
@@ -221,8 +222,8 @@
                 HttpReply::Pointer rep = new HttpReply;
                 rep->sline.set(Http::ProtocolVersion(), Http::scContinue);
 
-                typedef UnaryMemFunT<Http1::Server, ClientSocketContext::Pointer> CbDialer;
-                const AsyncCall::Pointer cb = asyncCall(11, 3,  "Http1::Server::proceedAfterBodyContinuation", CbDialer(this, &Http1::Server::proceedAfterBodyContinuation, ClientSocketContext::Pointer(context)));
+                typedef UnaryMemFunT<Http1::Server, Http::StreamContextPointer> CbDialer;
+                const AsyncCall::Pointer cb = asyncCall(11, 3,  "Http1::Server::proceedAfterBodyContinuation", CbDialer(this, &Http1::Server::proceedAfterBodyContinuation, Http::StreamContextPointer(context)));
                 sendControlMsg(HttpControlMsg(rep, cb));
                 return;
             }
@@ -242,7 +243,7 @@
 Http::One::Server::handleReply(HttpReply *rep, StoreIOBuffer receivedData)
 {
     // the caller guarantees that we are dealing with the current context only
-    ClientSocketContext::Pointer context = pipeline.front();
+    Http::StreamContextPointer context = pipeline.front();
     Must(context != nullptr);
     const ClientHttpRequest *http = context->http;
     Must(http != NULL);
@@ -262,7 +263,7 @@
     }
 
     if (!context->startOfOutput()) {
-        context->sendBody(rep, receivedData);
+        context->sendBody(receivedData);
         return;
     }
 

=== modified file 'src/servers/Http1Server.h'
--- src/servers/Http1Server.h	2016-01-01 00:12:18 +0000
+++ src/servers/Http1Server.h	2016-01-07 09:54:38 +0000
@@ -29,8 +29,8 @@
 
 protected:
     /* ConnStateData API */
-    virtual ClientSocketContext *parseOneRequest();
-    virtual void processParsedRequest(ClientSocketContext *context);
+    virtual Http::StreamContext *parseOneRequest();
+    virtual void processParsedRequest(Http::StreamContext *context);
     virtual void handleReply(HttpReply *rep, StoreIOBuffer receivedData);
     virtual void writeControlMsgAndCall(HttpReply *rep, AsyncCall::Pointer &call);
     virtual time_t idleTimeout() const;
@@ -42,17 +42,17 @@
     /* AsyncJob API */
     virtual void start();
 
-    void proceedAfterBodyContinuation(ClientSocketContext::Pointer context);
+    void proceedAfterBodyContinuation(Http::StreamContextPointer context);
 
 private:
-    void processHttpRequest(ClientSocketContext *const context);
+    void processHttpRequest(Http::StreamContext *const context);
     void handleHttpRequestData();
 
     /// Handles parsing results. May generate and deliver an error reply
     /// to the client if parsing is failed, or parses the url and build the
     /// HttpRequest object using parsing results.
     /// Return false if parsing is failed, true otherwise.
-    bool buildHttpRequest(ClientSocketContext *context);
+    bool buildHttpRequest(Http::StreamContext *context);
 
     Http1::RequestParserPointer parser_;
     HttpRequestMethod method_; ///< parsed HTTP method

=== modified file 'src/servers/Server.cc'
--- src/servers/Server.cc	2016-01-01 00:12:18 +0000
+++ src/servers/Server.cc	2016-01-08 15:14:16 +0000
@@ -14,6 +14,7 @@
 #include "Debug.h"
 #include "fd.h"
 #include "fde.h"
+#include "http/StreamContext.h"
 #include "MasterXaction.h"
 #include "servers/Server.h"
 #include "SquidConfig.h"
@@ -25,7 +26,8 @@
     clientConnection(xact->tcpClient),
     transferProtocol(xact->squidPort->transport),
     port(xact->squidPort),
-    receivedFirstByte_(false)
+    receivedFirstByte_(false),
+    nextStreamId_(0)
 {}
 
 bool

=== modified file 'src/servers/Server.h'
--- src/servers/Server.h	2016-01-01 00:12:18 +0000
+++ src/servers/Server.h	2016-01-08 15:31:26 +0000
@@ -35,6 +35,9 @@
     virtual bool doneAll() const;
     virtual void swanSong();
 
+    /// fetch the next available stream ID
+    virtual uint32_t nextStreamId() = 0;
+
     /// ??
     virtual bool connFinishedWithConn(int size) = 0;
 
@@ -117,6 +120,7 @@
     void doClientRead(const CommIoCbParams &io);
     void clientWriteDone(const CommIoCbParams &io);
 
+    uint32_t nextStreamId_;    ///< incremented as streams are initiated
     AsyncCall::Pointer reader; ///< set when we are reading
     AsyncCall::Pointer writer; ///< set when we are writing
 };

=== modified file 'src/ssl/PeerConnector.cc'
--- src/ssl/PeerConnector.cc	2016-01-01 00:12:18 +0000
+++ src/ssl/PeerConnector.cc	2016-01-07 14:08:16 +0000
@@ -18,6 +18,7 @@
 #include "fde.h"
 #include "globals.h"
 #include "helper/ResultCode.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "neighbors.h"
 #include "security/NegotiationHistory.h"

=== modified file 'src/ssl/ServerBump.cc'
--- src/ssl/ServerBump.cc	2016-01-01 00:12:18 +0000
+++ src/ssl/ServerBump.cc	2016-01-07 14:09:55 +0000
@@ -12,6 +12,7 @@
 
 #include "client_side.h"
 #include "FwdState.h"
+#include "http/StreamContext.h"
 #include "ssl/ServerBump.h"
 #include "Store.h"
 #include "StoreClient.h"

=== modified file 'src/stat.cc'
--- src/stat.cc	2016-01-01 00:12:18 +0000
+++ src/stat.cc	2016-01-07 15:23:50 +0000
@@ -19,6 +19,7 @@
 #include "fde.h"
 #include "format/Token.h"
 #include "globals.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "IoStats.h"
 #include "mem/Pool.h"

=== modified file 'src/tests/stub_client_side.cc'
--- src/tests/stub_client_side.cc	2016-01-01 00:12:18 +0000
+++ src/tests/stub_client_side.cc	2016-01-09 18:44:01 +0000
@@ -7,72 +7,54 @@
  */
 
 #include "squid.h"
-#include "client_side.h"
+#include "client_side_request.h"
+#include "http/StreamContext.h"
 
 #define STUB_API "client_side.cc"
 #include "tests/STUB.h"
 
-//ClientSocketContext::ClientSocketContext(const ConnectionPointer&, ClientHttpRequest*) STUB
-//ClientSocketContext::~ClientSocketContext() STUB
-bool ClientSocketContext::startOfOutput() const STUB_RETVAL(false)
-void ClientSocketContext::writeComplete(size_t size) STUB
-void ClientSocketContext::pullData() STUB
-int64_t ClientSocketContext::getNextRangeOffset() const STUB_RETVAL(0)
-bool ClientSocketContext::canPackMoreRanges() const STUB_RETVAL(false)
-clientStream_status_t ClientSocketContext::socketState() STUB_RETVAL(STREAM_NONE)
-void ClientSocketContext::sendBody(HttpReply * rep, StoreIOBuffer bodyData) STUB
-void ClientSocketContext::sendStartOfMessage(HttpReply * rep, StoreIOBuffer bodyData) STUB
-size_t ClientSocketContext::lengthToSend(Range<int64_t> const &available) STUB_RETVAL(0)
-void ClientSocketContext::noteSentBodyBytes(size_t) STUB
-void ClientSocketContext::buildRangeHeader(HttpReply * rep) STUB
-clientStreamNode * ClientSocketContext::getTail() const STUB_RETVAL(NULL)
-clientStreamNode * ClientSocketContext::getClientReplyContext() const STUB_RETVAL(NULL)
-void ClientSocketContext::finished() STUB
-void ClientSocketContext::deferRecipientForLater(clientStreamNode * node, HttpReply * rep, StoreIOBuffer receivedData) STUB
-bool ClientSocketContext::multipartRangeRequest() const STUB_RETVAL(false)
-void ClientSocketContext::registerWithConn() STUB
-void ClientSocketContext::noteIoError(const int xerrno) STUB
-
+#include "client_side.h"
 bool ConnStateData::clientParseRequests() STUB_RETVAL(false)
 void ConnStateData::readNextRequest() STUB
 bool ConnStateData::isOpen() const STUB_RETVAL(false)
 void ConnStateData::kick() STUB
-void ConnStateData::sendControlMsg(HttpControlMsg msg) STUB
+void ConnStateData::sendControlMsg(HttpControlMsg) STUB
 int64_t ConnStateData::mayNeedToReadMoreBody() const STUB_RETVAL(0)
 #if USE_AUTH
-void ConnStateData::setAuth(const Auth::UserRequest::Pointer &aur, const char *cause) STUB
+void ConnStateData::setAuth(const Auth::UserRequest::Pointer &, const char *) STUB
 #endif
 bool ConnStateData::transparent() const STUB_RETVAL(false)
-void ConnStateData::stopReceiving(const char *error) STUB
-void ConnStateData::stopSending(const char *error) STUB
+void ConnStateData::stopReceiving(const char *) STUB
+void ConnStateData::stopSending(const char *) STUB
 void ConnStateData::expectNoForwarding() STUB
 void ConnStateData::noteMoreBodySpaceAvailable(BodyPipe::Pointer) STUB
 void ConnStateData::noteBodyConsumerAborted(BodyPipe::Pointer) STUB
 bool ConnStateData::handleReadData() STUB_RETVAL(false)
 bool ConnStateData::handleRequestBodyData() STUB_RETVAL(false)
-void ConnStateData::pinConnection(const Comm::ConnectionPointer &pinServerConn, HttpRequest *request, CachePeer *peer, bool auth, bool monitor) STUB
-void ConnStateData::unpinConnection(const bool andClose) STUB
-const Comm::ConnectionPointer ConnStateData::validatePinnedConnection(HttpRequest *request, const CachePeer *peer) STUB_RETVAL(NULL)
-void ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &io) STUB
-void ConnStateData::connStateClosed(const CommCloseCbParams &io) STUB
-void ConnStateData::requestTimeout(const CommTimeoutCbParams &params) STUB
+void ConnStateData::pinConnection(const Comm::ConnectionPointer &, HttpRequest *, CachePeer *, bool, bool) STUB
+void ConnStateData::unpinConnection(const bool) STUB
+const Comm::ConnectionPointer ConnStateData::validatePinnedConnection(HttpRequest *, const CachePeer *) STUB_RETVAL(NULL)
+void ConnStateData::clientPinnedConnectionClosed(const CommCloseCbParams &) STUB
+void ConnStateData::connStateClosed(const CommCloseCbParams &) STUB
+void ConnStateData::requestTimeout(const CommTimeoutCbParams &) STUB
 void ConnStateData::swanSong() STUB
-void ConnStateData::quitAfterError(HttpRequest *request) STUB
+void ConnStateData::quitAfterError(HttpRequest *) STUB
 #if USE_OPENSSL
-void ConnStateData::httpsPeeked(Comm::ConnectionPointer serverConnection) STUB
+void ConnStateData::httpsPeeked(Comm::ConnectionPointer) STUB
 void ConnStateData::getSslContextStart() STUB
 void ConnStateData::getSslContextDone(Security::ContextPtr, bool) STUB
-void ConnStateData::sslCrtdHandleReplyWrapper(void *data, const Helper::Reply &reply) STUB
-void ConnStateData::sslCrtdHandleReply(const Helper::Reply &reply) STUB
-void ConnStateData::switchToHttps(HttpRequest *request, Ssl::BumpMode bumpServerMode) STUB
-void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &certProperties) STUB
-bool ConnStateData::serveDelayedError(ClientSocketContext *context) STUB_RETVAL(false)
+void ConnStateData::sslCrtdHandleReplyWrapper(void *, const Helper::Reply &) STUB
+void ConnStateData::sslCrtdHandleReply(const Helper::Reply &) STUB
+void ConnStateData::switchToHttps(HttpRequest *, Ssl::BumpMode) STUB
+void ConnStateData::buildSslCertGenerationParams(Ssl::CertificateProperties &) STUB
+bool ConnStateData::serveDelayedError(Http::StreamContext *) STUB_RETVAL(false)
 #endif
 
-void setLogUri(ClientHttpRequest * http, char const *uri, bool cleanUrl) STUB
-const char *findTrailingHTTPVersion(const char *uriAndHTTPVersion, const char *end) STUB_RETVAL(NULL)
-int varyEvaluateMatch(StoreEntry * entry, HttpRequest * req) STUB_RETVAL(0)
+void setLogUri(ClientHttpRequest *, char const *, bool) STUB
+const char *findTrailingHTTPVersion(const char *, const char *) STUB_RETVAL(NULL)
+int varyEvaluateMatch(StoreEntry *, HttpRequest *) STUB_RETVAL(0)
 void clientOpenListenSockets(void) STUB
 void clientHttpConnectionsClose(void) STUB
 void httpRequestFree(void *) STUB
-
+void clientPackRangeHdr(const HttpReply *, const HttpHdrRangeSpec *, String, MemBuf *) STUB
+void clientPackTermBound(String, MemBuf *) STUB

=== modified file 'src/tests/stub_client_side_request.cc'
--- src/tests/stub_client_side_request.cc	2016-01-01 00:12:18 +0000
+++ src/tests/stub_client_side_request.cc	2016-01-07 16:15:19 +0000
@@ -8,6 +8,7 @@
 
 #include "squid.h"
 #include "client_side_request.h"
+#include "http/StreamContext.h"
 #include "Store.h"
 
 #if !_USE_INLINE_

=== modified file 'src/tools.cc'
--- src/tools.cc	2016-01-01 00:12:18 +0000
+++ src/tools.cc	2016-01-07 15:29:08 +0000
@@ -17,6 +17,7 @@
 #include "fqdncache.h"
 #include "fs_io.h"
 #include "htcp.h"
+#include "http/StreamContext.h"
 #include "ICP.h"
 #include "ip/Intercept.h"
 #include "ip/QosConfig.h"

=== modified file 'src/tunnel.cc'
--- src/tunnel.cc	2016-01-01 00:12:18 +0000
+++ src/tunnel.cc	2016-01-07 15:45:17 +0000
@@ -25,6 +25,7 @@
 #include "FwdState.h"
 #include "globals.h"
 #include "http.h"
+#include "http/StreamContext.h"
 #include "HttpRequest.h"
 #include "HttpStateFlags.h"
 #include "ip/QosConfig.h"
@@ -1240,7 +1241,7 @@
     tunnelState = new TunnelStateData;
     tunnelState->url = SBufToCstring(url);
     tunnelState->request = request;
-    tunnelState->server.size_ptr = NULL; //Set later if ClientSocketContext is available
+    tunnelState->server.size_ptr = NULL; //Set later if Http::StreamContext is available
 
     // Temporary static variable to store the unneeded for our case status code
     static int status_code = 0;
@@ -1249,7 +1250,7 @@
 
     ConnStateData *conn;
     if ((conn = request->clientConnectionManager.get())) {
-        ClientSocketContext::Pointer context = conn->pipeline.front();
+        Http::StreamContextPointer context = conn->pipeline.front();
         if (context != nullptr && context->http != nullptr) {
             tunnelState->logTag_ptr = &context->http->logType;
             tunnelState->server.size_ptr = &context->http->out.size;



More information about the squid-dev mailing list