/*
HttpUtil.java
Copyright (c) 2016 NTT DOCOMO,INC.
Released under the MIT license
http://opensource.org/licenses/mit-license.php
*/
package org.deviceconnect.android.deviceplugin.awsiot.cores.util;
import org.deviceconnect.android.deviceplugin.awsiot.remote.BuildConfig;
import android.util.Log;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
import java.util.Objects;
import java.util.Random;
/**
* HTTP通信を行うためのユーティリティクラス.
*/
public final class HttpUtil {
/** デバック用フラグ. */
private static final boolean DEBUG = BuildConfig.DEBUG;
/** デバック用タグを定義します. */
private static final String TAG = "HTTP";
/** バッファサイズを定義します. */
private static final int BUF_SIZE = 4096;
/** エラーとなるレスポンスコードを定義します. */
private static final int ERROR_RESPONSE_CODE = 400;
/** 接続のタイムアウト. */
private static final int CONNECT_TIMEOUT = 30 * 1000;
/** 読み込みのタイムアウト時間. */
private static final int READ_TIMEOUT = 3 * 60 * 1000;
/** POSTメソッドを定義します. */
private static final String METHOD_POST = "POST";
/** GETメソッドを定義します. */
private static final String METHOD_GET = "GET";
/** PUTメソッドを定義します. */
private static final String METHOD_PUT = "PUT";
/** DELETEメソッドを定義します. */
private static final String METHOD_DELETE = "DELETE";
/** マルチパートで使用するハイフンの定義. */
private final static String TWO_HYPHEN = "--";
/** マルチパートで使用する改行コードの定義. */
private final static String EOL = "\r\n";
/** マルチパートのバウンダリーの定義. */
private final static String BOUNDARY = String.format("%x", new Random().hashCode());
private HttpUtil() {
}
/**
* 指定されたファイルを読み込みます.
* @param file ファイル
* @return ファイルのデータ
* @throws IOException ファイルの読み込みに失敗した場合に発生
*/
private static byte[] getBytes(final File file) throws IOException {
FileInputStream in = null;
try {
in = new FileInputStream(file);
return getBytes(in);
} finally {
if (in != null) {
in.close();
}
}
}
/**
* 指定したストリームを読み込みます.
* @param in ストリーム
* @return ストリームのデータ
* @throws IOException ストリームの読み込みに失敗した場合に発生
*/
private static byte[] getBytes(final InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
int len;
byte[] buf = new byte[1024];
while ((len = in.read(buf)) != -1) {
out.write(buf, 0, len);
}
return out.toByteArray();
}
/**
* マルチパートのBody部分を書き込みます.
* @param os 書き込む先のストリーム
* @param body 書き込むデータ
* @throws IOException 書き込みに失敗した場合に発生
*/
private static void multipart(final OutputStream os, final Map<String, Object> body) throws IOException {
for (Map.Entry<String, Object> data : body.entrySet()) {
String key = data.getKey();
Object val = data.getValue();
os.write(String.format("%s%s%s", TWO_HYPHEN, BOUNDARY, EOL).getBytes());
if (val instanceof String) {
os.write(String.format("Content-Disposition: form-data; name=\"%s\"%s", key, EOL).getBytes());
os.write(EOL.getBytes());
os.write(((String) val).getBytes());
os.write(EOL.getBytes());
} else if (val instanceof byte[]) {
os.write(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s", key, key, EOL).getBytes());
os.write(EOL.getBytes());
os.write(((byte[])val));
os.write(EOL.getBytes());
} else if (val instanceof File) {
os.write(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s", key, key, EOL).getBytes());
os.write(EOL.getBytes());
os.write(getBytes((File) val));
os.write(EOL.getBytes());
} else if (val instanceof InputStream) {
os.write(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"%s", key, key, EOL).getBytes());
os.write(EOL.getBytes());
os.write(getBytes((InputStream) val));
os.write(EOL.getBytes());
}
}
os.write(String.format("%s%s%s%s", TWO_HYPHEN, BOUNDARY, TWO_HYPHEN, EOL).getBytes());
}
/**
* HTTPサーバに接続を行います.
* <p>
* HTTPサーバに接続が失敗した場合には<code>null</code>を返却します。
* </p>
* @param method HTTPメソッド
* @param uri HTTPサーバへのURI
* @param headers ヘッダー
* @param body ボディ
* @return HTTPサーバからのレスポンス
*/
public static byte[] connect(final String method, final String uri, final Map<String, String> headers, final Object body) {
if (DEBUG) {
Log.d(TAG, "connect: method=" + method + " uri=" + uri);
if (headers != null) {
for (String key : headers.keySet()) {
Log.d(TAG, "header: " + key + ": " + headers.get(key));
}
}
if (body != null) {
Log.d(TAG, "body: " + body);
}
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL(uri).openConnection();
conn.setConnectTimeout(CONNECT_TIMEOUT);
conn.setReadTimeout(READ_TIMEOUT);
conn.setRequestMethod(method);
conn.setDoInput(true);
conn.setDoOutput(body != null);
if (headers != null) {
for (String key : headers.keySet()) {
conn.setRequestProperty(key, headers.get(key));
}
}
// 4.x系はkeep-aliveを行うと例外が発生するため、offにする
// 参考: http://osa030.hatenablog.com/entry/2015/05/22/181155
// if (Build.VERSION.SDK_INT > 13 && Build.VERSION.SDK_INT < 19) {
conn.setRequestProperty("Connection", "close");
// }
// データがMapの場合にはマルチパート
if (body instanceof Map) {
conn.setRequestProperty("Content-Type", String.format("multipart/form-data; boundary=%s", BOUNDARY));
}
conn.connect();
if (body != null) {
OutputStream os = conn.getOutputStream();
if (body instanceof byte[]) {
os.write((byte[]) body);
} else if (body instanceof String) {
os.write(((String) body).getBytes());
} else if (body instanceof Map<?, ?>) {
multipart(os, (Map<String, Object>) body);
}
os.flush();
os.close();
}
int resp = conn.getResponseCode();
if (DEBUG) {
Log.d(TAG, "response code=" + resp);
}
if (resp < ERROR_RESPONSE_CODE) {
InputStream in = conn.getInputStream();
int len;
byte[] buf = new byte[BUF_SIZE];
while ((len = in.read(buf)) > 0) {
outputStream.write(buf, 0, len);
}
in.close();
} else {
if (DEBUG) {
Log.w(TAG, "Failed to connect the server. response=" + resp);
}
return null;
}
} catch (IOException e) {
if (DEBUG) {
Log.e(TAG, "Failed to connect the server.", e);
}
return null;
} catch (Exception e) {
if (DEBUG) {
Log.e(TAG, "Failed to connect the server.", e);
}
return null;
} catch (OutOfMemoryError e) {
if (DEBUG) {
Log.e(TAG, "Failed to connect the server.", e);
}
return null;
} finally {
if (conn != null) {
conn.disconnect();
}
}
return outputStream.toByteArray();
}
/**
* GETメソッドでHTTPサーバにアクセスします.
* @param uri HTTPサーバのURI
* @return HTTPサーバのレスポンス
*/
public static byte[] get(final String uri) {
return get(uri, null);
}
public static byte[] get(final String uri, final Map<String, String> headers) {
return connect(METHOD_GET, uri, headers, null);
}
/**
* PUTメソッドでHTTPサーバにアクセスします.
* @param uri HTTPサーバのURI
* @param body 送信するデータ
* @return HTTPサーバのレスポンス
*/
public static byte[] put(final String uri, final Objects body) {
return put(uri, null, body);
}
public static byte[] put(final String uri, final Map<String, String> headers, final Object body) {
return connect(METHOD_PUT, uri, headers, body);
}
/**
* POSTメソッドでHTTPサーバにアクセスします.
* @param uri HTTPサーバのURI
* @param body 送信するデータ
* @return HTTPサーバのレスポンス
*/
public static byte[] post(final String uri, final Object body) {
return put(uri, null, body);
}
public static byte[] post(final String uri, final Map<String, String> headers, final Object body) {
return connect(METHOD_POST, uri, headers, body);
}
/**
* DELETEメソッドでHTTPサーバにアクセスします.
* @param uri HTTPサーバのURI
* @return HTTPサーバのレスポンス
*/
public static byte[] delete(final String uri) {
return delete(uri, null);
}
public static byte[] delete(final String uri, final Map<String, String> headers) {
return connect(METHOD_DELETE, uri, headers, null);
}
/**
* POSTメソッドでHTTPサーバにマルチパートのデータを送信します.
* @param uri HTTPサーバのURI
* @param headers ヘッダー
* @param body 送信するデータ
* @return HTTPサーバのレスポンス
*/
public static byte[] postMultipart(final String uri, final Map<String, String> headers, final Map<String, Object> body) {
return connect(METHOD_POST, uri, headers, body);
}
/**
* PUTメソッドでHTTPサーバにマルチパートのデータを送信します.
* @param uri HTTPサーバのURI
* @param headers ヘッダー
* @param body 送信するデータ
* @return HTTPサーバのレスポンス
*/
public static byte[] putMultipart(final String uri, final Map<String, String> headers, final Map<String, Object> body) {
return connect(METHOD_PUT, uri, headers, body);
}
}