Skip to content

Commit 1d9cd72

Browse files
committed
Customize which error is considered connection lost fix #2369
- Configure connection lost as well as addess in use
1 parent 1457142 commit 1d9cd72

2 files changed

Lines changed: 97 additions & 18 deletions

File tree

jooby/src/main/java/io/jooby/Server.java

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,21 @@
55
*/
66
package io.jooby;
77

8-
import javax.annotation.Nonnull;
9-
import javax.annotation.Nullable;
8+
import static java.util.Collections.singletonList;
9+
1010
import java.io.EOFException;
1111
import java.io.IOException;
1212
import java.net.BindException;
1313
import java.nio.channels.ClosedChannelException;
1414
import java.util.List;
1515
import java.util.Optional;
16+
import java.util.concurrent.CopyOnWriteArrayList;
1617
import java.util.concurrent.Executor;
1718
import java.util.concurrent.atomic.AtomicBoolean;
19+
import java.util.function.Predicate;
20+
21+
import javax.annotation.Nonnull;
22+
import javax.annotation.Nullable;
1823

1924
/**
2025
* Web server contract. Defines operations to start, join and stop a web server. Jooby comes
@@ -49,6 +54,35 @@ public interface Server {
4954
*/
5055
abstract class Base implements Server {
5156

57+
private static final Predicate<Throwable> CONNECTION_LOST = cause -> {
58+
if (cause instanceof IOException) {
59+
String message = cause.getMessage();
60+
if (message != null) {
61+
String msg = message.toLowerCase();
62+
return msg.contains("reset by peer")
63+
|| msg.contains("broken pipe")
64+
|| msg.contains("forcibly closed")
65+
|| msg.contains("connection reset");
66+
}
67+
}
68+
return (cause instanceof ClosedChannelException) || (cause instanceof EOFException);
69+
};
70+
71+
private static final Predicate<Throwable> ADDRESS_IN_USE = cause ->
72+
(cause instanceof BindException) ||
73+
(Optional.ofNullable(cause)
74+
.map(Throwable::getMessage)
75+
.map(String::toLowerCase)
76+
.filter(msg -> msg.contains("address already in use"))
77+
.isPresent()
78+
);
79+
80+
private static final List<Predicate<Throwable>> connectionLostListeners =
81+
new CopyOnWriteArrayList<>(singletonList(CONNECTION_LOST));
82+
83+
private static final List<Predicate<Throwable>> addressInUseListeners =
84+
new CopyOnWriteArrayList<>(singletonList(ADDRESS_IN_USE));
85+
5286
private static final boolean useShutdownHook = Boolean
5387
.parseBoolean(System.getProperty("jooby.useShutdownHook", "true"));
5488

@@ -127,6 +161,28 @@ protected void addShutdownHook() {
127161
*/
128162
@Nonnull Server stop();
129163

164+
/**
165+
* Add a connection lost predicate. On unexpected exception, this method allows to customize which
166+
* error are considered connection lost. If the error is considered a connection lost, no log
167+
* statement will be emitted by the application.
168+
*
169+
* @param predicate Customize connection lost error.
170+
*/
171+
static void addConnectionLost(@Nonnull Predicate<Throwable> predicate) {
172+
Base.connectionLostListeners.add(predicate);
173+
}
174+
175+
/**
176+
* Add an address in use predicate. On unexpected exception, this method allows to customize which
177+
* error are considered address in use. If the error is considered an address in use, no log
178+
* statement will be emitted by the application.
179+
*
180+
* @param predicate Customize connection lost error.
181+
*/
182+
static void addAddressInUse(@Nonnull Predicate<Throwable> predicate) {
183+
Base.addressInUseListeners.add(predicate);
184+
}
185+
130186
/**
131187
* Test whenever the given exception is a connection-lost. Connection-lost exceptions don't
132188
* generate log.error statements.
@@ -135,17 +191,12 @@ protected void addShutdownHook() {
135191
* @return True for connection lost.
136192
*/
137193
static boolean connectionLost(@Nullable Throwable cause) {
138-
if (cause instanceof IOException) {
139-
String message = cause.getMessage();
140-
if (message != null) {
141-
String msg = message.toLowerCase();
142-
return msg.contains("reset by peer")
143-
|| msg.contains("broken pipe")
144-
|| msg.contains("forcibly closed")
145-
|| msg.contains("connection reset");
194+
for (Predicate<Throwable> connectionLost : Base.connectionLostListeners) {
195+
if (connectionLost.test(cause)) {
196+
return true;
146197
}
147198
}
148-
return (cause instanceof ClosedChannelException) || (cause instanceof EOFException);
199+
return false;
149200
}
150201

151202
/**
@@ -156,12 +207,11 @@ static boolean connectionLost(@Nullable Throwable cause) {
156207
* @return True address alaredy in use.
157208
*/
158209
static boolean isAddressInUse(@Nullable Throwable cause) {
159-
return (cause instanceof BindException)
160-
|| (Optional.ofNullable(cause)
161-
.map(Throwable::getMessage)
162-
.map(String::toLowerCase)
163-
.filter(msg -> msg.contains("address already in use"))
164-
.isPresent()
165-
);
210+
for (Predicate<Throwable> addressInUse : Base.addressInUseListeners) {
211+
if (addressInUse.test(cause)) {
212+
return true;
213+
}
214+
}
215+
return false;
166216
}
167217
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.jooby;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import org.junit.jupiter.api.Test;
7+
8+
public class Issue2369 {
9+
10+
@Test
11+
public void shouldCustomizeServerLostException() {
12+
Throwable cause = new IllegalArgumentException();
13+
14+
Server.addConnectionLost(it -> it == cause);
15+
16+
assertTrue(Server.connectionLost(cause));
17+
assertFalse(Server.connectionLost(new IllegalArgumentException()));
18+
}
19+
20+
@Test
21+
public void shouldCustomizeAddressInUseException() {
22+
Throwable cause = new IllegalArgumentException();
23+
24+
Server.addAddressInUse(it -> it == cause);
25+
26+
assertTrue(Server.isAddressInUse(cause));
27+
assertFalse(Server.isAddressInUse(new IllegalArgumentException()));
28+
}
29+
}

0 commit comments

Comments
 (0)