mirror of
https://github.com/zhigang1992/AndroidVideoCache.git
synced 2026-03-06 22:32:57 +08:00
ping server each time before wrapping to proxy url
This commit is contained in:
@@ -1,7 +1,6 @@
|
||||
package com.danikula.videocache;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.SystemClock;
|
||||
|
||||
import com.danikula.videocache.file.DiskUsage;
|
||||
import com.danikula.videocache.file.FileNameGenerator;
|
||||
@@ -16,26 +15,19 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static com.danikula.videocache.Preconditions.checkAllNotNull;
|
||||
import static com.danikula.videocache.Preconditions.checkNotNull;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* Simple lightweight proxy server with file caching support that handles HTTP requests.
|
||||
@@ -59,10 +51,7 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
public class HttpProxyCacheServer {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger("HttpProxyCacheServer");
|
||||
|
||||
private static final String PROXY_HOST = "127.0.0.1";
|
||||
private static final String PING_REQUEST = "ping";
|
||||
private static final String PING_RESPONSE = "ping ok";
|
||||
|
||||
private final Object clientsLock = new Object();
|
||||
private final ExecutorService socketProcessor = Executors.newFixedThreadPool(8);
|
||||
@@ -71,7 +60,7 @@ public class HttpProxyCacheServer {
|
||||
private final int port;
|
||||
private final Thread waitConnectionThread;
|
||||
private final Config config;
|
||||
private boolean pinged;
|
||||
private final Pinger pinger;
|
||||
|
||||
public HttpProxyCacheServer(Context context) {
|
||||
this(new Builder(context).buildConfig());
|
||||
@@ -87,65 +76,16 @@ public class HttpProxyCacheServer {
|
||||
this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
|
||||
this.waitConnectionThread.start();
|
||||
startSignal.await(); // freeze thread, wait for server starts
|
||||
LOG.info("Proxy cache server started. Ping it...");
|
||||
makeSureServerWorks();
|
||||
this.pinger = new Pinger(PROXY_HOST, port);
|
||||
LOG.info("Proxy cache server started. Is it alive? " + isAlive());
|
||||
} catch (IOException | InterruptedException e) {
|
||||
socketProcessor.shutdown();
|
||||
throw new IllegalStateException("Error starting local proxy server", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void makeSureServerWorks() {
|
||||
int maxPingAttempts = 3;
|
||||
int delay = 300;
|
||||
int pingAttempts = 0;
|
||||
while (pingAttempts < maxPingAttempts) {
|
||||
try {
|
||||
Future<Boolean> pingFuture = socketProcessor.submit(new PingCallable());
|
||||
this.pinged = pingFuture.get(delay, MILLISECONDS);
|
||||
if (this.pinged) {
|
||||
return;
|
||||
}
|
||||
SystemClock.sleep(delay);
|
||||
} catch (InterruptedException | ExecutionException | TimeoutException e) {
|
||||
LOG.error("Error pinging server [attempt: " + pingAttempts + ", timeout: " + delay + "]. ", e);
|
||||
}
|
||||
pingAttempts++;
|
||||
delay *= 2;
|
||||
}
|
||||
LOG.error("Shutdown server… Error pinging server [attempts: " + pingAttempts + ", max timeout: " + delay / 2 + "]. " +
|
||||
"If you see this message, please, email me danikula@gmail.com");
|
||||
shutdown();
|
||||
}
|
||||
|
||||
private boolean pingServer() throws ProxyCacheException {
|
||||
String pingUrl = appendToProxyUrl(PING_REQUEST);
|
||||
HttpUrlSource source = new HttpUrlSource(pingUrl);
|
||||
try {
|
||||
byte[] expectedResponse = PING_RESPONSE.getBytes();
|
||||
source.open(0);
|
||||
byte[] response = new byte[expectedResponse.length];
|
||||
source.read(response);
|
||||
boolean pingOk = Arrays.equals(expectedResponse, response);
|
||||
LOG.info("Ping response: `" + new String(response) + "`, pinged? " + pingOk);
|
||||
return pingOk;
|
||||
} catch (ProxyCacheException e) {
|
||||
LOG.error("Error reading ping response", e);
|
||||
return false;
|
||||
} finally {
|
||||
source.close();
|
||||
}
|
||||
}
|
||||
|
||||
public String getProxyUrl(String url) {
|
||||
if (!pinged) {
|
||||
LOG.error("Proxy server isn't pinged. Caching doesn't work. If you see this message, please, email me danikula@gmail.com");
|
||||
}
|
||||
return pinged ? appendToProxyUrl(url) : url;
|
||||
}
|
||||
|
||||
private String appendToProxyUrl(String url) {
|
||||
return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
|
||||
return isAlive() ? appendToProxyUrl(url) : url;
|
||||
}
|
||||
|
||||
public void registerCacheListener(CacheListener cacheListener, String url) {
|
||||
@@ -210,6 +150,14 @@ public class HttpProxyCacheServer {
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isAlive() {
|
||||
return pinger.ping(3, 70); // 70+140+280=max~500ms
|
||||
}
|
||||
|
||||
private String appendToProxyUrl(String url) {
|
||||
return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
|
||||
}
|
||||
|
||||
private void shutdownClients() {
|
||||
synchronized (clientsLock) {
|
||||
for (HttpProxyCacheServerClients clients : clientsMap.values()) {
|
||||
@@ -236,8 +184,8 @@ public class HttpProxyCacheServer {
|
||||
GetRequest request = GetRequest.read(socket.getInputStream());
|
||||
LOG.debug("Request to cache proxy:" + request);
|
||||
String url = ProxyCacheUtils.decode(request.uri);
|
||||
if (PING_REQUEST.equals(url)) {
|
||||
responseToPing(socket);
|
||||
if (pinger.isPingRequest(url)) {
|
||||
pinger.responseToPing(socket);
|
||||
} else {
|
||||
HttpProxyCacheServerClients clients = getClients(url);
|
||||
clients.processRequest(request, socket);
|
||||
@@ -254,12 +202,6 @@ public class HttpProxyCacheServer {
|
||||
}
|
||||
}
|
||||
|
||||
private void responseToPing(Socket socket) throws IOException {
|
||||
OutputStream out = socket.getOutputStream();
|
||||
out.write("HTTP/1.1 200 OK\n\n".getBytes());
|
||||
out.write(PING_RESPONSE.getBytes());
|
||||
}
|
||||
|
||||
private HttpProxyCacheServerClients getClients(String url) throws ProxyCacheException {
|
||||
synchronized (clientsLock) {
|
||||
HttpProxyCacheServerClients clients = clientsMap.get(url);
|
||||
@@ -354,14 +296,6 @@ public class HttpProxyCacheServer {
|
||||
}
|
||||
}
|
||||
|
||||
private class PingCallable implements Callable<Boolean> {
|
||||
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
return pingServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link HttpProxyCacheServer}.
|
||||
*/
|
||||
|
||||
112
library/src/main/java/com/danikula/videocache/Pinger.java
Normal file
112
library/src/main/java/com/danikula/videocache/Pinger.java
Normal file
@@ -0,0 +1,112 @@
|
||||
package com.danikula.videocache;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
import static com.danikula.videocache.Preconditions.checkArgument;
|
||||
import static com.danikula.videocache.Preconditions.checkNotNull;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
|
||||
/**
|
||||
* Pings {@link HttpProxyCacheServer} to make sure it works.
|
||||
*
|
||||
* @author Alexey Danilov (danikula@gmail.com).
|
||||
*/
|
||||
|
||||
class Pinger {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger("Pinger");
|
||||
private static final String PING_REQUEST = "ping";
|
||||
private static final String PING_RESPONSE = "ping ok";
|
||||
|
||||
private final ExecutorService pingExecutor = Executors.newSingleThreadExecutor();
|
||||
private final String host;
|
||||
private final int port;
|
||||
|
||||
Pinger(String host, int port) {
|
||||
this.host = checkNotNull(host);
|
||||
this.port = port;
|
||||
}
|
||||
|
||||
boolean ping(int maxAttempts, int startTimeout) {
|
||||
checkArgument(maxAttempts >= 1);
|
||||
checkArgument(startTimeout > 0);
|
||||
|
||||
int timeout = startTimeout;
|
||||
int attempts = 0;
|
||||
while (attempts < maxAttempts) {
|
||||
try {
|
||||
Future<Boolean> pingFuture = pingExecutor.submit(new PingCallable());
|
||||
boolean pinged = pingFuture.get(timeout, MILLISECONDS);
|
||||
if (pinged) {
|
||||
return true;
|
||||
}
|
||||
} catch (TimeoutException e) {
|
||||
LOG.warn("Error pinging server (attempt: " + attempts + ", timeout: " + timeout + "). ");
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
LOG.error("Error pinging server due to unexpected error", e);
|
||||
}
|
||||
attempts++;
|
||||
timeout *= 2;
|
||||
}
|
||||
String error = String.format("Error pinging server (attempts: %d, max timeout: %d). " +
|
||||
"If you see this message, please, email me danikula@gmail.com " +
|
||||
"or create issue here https://github.com/danikula/AndroidVideoCache/issues", attempts, timeout / 2);
|
||||
LOG.error(error, new ProxyCacheException(error));
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean isPingRequest(String request) {
|
||||
return PING_REQUEST.equals(request);
|
||||
}
|
||||
|
||||
void responseToPing(Socket socket) throws IOException {
|
||||
OutputStream out = socket.getOutputStream();
|
||||
out.write("HTTP/1.1 200 OK\n\n".getBytes());
|
||||
out.write(PING_RESPONSE.getBytes());
|
||||
}
|
||||
|
||||
private boolean pingServer() throws ProxyCacheException {
|
||||
String pingUrl = getPingUrl();
|
||||
HttpUrlSource source = new HttpUrlSource(pingUrl);
|
||||
try {
|
||||
byte[] expectedResponse = PING_RESPONSE.getBytes();
|
||||
source.open(0);
|
||||
byte[] response = new byte[expectedResponse.length];
|
||||
source.read(response);
|
||||
boolean pingOk = Arrays.equals(expectedResponse, response);
|
||||
LOG.info("Ping response: `" + new String(response) + "`, pinged? " + pingOk);
|
||||
return pingOk;
|
||||
} catch (ProxyCacheException e) {
|
||||
LOG.error("Error reading ping response", e);
|
||||
return false;
|
||||
} finally {
|
||||
source.close();
|
||||
}
|
||||
}
|
||||
|
||||
private String getPingUrl() {
|
||||
return String.format(Locale.US, "http://%s:%d/%s", host, port, PING_REQUEST);
|
||||
}
|
||||
|
||||
private class PingCallable implements Callable<Boolean> {
|
||||
|
||||
@Override
|
||||
public Boolean call() throws Exception {
|
||||
return pingServer();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
64
test/src/test/java/com/danikula/videocache/PingerTest.java
Normal file
64
test/src/test/java/com/danikula/videocache/PingerTest.java
Normal file
@@ -0,0 +1,64 @@
|
||||
package com.danikula.videocache;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.robolectric.RuntimeEnvironment;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.net.Socket;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static org.fest.assertions.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests {@link Pinger}.
|
||||
*
|
||||
* @author Alexey Danilov (danikula@gmail.com).
|
||||
*/
|
||||
public class PingerTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testPingSuccess() throws Exception {
|
||||
HttpProxyCacheServer server = new HttpProxyCacheServer(RuntimeEnvironment.application);
|
||||
Pinger pinger = new Pinger("127.0.0.1", getPort(server));
|
||||
boolean pinged = pinger.ping(1, 100);
|
||||
assertThat(pinged).isTrue();
|
||||
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPingFail() throws Exception {
|
||||
Pinger pinger = new Pinger("127.0.0.1", 33);
|
||||
boolean pinged = pinger.ping(3, 70);
|
||||
assertThat(pinged).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testIsPingRequest() throws Exception {
|
||||
Pinger pinger = new Pinger("127.0.0.1", 1);
|
||||
assertThat(pinger.isPingRequest("ping")).isTrue();
|
||||
assertThat(pinger.isPingRequest("notPing")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testResponseToPing() throws Exception {
|
||||
Pinger pinger = new Pinger("127.0.0.1", 1);
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
Socket socket = mock(Socket.class);
|
||||
when(socket.getOutputStream()).thenReturn(out);
|
||||
pinger.responseToPing(socket);
|
||||
assertThat(out.toString()).isEqualTo("HTTP/1.1 200 OK\n\nping ok");
|
||||
}
|
||||
|
||||
private int getPort(HttpProxyCacheServer server) {
|
||||
String proxyUrl = server.getProxyUrl("test");
|
||||
Pattern pattern = Pattern.compile("http://127.0.0.1:(\\d*)/test");
|
||||
Matcher matcher = pattern.matcher(proxyUrl);
|
||||
assertThat(matcher.find()).isTrue();
|
||||
String portAsString = matcher.group(1);
|
||||
return Integer.parseInt(portAsString);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user