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