From 9dc1bfbbe7bdba0ddf483f6fd7ee4b0d8f97534a Mon Sep 17 00:00:00 2001 From: Oliver Woodman Date: Thu, 23 Jul 2015 13:09:52 +0100 Subject: [PATCH] Support POST requests with DefaultHttpDataSource/DataSpec --- .../android/exoplayer/upstream/DataSpec.java | 28 ++++++++++++- .../upstream/DefaultHttpDataSource.java | 39 +++++++++++++------ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java b/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java index 072ba82294..618605c2c8 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DataSpec.java @@ -20,6 +20,8 @@ import com.google.android.exoplayer.util.Assertions; import android.net.Uri; +import java.util.Arrays; + /** * Defines a region of media data. */ @@ -42,6 +44,10 @@ public final class DataSpec { * Identifies the source from which data should be read. */ public final Uri uri; + /** + * Body for a POST request, null otherwise. + */ + public final byte[] postBody; /** * The absolute position of the data in the full stream. */ @@ -124,10 +130,28 @@ public final class DataSpec { */ public DataSpec(Uri uri, long absoluteStreamPosition, long position, long length, String key, int flags) { + this(uri, null, absoluteStreamPosition, position, length, key, flags); + } + + /** + * Construct a {@link DataSpec} where {@link #position} may differ from + * {@link #absoluteStreamPosition}. + * + * @param uri {@link #uri}. + * @param postBody {@link #postBody}. + * @param absoluteStreamPosition {@link #absoluteStreamPosition}. + * @param position {@link #position}. + * @param length {@link #length}. + * @param key {@link #key}. + * @param flags {@link #flags}. + */ + public DataSpec(Uri uri, byte[] postBody, long absoluteStreamPosition, long position, long length, + String key, int flags) { Assertions.checkArgument(absoluteStreamPosition >= 0); Assertions.checkArgument(position >= 0); Assertions.checkArgument(length > 0 || length == C.LENGTH_UNBOUNDED); this.uri = uri; + this.postBody = postBody; this.absoluteStreamPosition = absoluteStreamPosition; this.position = position; this.length = length; @@ -137,8 +161,8 @@ public final class DataSpec { @Override public String toString() { - return "DataSpec[" + uri + ", " + absoluteStreamPosition + ", " + position + ", " + length - + ", " + key + ", " + flags + "]"; + return "DataSpec[" + uri + ", " + Arrays.toString(postBody) + ", " + absoluteStreamPosition + + ", " + position + ", " + length + ", " + key + ", " + flags + "]"; } } diff --git a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java index 3f9bf27366..2c70748e28 100644 --- a/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java +++ b/library/src/main/java/com/google/android/exoplayer/upstream/DefaultHttpDataSource.java @@ -27,6 +27,7 @@ import java.io.EOFException; import java.io.IOException; import java.io.InputStream; import java.io.InterruptedIOException; +import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.NoRouteToHostException; import java.net.ProtocolException; @@ -329,6 +330,7 @@ public class DefaultHttpDataSource implements HttpDataSource { */ private HttpURLConnection makeConnection(DataSpec dataSpec) throws IOException { URL url = new URL(dataSpec.uri.toString()); + byte[] postBody = dataSpec.postBody; long position = dataSpec.position; long length = dataSpec.length; boolean allowGzip = (dataSpec.flags & DataSpec.FLAG_ALLOW_GZIP) != 0; @@ -336,24 +338,27 @@ public class DefaultHttpDataSource implements HttpDataSource { if (!allowCrossProtocolRedirects) { // HttpURLConnection disallows cross-protocol redirects, but otherwise performs redirection // automatically. This is the behavior we want, so use it. - HttpURLConnection connection = configureConnection(url, position, length, allowGzip); - connection.connect(); + HttpURLConnection connection = makeConnection( + url, postBody, position, length, allowGzip, true /* followRedirects */); return connection; } // We need to handle redirects ourselves to allow cross-protocol redirects. int redirectCount = 0; while (redirectCount++ <= MAX_REDIRECTS) { - HttpURLConnection connection = configureConnection(url, position, length, allowGzip); - connection.setInstanceFollowRedirects(false); - connection.connect(); + HttpURLConnection connection = makeConnection( + url, postBody, position, length, allowGzip, false /* followRedirects */); int responseCode = connection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_MULT_CHOICE || responseCode == HttpURLConnection.HTTP_MOVED_PERM || responseCode == HttpURLConnection.HTTP_MOVED_TEMP || responseCode == HttpURLConnection.HTTP_SEE_OTHER - || responseCode == 307 /* HTTP_TEMP_REDIRECT */ - || responseCode == 308 /* HTTP_PERM_REDIRECT */) { + || (postBody == null + && (responseCode == 307 /* HTTP_TEMP_REDIRECT */ + || responseCode == 308 /* HTTP_PERM_REDIRECT */))) { + // For 300, 301, 302, and 303 POST requests follow the redirect and are transformed into + // GET requests. For 307 and 308 POST requests are not redirected. + postBody = null; String location = connection.getHeaderField("Location"); connection.disconnect(); url = handleRedirect(url, location); @@ -367,19 +372,20 @@ public class DefaultHttpDataSource implements HttpDataSource { } /** - * Configures a connection, but does not open it. + * Configures a connection and opens it. * * @param url The url to connect to. + * @param postBody The body data for a POST request. * @param position The byte offset of the requested data. * @param length The length of the requested data, or {@link C#LENGTH_UNBOUNDED}. * @param allowGzip Whether to allow the use of gzip. + * @param followRedirects Whether to follow redirects. */ - private HttpURLConnection configureConnection(URL url, long position, long length, - boolean allowGzip) throws IOException { + private HttpURLConnection makeConnection(URL url, byte[] postBody, long position, + long length, boolean allowGzip, boolean followRedirects) throws IOException { HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setConnectTimeout(connectTimeoutMillis); connection.setReadTimeout(readTimeoutMillis); - connection.setDoOutput(false); synchronized (requestProperties) { for (Map.Entry property : requestProperties.entrySet()) { connection.setRequestProperty(property.getKey(), property.getValue()); @@ -396,6 +402,17 @@ public class DefaultHttpDataSource implements HttpDataSource { if (!allowGzip) { connection.setRequestProperty("Accept-Encoding", "identity"); } + connection.setInstanceFollowRedirects(followRedirects); + connection.setDoOutput(postBody != null); + if (postBody != null) { + connection.setFixedLengthStreamingMode(postBody.length); + connection.connect(); + OutputStream os = connection.getOutputStream(); + os.write(postBody); + os.close(); + } else { + connection.connect(); + } return connection; }