Skip to content

Commit 699f946

Browse files
Merge pull request #200 from yperess/peressAddHttpClientLogging
Add the ability to enable logging.
2 parents 6705fd6 + 8a50094 commit 699f946

File tree

3 files changed

+346
-14
lines changed

3 files changed

+346
-14
lines changed

aws-android-sdk-core/src/main/java/com/amazonaws/ClientConfiguration.java

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,11 @@ public class ClientConfiguration {
174174
*/
175175
private TrustManager trustManager = null;
176176

177+
/**
178+
* Enable/disable logging.
179+
*/
180+
private boolean logging = false;
181+
177182
public ClientConfiguration() {
178183
}
179184

@@ -198,6 +203,7 @@ public ClientConfiguration(ClientConfiguration other) {
198203
this.socketSendBufferSizeHint = other.socketSendBufferSizeHint;
199204
this.signerOverride = other.signerOverride;
200205
this.trustManager = other.trustManager;
206+
this.logging = other.logging;
201207
}
202208

203209
/**
@@ -1029,4 +1035,37 @@ public ClientConfiguration withTrustManager(TrustManager trustManager) {
10291035
return this;
10301036
}
10311037

1038+
/**
1039+
* Tells whether or not the client should be logging anything. Currently, logging will print
1040+
* curl commands to replay http requests.
1041+
*
1042+
* @return Whether or not the client will be logging.
1043+
*/
1044+
public boolean isLogging() {
1045+
return logging;
1046+
}
1047+
1048+
/**
1049+
* Sets whether or not the client should be logging any information. This should be used for
1050+
* debug builds only. Defaults to false.
1051+
*
1052+
* @param logging Whether or not the client should be logging operations.
1053+
*/
1054+
public void setLogging(boolean logging) {
1055+
this.logging = logging;
1056+
}
1057+
1058+
/**
1059+
* Sets whether or not the client should be logging any information. This should be used for
1060+
* debug builds only, and returns the updated ClientConfiguration object so that additional
1061+
* calls may be chained together. Defaults to false.
1062+
*
1063+
* @param logging Whether or not the client should be logging operations.
1064+
* @return The updated ClientConfiguration object.
1065+
*/
1066+
public ClientConfiguration withLogging(boolean logging) {
1067+
this.logging = logging;
1068+
return this;
1069+
}
1070+
10321071
}

aws-android-sdk-core/src/main/java/com/amazonaws/http/UrlHttpClient.java

Lines changed: 189 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
package com.amazonaws.http;
1717

18+
import android.util.Log;
19+
1820
import com.amazonaws.ClientConfiguration;
1921

2022
import java.io.IOException;
@@ -23,7 +25,9 @@
2325
import java.net.HttpURLConnection;
2426
import java.net.ProtocolException;
2527
import java.net.URL;
28+
import java.nio.ByteBuffer;
2629
import java.security.GeneralSecurityException;
30+
import java.util.HashMap;
2731
import java.util.List;
2832
import java.util.Map;
2933

