void spewInternal() {
      if (pending.remaining() > 0) {
        com.koushikdutta.async.Util.emitAllData(BodySpewer.this, pending);
        if (pending.remaining() > 0) return;
      }

      // fill pending
      try {
        while (pending.remaining() == 0) {
          ByteBuffer buffer = ByteBuffer.allocate(8192);
          int read = cacheResponse.getBody().read(buffer.array());
          if (read == -1) {
            allowEnd = true;
            report(null);
            return;
          }
          buffer.limit(read);
          pending.add(buffer);
          com.koushikdutta.async.Util.emitAllData(BodySpewer.this, pending);
        }
      } catch (IOException e) {
        allowEnd = true;
        report(e);
      }
    }
 @Override
 public void write(AsyncHttpRequest request, AsyncHttpResponse sink) {
   Util.writeAll(
       sink,
       mBodyBytes,
       new CompletedCallback() {
         @Override
         public void onCompleted(Exception ex) {}
       });
 }
  void setSocket(AsyncSocket exchange) {
    mSocket = exchange;

    if (mSocket == null) return;

    mWriter = mRequest.getBody();
    if (mWriter != null) {
      mRequest.getHeaders().setContentType(mWriter.getContentType());
      if (mWriter.length() != -1) {
        mRequest.getHeaders().setContentLength(mWriter.length());
        mSink = mSocket;
      } else {
        mRequest.getHeaders().getHeaders().set("Transfer-Encoding", "Chunked");
        mSink = new ChunkedOutputFilter(mSocket);
      }
    } else {
      mSink = mSocket;
    }

    String rs = mRequest.getRequestString();
    com.koushikdutta.async.Util.writeAll(
        exchange,
        rs.getBytes(),
        new CompletedCallback() {
          @Override
          public void onCompleted(Exception ex) {
            if (mWriter != null) mWriter.write(mRequest, AsyncHttpResponseImpl.this);
          }
        });

    LineEmitter liner = new LineEmitter();
    exchange.setDataCallback(liner);
    liner.setLineCallback(mHeaderCallback);

    mSocket.setEndCallback(mReporter);
    mSocket.setClosedCallback(
        new CompletedCallback() {
          @Override
          public void onCompleted(Exception ex) {
            // TODO: do we care? throw if socket is still writing or something?
          }
        });
  }
  @Override
  public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
    try {
      ByteBufferList transformed = new ByteBufferList();
      ByteBuffer output = ByteBuffer.allocate(bb.remaining() * 2);
      int totalInflated = 0;
      int totalRead = 0;
      while (bb.size() > 0) {
        ByteBuffer b = bb.remove();
        if (b.hasRemaining()) {
          totalRead = +b.remaining();
          mInflater.setInput(b.array(), b.arrayOffset() + b.position(), b.remaining());
          do {
            int inflated =
                mInflater.inflate(
                    output.array(), output.arrayOffset() + output.position(), output.remaining());
            totalInflated += inflated;
            output.position(output.position() + inflated);
            if (!output.hasRemaining()) {
              output.limit(output.position());
              output.position(0);
              transformed.add(output);
              Assert.assertNotSame(totalRead, 0);
              int newSize = output.capacity() * 2;
              output = ByteBuffer.allocate(newSize);
            }
          } while (!mInflater.needsInput() && !mInflater.finished());
        }
      }
      output.limit(output.position());
      output.position(0);
      transformed.add(output);

      Util.emitAllData(this, transformed);
    } catch (Exception ex) {
      report(ex);
    }
  }
    @Override
    public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
      if (cached != null) {
        com.koushikdutta.async.Util.emitAllData(this, cached);
        // couldn't emit it all, so just wait for another day...
        if (cached.remaining() > 0) return;
        cached = null;
      }

      // write to cache... any data not consumed needs to be retained for the next callback
      try {
        if (cacheRequest != null) {
          OutputStream outputStream = cacheRequest.getBody();
          if (outputStream != null) {
            int count = bb.size();
            for (int i = 0; i < count; i++) {
              ByteBuffer b = bb.remove();
              outputStream.write(b.array(), b.arrayOffset() + b.position(), b.remaining());
              bb.add(b);
            }
          } else {
            abort();
          }
        }
      } catch (Exception e) {
        abort();
      }

      super.onDataAvailable(emitter, bb);

      if (cacheRequest != null && bb.remaining() > 0) {
        cached = new ByteBufferList();
        cached.add(bb);
        bb.clear();
      }
    }
  @Override
  public boolean exchangeHeaders(final OnExchangeHeaderData data) {
    Protocol p = Protocol.get(data.protocol);
    if (p != null && p != Protocol.HTTP_1_0 && p != Protocol.HTTP_1_1)
      return super.exchangeHeaders(data);

    AsyncHttpRequest request = data.request;
    AsyncHttpRequestBody requestBody = data.request.getBody();

    if (requestBody != null) {
      if (requestBody.length() >= 0) {
        request.getHeaders().set("Content-Length", String.valueOf(requestBody.length()));
        data.response.sink(data.socket);
      } else if ("close".equals(request.getHeaders().get("Connection"))) {
        data.response.sink(data.socket);
      } else {
        request.getHeaders().set("Transfer-Encoding", "Chunked");
        data.response.sink(new ChunkedOutputFilter(data.socket));
      }
    }

    String rl = request.getRequestLine().toString();
    String rs = request.getHeaders().toPrefixString(rl);
    request.logv("\n" + rs);

    Util.writeAll(data.socket, rs.getBytes(), data.sendHeadersCallback);

    LineEmitter.StringCallback headerCallback =
        new LineEmitter.StringCallback() {
          Headers mRawHeaders = new Headers();
          String statusLine;

          @Override
          public void onStringAvailable(String s) {
            try {
              s = s.trim();
              if (statusLine == null) {
                statusLine = s;
              } else if (!TextUtils.isEmpty(s)) {
                mRawHeaders.addLine(s);
              } else {
                String[] parts = statusLine.split(" ", 3);
                if (parts.length < 2) throw new Exception(new IOException("Not HTTP"));

                data.response.headers(mRawHeaders);
                String protocol = parts[0];
                data.response.protocol(protocol);
                data.response.code(Integer.parseInt(parts[1]));
                data.response.message(parts.length == 3 ? parts[2] : "");
                data.receiveHeadersCallback.onCompleted(null);

                // socket may get detached after headers (websocket)
                AsyncSocket socket = data.response.socket();
                if (socket == null) return;
                DataEmitter emitter;
                // HEAD requests must not return any data. They still may
                // return content length, etc, which will confuse the body decoder
                if (AsyncHttpHead.METHOD.equalsIgnoreCase(data.request.getMethod())) {
                  emitter = HttpUtil.EndEmitter.create(socket.getServer(), null);
                } else {
                  emitter =
                      HttpUtil.getBodyDecoder(socket, Protocol.get(protocol), mRawHeaders, false);
                }
                data.response.emitter(emitter);
              }
            } catch (Exception ex) {
              data.receiveHeadersCallback.onCompleted(ex);
            }
          }
        };

    LineEmitter liner = new LineEmitter();
    data.socket.setDataCallback(liner);
    liner.setLineCallback(headerCallback);
    return true;
  }
  // step 3) if this is a conditional cache request, serve it from the cache if necessary
  // otherwise, see if it is cacheable
  @Override
  public void onBodyDecoder(OnBodyData data) {
    CachedSocket cached =
        (CachedSocket)
            com.koushikdutta.async.Util.getWrappedSocket(data.socket, CachedSocket.class);
    if (cached != null) return;

    CacheData cacheData = data.state.getParcelable("cache-data");
    if (cacheData != null) {
      if (cacheData.cachedResponseHeaders.validate(data.headers)) {
        data.headers = cacheData.cachedResponseHeaders.combine(data.headers);
        data.headers
            .getHeaders()
            .setStatusLine(cacheData.cachedResponseHeaders.getHeaders().getStatusLine());

        conditionalCacheHitCount++;

        BodySpewer bodySpewer = new BodySpewer();
        bodySpewer.cacheResponse = cacheData.candidate;
        bodySpewer.setDataEmitter(data.bodyEmitter);
        data.bodyEmitter = bodySpewer;
        bodySpewer.spew();
        return;
      }

      // did not validate, so fall through and cache the response
      data.state.remove("cache-data");
    }

    if (!caching) return;

    if (!data.headers.isCacheable(data.request.getHeaders())
        || !data.request.getMethod().equals(AsyncHttpGet.METHOD)) {
      /*
       * Don't cache non-GET responses. We're technically allowed to cache
       * HEAD requests and some POST requests, but the complexity of doing
       * so is high and the benefit is low.
       */
      networkCount++;
      return;
    }

    String key = uriToKey(data.request.getUri());
    RawHeaders varyHeaders =
        data.request.getHeaders().getHeaders().getAll(data.headers.getVaryFields());
    Entry entry = new Entry(data.request.getUri(), varyHeaders, data.request, data.headers);
    DiskLruCache.Editor editor = null;
    BodyCacher cacher = new BodyCacher();
    try {
      editor = cache.edit(key);
      if (editor == null) {
        // Log.i(LOGTAG, "can't cache");
        return;
      }
      entry.writeTo(editor);

      cacher.cacheRequest = new CacheRequestImpl(editor);
      if (cacher.cacheRequest.getBody() == null) return;
      //            cacher.cacheData =
      cacher.setDataEmitter(data.bodyEmitter);
      data.bodyEmitter = cacher;

      data.state.putParcelable("body-cacher", cacher);
      cacheStoreCount++;
    } catch (Exception e) {
      // Log.e(LOGTAG, "error", e);
      if (cacher.cacheRequest != null) cacher.cacheRequest.abort();
      cacher.cacheRequest = null;
      networkCount++;
    }
  }
 @Override
 public void write(
     final AsyncHttpRequest request, DataSink sink, final CompletedCallback completed) {
   Util.pump(emitter, sink, completed);
   if (emitter.isPaused()) emitter.resume();
 }