1515
1616package com .amazonaws .http ;
1717
18+ import android .util .Log ;
19+
1820import com .amazonaws .ClientConfiguration ;
1921
2022import java .io .IOException ;
2325import java .net .HttpURLConnection ;
2426import java .net .ProtocolException ;
2527import java .net .URL ;
28+ import java .nio .ByteBuffer ;
2629import java .security .GeneralSecurityException ;
30+ import java .util .HashMap ;
2731import java .util .List ;
2832import java .util .Map ;
2933
4347 */
4448public class UrlHttpClient implements HttpClient {
4549
50+ private static final String TAG = "amazonaws" ;
51+
4652 private final ClientConfiguration config ;
4753
4854 public UrlHttpClient (ClientConfiguration config ) {
@@ -53,10 +59,21 @@ public UrlHttpClient(ClientConfiguration config) {
5359 public HttpResponse execute (final HttpRequest request ) throws IOException {
5460 final URL url = request .getUri ().toURL ();
5561 final HttpURLConnection connection = (HttpURLConnection ) url .openConnection ();
62+ final CurlBuilder curlBuilder = config .isLogging ()
63+ ? null : new CurlBuilder (request .getUri ().toURL ());
5664
5765 configureConnection (request , connection );
58- applyHeadersAndMethod (request , connection );
59- writeContentToConnection (request , connection );
66+ applyHeadersAndMethod (request , connection , curlBuilder );
67+ writeContentToConnection (request , connection , curlBuilder );
68+
69+ if (curlBuilder != null ) {
70+ if (curlBuilder .isValid ()) {
71+ Log .v (TAG , curlBuilder .build ());
72+ } else {
73+ Log .v (TAG , "Failed to create curl, content too long" );
74+ }
75+ }
76+
6077 return createHttpResponse (request , connection );
6178 }
6279
@@ -103,15 +120,25 @@ public void shutdown() {
103120 // No op
104121 }
105122
123+ /**
124+ * Needed to pass UrlHttpClientTest.
125+ * @see #writeContentToConnection(HttpRequest, HttpURLConnection, CurlBuilder)
126+ */
127+ void writeContentToConnection (HttpRequest request , HttpURLConnection connection )
128+ throws IOException {
129+ writeContentToConnection (request , connection , null /* curlBuilder */ );
130+ }
131+
106132 /**
107133 * Writes the content (if any) of the request to the passed connection
108134 *
109135 * @param request
110136 * @param connection
137+ * @param curlBuilder
111138 * @throws IOException
112139 */
113- void writeContentToConnection (final HttpRequest request , final HttpURLConnection connection )
114- throws IOException {
140+ void writeContentToConnection (final HttpRequest request , final HttpURLConnection connection ,
141+ final CurlBuilder curlBuilder ) throws IOException {
115142 // Note: if DoOutput is set to true and method is GET, HttpUrlConnection
116143 // will silently change the method to POST.
117144 if (request .getContent () != null && request .getContentLength () >= 0 ) {
@@ -122,17 +149,41 @@ void writeContentToConnection(final HttpRequest request, final HttpURLConnection
122149 connection .setFixedLengthStreamingMode ((int ) request .getContentLength ());
123150 }
124151 final OutputStream os = connection .getOutputStream ();
125- write (request .getContent (), os );
152+ ByteBuffer curlBuffer = null ;
153+ if (curlBuilder != null ) {
154+ if (request .getContentLength () < Integer .MAX_VALUE ) {
155+ curlBuffer = ByteBuffer .allocate ((int ) request .getContentLength ());
156+ } else {
157+ curlBuilder .setContentOverflow (true );
158+ }
159+ }
160+ write (request .getContent (), os , curlBuffer );
161+ if (curlBuilder != null && curlBuffer != null && curlBuffer .position () != 0 ) {
162+ // has content
163+ curlBuilder .setContent (new String (curlBuffer .array (), "UTF-8" ));
164+ }
126165 os .flush ();
127166 os .close ();
128167 }
129168 }
130169
170+ /**
171+ * Needed to pass UrlHttpClientTest.
172+ * @see #applyHeadersAndMethod(HttpRequest, HttpURLConnection, CurlBuilder)
173+ */
174+ HttpURLConnection applyHeadersAndMethod (final HttpRequest request ,
175+ final HttpURLConnection connection ) throws ProtocolException {
176+ return applyHeadersAndMethod (request , connection , null /* curlBuilder */ );
177+ }
178+
131179 HttpURLConnection applyHeadersAndMethod (final HttpRequest request ,
132- final HttpURLConnection connection )
180+ final HttpURLConnection connection , final CurlBuilder curlBuilder )
133181 throws ProtocolException {
134182 // add headers
135183 if (request .getHeaders () != null && !request .getHeaders ().isEmpty ()) {
184+ if (curlBuilder != null ) {
185+ curlBuilder .setHeaders (request .getHeaders ());
186+ }
136187 for (final Map .Entry <String , String > header : request .getHeaders ().entrySet ()) {
137188 final String key = header .getKey ();
138189 // Skip reserved headers for HttpURLConnection
@@ -159,13 +210,19 @@ HttpURLConnection applyHeadersAndMethod(final HttpRequest request,
159210
160211 final String method = request .getMethod ();
161212 connection .setRequestMethod (method );
213+ if (curlBuilder != null ) {
214+ curlBuilder .setMethod (method );
215+ }
162216 return connection ;
163217 }
164218
165- private void write (InputStream is , OutputStream os ) throws IOException {
219+ private void write (InputStream is , OutputStream os , ByteBuffer curlBuffer ) throws IOException {
166220 final byte [] buf = new byte [1024 * 8 ];
167221 int len ;
168222 while ((len = is .read (buf )) != -1 ) {
223+ if (curlBuffer != null ) {
224+ curlBuffer .put (buf , 0 /* offset */ , len );
225+ }
169226 os .write (buf , 0 , len );
170227 }
171228 }
@@ -270,4 +327,121 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) {
270327 }
271328 }
272329 */
330+
331+ /**
332+ * Helper class to build a curl message.
333+ */
334+ private final class CurlBuilder {
335+
336+ /** The {@link URL} of the operation. */
337+ private final URL url ;
338+ /** The method to execute on the given url. */
339+ private String method = null ;
340+ /** A map of headers and their values to be sent with the curl request. */
341+ private HashMap <String ,String > headers = new HashMap <String ,String >();
342+ /** The content to send with the curl request. */
343+ private String content = null ;
344+ /** Whether or not the content cannot be written to the curl command. */
345+ private boolean contentOverflow = false ;
346+
347+ /**
348+ * Builds a new curl command for the given {@link URL}.
349+ * @param url The {@link URL} for the operation, must not be {@code null}.
350+ */
351+ public CurlBuilder (URL url ) {
352+ if (url == null ) {
353+ throw new IllegalArgumentException ("Must have a valid url" );
354+ }
355+ this .url = url ;
356+ }
357+
358+ /**
359+ * Set the method to call for the given curl command. This method will override the previous
360+ * value.
361+ *
362+ * @param method The method to use for the request.
363+ * @return This object for chaining.
364+ */
365+ public CurlBuilder setMethod (String method ) {
366+ this .method = method ;
367+ return this ;
368+ }
369+
370+ /**
371+ * Set the headers used for the given curl command. This method will override the previous
372+ * values.
373+ *
374+ * @param headers The headers to use for the request.
375+ * @return This object for chaining.
376+ */
377+ public CurlBuilder setHeaders (Map <String ,String > headers ) {
378+ this .headers .clear ();
379+ this .headers .putAll (headers );
380+ return this ;
381+ }
382+
383+ /**
384+ * Set the content used for the given curl command. This method will override the previous
385+ * value.
386+ *
387+ * @param content The content to use for the request.
388+ * @return This object for chaining.
389+ */
390+ public CurlBuilder setContent (String content ) {
391+ this .content = content ;
392+ return this ;
393+ }
394+
395+ /**
396+ * Sets whether or not the content is too large for the curl command. Content of length
397+ * greater than {@link Integer#MAX_VALUE} are considered too long. If set, the curl should
398+ * not be logged as it will be invalid.
399+ *
400+ * @param contentOverflow Whether or not the content is too long to print.
401+ * @return This object for chaining.
402+ */
403+ public CurlBuilder setContentOverflow (boolean contentOverflow ) {
404+ this .contentOverflow = contentOverflow ;
405+ return this ;
406+ }
407+
408+ /**
409+ * @return Whether or not this object is valid for printing.
410+ */
411+ public boolean isValid () {
412+ return !contentOverflow ;
413+ }
414+
415+ /**
416+ * Creates a curl command that can be replayed from command line.
417+ *
418+ * @return The curl command.
419+ * @throws IllegalStateException If {@link #isValid()} returns false.
420+ */
421+ public String build () {
422+ if (!isValid ()) {
423+ throw new IllegalStateException ("Invalid state, cannot create curl command" );
424+ }
425+ StringBuilder stringBuilder = new StringBuilder ("curl" );
426+ if (method != null ) {
427+ stringBuilder .append (" -X " )
428+ .append (method );
429+ }
430+ for (Map .Entry <String ,String > entry : headers .entrySet ()) {
431+ stringBuilder .append (" -H \" " )
432+ .append (entry .getKey ())
433+ .append (":" )
434+ .append (entry .getValue ())
435+ .append ("\" " );
436+ }
437+ if (content != null ) {
438+ stringBuilder .append (" -d '" )
439+ .append (content )
440+ .append ("'" );
441+ }
442+ return stringBuilder .append (" " )
443+ .append (url .toString ())
444+ .toString ();
445+ }
446+ }
273447}
0 commit comments