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.WritableByteChannel; 033 034import org.apache.http.impl.io.HttpTransportMetricsImpl; 035import org.apache.http.io.BufferInfo; 036import org.apache.http.nio.reactor.SessionOutputBuffer; 037import org.apache.http.util.CharArrayBuffer; 038 039/** 040 * Implements chunked transfer coding. The content is sent in small chunks. 041 * Entities transferred using this decoder can be of unlimited length. 042 * 043 * @since 4.0 044 */ 045public class ChunkEncoder extends AbstractContentEncoder { 046 047 private final int fragHint; 048 private final CharArrayBuffer lineBuffer; 049 050 private final BufferInfo bufferinfo; 051 052 /** 053 * @since 4.3 054 * 055 * @param channel underlying channel. 056 * @param buffer session buffer. 057 * @param metrics transport metrics. 058 * @param fragementSizeHint fragment size hint defining an minimal size of a fragment 059 * that should be written out directly to the channel bypassing the session buffer. 060 * Value {@code 0} disables fragment buffering. 061 */ 062 public ChunkEncoder( 063 final WritableByteChannel channel, 064 final SessionOutputBuffer buffer, 065 final HttpTransportMetricsImpl metrics, 066 final int fragementSizeHint) { 067 super(channel, buffer, metrics); 068 this.fragHint = fragementSizeHint > 0 ? fragementSizeHint : 0; 069 this.lineBuffer = new CharArrayBuffer(16); 070 if (buffer instanceof BufferInfo) { 071 this.bufferinfo = (BufferInfo) buffer; 072 } else { 073 this.bufferinfo = null; 074 } 075 } 076 077 public ChunkEncoder( 078 final WritableByteChannel channel, 079 final SessionOutputBuffer buffer, 080 final HttpTransportMetricsImpl metrics) { 081 this(channel, buffer, metrics, 0); 082 } 083 084 @Override 085 public int write(final ByteBuffer src) throws IOException { 086 if (src == null) { 087 return 0; 088 } 089 assertNotCompleted(); 090 091 int total = 0; 092 while (src.hasRemaining()) { 093 int chunk = src.remaining(); 094 int avail; 095 if (this.bufferinfo != null) { 096 avail = this.bufferinfo.available(); 097 } else { 098 avail = 4096; 099 } 100 101 // subtract the length of the longest chunk header 102 // 12345678\r\n 103 // <chunk-data>\r\n 104 avail -= 12; 105 if (avail > 0) { 106 if (avail < chunk) { 107 // write no more than 'avail' bytes 108 chunk = avail; 109 this.lineBuffer.clear(); 110 this.lineBuffer.append(Integer.toHexString(chunk)); 111 this.buffer.writeLine(this.lineBuffer); 112 final int oldlimit = src.limit(); 113 src.limit(src.position() + chunk); 114 this.buffer.write(src); 115 src.limit(oldlimit); 116 } else { 117 // write all 118 this.lineBuffer.clear(); 119 this.lineBuffer.append(Integer.toHexString(chunk)); 120 this.buffer.writeLine(this.lineBuffer); 121 this.buffer.write(src); 122 } 123 this.lineBuffer.clear(); 124 this.buffer.writeLine(this.lineBuffer); 125 total += chunk; 126 } 127 if (this.buffer.length() >= this.fragHint || src.hasRemaining()) { 128 final int bytesWritten = flushToChannel(); 129 if (bytesWritten == 0) { 130 break; 131 } 132 } 133 } 134 return total; 135 } 136 137 @Override 138 public void complete() throws IOException { 139 assertNotCompleted(); 140 this.lineBuffer.clear(); 141 this.lineBuffer.append("0"); 142 this.buffer.writeLine(this.lineBuffer); 143 this.lineBuffer.clear(); 144 this.buffer.writeLine(this.lineBuffer); 145 super.complete(); 146 } 147 148 @Override 149 public String toString() { 150 final StringBuilder sb = new StringBuilder(); 151 sb.append("[chunk-coded; completed: "); 152 sb.append(isCompleted()); 153 sb.append("]"); 154 return sb.toString(); 155 } 156 157}