View Javadoc

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   * An AsyncResponseConsumer that buffers input until the buffer contains {@code maxEntitySize} bytes. If more data
22   * is read, a {@link ContentTooLongException} is thrown.
23   *
24   * @since 0.23.5
25   */
26  public class BoundedAsyncResponseConsumer extends AbstractAsyncResponseConsumer<HttpResponse> {
27  
28      // limit the amount of memory that is pre-allocated based on the reported Content-Length. Let's be a bit paranoid
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              // start with a 4k buffer
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                  // must be integer overflow
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 }