HttpUtil.java
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.datasource;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.Math.max;
import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import com.google.common.net.HttpHeaders;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Utility methods for HTTP. */
@UnstableApi
public final class HttpUtil {
private static final String TAG = "HttpUtil";
private static final Pattern CONTENT_RANGE_WITH_START_AND_END =
Pattern.compile("bytes (\d+)-(\d+)/(?:\d+|\*)");
private static final Pattern CONTENT_RANGE_WITH_SIZE =
Pattern.compile("bytes (?:(?:\d+-\d+)|\*)/(\d+)");
/** Class only contains static methods. */
private HttpUtil() {}
/**
* Builds a {@link HttpHeaders#RANGE Range header} for the given position and length.
*
* @param position The request position.
* @param length The request length, or {@link C#LENGTH_UNSET} if the request is unbounded.
* @return The corresponding range header, or {@code null} if a header is unnecessary because the
* whole resource is being requested.
*/
@Nullable
public static String buildRangeRequestHeader(long position, long length) {
if (position == 0 && length == C.LENGTH_UNSET) {
return null;
}
StringBuilder rangeValue = new StringBuilder();
rangeValue.append("bytes=");
rangeValue.append(position);
rangeValue.append("-");
if (length != C.LENGTH_UNSET) {
rangeValue.append(position + length - 1);
}
return rangeValue.toString();
}
/**
* Attempts to parse the document size from a {@link HttpHeaders#CONTENT_RANGE Content-Range
* header}.
*
* @param contentRangeHeader The {@link HttpHeaders#CONTENT_RANGE Content-Range header}, or {@code
* null} if not set.
* @return The document size, or {@link C#LENGTH_UNSET} if it could not be determined.
*/
public static long getDocumentSize(@Nullable String contentRangeHeader) {
if (TextUtils.isEmpty(contentRangeHeader)) {
return C.LENGTH_UNSET;
}
Matcher matcher = CONTENT_RANGE_WITH_SIZE.matcher(contentRangeHeader);
return matcher.matches() ? Long.parseLong(checkNotNull(matcher.group(1))) : C.LENGTH_UNSET;
}
/**
* Attempts to parse the length of a response body from the corresponding response headers.
*
* @param contentLengthHeader The {@link HttpHeaders#CONTENT_LENGTH Content-Length header}, or
* {@code null} if not set.
* @param contentRangeHeader The {@link HttpHeaders#CONTENT_RANGE Content-Range header}, or {@code
* null} if not set.
* @return The length of the response body, or {@link C#LENGTH_UNSET} if it could not be
* determined.
*/
public static long getContentLength(
@Nullable String contentLengthHeader, @Nullable String contentRangeHeader) {
long contentLength = C.LENGTH_UNSET;
if (!TextUtils.isEmpty(contentLengthHeader)) {
try {
contentLength = Long.parseLong(contentLengthHeader);
} catch (NumberFormatException e) {
Log.e(TAG, "Unexpected Content-Length [" + contentLengthHeader + "]");
}
}
if (!TextUtils.isEmpty(contentRangeHeader)) {
Matcher matcher = CONTENT_RANGE_WITH_START_AND_END.matcher(contentRangeHeader);
if (matcher.matches()) {
try {
long contentLengthFromRange =
Long.parseLong(checkNotNull(matcher.group(2)))
- Long.parseLong(checkNotNull(matcher.group(1)))
+ 1;
if (contentLength < 0) {
// Some proxy servers strip the Content-Length header. Fall back to the length
// calculated here in this case.
contentLength = contentLengthFromRange;
} else if (contentLength != contentLengthFromRange) {
// If there is a discrepancy between the Content-Length and Content-Range headers,
// assume the one with the larger value is correct. We have seen cases where carrier
// change one of them to reduce the size of a request, but it is unlikely anybody would
// increase it.
Log.w(
TAG,
"Inconsistent headers [" + contentLengthHeader + "] [" + contentRangeHeader + "]");
contentLength = max(contentLength, contentLengthFromRange);
}
} catch (NumberFormatException e) {
Log.e(TAG, "Unexpected Content-Range [" + contentRangeHeader + "]");
}
}
}
return contentLength;
}
}