/** * Copyright 2016 LinkedIn Corp. All rights reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. */ package com.github.ambry.rest; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpResponseStatus; import io.netty.handler.codec.http.HttpUtil; import io.netty.handler.codec.http.HttpVersion; import io.netty.handler.codec.http.LastHttpContent; import io.netty.util.ReferenceCountUtil; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Responsible for responding to health check requests * {@link RestServerState} assists in knowing the state of the system at any point in time */ public class HealthCheckHandler extends ChannelDuplexHandler { private static final byte[] GOOD = "GOOD".getBytes(); private static final byte[] BAD = "BAD".getBytes(); private final String healthCheckUri; private final RestServerState restServerState; private final NettyMetrics nettyMetrics; private final Logger logger = LoggerFactory.getLogger(getClass()); private HttpRequest request; private FullHttpResponse response; private long startTimeInMs; public HealthCheckHandler(RestServerState restServerState, NettyMetrics nettyMetrics) { this.restServerState = restServerState; this.healthCheckUri = restServerState.getHealthCheckUri(); this.nettyMetrics = nettyMetrics; logger.trace("Created HealthCheckHandler for HealthCheckUri=" + healthCheckUri); } @Override public void channelRead(ChannelHandlerContext ctx, Object obj) throws Exception { logger.trace("Reading on channel {}", ctx.channel()); boolean forwardObj = false; if (obj instanceof HttpRequest) { if (request == null && ((HttpRequest) obj).uri().equals(healthCheckUri)) { nettyMetrics.healthCheckRequestRate.mark(); startTimeInMs = System.currentTimeMillis(); logger.trace("Handling health check request while in state " + restServerState.isServiceUp()); request = (HttpRequest) obj; if (restServerState.isServiceUp()) { response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, Unpooled.wrappedBuffer(GOOD)); HttpUtil.setKeepAlive(response, HttpUtil.isKeepAlive(request)); HttpUtil.setContentLength(response, GOOD.length); } else { response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.SERVICE_UNAVAILABLE, Unpooled.wrappedBuffer(BAD)); HttpUtil.setKeepAlive(response, false); HttpUtil.setContentLength(response, BAD.length); } nettyMetrics.healthCheckRequestProcessingTimeInMs.update(System.currentTimeMillis() - startTimeInMs); } else { // Rest server could be down even if not for health check request. We intentionally don't take any action in this // handler for such cases and leave it to the downstream handlers to handle it forwardObj = true; } } if (obj instanceof LastHttpContent) { if (response != null) { // response was created when we received the request with health check uri ChannelFuture future = ctx.writeAndFlush(response); if (!HttpUtil.isKeepAlive(response)) { future.addListener(ChannelFutureListener.CLOSE); } request = null; response = null; nettyMetrics.healthCheckRequestRoundTripTimeInMs.update(System.currentTimeMillis() - startTimeInMs); } else { // request was not for health check uri forwardObj = true; } } else if (request == null) { // http Content which is not LastHttpContent is not intended for this handler forwardObj = true; } if (forwardObj) { super.channelRead(ctx, obj); } else { ReferenceCountUtil.release(obj); } } @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { if (!restServerState.isServiceUp()) { if (msg instanceof LastHttpContent) { // Start closing client channels after we've completed writing to them (even if they are keep-alive) logger.info("Health check request handler closing connection " + ctx.channel() + " since in shutdown mode."); promise.addListener(ChannelFutureListener.CLOSE); nettyMetrics.healthCheckHandlerChannelCloseOnWriteCount.inc(); } } super.write(ctx, msg, promise); } }