@Override
  protected ChannelFuture doOnPut(ChannelHandlerContext context, HttpRequest request) {
    NettyTransferService<HttpProtocolInfo>.NettyMoverChannel file = null;
    Exception exception = null;

    try {
      checkContentHeader(request.headers().names(), Collections.singletonList(CONTENT_LENGTH));

      file = open(request, true);

      if (file.getIoMode() != IoMode.WRITE) {
        throw new HttpException(METHOD_NOT_ALLOWED.code(), "Resource is not open for writing");
      }

      if (is100ContinueExpected(request)) {
        context
            .writeAndFlush(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE))
            .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
      }
      _writeChannel = file;
      file = null;
      return null;
    } catch (HttpException e) {
      exception = e;
      return context.writeAndFlush(
          createErrorResponse(HttpResponseStatus.valueOf(e.getErrorCode()), e.getMessage()));
    } catch (URISyntaxException e) {
      exception = e;
      return context.writeAndFlush(
          createErrorResponse(BAD_REQUEST, "URI is not valid: " + e.getMessage()));
    } catch (IllegalArgumentException e) {
      exception = e;
      return context.writeAndFlush(createErrorResponse(BAD_REQUEST, e.getMessage()));
    } catch (RuntimeException e) {
      exception = e;
      return context.writeAndFlush(createErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage()));
    } finally {
      if (file != null) {
        file.release(exception);
        _files.remove(file);
      }
    }
  }
  @Override
  protected ChannelFuture doOnContent(ChannelHandlerContext context, HttpContent content) {
    if (_writeChannel != null) {
      try {
        ByteBuf data = content.content();
        while (data.isReadable()) {
          data.readBytes(_writeChannel, data.readableBytes());
        }
        if (content instanceof LastHttpContent) {
          checkContentHeader(
              ((LastHttpContent) content).trailingHeaders().names(),
              Collections.singletonList(CONTENT_LENGTH));

          context.channel().config().setAutoRead(false);

          NettyTransferService<HttpProtocolInfo>.NettyMoverChannel writeChannel = _writeChannel;
          _writeChannel = null;
          _files.remove(writeChannel);

          long size = writeChannel.size();
          URI location = writeChannel.getProtocolInfo().getLocation();

          ChannelPromise promise = context.newPromise();
          Futures.addCallback(
              writeChannel.release(),
              new FutureCallback<Void>() {
                @Override
                public void onSuccess(Void result) {
                  try {
                    context.writeAndFlush(new HttpPutResponse(size, location), promise);
                  } catch (IOException e) {
                    context.writeAndFlush(
                        createErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage()), promise);
                  }
                  context.channel().config().setAutoRead(true);
                }

                @Override
                public void onFailure(Throwable t) {
                  if (t instanceof FileCorruptedCacheException) {
                    context.writeAndFlush(
                        createErrorResponse(BAD_REQUEST, t.getMessage()), promise);
                  } else if (t instanceof CacheException) {
                    context.writeAndFlush(
                        createErrorResponse(INTERNAL_SERVER_ERROR, t.getMessage()), promise);
                  } else {
                    context.writeAndFlush(
                        createErrorResponse(INTERNAL_SERVER_ERROR, t.toString()), promise);
                  }
                  context.channel().config().setAutoRead(true);
                }
              },
              MoreExecutors.directExecutor());
          return promise;
        }
      } catch (IOException e) {
        _writeChannel.release(e);
        _files.remove(_writeChannel);
        _writeChannel = null;
        return context.writeAndFlush(createErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage()));
      } catch (HttpException e) {
        _writeChannel.release(e);
        _files.remove(_writeChannel);
        _writeChannel = null;
        return context.writeAndFlush(
            createErrorResponse(HttpResponseStatus.valueOf(e.getErrorCode()), e.getMessage()));
      }
    }
    return null;
  }
  /**
   * Single GET operation.
   *
   * <p>Finds the correct mover channel using the UUID in the GET. Range queries are supported. The
   * file will be sent to the remote peer in chunks to avoid server side memory issues.
   */
  @Override
  protected ChannelFuture doOnGet(ChannelHandlerContext context, HttpRequest request) {
    NettyTransferService<HttpProtocolInfo>.NettyMoverChannel file;
    List<HttpByteRange> ranges;
    long fileSize;

    try {
      file = open(request, false);

      if (file.getIoMode() != IoMode.READ) {
        throw new HttpException(METHOD_NOT_ALLOWED.code(), "Resource is not open for reading");
      }

      fileSize = file.size();
      ranges = parseHttpRange(request, 0, fileSize - 1);
    } catch (HttpException e) {
      return context.writeAndFlush(createErrorResponse(e.getErrorCode(), e.getMessage()));
    } catch (URISyntaxException e) {
      return context.writeAndFlush(
          createErrorResponse(BAD_REQUEST, "URI not valid: " + e.getMessage()));
    } catch (IllegalArgumentException e) {
      return context.writeAndFlush(createErrorResponse(BAD_REQUEST, e.getMessage()));
    } catch (IOException e) {
      return context.writeAndFlush(createErrorResponse(INTERNAL_SERVER_ERROR, e.getMessage()));
    }

    if (ranges == null || ranges.isEmpty()) {
      /*
       * GET for a whole file
       */
      context
          .write(new HttpGetResponse(fileSize, file))
          .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
      context
          .write(read(file, 0, fileSize - 1))
          .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
      return context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
    } else if (ranges.size() == 1) {
      /* RFC 2616: 14.16. A response to a request for a single range
       * MUST NOT be sent using the multipart/byteranges media type.
       */
      HttpByteRange range = ranges.get(0);
      context
          .write(
              new HttpPartialContentResponse(
                  range.getLower(), range.getUpper(), fileSize, buildDigest(file)))
          .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
      context
          .write(read(file, range.getLower(), range.getUpper()))
          .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
      return context.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
    } else {
      /*
       * GET for multiple ranges
       */

      long totalLen = 0;
      ByteBuf[] fragmentMarkers = new ByteBuf[ranges.size()];
      for (int i = 0; i < ranges.size(); i++) {
        HttpByteRange range = ranges.get(i);
        long upper = range.getUpper();
        long lower = range.getLower();
        totalLen += upper - lower + 1;

        ByteBuf buffer = fragmentMarkers[i] = createMultipartFragmentMarker(lower, upper, fileSize);
        totalLen += buffer.readableBytes();
      }
      ByteBuf endMarker = createMultipartEnd();
      totalLen += endMarker.readableBytes();

      context
          .write(new HttpMultipartResponse(buildDigest(file), totalLen))
          .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
      for (int i = 0; i < ranges.size(); i++) {
        HttpByteRange range = ranges.get(i);
        context
            .write(fragmentMarkers[i])
            .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
        context
            .write(read(file, range.getLower(), range.getUpper()))
            .addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
      }
      return context.writeAndFlush(new DefaultLastHttpContent(endMarker));
    }
  }