View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.client.util;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.nio.ByteBuffer;
23  import java.nio.charset.Charset;
24  import java.nio.charset.StandardCharsets;
25  import java.nio.charset.UnsupportedCharsetException;
26  import java.util.Locale;
27  
28  import org.eclipse.jetty.client.api.Response;
29  import org.eclipse.jetty.client.api.Response.Listener;
30  import org.eclipse.jetty.client.api.Result;
31  import org.eclipse.jetty.http.HttpFields;
32  import org.eclipse.jetty.http.HttpHeader;
33  
34  /**
35   * <p>Implementation of {@link Listener} that buffers the content up to a maximum length
36   * specified to the constructors.</p>
37   * <p>The content may be retrieved from {@link #onSuccess(Response)} or {@link #onComplete(Result)}
38   * via {@link #getContent()} or {@link #getContentAsString()}.</p>
39   */
40  public abstract class BufferingResponseListener extends Listener.Adapter
41  {
42      private final int maxLength;
43      private volatile byte[] buffer = new byte[0];
44      private volatile String encoding;
45  
46      /**
47       * Creates an instance with a default maximum length of 2 MiB.
48       */
49      public BufferingResponseListener()
50      {
51          this(2 * 1024 * 1024);
52      }
53  
54      /**
55       * Creates an instance with the given maximum length
56       *
57       * @param maxLength the maximum length of the content
58       */
59      public BufferingResponseListener(int maxLength)
60      {
61          this.maxLength = maxLength;
62      }
63  
64      @Override
65      public void onHeaders(Response response)
66      {
67          HttpFields headers = response.getHeaders();
68          long length = headers.getLongField(HttpHeader.CONTENT_LENGTH.asString());
69          if (length > maxLength)
70          {
71              response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
72          }
73          else
74          {
75              String contentType = headers.get(HttpHeader.CONTENT_TYPE);
76              if (contentType != null)
77              {
78                  String charset = "charset=";
79                  int index = contentType.toLowerCase(Locale.ENGLISH).indexOf(charset);
80                  if (index > 0)
81                  {
82                      String encoding = contentType.substring(index + charset.length());
83                      // Sometimes charsets arrive with an ending semicolon
84                      index = encoding.indexOf(';');
85                      if (index > 0)
86                          encoding = encoding.substring(0, index);
87                      this.encoding = encoding;
88                  }
89              }
90          }
91      }
92  
93      @Override
94      public void onContent(Response response, ByteBuffer content)
95      {
96          long newLength = buffer.length + content.remaining();
97          if (newLength > maxLength)
98          {
99              response.abort(new IllegalArgumentException("Buffering capacity exceeded"));
100         }
101         else
102         {
103             byte[] newBuffer = new byte[(int)newLength];
104             System.arraycopy(buffer, 0, newBuffer, 0, buffer.length);
105             content.get(newBuffer, buffer.length, content.remaining());
106             buffer = newBuffer;
107         }
108     }
109 
110     @Override
111     public abstract void onComplete(Result result);
112 
113     public String getEncoding()
114     {
115         return encoding;
116     }
117 
118     /**
119      * @return the content as bytes
120      * @see #getContentAsString()
121      */
122     public byte[] getContent()
123     {
124         return buffer;
125     }
126 
127     /**
128      * @return the content as a string, using the "Content-Type" header to detect the encoding
129      *         or defaulting to UTF-8 if the encoding could not be detected.
130      * @see #getContentAsString(String)
131      */
132     public String getContentAsString()
133     {
134         String encoding = this.encoding;
135         if (encoding == null)
136             return getContentAsString(StandardCharsets.UTF_8);
137         return getContentAsString(encoding);
138     }
139 
140     /**
141      * @param encoding the encoding of the content bytes
142      * @return the content as a string, with the specified encoding
143      * @see #getContentAsString()
144      */
145     public String getContentAsString(String encoding)
146     {
147         try
148         {
149             return new String(getContent(), encoding);
150         }
151         catch (UnsupportedEncodingException x)
152         {
153             throw new UnsupportedCharsetException(encoding);
154         }
155     }
156 
157     /**
158      * @param encoding the encoding of the content bytes
159      * @return the content as a string, with the specified encoding
160      * @see #getContentAsString()
161      */
162     public String getContentAsString(Charset encoding)
163     {
164         return new String(getContent(), encoding);
165     }
166 }