@@ -43,6 +47,8 @@
4347
*/
4448
public 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+
printToLog(Log.VERBOSE, curlBuilder.build());
72+
} else {
73+
printToLog(Log.VERBOSE, "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,27 @@ 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+
protected void printToLog(int priority, String message) {
220+
Log.println(priority, TAG, message);
221+
}
222+
223+
protected HttpURLConnection getUrlConnection(URL url) throws IOException {
224+
return (HttpURLConnection) url.openConnection();
225+
}
226+
227+
private void write(InputStream is, OutputStream os, ByteBuffer curlBuffer) throws IOException {
166228
final byte[] buf = new byte[1024 * 8];
167229
int len;
168230
while ((len = is.read(buf)) != -1) {
231+
if (curlBuffer != null) {
232+
curlBuffer.put(buf, 0 /* offset */, len);
233+
}
169234
os.write(buf, 0, len);
170235
}
171236
}
@@ -270,4 +335,121 @@ public void checkServerTrusted(X509Certificate[] certs, String authType) {
270335
}
271336
}
272337
*/
338+
339+
/**
340+
* Helper class to build a curl message.
341+
*/
342+
private final class CurlBuilder {
343+
344+
/** The {@link URL} of the operation. */
345+
private final URL url;
346+
/** The method to execute on the given url. */
347+
private String method = null;
348+
/** A map of headers and their values to be sent with the curl request. */
349+
private HashMap<String,String> headers = new HashMap<String,String>();
350+
/** The content to send with the curl request. */
351+
private String content = null;
352+
/** Whether or not the content cannot be written to the curl command. */
353+
private boolean contentOverflow = false;
354+
355+
/**
356+
* Builds a new curl command for the given {@link URL}.
357+
* @param url The {@link URL} for the operation, must not be {@code null}.
358+
*/
359+
public CurlBuilder(URL url) {
360+
if (url == null) {
361+
throw new IllegalArgumentException("Must have a valid url");
362+
}
363+
this.url = url;
364+
}
365+
366+
/**
367+
* Set the method to call for the given curl command. This method will override the previous
368+
* value.
369+
*
370+
* @param method The method to use for the request.
371+
* @return This object for chaining.
372+
*/
373+
public CurlBuilder setMethod(String method) {
374+
this.method = method;
375+
return this;
376+
}
377+
378+
/**
379+
* Set the headers used for the given curl command. This method will override the previous
380+
* values.
381+
*
382+
* @param headers The headers to use for the request.
383+
* @return This object for chaining.
384+
*/
385+
public CurlBuilder setHeaders(Map<String,String> headers) {
386+
this.headers.clear();
387+
this.headers.putAll(headers);
388+
return this;
389+
}
390+
391+
/**
392+
* Set the content used for the given curl command. This method will override the previous
393+
* value.
394+
*
395+
* @param content The content to use for the request.
396+
* @return This object for chaining.
397+
*/
398+
public CurlBuilder setContent(String content) {
399+
this.content = content;
400+
return this;
401+
}
402+
403+
/**
404+
* Sets whether or not the content is too large for the curl command. Content of length
405+
* greater than {@link Integer#MAX_VALUE} are considered too long. If set, the curl should
406+
* not be logged as it will be invalid.
407+
*
408+
* @param contentOverflow Whether or not the content is too long to print.
409+
* @return This object for chaining.
410+
*/
411+
public CurlBuilder setContentOverflow(boolean contentOverflow) {
412+
this.contentOverflow = contentOverflow;
413+
return this;
414+
}
415+
416+
/**
417+
* @return Whether or not this object is valid for printing.
418+
*/
419+
public boolean isValid() {
420+
return !contentOverflow;
421+
}
422+
423+
/**
424+
* Creates a curl command that can be replayed from command line.
425+
*
426+
* @return The curl command.
427+
* @throws IllegalStateException If {@link #isValid()} returns false.
428+
*/
429+
public String build() {
430+
if (!isValid()) {
431+
throw new IllegalStateException("Invalid state, cannot create curl command");
432+
}
433+
StringBuilder stringBuilder = new StringBuilder("curl");
434+
if (method != null) {
435+
stringBuilder.append(" -X ")
436+
.append(method);
437+
}
438+
for (Map.Entry<String,String> entry : headers.entrySet()) {
439+
stringBuilder.append(" -H \"")
440+
.append(entry.getKey())
441+
.append(":")
442+
.append(entry.getValue())
443+
.append("\"");
444+
}
445+
if (content != null) {
446+
stringBuilder.append(" -d '")
447+
.append(content)
448+
.append("'");
449+
}
450+
return stringBuilder.append(" ")
451+
.append(url.toString())
452+
.toString();
453+
}
454+
}
273455
}

0 commit comments

Comments
 (0)