Skip to content

Commit 0d1131b

Browse files
committed
ping server each time before wrapping to proxy url
1 parent 44318d8 commit 0d1131b

File tree

3 files changed

+190
-80
lines changed

3 files changed

+190
-80
lines changed

library/src/main/java/com/danikula/videocache/HttpProxyCacheServer.java

Lines changed: 14 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.danikula.videocache;
22

33
import android.content.Context;
4-
import android.os.SystemClock;
54

65
import com.danikula.videocache.file.DiskUsage;
76
import com.danikula.videocache.file.FileNameGenerator;
@@ -16,26 +15,19 @@
1615

1716
import java.io.File;
1817
import java.io.IOException;
19-
import java.io.OutputStream;
2018
import java.net.InetAddress;
2119
import java.net.ServerSocket;
2220
import java.net.Socket;
2321
import java.net.SocketException;
24-
import java.util.Arrays;
2522
import java.util.Locale;
2623
import java.util.Map;
27-
import java.util.concurrent.Callable;
2824
import java.util.concurrent.ConcurrentHashMap;
2925
import java.util.concurrent.CountDownLatch;
30-
import java.util.concurrent.ExecutionException;
3126
import java.util.concurrent.ExecutorService;
3227
import java.util.concurrent.Executors;
33-
import java.util.concurrent.Future;
34-
import java.util.concurrent.TimeoutException;
3528

3629
import static com.danikula.videocache.Preconditions.checkAllNotNull;
3730
import static com.danikula.videocache.Preconditions.checkNotNull;
38-
import static java.util.concurrent.TimeUnit.MILLISECONDS;
3931

