From f0a413c98d98df560d68a83069a37ebfd1df91c4 Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Wed, 2 Feb 2011 14:19:16 +0000 Subject: [PATCH 1/2] add support for charset to request --- src/main/java/org/scribe/model/Request.java | 88 +++++++++++++++++++-- 1 file changed, 81 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/scribe/model/Request.java b/src/main/java/org/scribe/model/Request.java index 1d6b6776d..30543af5c 100644 --- a/src/main/java/org/scribe/model/Request.java +++ b/src/main/java/org/scribe/model/Request.java @@ -2,6 +2,7 @@ import java.io.*; import java.net.*; +import java.nio.charset.Charset; import java.util.*; import java.util.concurrent.TimeUnit; @@ -16,13 +17,14 @@ class Request { private static final String CONTENT_LENGTH = "Content-Length"; + private static final String CONTENT_TYPE = "Content-Type"; - private String url; + private String url; private Verb verb; private Map querystringParams; private Map bodyParams; private Map headers; - private String payload = null; + private byte[] payload = null; private HttpURLConnection connection; /** @@ -83,13 +85,16 @@ void addHeaders(HttpURLConnection conn) { for (String key : headers.keySet()) conn.setRequestProperty(key, headers.get(key)); + if (!headers.containsKey(CONTENT_TYPE)) { + conn.setRequestProperty(CONTENT_TYPE, ""); + } } - void addBody(HttpURLConnection conn, String content) throws IOException + void addBody(HttpURLConnection conn, byte[] content) throws IOException { - conn.setRequestProperty(CONTENT_LENGTH, String.valueOf(content.getBytes().length)); + conn.setRequestProperty(CONTENT_LENGTH, String.valueOf(content.length)); conn.setDoOutput(true); - conn.getOutputStream().write(content.getBytes()); + conn.getOutputStream().write(content); } /** @@ -136,10 +141,79 @@ public void addQuerystringParameter(String key, String value) * @param payload the body of the request */ public void addPayload(String payload) + { + this.payload = payload.getBytes(); + } + + /** + * Add body payload. + * + * This method is used when the HTTP body is not a form-url-encoded string, + * but another thing. Like for example XML. + * + * Note: The contents are not part of the OAuth signature + * + * @param payload the body of the request + */ + public void addPayload(byte[] payload) { this.payload = payload; } + /** + * Add body payload and set the content type header. + * + * This method is used when the HTTP body is not a form-url-encoded string, + * but another thing, like for example {@code text/xml}. + * + * @param payload The payload. + * @param contentType The content type of the payload (if the content type starts with {@code text/} and does not + * include the charset, it will be added automatically. + * @param charset The charset to encode the payload as. + */ + public void addPayload(String payload, String contentType, Charset charset) { + payload.getClass(); // throw NPE if null + contentType.getClass(); // throw NPE if null + charset.getClass(); // throw NPE if null + contentType = contentType.trim(); + if (contentType.startsWith("text/")) { + if (!contentType.contains("charset=")) { + contentType = contentType + "; charset=" + charset.name(); + } + } + addPayload(payload.getBytes(charset),contentType); + } + + /** + * Add body payload and set the content type header. + * + * This method is used when the HTTP body is not a form-url-encoded string, + * but another thing, like for example {@code text/xml}. + * + * @param payload The payload (which will be encoded using the system default charset). + * @param contentType The content type of the payload (if the content type starts with {@code text/} and does not + * include the charset, it will be added automatically. + */ + public void addPayload(String payload, String contentType) { + addPayload(payload, contentType, Charset.defaultCharset()); + } + + /** + * Add body payload and set the content type header. + * + * This method is used when the HTTP body is not a form-url-encoded string, + * but another thing, like for example {@code text/xml}. + * + * @param payload The payload + * @param contentType The content type of the payload. + */ + public void addPayload(byte[] payload, String contentType) { + payload.getClass(); // throw NPE if null + contentType.getClass(); // throw NPE if null + addHeader(CONTENT_TYPE, contentType); + addPayload(payload); + } + /** * Get a {@link Map} of the query string parameters. * @@ -203,9 +277,9 @@ public String getSanitizedUrl() * * @return form encoded string */ - public String getBodyContents() + public byte[] getBodyContents() { - return (payload != null) ? payload : URLUtils.formURLEncodeMap(bodyParams); + return (payload != null) ? payload : URLUtils.formURLEncodeMap(bodyParams).getBytes(); } /** From d741f35fe9354919ea5179127e795e71541a5b9a Mon Sep 17 00:00:00 2001 From: Stephen Connolly Date: Wed, 2 Feb 2011 15:27:54 +0000 Subject: [PATCH 2/2] fix broken tests and tidy-up response to allow extracting the raw byte array of data --- src/main/java/org/scribe/model/Request.java | 2 +- src/main/java/org/scribe/model/Response.java | 20 ++++++-- .../java/org/scribe/utils/StreamUtils.java | 47 ++++++++++++------- .../java/org/scribe/model/RequestTest.java | 39 +++++++++++++-- .../org/scribe/utils/StreamUtilsTest.java | 2 +- 5 files changed, 84 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/scribe/model/Request.java b/src/main/java/org/scribe/model/Request.java index 30543af5c..355295eae 100644 --- a/src/main/java/org/scribe/model/Request.java +++ b/src/main/java/org/scribe/model/Request.java @@ -86,7 +86,7 @@ void addHeaders(HttpURLConnection conn) for (String key : headers.keySet()) conn.setRequestProperty(key, headers.get(key)); if (!headers.containsKey(CONTENT_TYPE)) { - conn.setRequestProperty(CONTENT_TYPE, ""); + conn.setRequestProperty(CONTENT_TYPE, "application/x-www-form-urlencoded"); } } diff --git a/src/main/java/org/scribe/model/Response.java b/src/main/java/org/scribe/model/Response.java index 6164e1495..3a42093a2 100644 --- a/src/main/java/org/scribe/model/Response.java +++ b/src/main/java/org/scribe/model/Response.java @@ -13,10 +13,11 @@ */ public class Response { - private static final String EMPTY = ""; + private static final byte[] EMPTY = new byte[0]; private int code; - private String body; + private byte[] body; + private String bodyUTF8; private InputStream stream; private Map headers; @@ -35,7 +36,7 @@ public class Response } } - private String parseBodyContents() + private byte[] parseBodyContents() { body = StreamUtils.getStreamContents(getStream()); return body; @@ -62,6 +63,19 @@ private boolean wasSuccessful() * @return response body */ public String getBody() + { + if (bodyUTF8 == null) { + bodyUTF8 = new String(getBodyAsByteArray()); + } + return bodyUTF8; + } + + /** + * Obtains the HTTP Response body + * + * @return response body + */ + public byte[] getBodyAsByteArray() { return body != null ? body : parseBodyContents(); } diff --git a/src/main/java/org/scribe/utils/StreamUtils.java b/src/main/java/org/scribe/utils/StreamUtils.java index 0799b9c1e..c1165d4cf 100644 --- a/src/main/java/org/scribe/utils/StreamUtils.java +++ b/src/main/java/org/scribe/utils/StreamUtils.java @@ -12,31 +12,44 @@ public class StreamUtils /** * Returns the stream contents as an UTF-8 encoded string * - * @param is input stream - * @return string contents + * @param in input stream + * @return stream contents */ - public static String getStreamContents(InputStream is) + public static byte[] getStreamContents(InputStream in) { - Preconditions.checkNotNull(is, "Cannot get String from a null object"); + Preconditions.checkNotNull(in, "Cannot get String from a null object"); + ByteArrayOutputStream out = null; try { - final char[] buffer = new char[0x10000]; - StringBuilder out = new StringBuilder(); - Reader in = new InputStreamReader(is, "UTF-8"); + final byte[] buffer = new byte[0x10000]; + out = new ByteArrayOutputStream(); int read; - do - { - read = in.read(buffer, 0, buffer.length); - if (read > 0) - { - out.append(buffer, 0, read); - } - } while (read >= 0); - in.close(); - return out.toString(); + while (0 < (read = in.read(buffer, 0, buffer.length))) + out.write(buffer, 0, read); + return out.toByteArray(); } catch (IOException ioe) { throw new IllegalStateException("Error while reading response body", ioe); + } finally { + close(in); + close(out); + } + } + + /** + * Closes any {@link Closeable} while quashing any exceptions. + * @param closeable the {@link Closeable} to close (may be {@code null}). + */ + public static void close(Closeable closeable) { + if (closeable != null) + { + try + { + closeable.close(); + } catch (IOException e) + { + // ignore + } } } } diff --git a/src/test/java/org/scribe/model/RequestTest.java b/src/test/java/org/scribe/model/RequestTest.java index 750f004ba..36971f6ec 100644 --- a/src/test/java/org/scribe/model/RequestTest.java +++ b/src/test/java/org/scribe/model/RequestTest.java @@ -1,9 +1,16 @@ package org.scribe.model; +import static org.hamcrest.CoreMatchers.is; import static org.junit.Assert.*; import org.junit.*; +import java.nio.charset.Charset; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + public class RequestTest { @@ -42,8 +49,21 @@ public void shouldAddRequestHeaders() getRequest.addHeader("Header", "1"); getRequest.addHeader("Header2", "2"); getRequest.send(); - assertEquals(2, getRequest.getHeaders().size()); - assertEquals(2, connection.getHeaders().size()); + assertThat(getRequest.getHeaders(), is(map(entry("Header", "1"), entry("Header2", "2")))); + assertThat(connection.getHeaders(), is(map(entry("Header", "1"), entry("Header2", "2"), + entry("Content-Type", "application/x-www-form-urlencoded")))); + } + + public static Map.Entry entry(K k,V v) { + return new AbstractMap.SimpleEntry(k,v); + } + + public static Map map(Map.Entry... entries) { + Map result = new LinkedHashMap(); + for (Map.Entry entry: entries) { + result.put(entry.getKey(), entry.getValue()); + } + return result; } @Test @@ -52,7 +72,7 @@ public void shouldSetBodyParamsAndHeaders() postRequest.addBodyParameter("param", "value"); postRequest.addBodyParameter("param two", "value with spaces"); postRequest.send(); - assertEquals("param%20two=value%20with%20spaces¶m=value", postRequest.getBodyContents()); + assertEquals("param%20two=value%20with%20spaces¶m=value", new String(postRequest.getBodyContents())); assertTrue(connection.getHeaders().containsKey("Content-Length")); } @@ -61,8 +81,19 @@ public void shouldSetPayloadAndHeaders() { postRequest.addPayload("PAYLOAD"); postRequest.send(); - assertEquals("PAYLOAD", postRequest.getBodyContents()); + assertEquals("PAYLOAD", new String(postRequest.getBodyContents())); + assertTrue(connection.getHeaders().containsKey("Content-Length")); + } + + @Test + public void shouldSetPayloadContentTypeAndHeaders() + { + postRequest.addPayload("PAYLOAD", "text/plain", Charset.forName("UTF-8")); + postRequest.send(); + assertEquals("PAYLOAD", new String(postRequest.getBodyContents())); assertTrue(connection.getHeaders().containsKey("Content-Length")); + assertTrue(connection.getHeaders().containsKey("Content-Type")); + assertThat(connection.getHeaders().get("Content-Type").toLowerCase(), is("text/plain; charset=utf-8")); } @Test diff --git a/src/test/java/org/scribe/utils/StreamUtilsTest.java b/src/test/java/org/scribe/utils/StreamUtilsTest.java index 976914d05..82f715454 100644 --- a/src/test/java/org/scribe/utils/StreamUtilsTest.java +++ b/src/test/java/org/scribe/utils/StreamUtilsTest.java @@ -15,7 +15,7 @@ public void shouldCorrectlyDecodeAStream() { String value = "expected"; InputStream is = new ByteArrayInputStream(value.getBytes()); - String decoded = StreamUtils.getStreamContents(is); + String decoded = new String(StreamUtils.getStreamContents(is)); assertEquals("expected", decoded); }