55 */
66package io .jooby ;
77
8- import javax . annotation . Nonnull ;
9- import javax . annotation . Nullable ;
8+ import static java . util . Collections . singletonList ;
9+
1010import java .io .EOFException ;
1111import java .io .IOException ;
1212import java .net .BindException ;
1313import java .nio .channels .ClosedChannelException ;
1414import java .util .List ;
1515import java .util .Optional ;
16+ import java .util .concurrent .CopyOnWriteArrayList ;
1617import java .util .concurrent .Executor ;
1718import 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}
0 commit comments