1 /* 2 * Copyright 2014 The Netty Project 3 * 4 * The Netty Project licenses this file to you under the Apache License, 5 * version 2.0 (the "License"); you may not use this file except in compliance 6 * with the License. You may obtain a copy of the License at: 7 * 8 * https://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations 14 * under the License. 15 */ 16 package io.netty5.handler.codec.http; 17 18 import io.netty5.buffer.api.Buffer; 19 import io.netty5.buffer.api.BufferAllocator; 20 import io.netty5.handler.stream.ChunkedInput; 21 import io.netty5.util.Resource; 22 23 /** 24 * A {@link ChunkedInput} that fetches data chunk by chunk for use with HTTP chunked transfers. 25 * <p> 26 * Each chunk from the input data will be wrapped within a {@link HttpContent}. At the end of the input data, 27 * {@link LastHttpContent} will be written. 28 * <p> 29 * Ensure that your HTTP response header contains {@code Transfer-Encoding: chunked}. 30 * <p> 31 * <pre> 32 * public void messageReceived(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception { 33 * HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK); 34 * response.headers().set(TRANSFER_ENCODING, CHUNKED); 35 * ctx.write(response); 36 * 37 * HttpContentChunkedInput httpChunkWriter = new HttpChunkedInput( 38 * new ChunkedFile("/tmp/myfile.txt")); 39 * Future<Void> sendFileFuture = ctx.write(httpChunkWriter); 40 * } 41 * </pre> 42 */ 43 public class HttpChunkedInput implements ChunkedInput<HttpContent<?>> { 44 private final ChunkedInput<Buffer> input; 45 private final LastHttpContent<?> lastHttpContent; 46 private boolean sentLastChunk; 47 48 /** 49 * Creates a new instance using the specified input. {@code lastHttpContent} will be written as the terminating 50 * chunk. 51 * @param input {@link ChunkedInput} containing data to write 52 * @param lastHttpContent {@link LastHttpContent} that will be written as the terminating chunk. Use this for 53 * training headers. 54 */ 55 public HttpChunkedInput(ChunkedInput<Buffer> input, LastHttpContent<?> lastHttpContent) { 56 this.input = input; 57 this.lastHttpContent = lastHttpContent; 58 } 59 60 @Override 61 public boolean isEndOfInput() throws Exception { 62 if (input.isEndOfInput()) { 63 // Only end of input after last HTTP chunk has been sent 64 return sentLastChunk; 65 } else { 66 return false; 67 } 68 } 69 70 @Override 71 public void close() throws Exception { 72 // Using try-with-resources to correctly handle exceptions from close(). 73 try (input) { 74 if (!sentLastChunk && Resource.isAccessible(lastHttpContent, false)) { 75 Resource.dispose(lastHttpContent); 76 } 77 } 78 } 79 80 @Override 81 public HttpContent<?> readChunk(BufferAllocator allocator) throws Exception { 82 if (input.isEndOfInput()) { 83 if (sentLastChunk) { 84 return null; 85 } else { 86 // Send last chunk for this input 87 sentLastChunk = true; 88 return lastHttpContent; 89 } 90 } else { 91 Buffer buf = input.readChunk(allocator); 92 if (buf == null) { 93 return null; 94 } 95 return new DefaultHttpContent(buf); 96 } 97 } 98 99 @Override 100 public long length() { 101 return input.length(); 102 } 103 104 @Override 105 public long progress() { 106 return input.progress(); 107 } 108 }