001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.nio.codecs; 029 030import java.io.IOException; 031import java.nio.ByteBuffer; 032import java.nio.channels.FileChannel; 033import java.nio.channels.ReadableByteChannel; 034 035import org.apache.http.ConnectionClosedException; 036import org.apache.http.impl.io.HttpTransportMetricsImpl; 037import org.apache.http.nio.FileContentDecoder; 038import org.apache.http.nio.reactor.SessionInputBuffer; 039import org.apache.http.util.Args; 040 041/** 042 * Content decoder that cuts off after a defined number of bytes. This class 043 * is used to receive content of HTTP messages where the end of the content 044 * entity is determined by the value of the {@code Content-Length header}. 045 * Entities transferred using this stream can be maximum {@link Long#MAX_VALUE} 046 * long. 047 * <p> 048 * This decoder is optimized to transfer data directly from the underlying 049 * I/O session's channel to a {@link FileChannel}, whenever 050 * possible avoiding intermediate buffering in the session buffer. 051 * 052 * @since 4.0 053 */ 054public class LengthDelimitedDecoder extends AbstractContentDecoder 055 implements FileContentDecoder { 056 057 private final long contentLength; 058 059 private long len; 060 061 public LengthDelimitedDecoder( 062 final ReadableByteChannel channel, 063 final SessionInputBuffer buffer, 064 final HttpTransportMetricsImpl metrics, 065 final long contentLength) { 066 super(channel, buffer, metrics); 067 Args.notNegative(contentLength, "Content length"); 068 this.contentLength = contentLength; 069 } 070 071 @Override 072 public int read(final ByteBuffer dst) throws IOException { 073 Args.notNull(dst, "Byte buffer"); 074 if (this.completed) { 075 return -1; 076 } 077 final int chunk = (int) Math.min((this.contentLength - this.len), Integer.MAX_VALUE); 078 079 final int bytesRead; 080 if (this.buffer.hasData()) { 081 final int maxLen = Math.min(chunk, this.buffer.length()); 082 bytesRead = this.buffer.read(dst, maxLen); 083 } else { 084 bytesRead = readFromChannel(dst, chunk); 085 } 086 if (bytesRead == -1) { 087 this.completed = true; 088 if (this.len < this.contentLength) { 089 throw new ConnectionClosedException( 090 "Premature end of Content-Length delimited message body (expected: " 091 + this.contentLength + "; received: " + this.len); 092 } 093 } 094 this.len += bytesRead; 095 if (this.len >= this.contentLength) { 096 this.completed = true; 097 } 098 if (this.completed && bytesRead == 0) { 099 return -1; 100 } else { 101 return bytesRead; 102 } 103 } 104 105 @Override 106 public long transfer( 107 final FileChannel dst, 108 final long position, 109 final long count) throws IOException { 110 111 if (dst == null) { 112 return 0; 113 } 114 if (this.completed) { 115 return -1; 116 } 117 118 final int chunk = (int) Math.min((this.contentLength - this.len), Integer.MAX_VALUE); 119 120 final long bytesRead; 121 if (this.buffer.hasData()) { 122 final int maxLen = Math.min(chunk, this.buffer.length()); 123 dst.position(position); 124 bytesRead = this.buffer.read(dst, maxLen); 125 } else { 126 if (this.channel.isOpen()) { 127 if (position > dst.size()) { 128 throw new IOException("Position past end of file [" + position + 129 " > " + dst.size() + "]"); 130 } 131 bytesRead = dst.transferFrom(this.channel, position, count < chunk ? count : chunk); 132 } else { 133 bytesRead = -1; 134 } 135 if (bytesRead > 0) { 136 this.metrics.incrementBytesTransferred(bytesRead); 137 } 138 } 139 if (bytesRead == -1) { 140 this.completed = true; 141 if (this.len < this.contentLength) { 142 throw new ConnectionClosedException( 143 "Premature end of Content-Length delimited message body (expected: " 144 + this.contentLength + "; received: " + this.len); 145 } 146 } 147 this.len += bytesRead; 148 if (this.len >= this.contentLength) { 149 this.completed = true; 150 } 151 return bytesRead; 152 } 153 154 @Override 155 public String toString() { 156 final StringBuilder sb = new StringBuilder(); 157 sb.append("[content length: "); 158 sb.append(this.contentLength); 159 sb.append("; pos: "); 160 sb.append(this.len); 161 sb.append("; completed: "); 162 sb.append(this.completed); 163 sb.append("]"); 164 return sb.toString(); 165 } 166}