/*
WebClient.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.p2p;
import android.content.ContentResolver;
import android.content.Context;
import android.net.Uri;
import android.util.Log;
import org.deviceconnect.android.deviceplugin.awsiot.remote.BuildConfig;
import org.deviceconnect.android.deviceplugin.awsiot.udt.P2PConnection;
import org.deviceconnect.android.deviceplugin.awsiot.cores.util.AWSIotUtil;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.TimeZone;
import java.util.concurrent.Executors;
public class WebClient extends AWSIotP2PManager {
private static final boolean DEBUG = BuildConfig.DEBUG;
private static final String TAG = "AWS";
private static final int BUF_SIZE = 1024 * 8;
public static final String PATH_CONTENT_PROVIDER = "/contentProvider";
private P2PConnection mP2PConnection;
private ISocketAdapter mSocket;
private Context mContext;
private ByteArrayOutputStream mHttpHeaderData = new ByteArrayOutputStream();
public WebClient(final Context context) {
mContext = context;
}
public void onDisconnected(final WebClient webClient) {
}
@Override
public void onReceivedSignaling(final String signaling) {
mP2PConnection = createP2PConnection(signaling, mOnP2PConnectionListener);
if (mP2PConnection == null) {
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
mP2PConnection = new P2PConnection();
mP2PConnection.setOnP2PConnectionListener(mOnP2PConnectionListener);
mP2PConnection.setConnectionId(getConnectionId(signaling));
try {
mP2PConnection.open();
} catch (IOException e) {
if (DEBUG) {
Log.w(TAG, "WebClient#onReceivedSignaling", e);
}
}
}
});
}
}
public void close() {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
if (DEBUG) {
Log.e(TAG, "", e);
}
}
}
if (mP2PConnection != null) {
try {
mP2PConnection.close();
} catch (IOException e) {
if (DEBUG) {
Log.e(TAG, "", e);
}
}
}
}
private void sendFailedToConnect() {
if (mP2PConnection != null) {
try {
mP2PConnection.sendData(generateInternalServerError().getBytes());
} catch (IOException e) {
if (DEBUG) {
Log.w(TAG, "WebClient#sendFailedToConnect", e);
}
}
}
}
private ISocketAdapter openSocket(final byte[] data) throws IOException {
if (DEBUG) {
Log.i(TAG, "WebClient#readHttpHeader: " + data.length);
Log.i(TAG, "WebClient#readHttpHeader: " + new String(data).replace("\r\n", " "));
}
final ISocketAdapter socket = openSocketFromHttpHeader(data, data.length);
Executors.newSingleThreadExecutor().submit(new Runnable() {
@Override
public void run() {
socket.r();
}
});
return socket;
}
private ISocketAdapter openSocketFromHttpHeader(final byte[] buf, final int len) throws IOException {
BufferedReader in = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, len)));
String address = null;
int port = 0;
String inLine = in.readLine();
if (inLine == null) {
throw new IOException("Cannot open socket.");
}
StringTokenizer st = new StringTokenizer(inLine);
if (!st.hasMoreTokens()) {
throw new IOException("Cannot open socket.");
}
st.nextToken();
if (!st.hasMoreTokens()) {
throw new IOException("Cannot open socket.");
}
String uri = st.nextToken();
// TODO 他のパターンがあれば検討すること。
if (uri.startsWith(PATH_CONTENT_PROVIDER)) {
int qmi = uri.indexOf('?');
if (qmi >= 0) {
return new ContentProviderSocketAdapter(mContext, uri.substring(qmi + 1));
}
}
String line;
while ((line = in.readLine()) != null) {
if (line.toLowerCase().startsWith("host")) {
String host = line.substring(line.indexOf(":") + 1).trim();
if (host.contains(":")) {
String[] split = host.split(":");
if (split.length == 2) {
address = DomainResolution.lookup(split[0]);
port = Integer.parseInt(split[1]);
}
} else {
address = DomainResolution.lookup(host);
port = 80;
}
if (address == null || port == 0) {
throw new IOException("Cannot open socket. host=" + line);
}
return new SocketAdapter(new Socket(address, port));
}
}
throw new IOException("Cannot open socket.");
}
private P2PConnection.OnP2PConnectionListener mOnP2PConnectionListener = new P2PConnection.OnP2PConnectionListener() {
@Override
public void onRetrievedAddress(final String address, final int port) {
if (DEBUG) {
Log.d(TAG, "WebClient#onRetrievedAddress=" + address + ":" + port);
}
onNotifySignaling(createSignaling(mContext, mP2PConnection.getConnectionId(), address, port));
}
@Override
public void onConnected(final String address, final int port) {
if (DEBUG) {
Log.d(TAG, "WebClient#onConnected=" + address + ":" + port);
}
}
@Override
public void onReceivedData(final byte[] data, final String address, final int port) {
if (DEBUG) {
Log.d(TAG, "WebClient#onReceivedData=" + data.length);
Log.d(TAG, "" + new String(data).replace("\r\n", " "));
}
try {
if (mSocket == null) {
mHttpHeaderData.write(data);
if (findHeaderEnd(mHttpHeaderData.toByteArray(), mHttpHeaderData.size()) > 0) {
mSocket = openSocket(mHttpHeaderData.toByteArray());
mSocket.w(mHttpHeaderData.toByteArray());
mHttpHeaderData = null;
}
} else {
mSocket.w(data);
}
} catch (Exception e) {
sendFailedToConnect();
close();
}
}
@Override
public void onDisconnected(final String address, final int port) {
if (DEBUG) {
Log.e(TAG, "WebClient#onDisconnect: " + address + ":" + port);
}
WebClient.this.onDisconnected(WebClient.this);
}
@Override
public void onTimeout() {
Log.e(TAG, "WebClient#onTimeout: ");
}
};
private interface ISocketAdapter {
void r();
void w(byte[] data) throws IOException;
void close() throws IOException;
}
private class SocketAdapter implements ISocketAdapter {
private Socket mSocket;
private OutputStream mOutputStream;
public SocketAdapter(final Socket socket) throws IOException {
mSocket = socket;
mOutputStream = socket.getOutputStream();
}
@Override
public void r() {
try {
InputStream is = mSocket.getInputStream();
int len;
byte[] buf = new byte[BUF_SIZE];
while ((len = is.read(buf)) > 0) {
mP2PConnection.sendData(buf, 0, len);
}
} catch (IOException e) {
if (DEBUG) {
Log.w(TAG, "WebClient#relayHttpResponse", e);
}
} finally {
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Override
public void w(final byte[] data) throws IOException {
mOutputStream.write(data);
mOutputStream.flush();
}
@Override
public void close() throws IOException {
if (mSocket != null) {
try {
mSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private class ContentProviderSocketAdapter implements ISocketAdapter {
private Context mContext;
private File mFile;
private String mUri;
private InputStream mInputStream;
public ContentProviderSocketAdapter(final Context context, final String uri) throws IOException {
mContext = context;
mUri = uri;
}
public Context getContext() {
return mContext;
}
private File load(final String uri) throws IOException {
String fileName = AWSIotUtil.md5(uri);
if (fileName == null) {
throw new IOException("");
}
File file = File.createTempFile(fileName, null, getContext().getCacheDir());
FileOutputStream out = null;
InputStream in = null;
byte[] buf = new byte[BUF_SIZE];
int len;
try {
out = new FileOutputStream(file);
ContentResolver r = getContext().getContentResolver();
in = r.openInputStream(Uri.parse(uri));
if (in != null) {
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.flush();
}
return file;
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (in != null) {
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
private void createHeader() throws IOException {
String a = generateHttpHeader(mFile.length());
byte[] data = a.getBytes();
mP2PConnection.sendData(data, 0, data.length);
}
private String generateHttpHeader(final long fileSize) {
SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
StringBuilder sb = new StringBuilder();
sb.append("HTTP/1.1 200 OK\r\n");
sb.append("Date: ").append(gmtFrmt.format(new Date())).append("\r\n");
sb.append("Server: AWSIot-Remote-Server(Android)\r\n");
sb.append("Content-Length: ").append(fileSize).append("\r\n");
sb.append("Connection: close\r\n");
sb.append("\r\n");
return sb.toString();
}
@Override
public void r() {
try {
if (DEBUG) {
Log.i(TAG, "WebClient#r: uri=" + mUri);
}
mFile = load(mUri);
createHeader();
mInputStream = new FileInputStream(mFile);
int len;
byte[] buf = new byte[BUF_SIZE];
while ((len = mInputStream.read(buf)) > 0) {
mP2PConnection.sendData(buf, 0, len);
}
} catch (Exception e) {
if (DEBUG) {
Log.e(TAG, "", e);
}
} finally {
WebClient.this.close();
if (mFile != null && !mFile.delete()) {
if (DEBUG) {
Log.w(TAG, "Failed to delete file. mFile=" + mFile.getAbsolutePath());
}
}
}
}
@Override
public void w(final byte[] data) {
}
@Override
public void close() throws IOException {
if (mInputStream != null) {
try {
mInputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}