1 package com.atlassian.httpclient.apache.httpcomponents;
2
3 import com.google.common.primitives.Ints;
4 import org.apache.http.ContentTooLongException;
5 import org.apache.http.HttpEntity;
6 import org.apache.http.HttpResponse;
7 import org.apache.http.entity.ContentType;
8 import org.apache.http.nio.ContentDecoder;
9 import org.apache.http.nio.IOControl;
10 import org.apache.http.nio.entity.ContentBufferEntity;
11 import org.apache.http.nio.protocol.AbstractAsyncResponseConsumer;
12 import org.apache.http.nio.util.ByteBufferAllocator;
13 import org.apache.http.nio.util.HeapByteBufferAllocator;
14 import org.apache.http.nio.util.SimpleInputBuffer;
15 import org.apache.http.protocol.HttpContext;
16 import org.apache.http.util.Asserts;
17
18 import java.io.IOException;
19
20
21
22
23
24
25
26 public class BoundedAsyncResponseConsumer extends AbstractAsyncResponseConsumer<HttpResponse> {
27
28
29 private static final int MAX_INITIAL_BUFFER_SIZE = 256 * 1024;
30
31 private final int maxEntitySize;
32
33 private volatile BoundedInputBuffer buf;
34 private volatile HttpResponse response;
35
36 BoundedAsyncResponseConsumer(int maxEntitySize) {
37 this.maxEntitySize = maxEntitySize;
38 }
39
40 protected HttpResponse buildResult(HttpContext context) {
41 return response;
42 }
43
44 protected void onContentReceived(ContentDecoder decoder, IOControl ioctrl) throws IOException {
45 Asserts.notNull(buf, "Content buffer");
46 try {
47 buf.consumeContent(decoder);
48 } catch (BufferFullException e) {
49 throw new EntityTooLargeException(response,
50 "Entity content is too long; larger than " + maxEntitySize + " bytes");
51 }
52 }
53
54 protected void onEntityEnclosed(HttpEntity entity, ContentType contentType) throws IOException {
55 int length = Math.min(Ints.saturatedCast(entity.getContentLength()), maxEntitySize);
56 if (length < 0L) {
57
58 length = Math.min(4096, maxEntitySize);
59 }
60 int initialBufferSize = Math.min(MAX_INITIAL_BUFFER_SIZE, length);
61
62 buf = new BoundedInputBuffer(initialBufferSize, maxEntitySize, new HeapByteBufferAllocator());
63 Asserts.notNull(response, "response");
64 response.setEntity(new ContentBufferEntity(entity, buf));
65 }
66
67 protected void onResponseReceived(HttpResponse response) throws IOException {
68 this.response = response;
69 }
70
71 protected void releaseResources() {
72 this.response = null;
73 this.buf = null;
74 }
75
76 private static class BoundedInputBuffer extends SimpleInputBuffer {
77
78 private final int maxSize;
79
80 BoundedInputBuffer(int initialSize, int maxSize, ByteBufferAllocator allocator) {
81 super(Math.min(maxSize, initialSize), allocator);
82
83 this.maxSize = maxSize;
84 }
85
86 @Override
87 protected void expand() {
88 int capacity = buffer.capacity();
89 int newCapacity = capacity < 2 ? 2 : capacity + (capacity >>> 1);
90 if (newCapacity < capacity) {
91
92 newCapacity = Integer.MAX_VALUE;
93 }
94 ensureCapacity(newCapacity);
95 }
96
97 @Override
98 protected void ensureCapacity(int requiredCapacity) {
99 if (buffer.capacity() == maxSize && requiredCapacity > maxSize) {
100 throw new BufferFullException();
101 }
102 super.ensureCapacity(Math.min(requiredCapacity, maxSize));
103 }
104 }
105
106 private static class BufferFullException extends RuntimeException {
107 }
108 }