/*
* Copyright 2014 Baidu, Inc.
*
* 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.baidubce.util;
import static com.google.common.base.Preconditions.checkNotNull;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.BitSet;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.http.Header;
import org.apache.http.StatusLine;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpRequestBase;
import com.baidubce.Protocol;
import com.baidubce.http.Headers;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
public class HttpUtils {
private static final String DEFAULT_ENCODING = "UTF-8";
private static BitSet URI_UNRESERVED_CHARACTERS = new BitSet();
private static String[] PERCENT_ENCODED_STRINGS = new String[256];
private static final Joiner queryStringJoiner = Joiner.on('&');
private static boolean HTTP_VERBOSE = Boolean.parseBoolean(System.getProperty("bce.sdk.http", "false"));
// private static boolean HTTP_VERBOSE = true;
/**
* Regex which matches any of the sequences that we need to fix up after URLEncoder.encode().
*/
// private static final Pattern ENCODED_CHARACTERS_PATTERN;
static {
/*
* StringBuilder pattern = new StringBuilder();
*
* pattern .append(Pattern.quote("+")) .append("|") .append(Pattern.quote("*")) .append("|")
* .append(Pattern.quote("%7E")) .append("|") .append(Pattern.quote("%2F"));
*
* ENCODED_CHARACTERS_PATTERN = Pattern.compile(pattern.toString());
*/
for (int i = 'a'; i <= 'z'; i++) {
URI_UNRESERVED_CHARACTERS.set(i);
}
for (int i = 'A'; i <= 'Z'; i++) {
URI_UNRESERVED_CHARACTERS.set(i);
}
for (int i = '0'; i <= '9'; i++) {
URI_UNRESERVED_CHARACTERS.set(i);
}
URI_UNRESERVED_CHARACTERS.set('-');
URI_UNRESERVED_CHARACTERS.set('.');
URI_UNRESERVED_CHARACTERS.set('_');
URI_UNRESERVED_CHARACTERS.set('~');
for (int i = 0; i < PERCENT_ENCODED_STRINGS.length; ++i) {
PERCENT_ENCODED_STRINGS[i] = String.format("%%%02X", i);
}
}
/**
* Normalize a string for use in url path. The algorithm is:
* <p>
*
* <ol>
* <li>Normalize the string</li>
* <li>replace all "%2F" with "/"</li>
* <li>replace all "//" with "/%2F"</li>
* </ol>
*
* <p>
* Bos object key can contain arbitrary characters, which may result double slash in the url path. Apache http
* client will replace "//" in the path with a single '/', which makes the object key incorrect. Thus we replace
* "//" with "/%2F" here.
*
* @param path the path string to normalize.
* @return the normalized path string.
* @see #normalize(String)
*/
public static String normalizePath(String path) {
return normalize(path).replace("%2F", "/");
}
/**
* Normalize a string for use in BCE web service APIs. The normalization algorithm is:
* <p>
* <ol>
* <li>Convert the string into a UTF-8 byte array.</li>
* <li>Encode all octets into percent-encoding, except all URI unreserved characters per the RFC 3986.</li>
* </ol>
*
* <p>
* All letters used in the percent-encoding are in uppercase.
*
* @param value the string to normalize.
* @return the normalized string.
* @throws UnsupportedEncodingException
*/
public static String normalize(String value) {
try {
StringBuilder builder = new StringBuilder();
for (byte b : value.getBytes(DEFAULT_ENCODING)) {
if (URI_UNRESERVED_CHARACTERS.get(b & 0xFF)) {
builder.append((char) b);
} else {
builder.append(PERCENT_ENCODED_STRINGS[b & 0xFF]);
}
}
return builder.toString();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException(e);
}
}
/**
* Encode a string for use in the path of a URL; uses URLEncoder.encode, (which encodes a string for use in the
* query portion of a URL), then applies some postfilters to fix things up per the RFC. Can optionally handle
* strings which are meant to encode a path (ie include '/'es which should NOT be escaped).
*
* @param value the value to encode
* @param path true if the value is intended to represent a path
* @return the encoded value
*/
/*
* public static String urlEncode(String value) { if (value == null) { return ""; }
*
* try { String encoded = URLEncoder.encode(value, DEFAULT_ENCODING);
*
* Matcher matcher = ENCODED_CHARACTERS_PATTERN.matcher(encoded); StringBuffer buffer = new
* StringBuffer(encoded.length());
*
* while (matcher.find()) { String replacement = matcher.group(0);
*
* if ("+".equals(replacement)) { replacement = "%20"; } else if ("*".equals(replacement)) { replacement = "%2A"; }
* else if ("%7E".equals(replacement)) { replacement = "~"; } else if (path && "%2F".equals(replacement)) {
* replacement = "/"; }
*
* matcher.appendReplacement(buffer, replacement); }
*
* matcher.appendTail(buffer); return buffer.toString();
*
* } catch (UnsupportedEncodingException ex) { throw new RuntimeException(ex); } }
*/
/**
* Returns a host header according to the specified URI. The host header is generated with the same logic used by
* apache http client, that is, append the port to hostname only if it is not the default port.
*
* @param uri the URI
* @return a host header according to the specified URI.
*/
public static String generateHostHeader(URI uri) {
String host = uri.getHost();
if (isUsingNonDefaultPort(uri)) {
host += ":" + uri.getPort();
}
return host;
}
/**
* Returns true if the specified URI is using a non-standard port (i.e. any port other than 80 for HTTP URIs or any
* port other than 443 for HTTPS URIs).
*
* @param uri the URI
* @return True if the specified URI is using a non-standard port, otherwise false.
*/
public static boolean isUsingNonDefaultPort(URI uri) {
String scheme = uri.getScheme().toLowerCase();
int port = uri.getPort();
if (port <= 0) {
return false;
}
if (scheme.equals(Protocol.HTTP.toString())) {
return port != Protocol.HTTP.getDefaultPort();
}
if (scheme.equals(Protocol.HTTPS.toString())) {
return port != Protocol.HTTPS.getDefaultPort();
}
return false;
}
public static String getCanonicalQueryString(Map<String, String> parameters, boolean forSignature) {
if (parameters.isEmpty()) {
return "";
}
List<String> parameterStrings = Lists.newArrayList();
for (Map.Entry<String, String> entry : parameters.entrySet()) {
if (forSignature && Headers.AUTHORIZATION.equalsIgnoreCase(entry.getKey())) {
continue;
}
String key = entry.getKey();
checkNotNull(key, "parameter key should not be null");
String value = entry.getValue();
if (value == null) {
if (forSignature) {
parameterStrings.add(normalize(key) + '=');
} else {
parameterStrings.add(normalize(key));
}
} else {
parameterStrings.add(normalize(key) + '=' + normalize(value));
}
}
Collections.sort(parameterStrings);
return queryStringJoiner.join(parameterStrings);
}
/**
* Append the given path to the given baseUri.
*
* <p>
* This method will encode the given path but not the given baseUri.
*
* @param baseUri
* @param pathComponents
*/
public static URI appendUri(URI baseUri, String... pathComponents) {
StringBuilder builder = new StringBuilder(baseUri.toASCIIString());
for (String path : pathComponents) {
if (path != null && path.length() > 0) {
path = normalizePath(path);
if (path.startsWith("/")) {
if (builder.charAt(builder.length() - 1) == '/') {
builder.setLength(builder.length() - 1);
}
} else {
if (builder.charAt(builder.length() - 1) != '/') {
builder.append('/');
}
}
builder.append(path);
}
}
try {
return new URI(builder.toString());
} catch (URISyntaxException e) {
throw new RuntimeException("Unexpected error", e);
}
}
public static void printRequest(HttpRequestBase request) {
if (!HTTP_VERBOSE) {
return;
}
System.out.println("\n-------------> ");
System.out.println(request.getRequestLine());
for (Header h : request.getAllHeaders()) {
System.out.println(h.getName() + " : " + h.getValue());
}
RequestConfig config = request.getConfig();
if (config != null) {
System.out.println("getConnectionRequestTimeout: "
+ config.getConnectionRequestTimeout());
System.out.println("getConnectTimeout: "
+ config.getConnectTimeout());
System.out.println("getCookieSpec: " + config.getCookieSpec());
System.out.println("getLocalAddress: " + config.getLocalAddress());
}
}
public static void printResponse(CloseableHttpResponse response) {
if (!HTTP_VERBOSE) {
return;
}
System.out.println("\n<------------- ");
StatusLine status = response.getStatusLine();
System.out.println(status.getStatusCode() + " - "
+ status.getReasonPhrase());
Header[] heads = response.getAllHeaders();
for (Header h : heads) {
System.out.println(h.getName() + " : " + h.getValue());
}
}
}