@Override
    public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
        final long longPollingTimeoutMillis = getLongPollingTimeoutMillis(req);
        final boolean isHealthy = isHealthy();
        final boolean useLongPolling;
        if (longPollingTimeoutMillis > 0) {
            final String expectedState =
                    Ascii.toLowerCase(req.headers().get(HttpHeaderNames.IF_NONE_MATCH, ""));
            if ("\"healthy\"".equals(expectedState) || "w/\"healthy\"".equals(expectedState)) {
                useLongPolling = isHealthy;
            } else if ("\"unhealthy\"".equals(expectedState) || "w/\"unhealthy\"".equals(expectedState)) {
                useLongPolling = !isHealthy;
            } else {
                useLongPolling = false;
            }
        } else {
            useLongPolling = false;
        }

        final HttpMethod method = ctx.method();
        if (useLongPolling) {
            // Disallow other methods than HEAD/GET for long polling.
            switch (method) {
                case HEAD:
                case GET:
                    break;
                default:
                    throw HttpStatusException.of(HttpStatus.METHOD_NOT_ALLOWED);
            }

            assert healthCheckerListener != null : "healthCheckerListener is null.";
            assert pendingHealthyResponses != null : "pendingHealthyResponses is null.";
            assert pendingUnhealthyResponses != null : "pendingUnhealthyResponses is null.";

            // If healthy, wait until it becomes unhealthy, and vice versa.
            lock.lock();
            try {
                final boolean currentHealthiness = isHealthy();
                if (isHealthy == currentHealthiness) {
                    final HttpResponseWriter res = HttpResponse.streaming();

                    final Set<PendingResponse> pendingResponses = isHealthy ? pendingUnhealthyResponses
                                                                            : pendingHealthyResponses;

                    // Send the initial ack (102 Processing) to let the client know that the request
                    // was accepted.
                    res.write(ping);

                    // Send pings (102 Processing) periodically afterwards.
                    final ScheduledFuture<?> pingFuture;
                    if (pingIntervalMillis != 0 && pingIntervalMillis < longPollingTimeoutMillis) {
                        pingFuture = ctx.eventLoop().withoutContext().scheduleWithFixedDelay(
                                new PingTask(res),
                                pingIntervalMillis, pingIntervalMillis, TimeUnit.MILLISECONDS);
                    } else {
                        pingFuture = null;
                    }

                    // Send 304 Not Modified on timeout.
                    final ScheduledFuture<?> timeoutFuture = ctx.eventLoop().withoutContext().schedule(
                            new TimeoutTask(res), longPollingTimeoutMillis, TimeUnit.MILLISECONDS);

                    final PendingResponse pendingResponse =
                            new PendingResponse(method, res, pingFuture, timeoutFuture);
                    pendingResponses.add(pendingResponse);
                    timeoutFuture.addListener((FutureListener<Object>) f -> {
                        lock.lock();
                        try {
                            pendingResponses.remove(pendingResponse);
                        } finally {
                            lock.unlock();
                        }
                    });

                    updateRequestTimeout(ctx, longPollingTimeoutMillis);

                    // Cancel the scheduled timeout and ping task if the response is closed,
                    // so that they are removed from the event loop's task queue.
                    res.whenComplete().handle((unused1, unused2) -> {
                        pendingResponse.cancelAllScheduledFutures();
                        return null;
                    });
                    return res;
                } else {
                    // State has been changed before we acquire the lock.
                    // Fall through because there's no need for long polling.
                }
            } finally {
                lock.unlock();
            }
        }

        switch (method) {
            case HEAD:
            case GET:
                return newResponse(method, isHealthy);
            case CONNECT:
            case DELETE:
            case OPTIONS:
            case TRACE:
                return HttpResponse.of(HttpStatus.METHOD_NOT_ALLOWED);
        }

        assert method == HttpMethod.POST ||
               method == HttpMethod.PUT ||
               method == HttpMethod.PATCH;

        if (updateHandler == null) {
            return HttpResponse.of(HttpStatus.METHOD_NOT_ALLOWED);
        }

        return HttpResponse.of(updateHandler.handle(ctx, req).thenApply(updateResult -> {
            if (updateResult != null) {
                switch (updateResult) {
                    case HEALTHY:
                        serverHealth.setHealthy(true);
                        break;
                    case UNHEALTHY:
                        serverHealth.setHealthy(false);
                        break;
                }
            }
            return HttpResponse.of(newResponse(method, isHealthy()));
        }));
    }
Returns a new builder which builds a new {@link HealthCheckService}.
core/src/main/java/com/linecorp/armeria/server/healthcheck/HealthCheckService.java