/**
* Copyright Microsoft Corporation
*
* 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 com.microsoft.azure.storage.core;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.InvalidKeyException;
import java.util.HashMap;
import java.util.Map.Entry;
import com.microsoft.azure.storage.Constants;
import com.microsoft.azure.storage.Credentials;
import com.microsoft.azure.storage.OperationContext;
import com.microsoft.azure.storage.RequestOptions;
import com.microsoft.azure.storage.StorageException;
import com.microsoft.azure.storage.StorageKey;
/**
* RESERVED FOR INTERNAL USE. The Base Request class for the protocol layer.
*/
public final class BaseRequest {
private static final String METADATA = "metadata";
private static final String SERVICE = "service";
private static final String STATS = "stats";
private static final String TIMEOUT = "timeout";
/**
* Stores the user agent to send over the wire to identify the client.
*/
private static String userAgent;
/**
* Adds the lease id.
*
* @param request
* a HttpURLConnection for the operation.
* @param leaseId
* the lease id to add to the HttpURLConnection.
*/
public static void addLeaseId(final HttpURLConnection request, final String leaseId) {
if (leaseId != null) {
BaseRequest.addOptionalHeader(request, Constants.HeaderConstants.LEASE_ID_HEADER, leaseId);
}
}
/**
* Adds the metadata.
*
* @param request
* The request.
* @param metadata
* The metadata.
*/
public static void addMetadata(final HttpURLConnection request, final HashMap<String, String> metadata,
final OperationContext opContext) {
if (metadata != null) {
for (final Entry<String, String> entry : metadata.entrySet()) {
addMetadata(request, entry.getKey(), entry.getValue(), opContext);
}
}
}
/**
* Adds the metadata.
*
* @param opContext
* an object used to track the execution of the operation
* @param request
* The request.
* @param name
* The metadata name.
* @param value
* The metadata value.
*/
private static void addMetadata(final HttpURLConnection request, final String name, final String value,
final OperationContext opContext) {
if (Utility.isNullOrEmptyOrWhitespace(name)) {
throw new IllegalArgumentException(SR.METADATA_KEY_INVALID);
}
else if (Utility.isNullOrEmptyOrWhitespace(value)) {
throw new IllegalArgumentException(SR.METADATA_VALUE_INVALID);
}
request.setRequestProperty(Constants.HeaderConstants.PREFIX_FOR_STORAGE_METADATA + name, value);
}
/**
* Adds the optional header.
*
* @param request
* a HttpURLConnection for the operation.
* @param name
* the metadata name.
* @param value
* the metadata value.
*/
public static void addOptionalHeader(final HttpURLConnection request, final String name, final String value) {
if (value != null && !value.equals(Constants.EMPTY_STRING)) {
request.setRequestProperty(name, value);
}
}
/**
* Creates the specified resource. Note request is set to setFixedLengthStreamingMode(0); Sign with 0 length.
*
* @param uri
* the request Uri.
* @param options
* TODO
* @param builder
* the UriQueryBuilder for the request
* @param opContext
* an object used to track the execution of the operation
*
* @return a HttpURLConnection to perform the operation.
* @throws IOException
* if there is an error opening the connection
* @throws URISyntaxException
* if there is an improperly formated URI
* @throws StorageException
* @throws IllegalArgumentException
*/
public static HttpURLConnection create(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setFixedLengthStreamingMode(0);
retConnection.setDoOutput(true);
retConnection.setRequestMethod(Constants.HTTP_PUT);
return retConnection;
}
/**
* Creates the web request.
*
* @param uri
* the request Uri.
* @param options
* A {@link RequestOptions} object that specifies execution options such as retry policy and timeout
* settings for the operation. This parameter is unused.
* @param builder
* the UriQueryBuilder for the request
* @param opContext
* an object used to track the execution of the operation
* @return a HttpURLConnection to perform the operation.
* @throws IOException
* if there is an error opening the connection
* @throws URISyntaxException
* if there is an improperly formated URI
* @throws StorageException
*/
public static HttpURLConnection createURLConnection(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
final URL resourceUrl = builder.addToURI(uri).toURL();
final HttpURLConnection retConnection = (HttpURLConnection) resourceUrl.openConnection();
if (options.getTimeoutIntervalInMs() != null && options.getTimeoutIntervalInMs() != 0) {
builder.add(TIMEOUT, String.valueOf(options.getTimeoutIntervalInMs() / 1000));
}
// Note: ReadTimeout must be explicitly set
retConnection.setReadTimeout(Utility.getRemainingTimeout(options.getOperationExpiryTimeInMs()));
// Note : by default sends Accept behavior as text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2
retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT, Constants.HeaderConstants.XML_TYPE);
retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_CHARSET, Constants.UTF8_CHARSET);
// Note: by default sends Accept-Encoding as gzip
retConnection.setRequestProperty(Constants.HeaderConstants.ACCEPT_ENCODING, Constants.EMPTY_STRING);
// Note : by default sends Content-type behavior as application/x-www-form-urlencoded for posts.
retConnection.setRequestProperty(Constants.HeaderConstants.CONTENT_TYPE, Constants.EMPTY_STRING);
retConnection.setRequestProperty(Constants.HeaderConstants.STORAGE_VERSION_HEADER,
Constants.HeaderConstants.TARGET_STORAGE_VERSION);
retConnection.setRequestProperty(Constants.HeaderConstants.USER_AGENT, getUserAgent());
retConnection.setRequestProperty(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER,
opContext.getClientRequestID());
return retConnection;
}
/**
* Deletes the specified resource. Sign with no length specified.
*
* @param uri
* the request Uri.
* @param timeout
* the timeout for the request
* @param builder
* the UriQueryBuilder for the request
* @param opContext
* an object used to track the execution of the operation
* @return a HttpURLConnection to perform the operation.
* @throws IOException
* if there is an error opening the connection
* @throws URISyntaxException
* if there is an improperly formated URI
* @throws StorageException
*/
public static HttpURLConnection delete(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod(Constants.HTTP_DELETE);
return retConnection;
}
/**
* Gets the properties. Sign with no length specified.
*
* @param uri
* The Uri to query.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws StorageException
* @throws URISyntaxException
* @throws IOException
* */
public static HttpURLConnection getProperties(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod(Constants.HTTP_HEAD);
return retConnection;
}
/**
* Creates a HttpURLConnection used to retrieve the Analytics service properties from the storage service.
*
* @param uri
* The service endpoint.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws IOException
* @throws URISyntaxException
* @throws StorageException
*/
public static HttpURLConnection getServiceProperties(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);
builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod(Constants.HTTP_GET);
return retConnection;
}
/**
* Creates a web request to get the stats of the service.
*
* @param uri
* The service endpoint.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws IOException
* @throws URISyntaxException
* @throws StorageException
*/
public static HttpURLConnection getServiceStats(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, STATS);
builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setRequestMethod("GET");
return retConnection;
}
/**
* Gets the user agent to send over the wire to identify the client.
*
* @return the user agent to send over the wire to identify the client.
*/
public static String getUserAgent() {
if (userAgent == null) {
String userAgentComment = String.format(Utility.LOCALE_US, "(Android %s; %s; %s)",
android.os.Build.VERSION.RELEASE, android.os.Build.BRAND, android.os.Build.MODEL);
userAgent = String.format("%s/%s %s", Constants.HeaderConstants.USER_AGENT_PREFIX,
Constants.HeaderConstants.USER_AGENT_VERSION, userAgentComment);
}
return userAgent;
}
/**
* Sets the metadata. Sign with 0 length.
*
* @param uri
* The blob Uri.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws StorageException
* @throws URISyntaxException
* @throws IOException
* */
public static HttpURLConnection setMetadata(final URI uri, final RequestOptions options, UriQueryBuilder builder,
final OperationContext opContext) throws IOException, URISyntaxException, StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, METADATA);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setFixedLengthStreamingMode(0);
retConnection.setDoOutput(true);
retConnection.setRequestMethod(Constants.HTTP_PUT);
return retConnection;
}
/**
* Creates a HttpURLConnection used to set the Analytics service properties on the storage service.
*
* @param uri
* The service endpoint.
* @param timeout
* The timeout.
* @param builder
* The builder.
* @param opContext
* an object used to track the execution of the operation
* @return a web request for performing the operation.
* @throws IOException
* @throws URISyntaxException
* @throws StorageException
*/
public static HttpURLConnection setServiceProperties(final URI uri, final RequestOptions options,
UriQueryBuilder builder, final OperationContext opContext) throws IOException, URISyntaxException,
StorageException {
if (builder == null) {
builder = new UriQueryBuilder();
}
builder.add(Constants.QueryConstants.COMPONENT, Constants.QueryConstants.PROPERTIES);
builder.add(Constants.QueryConstants.RESOURCETYPE, SERVICE);
final HttpURLConnection retConnection = createURLConnection(uri, options, builder, opContext);
retConnection.setDoOutput(true);
retConnection.setRequestMethod(Constants.HTTP_PUT);
return retConnection;
}
/**
* Signs the request appropriately to make it an authenticated request for Blob and Queue.
*
* @param request
* a HttpURLConnection for the operation.
* @param credentials
* the credentials to use for signing.
* @param contentLength
* the length of the content written to the output stream, -1 if unknown.
* @param opContext
* an object used to track the execution of the operation
* @throws InvalidKeyException
* if the credentials key is invalid.
* @throws StorageException
*/
public static void signRequestForBlobAndQueue(final HttpURLConnection request, final Credentials credentials,
final Long contentLength, final OperationContext opContext) throws InvalidKeyException, StorageException {
request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime());
final Canonicalizer canonicalizer = CanonicalizerFactory.getBlobQueueFullCanonicalizer(request);
final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength);
final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign);
// V2 add logging
// System.out.println(String.format("Signing %s\r\n%s\r\n", stringToSign, computedBase64Signature));
request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION,
String.format("%s %s:%s", "SharedKey", credentials.getAccountName(), computedBase64Signature));
}
/**
*
* Signs the request appropriately to make it an authenticated request for Blob and Queue.
*
* @param request
* a HttpURLConnection for the operation.
* @param credentials
* the credentials to use for signing.
* @param contentLength
* the length of the content written to the output stream, -1 if unknown.
* @param opContext
* an object used to track the execution of the operation
* @throws InvalidKeyException
* if the credentials key is invalid.
* @throws StorageException
*/
public static void signRequestForBlobAndQueueSharedKeyLite(final HttpURLConnection request,
final Credentials credentials, final Long contentLength, final OperationContext opContext)
throws InvalidKeyException, StorageException {
request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime());
final Canonicalizer canonicalizer = CanonicalizerFactory.getBlobQueueLiteCanonicalizer(request);
final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength);
final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign);
// VNext add logging
// System.out.println(String.format("Signing %s\r\n%s\r\n",
// stringToSign, computedBase64Signature));
request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION,
String.format("%s %s:%s", "SharedKeyLite", credentials.getAccountName(), computedBase64Signature));
}
/**
*
* Signs the request appropriately to make it an authenticated request for Table.
*
* @param request
* a HttpURLConnection for the operation.
* @param credentials
* the credentials to use for signing.
* @param contentLength
* the length of the content written to the output stream, -1 if unknown.
* @param opContext
* an object used to track the execution of the operation
* @throws InvalidKeyException
* if the credentials key is invalid.
* @throws StorageException
*/
public static void signRequestForTableSharedKey(final HttpURLConnection request, final Credentials credentials,
final Long contentLength, final OperationContext opContext) throws InvalidKeyException, StorageException {
request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime());
final Canonicalizer canonicalizer = CanonicalizerFactory.getTableFullCanonicalizer(request);
final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength);
final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign);
request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION,
String.format("%s %s:%s", "SharedKey", credentials.getAccountName(), computedBase64Signature));
}
/**
*
* Signs the request appropriately to make it an authenticated request for Table.
*
* @param request
* a HttpURLConnection for the operation.
* @param credentials
* the credentials to use for signing.
* @param contentLength
* the length of the content written to the output stream, -1 if unknown.
* @param opContext
* an object used to track the execution of the operation
* @throws InvalidKeyException
* if the credentials key is invalid.
* @throws StorageException
*/
public static void signRequestForTableSharedKeyLite(final HttpURLConnection request, final Credentials credentials,
final Long contentLength, final OperationContext opContext) throws InvalidKeyException, StorageException {
request.setRequestProperty(Constants.HeaderConstants.DATE, Utility.getGMTTime());
final Canonicalizer canonicalizer = CanonicalizerFactory.getTableLiteCanonicalizer(request);
final String stringToSign = canonicalizer.canonicalize(request, credentials.getAccountName(), contentLength);
final String computedBase64Signature = StorageKey.computeMacSha256(credentials.getKey(), stringToSign);
request.setRequestProperty(Constants.HeaderConstants.AUTHORIZATION,
String.format("%s %s:%s", "SharedKeyLite", credentials.getAccountName(), computedBase64Signature));
}
/**
* Private Default Ctor
*/
private BaseRequest() {
// No op
}
}