ping server each time before wrapping to proxy url

This commit is contained in:
Alexey Danilov
2016-09-27 20:10:43 +03:00
parent 44318d87d9
commit 0d1131b0fc
3 changed files with 190 additions and 80 deletions

View File

@@ -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}.
*/

View 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();
}
}
}

View 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);
}
}