4032
/**
4133
* Simple lightweight proxy server with file caching support that handles HTTP requests.
@@ -59,10 +51,7 @@
5951
public class HttpProxyCacheServer {
6052

6153
private static final Logger LOG = LoggerFactory.getLogger("HttpProxyCacheServer");
62-
6354
private static final String PROXY_HOST = "127.0.0.1";
64-
private static final String PING_REQUEST = "ping";
65-
private static final String PING_RESPONSE = "ping ok";
6655

6756
private final Object clientsLock = new Object();
6857
private final ExecutorService socketProcessor = Executors.newFixedThreadPool(8);
@@ -71,7 +60,7 @@ public class HttpProxyCacheServer {
7160
private final int port;
7261
private final Thread waitConnectionThread;
7362
private final Config config;
74-
private boolean pinged;
63+
private final Pinger pinger;
7564

7665
public HttpProxyCacheServer(Context context) {
7766
this(new Builder(context).buildConfig());
@@ -87,65 +76,16 @@ private HttpProxyCacheServer(Config config) {
8776
this.waitConnectionThread = new Thread(new WaitRequestsRunnable(startSignal));
8877
this.waitConnectionThread.start();
8978
startSignal.await(); // freeze thread, wait for server starts
90-
LOG.info("Proxy cache server started. Ping it...");
91-
makeSureServerWorks();
79+
this.pinger = new Pinger(PROXY_HOST, port);
80+
LOG.info("Proxy cache server started. Is it alive? " + isAlive());
9281
} catch (IOException | InterruptedException e) {
9382
socketProcessor.shutdown();
9483
throw new IllegalStateException("Error starting local proxy server", e);
9584
}
9685
}
9786

98-
private void makeSureServerWorks() {
99-
int maxPingAttempts = 3;
100-
int delay = 300;
101-
int pingAttempts = 0;
102-
while (pingAttempts < maxPingAttempts) {
103-
try {
104-
Future<Boolean> pingFuture = socketProcessor.submit(new PingCallable());
105-
this.pinged = pingFuture.get(delay, MILLISECONDS);
106-
if (this.pinged) {
107-
return;
108-
}
109-
SystemClock.sleep(delay);
110-
} catch (InterruptedException | ExecutionException | TimeoutException e) {
111-
LOG.error("Error pinging server [attempt: " + pingAttempts + ", timeout: " + delay + "]. ", e);
112-
}
113-
pingAttempts++;
114-
delay *= 2;
115-
}
116-
LOG.error("Shutdown server… Error pinging server [attempts: " + pingAttempts + ", max timeout: " + delay / 2 + "]. " +
117-
"If you see this message, please, email me [email protected]");
118-
shutdown();
119-
}
120-
121-
private boolean pingServer() throws ProxyCacheException {
122-
String pingUrl = appendToProxyUrl(PING_REQUEST);
123-
HttpUrlSource source = new HttpUrlSource(pingUrl);
124-
try {
125-
byte[] expectedResponse = PING_RESPONSE.getBytes();
126-
source.open(0);
127-
byte[] response = new byte[expectedResponse.length];
128-
source.read(response);
129-
boolean pingOk = Arrays.equals(expectedResponse, response);
130-
LOG.info("Ping response: `" + new String(response) + "`, pinged? " + pingOk);
131-
return pingOk;
132-
} catch (ProxyCacheException e) {
133-
LOG.error("Error reading ping response", e);
134-
return false;
135-
} finally {
136-
source.close();
137-
}
138-
}
139-
14087
public String getProxyUrl(String url) {
141-
if (!pinged) {
142-
LOG.error("Proxy server isn't pinged. Caching doesn't work. If you see this message, please, email me [email protected]");
143-
}
144-
return pinged ? appendToProxyUrl(url) : url;
145-
}
146-
147-
private String appendToProxyUrl(String url) {
148-
return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
88+
return isAlive() ? appendToProxyUrl(url) : url;
14989
}
15090

15191
public void registerCacheListener(CacheListener cacheListener, String url) {
@@ -210,6 +150,14 @@ public void shutdown() {
210150
}
211151
}
212152

153+
private boolean isAlive() {
154+
return pinger.ping(3, 70); // 70+140+280=max~500ms
155+
}
156+
157+
private String appendToProxyUrl(String url) {
158+
return String.format(Locale.US, "http://%s:%d/%s", PROXY_HOST, port, ProxyCacheUtils.encode(url));
159+
}
160+
213161
private void shutdownClients() {
214162
synchronized (clientsLock) {
215163
for (HttpProxyCacheServerClients clients : clientsMap.values()) {
@@ -236,8 +184,8 @@ private void processSocket(Socket socket) {
236184
GetRequest request = GetRequest.read(socket.getInputStream());
237185
LOG.debug("Request to cache proxy:" + request);
238186
String url = ProxyCacheUtils.decode(request.uri);
239-
if (PING_REQUEST.equals(url)) {
240-
responseToPing(socket);
187+
if (pinger.isPingRequest(url)) {
188+
pinger.responseToPing(socket);
241189
} else {
242190
HttpProxyCacheServerClients clients = getClients(url);
243191
clients.processRequest(request, socket);
@@ -254,12 +202,6 @@ private void processSocket(Socket socket) {
254202
}
255203
}
256204

257-
private void responseToPing(Socket socket) throws IOException {
258-
OutputStream out = socket.getOutputStream();
259-
out.write("HTTP/1.1 200 OK\n\n".getBytes());
260-
out.write(PING_RESPONSE.getBytes());
261-
}
262-
263205
private HttpProxyCacheServerClients getClients(String url) throws ProxyCacheException {
264206
synchronized (clientsLock) {
265207
HttpProxyCacheServerClients clients = clientsMap.get(url);
@@ -354,14 +296,6 @@ public void run() {
354296
}
355297
}
356298

357-
private class PingCallable implements Callable<Boolean> {
358-
359-
@Override
360-
public Boolean call() throws Exception {
361-
return pingServer();
362-
}
363-
}
364-
365299
/**
366300
* Builder for {@link HttpProxyCacheServer}.
367301
*/
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.danikula.videocache;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import java.io.IOException;
7+
import java.io.OutputStream;
8+
import java.net.Socket;
9+
import java.util.Arrays;
10+
import java.util.Locale;
11+
import java.util.concurrent.Callable;
12+
import java.util.concurrent.ExecutionException;
13+
import java.util.concurrent.ExecutorService;
14+
import java.util.concurrent.Executors;
15+
import java.util.concurrent.Future;
16+
import java.util.concurrent.TimeoutException;
17+
18+
import static com.danikula.videocache.Preconditions.checkArgument;
19+
import static com.danikula.videocache.Preconditions.checkNotNull;
20+
import static java.util.concurrent.TimeUnit.MILLISECONDS;
21+
22+
/**
23+
* Pings {@link HttpProxyCacheServer} to make sure it works.
24+
*
25+
* @author Alexey Danilov ([email protected]).
26+
*/
27+
28+
class Pinger {
29+
30+
private static final Logger LOG = LoggerFactory.getLogger("Pinger");
31+
private static final String PING_REQUEST = "ping";
32+
private static final String PING_RESPONSE = "ping ok";
33+
34+
private final ExecutorService pingExecutor = Executors.newSingleThreadExecutor();
35+
private final String host;
36+
private final int port;
37+
38+
Pinger(String host, int port) {
39+
this.host = checkNotNull(host);
40+
this.port = port;
41+
}
42+
43+
boolean ping(int maxAttempts, int startTimeout) {
44+
checkArgument(maxAttempts >= 1);
45+
checkArgument(startTimeout > 0);
46+
47+
int timeout = startTimeout;
48+
int attempts = 0;
49+
while (attempts < maxAttempts) {
50+
try {
51+
Future<Boolean> pingFuture = pingExecutor.submit(new PingCallable());
52+
boolean pinged = pingFuture.get(timeout, MILLISECONDS);
53+
if (pinged) {
54+
return true;
55+
}
56+
} catch (TimeoutException e) {
57+
LOG.warn("Error pinging server (attempt: " + attempts + ", timeout: " + timeout + "). ");
58+
} catch (InterruptedException | ExecutionException e) {
59+
LOG.error("Error pinging server due to unexpected error", e);
60+
}
61+
attempts++;
62+
timeout *= 2;
63+
}
64+
String error = String.format("Error pinging server (attempts: %d, max timeout: %d). " +
65+
"If you see this message, please, email me [email protected] " +
66+
"or create issue here https://github.com/danikula/AndroidVideoCache/issues", attempts, timeout / 2);
67+
LOG.error(error, new ProxyCacheException(error));
68+
return false;
69+
}
70+
71+
boolean isPingRequest(String request) {
72+
return PING_REQUEST.equals(request);
73+
}
74+
75+
void responseToPing(Socket socket) throws IOException {
76+
OutputStream out = socket.getOutputStream();
77+
out.write("HTTP/1.1 200 OK\n\n".getBytes());
78+
out.write(PING_RESPONSE.getBytes());
79+
}
80+
81+
private boolean pingServer() throws ProxyCacheException {
82+
String pingUrl = getPingUrl();
83+
HttpUrlSource source = new HttpUrlSource(pingUrl);
84+
try {
85+
byte[] expectedResponse = PING_RESPONSE.getBytes();
86+
source.open(0);
87+
byte[] response = new byte[expectedResponse.length];
88+
source.read(response);
89+
boolean pingOk = Arrays.equals(expectedResponse, response);
90+
LOG.info("Ping response: `" + new String(response) + "`, pinged? " + pingOk);
91+
return pingOk;
92+
} catch (ProxyCacheException e) {
93+
LOG.error("Error reading ping response", e);
94+
return false;
95+
} finally {
96+
source.close();
97+
}
98+
}
99+
100+
private String getPingUrl() {
101+
return String.format(Locale.US, "http://%s:%d/%s", host, port, PING_REQUEST);
102+
}
103+
104+
private class PingCallable implements Callable<Boolean> {
105+
106+
@Override
107+
public Boolean call() throws Exception {
108+
return pingServer();
109+
}
110+
}
111+
112+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package com.danikula.videocache;
2+
3+
import org.junit.Test;
4+
import org.robolectric.RuntimeEnvironment;
5+
6+
import java.io.ByteArrayOutputStream;
7+
import java.net.Socket;
8+
import java.util.regex.Matcher;
9+
import java.util.regex.Pattern;
10+
11+
import static org.fest.assertions.api.Assertions.assertThat;
12+
import static org.mockito.Mockito.mock;
13+
import static org.mockito.Mockito.when;
14+
15+
/**
16+
* Tests {@link Pinger}.
17+
*
18+
* @author Alexey Danilov ([email protected]).
19+
*/
20+
public class PingerTest extends BaseTest {
21+
22+
@Test
23+
public void testPingSuccess() throws Exception {
24+
HttpProxyCacheServer server = new HttpProxyCacheServer(RuntimeEnvironment.application);
25+
Pinger pinger = new Pinger("127.0.0.1", getPort(server));
26+
boolean pinged = pinger.ping(1, 100);
27+
assertThat(pinged).isTrue();
28+
29+
server.shutdown();
30+
}
31+
32+
@Test
33+
public void testPingFail() throws Exception {
34+
Pinger pinger = new Pinger("127.0.0.1", 33);
35+
boolean pinged = pinger.ping(3, 70);
36+
assertThat(pinged).isFalse();
37+
}
38+
39+
@Test
40+
public void testIsPingRequest() throws Exception {
41+
Pinger pinger = new Pinger("127.0.0.1", 1);
42+
assertThat(pinger.isPingRequest("ping")).isTrue();
43+
assertThat(pinger.isPingRequest("notPing")).isFalse();
44+
}
45+
46+
@Test
47+
public void testResponseToPing() throws Exception {
48+
Pinger pinger = new Pinger("127.0.0.1", 1);
49+
ByteArrayOutputStream out = new ByteArrayOutputStream();
50+
Socket socket = mock(Socket.class);
51+
when(socket.getOutputStream()).thenReturn(out);
52+
pinger.responseToPing(socket);
53+
assertThat(out.toString()).isEqualTo("HTTP/1.1 200 OK\n\nping ok");
54+
}
55+
56+
private int getPort(HttpProxyCacheServer server) {
57+
String proxyUrl = server.getProxyUrl("test");
58+
Pattern pattern = Pattern.compile("http://127.0.0.1:(\\d*)/test");
59+
Matcher matcher = pattern.matcher(proxyUrl);
60+
assertThat(matcher.find()).isTrue();
61+
String portAsString = matcher.group(1);
62+
return Integer.parseInt(portAsString);
63+
}
64+
}

0 commit comments

Comments
 (0)