1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28 package org.apache.hc.core5.http.impl.nio;
29
30 import java.io.IOException;
31 import java.nio.ByteBuffer;
32 import java.nio.channels.FileChannel;
33 import java.nio.channels.ReadableByteChannel;
34
35 import org.apache.hc.core5.http.ConnectionClosedException;
36 import org.apache.hc.core5.http.impl.BasicHttpTransportMetrics;
37 import org.apache.hc.core5.http.nio.FileContentDecoder;
38 import org.apache.hc.core5.http.nio.SessionInputBuffer;
39 import org.apache.hc.core5.util.Args;
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 public class LengthDelimitedDecoder extends AbstractContentDecoder implements FileContentDecoder {
55
56 private final long contentLength;
57
58 private long len;
59
60 public LengthDelimitedDecoder(
61 final ReadableByteChannel channel,
62 final SessionInputBuffer buffer,
63 final BasicHttpTransportMetrics metrics,
64 final long contentLength) {
65 super(channel, buffer, metrics);
66 Args.notNegative(contentLength, "Content length");
67 this.contentLength = contentLength;
68 }
69
70 @Override
71 public int read(final ByteBuffer dst) throws IOException {
72 Args.notNull(dst, "Byte buffer");
73 if (isCompleted()) {
74 return -1;
75 }
76 final int chunk = (int) Math.min((this.contentLength - this.len), Integer.MAX_VALUE);
77
78 final int bytesRead;
79 if (this.buffer.hasData()) {
80 final int maxLen = Math.min(chunk, this.buffer.length());
81 bytesRead = this.buffer.read(dst, maxLen);
82 } else {
83 bytesRead = readFromChannel(dst, chunk);
84 }
85 if (bytesRead == -1) {
86 setCompleted();
87 if (this.len < this.contentLength) {
88 throw new ConnectionClosedException(
89 "Premature end of Content-Length delimited message body (expected: %d; received: %d)",
90 this.contentLength, this.len);
91 }
92 }
93 this.len += bytesRead;
94 if (this.len >= this.contentLength) {
95 this.completed = true;
96 }
97 if (this.completed && bytesRead == 0) {
98 return -1;
99 }
100 return bytesRead;
101 }
102
103 @Override
104 public long transfer(
105 final FileChannel dst,
106 final long position,
107 final long count) throws IOException {
108
109 if (dst == null) {
110 return 0;
111 }
112 if (isCompleted()) {
113 return -1;
114 }
115
116 final int chunk = (int) Math.min((this.contentLength - this.len), Integer.MAX_VALUE);
117
118 final long bytesRead;
119 if (this.buffer.hasData()) {
120 final int maxLen = Math.min(chunk, this.buffer.length());
121 dst.position(position);
122 bytesRead = this.buffer.read(dst, count < maxLen ? (int)count : maxLen);
123 } else {
124 if (this.channel.isOpen()) {
125 if (position > dst.size()) {
126 throw new IOException(String.format("Position past end of file [%d > %d]",
127 position, dst.size()));
128 }
129 bytesRead = dst.transferFrom(this.channel, position, count < chunk ? count : chunk);
130 } else {
131 bytesRead = -1;
132 }
133 if (bytesRead > 0) {
134 this.metrics.incrementBytesTransferred(bytesRead);
135 }
136 }
137 if (bytesRead == -1) {
138 setCompleted();
139 if (this.len < this.contentLength) {
140 throw new ConnectionClosedException(
141 "Premature end of Content-Length delimited message body (expected: %d; received: %d)",
142 this.contentLength, this.len);
143 }
144 }
145 this.len += bytesRead;
146 if (this.len >= this.contentLength) {
147 this.completed = true;
148 }
149 return bytesRead;
150 }
151
152 @Override
153 public String toString() {
154 final StringBuilder sb = new StringBuilder();
155 sb.append("[content length: ");
156 sb.append(this.contentLength);
157 sb.append("; pos: ");
158 sb.append(this.len);
159 sb.append("; completed: ");
160 sb.append(this.completed);
161 sb.append("]");
162 return sb.toString();
163 }
164 }