package com.tadpolemusic.media.http;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URL;
import java.util.Properties;
import com.tadpolemusic.media.http.HttpParser.Range;
import com.tadpolemusic.media.http.HttpParser.StatusLine;
import android.util.Log;
/**
* <br>=
* ========================= <br>
* author:Zenip <br>
* email:lxyczh@gmail.com <br>
* create:2013-2-1 <br>=
* =========================
*/
public class LocalRemoteIOComunicator implements Runnable {
/**
* Delimiter between Http Header and Http Content
*/
private static final String HEADER_CONTENT_DELIMITER = "\r\n\r\n";
private static final String LOCATION = "Location";
private static final String CONTENT_LENGTH = "Content-Length";
private InputStream mLocalIn = null;
private OutputStream mLocalOut = null;
private InputStream mRemoteIn;
private OutputStream mRemoteOut;
private int mLocalProxyPort = -1;
private HttpGetProxy mHttpGetProxy;
private Socket mRemoteSocket;
private Socket mLocalSocket;
/**
* a url that is requested firstly. useded to handle 302 redirect
*/
private String mRootUrl;
private boolean mCacheHeaderUsing = false;
private boolean mCacheBodyNeedSave = true;
private boolean mCacheHeaderHadWritten = false;
private int mCacheBodyLength = 0;
private long mCacheSaveRangeStart = 0;
private Runnable mWriteCacheHeaderTask;
private Runnable mWriteCacheContentStreamTask;
private Object mTmpWriteBodyLock = new Object();
/**
* local http range header
*/
private Range mLocalRequestRange = new Range();
public String getMusicCachePath() {
return mRootUrl;
}
public LocalRemoteIOComunicator(HttpGetProxy httpProxy, Socket localSocket,
int localPort) {
mHttpGetProxy = httpProxy;
mLocalProxyPort = localPort;
mLocalSocket = localSocket;
}
@Override
public void run() {
try {
connectLocal();
LocalRemoteIOComunicator.this.requestFromLocalToRemote();
} catch (Exception e) {
// Log.e("LocalRemoteIOComunicator", e.getMessage());
e.printStackTrace();
closeIO();
}
}
private void connectLocal() throws IOException {
// System.out.println("=====>init local Socket I/O");
// mLocalSocket.setSoTimeout(20000);
mLocalIn = mLocalSocket.getInputStream();
mLocalOut = mLocalSocket.getOutputStream();
}
private void connectRemote(String remoteHost, int remotePort)
throws IOException {
SocketAddress address = new InetSocketAddress(remoteHost, remotePort);
// --------连接目标服务器---------//
mRemoteSocket = new Socket();
mRemoteSocket.setSoTimeout(20000);
mRemoteSocket.connect(address);
// System.out.println("=====>remote Server connected");
mRemoteOut = mRemoteSocket.getOutputStream();
mRemoteIn = mRemoteSocket.getInputStream();
// System.out.println("=====>init remote Server I/O");
}
public void requestFromLocalToRemote() throws IOException,
InterruptedException {
// System.out.println("=====>local start to receive");
long byteRead = 0;
byte[] buffer = new byte[5120];
String bufferStr = "";
while ((byteRead = mLocalIn.read(buffer)) != -1) {
String reqStr = new String(buffer);
System.out.println("----->localSocket[local-proxy]:" + reqStr);
bufferStr = bufferStr + reqStr;
if (bufferStr.contains("GET")
&& bufferStr.contains(HEADER_CONTENT_DELIMITER)) {
// ----- parse proxy request
String messageHeaer = bufferStr;
String requestUri = HttpParser.getRequestLine(messageHeaer).uri;
String remoteAddr = HttpParser
.getRemoteAddrFromHackedUri(requestUri);
String[] arr = remoteAddr.split(":");
String remoteHostWithPort = arr[0];
String uri = HttpParser.getRemoteUri(requestUri); // uri without
// proxy
String musicUrl = remoteAddr + uri; // music url
mRootUrl = HttpParser.getMetaAddr(requestUri); // music root url
if (mRootUrl == null) {
mRootUrl = musicUrl;
}
// ----- end
// get http rrange
mLocalRequestRange = HttpParser.getRange(messageHeaer);
System.out.println("----->localRequestRange:"
+ mLocalRequestRange);
final HttpCache cache = new HttpCache(getMusicCachePath(),
mHttpGetProxy);
final Properties properties = cache.readProperties();
final long localRequestRangeStart = mLocalRequestRange.start;
//
if (cache.exist()) {
System.out
.println("=====>local request has response cahce");
int cacheStatusCode = 200;
if (localRequestRangeStart != 0) {
cacheStatusCode = 206; // Http Partial Content
}
System.out
.println("=====>local request has response cahce statusCode = "
+ cacheStatusCode);
final int finalCacheStatusCode = cacheStatusCode;
final int completeContentLength = cache.getContentLength();
mCacheBodyLength = (int) cache.getBodyLength(); // music
// stream
// seek pos
// to write
mCacheHeaderUsing = true;
System.out.println("----->cacheBodyLength = "
+ mCacheBodyLength + ", completeContentLength = "
+ completeContentLength);
// write header from cache
mWriteCacheHeaderTask = new Runnable() {
@Override
public void run() {
mCacheHeaderHadWritten = true;
try {
cache.writeHeaderWithModified(mLocalOut,
completeContentLength,
finalCacheStatusCode,
(int) localRequestRangeStart);
} catch (IOException e) {
e.printStackTrace();
closeIO();
}
}
};
if (mCacheBodyLength >= localRequestRangeStart) {
mCacheSaveRangeStart = mCacheBodyLength;
System.out
.println("----->local body cache used length = "
+ (mCacheSaveRangeStart - localRequestRangeStart));
// write music stream from cache
final long finalRangeStart = mCacheSaveRangeStart;
final long finalContentLength = completeContentLength;
mWriteCacheContentStreamTask = new Runnable() {
@Override
public void run() {
synchronized (mTmpWriteBodyLock) {
try {
cache.writeBodyCacheToLocalOut(
mLocalOut,
localRequestRangeStart,
mCacheBodyLength);
} catch (Exception e) {
e.printStackTrace();
// Log.e("LocalRemoteIOComunicator",
// e.getMessage());
closeIO();
}
if (finalRangeStart >= finalContentLength) {
System.out
.println("=====>local finish writing response totally by using cache");
closeIO();
}
}
}
};
} else {
mCacheBodyNeedSave = false;
System.out
.println("----->local no cache for requestRange = "
+ localRequestRangeStart);
mCacheSaveRangeStart = localRequestRangeStart;
}
if (mCacheSaveRangeStart >= completeContentLength) {
mWriteCacheHeaderTask.run();
mWriteCacheHeaderTask = null;
new Thread(mWriteCacheContentStreamTask).start();
mWriteCacheContentStreamTask = null;
System.out
.println("=====>local writing response totally by using cache");
return;
}
}
else {
System.out.println("=====>local request has no cache");
}
String remoteHostToConnect = arr[0];
int remotePortToConnect = HttpGetProxy.REMOTE_DEFAULT_PORT;
if (arr.length >= 2) {
remotePortToConnect = Integer.valueOf(arr[1]);
}
System.out.println("----->localMusicUrl: " + musicUrl);
if (messageHeaer.contains("GET")
&& messageHeaer.contains(HEADER_CONTENT_DELIMITER)) {
String locationStr = properties.getProperty(LOCATION);
if (locationStr != null && !"".equals(locationStr)) {
URL locationURL = new URL(locationStr);
int port = locationURL.getPort();
remotePortToConnect = (port != -1 ? port : 80);
remoteHostToConnect = locationURL.getHost();
remoteHostWithPort = HttpGetProxy.getHostWithPort(
locationStr).trim();
uri = HttpGetProxy.getUriFromUrl(locationStr).trim();
System.out.println("----->location:" + locationStr);
System.out.println("----->url:" + uri);
}
// ---把request中的本地ip改为远程ip---//
messageHeaer = messageHeaer.replace(requestUri, uri);
messageHeaer = messageHeaer.replace(
HttpGetProxy.LOCAL_IP_ADDRESS + ":"
+ mLocalProxyPort, remoteHostWithPort);
System.out.println("=====>replace request host");
if (mCacheSaveRangeStart != 0) {
messageHeaer = HttpParser.replaceOrAddHeader(
messageHeaer, "Range", "bytes="
+ mCacheSaveRangeStart + "-");
System.out.println("=====>replace request range");
}
}
System.out.println("----->connteRemote: remoteHostToConnect = "
+ remoteHostToConnect + ", remotePortToConnect = "
+ remotePortToConnect);
connectRemote(remoteHostToConnect, remotePortToConnect);
System.out.println("----->localSocket[proxy->remote]:"
+ messageHeaer);
// wait until reading response thread ready
synchronized (this) {
// since response reading has a while statement
// we must read response in a new thread
new Thread(new Runnable() {
@Override
public void run() {
try {
LocalRemoteIOComunicator.this
.responseFromRemoteToLocal();
} catch (IOException e) {
e.printStackTrace();
} finally {
closeIO();
}
}
}).start();
this.wait();
}
mRemoteOut.write(messageHeaer.getBytes());
mRemoteOut.flush();
break;
} else {
mRemoteOut.write(bufferStr.getBytes());
mRemoteOut.flush();
}
}
System.out.println("=====>local finish receive");
}
private int getMusicStreamLength(String messageHeader) {
String contentRange = HttpParser.getHeader("Content-Range",
messageHeader, "");
int contentLength = HttpParser.getContentLength(messageHeader);
if (!"".equals(contentRange)) {
try {
int i = contentRange.indexOf("/");
contentRange = contentRange
.subSequence(i + 1, contentRange.length()).toString()
.trim();
contentLength = Integer.valueOf(contentRange);
} catch (Exception e) {
e.printStackTrace();
}
}
return contentLength;
}
public void responseFromRemoteToLocal() throws IOException {
System.out.println("=====>remote start to receive");
int bytesRead;
// notify response ready
synchronized (this) {
this.notify();
}
byte[] buffer = new byte[5120];
// remote message header
byte[] messageHeaderRaw = HttpParser.readMessageHeaderRaw(mRemoteIn);
String messageHeader = new String(messageHeaderRaw);
StatusLine statusLine = HttpParser.getStatusLine(messageHeader);
System.out.println("statusLine = " + statusLine);
HttpCache httpCache = new HttpCache(getMusicCachePath(), mHttpGetProxy);
Properties properties = httpCache.readProperties();
// handle 302 redirect
if (statusLine.statusCode == 302) {
String location = HttpParser.getLocation(messageHeader);
System.out.println("=====>remote has 302 redirect. location = "
+ location);
if (location != null) {
try {
String proxyUrl = mHttpGetProxy.getProxyUrl(location,
mRootUrl);
messageHeader = messageHeader.replace(location, proxyUrl);
properties.setProperty(LOCATION, location);
} catch (Exception e) {
System.out.println("remote start proxy exception");
e.printStackTrace();
}
}
}
if ((statusLine.statusCode == 200 || statusLine.statusCode == 206)) {
// skip 164 byte
byte[] bb = new byte[164];
mRemoteIn.read(bb);
System.out.println("-----> bb = " + new String(bb));
if (mCacheHeaderUsing) {
if ((!mCacheHeaderHadWritten) && mWriteCacheHeaderTask != null) {
mWriteCacheHeaderTask.run();
}
}
else {
// save header cache
httpCache.writeCacheResponseHeader(messageHeaderRaw);
// save MusicStreamLength
int contentLength = getMusicStreamLength(messageHeader);
System.out.println("contentLength = " + contentLength);
properties.setProperty(CONTENT_LENGTH, "" + contentLength);
}
}
httpCache.writeProperties(getMusicCachePath(), properties);
System.out.println("----->remoteSocket messageHeader ..."
+ messageHeader);
System.out.println("----->remoteSocket rootUrl ..." + mRootUrl);
if (!mCacheHeaderHadWritten) {
mLocalOut.write(messageHeader.getBytes());
}
System.out.println("======>remote read message content");
RandomAccessFile ras = httpCache.openCacheResponseBody();
if (mCacheBodyNeedSave && null != ras) {
ras.seek(mCacheSaveRangeStart);
int saveBodyCacheLength = 0;
while ((bytesRead = mRemoteIn.read(buffer)) != -1) {
// write cache
if (mWriteCacheContentStreamTask != null) {
mWriteCacheContentStreamTask.run();
mWriteCacheContentStreamTask = null;
}
if (statusLine.statusCode == 200
|| statusLine.statusCode == 206) {
ras.write(buffer, 0, bytesRead);
saveBodyCacheLength += bytesRead;
}
if (statusLine.statusCode == 400) {
System.out.print(new String(buffer));
}
mLocalOut.write(buffer, 0, bytesRead);
mLocalOut.flush();
}
close(ras);
System.out.println("----->remote writeRangeStart(seekPos) = "
+ mCacheSaveRangeStart + ", saveBodyCacheLength = "
+ saveBodyCacheLength + ", contentLength = "
+ properties.getProperty(CONTENT_LENGTH));
} else {
while ((bytesRead = mRemoteIn.read(buffer)) != -1) {
mLocalOut.write(buffer, 0, bytesRead);
mLocalOut.flush();
}
}
System.out.println("=====>remote finish receive...........");
}
public void closeIO() {
close(mLocalIn);
close(mLocalOut);
close(mRemoteIn);
close(mRemoteOut);
try {
mLocalSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void close(Closeable obj) {
try {
if (obj != null) {
obj.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}