@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);
      }
    }
  }
  /**
   * 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));
    }
  }