diff --git a/java/google/registry/proxy/ProxyServer.java b/java/google/registry/proxy/ProxyServer.java
index 81cda3920..417255b77 100644
--- a/java/google/registry/proxy/ProxyServer.java
+++ b/java/google/registry/proxy/ProxyServer.java
@@ -129,6 +129,8 @@ public class ProxyServer implements Runnable {
.closeFuture()
.addListener(
(future) -> {
+ logger.atInfo().log(
+ "Connection terminated: %s %s", inboundProtocol.name(), inboundChannel);
// Check if there's a relay connection. In case that the outbound connection
// is not successful, this attribute is not set.
Channel outboundChannel = inboundChannel.attr(RELAY_CHANNEL_KEY).get();
@@ -177,13 +179,15 @@ public class ProxyServer implements Runnable {
Object[] messages = relayBuffer.toArray();
relayBuffer.clear();
for (Object msg : messages) {
- writeToRelayChannel(inboundChannel, outboundChannel, msg);
+ // TODO (jianglai): do not log the message once retry behavior is confirmed.
logger.atInfo().log(
- "Relay retried: %s <-> %s\nFRONTEND: %s\nBACKEND: %s",
+ "Relay retried: %s <-> %s\nFRONTEND: %s\nBACKEND: %s\nMESSAGE: %s",
inboundProtocol.name(),
outboundProtocol.name(),
inboundChannel,
- outboundChannel);
+ outboundChannel,
+ msg);
+ writeToRelayChannel(inboundChannel, outboundChannel, msg, true);
}
// When this outbound connection is closed, try reconnecting if the inbound connection
// is still active.
diff --git a/java/google/registry/proxy/handler/HttpsRelayServiceHandler.java b/java/google/registry/proxy/handler/HttpsRelayServiceHandler.java
index 917934294..92f848f42 100644
--- a/java/google/registry/proxy/handler/HttpsRelayServiceHandler.java
+++ b/java/google/registry/proxy/handler/HttpsRelayServiceHandler.java
@@ -14,13 +14,14 @@
package google.registry.proxy.handler;
-import static com.google.common.base.Preconditions.checkArgument;
import static java.nio.charset.StandardCharsets.UTF_8;
import com.google.common.base.Throwables;
+import com.google.common.collect.ImmutableSet;
import com.google.common.flogger.FluentLogger;
import google.registry.proxy.metric.FrontendMetrics;
import io.netty.buffer.ByteBuf;
+import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPromise;
@@ -58,10 +59,16 @@ import javax.net.ssl.SSLHandshakeException;
*
This handler is session aware and will store all the session cookies that the are contained in
* the HTTP response headers, which are added back to headers of subsequent HTTP requests.
*/
-abstract class HttpsRelayServiceHandler extends ByteToMessageCodec {
+public abstract class HttpsRelayServiceHandler extends ByteToMessageCodec {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+ protected static final ImmutableSet> NON_FATAL_INBOUND_EXCEPTIONS =
+ ImmutableSet.of(ReadTimeoutException.class, SSLHandshakeException.class);
+
+ protected static final ImmutableSet> NON_FATAL_OUTBOUND_EXCEPTIONS =
+ ImmutableSet.of(NonOkHttpResponseException.class);
+
private final Map cookieStore = new LinkedHashMap<>();
private final String relayHost;
private final String relayPath;
@@ -153,12 +160,9 @@ abstract class HttpsRelayServiceHandler extends ByteToMessageCodec {
if (!channelFuture.isSuccess()) {
Throwable cause = channelFuture.cause();
- // If the failure is caused by IllegalArgumentException, we know that it is because we
- // got a non 200 response. This is an expected error from the backend and should not be
- // logged at severe.
- if (Throwables.getRootCause(cause) instanceof IllegalArgumentException) {
+ if (NON_FATAL_OUTBOUND_EXCEPTIONS.contains(Throwables.getRootCause(cause).getClass())) {
logger.atWarning().withCause(channelFuture.cause()).log(
"Outbound exception caught for channel %s", channelFuture.channel());
} else {
@@ -202,4 +200,14 @@ abstract class HttpsRelayServiceHandler extends ByteToMessageCodec extends SimpleChannelInboundHandler {
logger.atSevere().log("Relay channel not specified for channel: %s", channel);
ChannelFuture unusedFuture = channel.close();
} else {
- writeToRelayChannel(channel, relayChannel, msg);
+ writeToRelayChannel(channel, relayChannel, msg, false);
}
}
- public static void writeToRelayChannel(Channel channel, Channel relayChannel, Object msg) {
+ public static void writeToRelayChannel(
+ Channel channel, Channel relayChannel, Object msg, boolean retry) {
ChannelFuture unusedFuture =
relayChannel
.writeAndFlush(msg)
.addListener(
future -> {
if (!future.isSuccess()) {
- logger.atWarning().log(
- "Relay failed: %s --> %s\nINBOUND: %s\nOUTBOUND: %s",
+ // TODO (jianglai): do not log the message once retry behavior is confirmed.
+ logger.atWarning().withCause(future.cause()).log(
+ "Relay failed: %s --> %s\nINBOUND: %s\nOUTBOUND: %s\nMESSAGE: %s",
channel.attr(PROTOCOL_KEY).get().name(),
relayChannel.attr(PROTOCOL_KEY).get().name(),
channel,
- relayChannel);
+ relayChannel,
+ msg);
// If we cannot write to the relay channel and the originating channel has
// a relay buffer (i. e. we tried to relay the frontend to the backend), store
- // the message in the buffer for retry later. Otherwise, we are relaying from
- // the backend to the frontend, and this relay failure cannot be recovered
- // from, we should just kill the relay (frontend) channel, which in turn will
- // kill the backend channel. We should not kill any backend channel while the
- // the frontend channel is open, because that will just trigger a reconnect.
- // It is fine to just save the message object itself, not a clone of it,
- // because if the relay is not successful, its content is not read, therefore
- // its buffer is not cleared.
+ // the message in the buffer for retry later. The relay channel (backend) should
+ // be killed (if it is not already dead, usually the relay is unsuccessful
+ // because the connection is closed), and a new backend channel will re-connect
+ // as long as the frontend channel is open. Otherwise, we are relaying from the
+ // backend to the frontend, and this relay failure cannot be recovered from: we
+ // should just kill the relay (frontend) channel, which in turn will kill the
+ // backend channel. It is fine to just save the message object itself, not a
+ // clone of it, because if the relay is not successful, its content is not read,
+ // therefore its buffer is not cleared.
Queue