/**
 *
 * Copyright 2018 Google LLC
 *
 * 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
 *
 *     https://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.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include "net/grpc/gateway/frontend/nginx_http_frontend.h"

#include <algorithm>

#include "net/grpc/gateway/frontend/nginx_bridge.h"
#include "net/grpc/gateway/log.h"
#include "net/grpc/gateway/nginx_utils.h"
#include "net/grpc/gateway/runtime/constants.h"
#include "net/grpc/gateway/runtime/request.h"
#include "net/grpc/gateway/runtime/runtime.h"
#include "net/grpc/gateway/runtime/tag.h"
#include "third_party/grpc/include/grpc/support/log.h"

#ifdef __cplusplus
extern "C" {
#endif

static ngx_chain_t *ngx_chain_seek_to_last(ngx_chain_t *chain) {
  while (chain->next) {
    chain = chain->next;
  }
  return chain;
}

// TODO(fengli): Defines the interface between C and C++.
ngx_int_t grpc_gateway_init_process(ngx_cycle_t *cycle) {
  grpc::gateway::Runtime::Get().Init();
  return NGX_OK;
}

void grpc_gateway_exit_process(ngx_cycle_t *cycle) {
  grpc::gateway::Runtime::Get().Shutdown();
}

extern ngx_module_t grpc_gateway_module;
grpc::gateway::Frontend *get_frontend(ngx_http_request_t *r) {
  grpc_gateway_request_context *context =
      static_cast<grpc_gateway_request_context *>(
          ngx_http_get_module_ctx(r, grpc_gateway_module));
  return context->frontend.get();
}

void grpc_gateway_request_cleanup_handler(void *data) {
  grpc_gateway_request_context *context =
      static_cast<grpc_gateway_request_context *>(data);
  auto *frontend =
      static_cast<grpc::gateway::NginxHttpFrontend *>(context->frontend.get());
  frontend->StopClientLivenessDetection();
  frontend->set_http_request(nullptr);
  context->frontend.reset();
}

ngx_int_t grpc_gateway_handler(ngx_http_request_t *r) {
  if (r->headers_in.content_type &&
      strncasecmp(
          grpc::gateway::kContentTypeGrpc,
          reinterpret_cast<char *>(r->headers_in.content_type->value.data),
          r->headers_in.content_type->value.len) != 0 &&
      r->method != NGX_HTTP_POST) {
    // Only POST method is allowed for GRPC-Web.
    return NGX_HTTP_NOT_ALLOWED;
  }
  ngx_grpc_gateway_loc_conf_t *mlcf =
      static_cast<ngx_grpc_gateway_loc_conf_t *>(
          ngx_http_get_module_loc_conf(r, grpc_gateway_module));
  std::string backend_address(reinterpret_cast<char *>(mlcf->grpc_pass.data),
                              mlcf->grpc_pass.len);
  std::string backend_host(reinterpret_cast<char *>(r->host_start),
                           r->host_end - r->host_start);
  std::string backend_method(reinterpret_cast<char *>(r->uri.data), r->uri.len);
  std::string backend_ssl_target_name_override(
      reinterpret_cast<char *>(mlcf->grpc_ssl_target_name_override.data),
      mlcf->grpc_ssl_target_name_override.len);
  std::string backend_ssl_pem_root_certs(
      reinterpret_cast<char *>(mlcf->grpc_ssl_pem_root_certs.data),
      mlcf->grpc_ssl_pem_root_certs.len);
  std::string backend_ssl_pem_private_key(
      reinterpret_cast<char *>(mlcf->grpc_ssl_pem_private_key.data),
      mlcf->grpc_ssl_pem_private_key.len);
  std::string backend_ssl_pem_cert_chain(
      reinterpret_cast<char *>(mlcf->grpc_ssl_pem_cert_chain.data),
      mlcf->grpc_ssl_pem_cert_chain.len);

  // Initiate nginx request context.
  grpc_gateway_request_context *context =
      static_cast<grpc_gateway_request_context *>(
          ngx_pcalloc(r->pool, sizeof(grpc_gateway_request_context)));
  if (context == nullptr) {
    return NGX_HTTP_INTERNAL_SERVER_ERROR;
  }
  context->frontend = grpc::gateway::Runtime::Get().CreateNginxFrontend(
      r, backend_address, backend_host, backend_method,
      mlcf->grpc_channel_reuse, mlcf->grpc_client_liveness_detection_interval,
      mlcf->grpc_ssl, backend_ssl_target_name_override,
      backend_ssl_pem_root_certs, backend_ssl_pem_private_key,
      backend_ssl_pem_cert_chain);
  ngx_http_set_ctx(r, context, grpc_gateway_module);
  ngx_pool_cleanup_t *http_cleanup =
      ngx_pool_cleanup_add(r->pool, sizeof(grpc_gateway_request_context));
  http_cleanup->data = context;
  http_cleanup->handler = grpc_gateway_request_cleanup_handler;
  context->frontend->Start();
  return NGX_DONE;
}

void continue_read_request_body(ngx_http_request_t *r) {
  static_cast<grpc::gateway::NginxHttpFrontend *>(get_frontend(r))
      ->ContinueReadRequestBody();
}

void continue_write_response(ngx_http_request_t *r) {
  if (ngx_http_output_filter(r, nullptr) == NGX_AGAIN) {
    r->write_event_handler = continue_write_response;
  } else {
    r->write_event_handler = ngx_http_request_empty_handler;
  }
  if (ngx_handle_write_event(r->connection->write, 0) != NGX_OK) {
    ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
  }
}

void client_liveness_detection_handler(ngx_event_t *event) {
  static_cast<::grpc::gateway::NginxHttpFrontend *>(
      static_cast<ngx_connection_t *>(event->data)->data)
      ->OnClientLivenessDetectionEvent(event);
}

#ifdef __cplusplus
}
#endif

namespace grpc {
namespace gateway {

NginxHttpFrontend::NginxHttpFrontend(std::unique_ptr<Backend> backend)
    : Frontend(std::move(backend)),
      http_request_(nullptr),
      request_protocol_(UNKNOWN),
      response_protocol_(UNKNOWN),
      is_request_half_closed_(false),
      is_request_half_closed_sent_(false),
      is_request_init_metadata_sent_(false),
      is_response_http_headers_sent_(false),
      is_response_status_sent_(false),
      client_liveness_detection_timer_(nullptr),
      client_liveness_detection_interval_(0),
      client_liveness_detection_timer_connection_(nullptr) {}

NginxHttpFrontend::~NginxHttpFrontend() { StopClientLivenessDetection(); }

void NginxHttpFrontend::Start() {
  if (!encoder_ || !decoder_) {
    DEBUG("Bad request received.");
    ngx_http_finalize_request(http_request_, NGX_HTTP_BAD_REQUEST);
    return;
  }

  backend()->Start();
  // Enable request streaming.
  if (request_protocol_ == Protocol::B64_PROTO ||
      request_protocol_ == Protocol::PROTO) {
    http_request_->request_body_no_buffering = false;
  } else {
    http_request_->request_body_no_buffering = true;
  }

  if (client_liveness_detection_interval_ > 0) {
    // Initialize the dummy connection of client liveness detection timer.
    client_liveness_detection_timer_connection_ =
        static_cast<ngx_connection_t *>(
            ngx_pcalloc(http_request_->pool, sizeof(ngx_connection_t)));
    client_liveness_detection_timer_connection_->fd =
        static_cast<ngx_socket_t>(-1);
    client_liveness_detection_timer_connection_->data = this;

    // Initialize the client liveness detection timer.
    client_liveness_detection_timer_ = static_cast<ngx_event_t *>(
        ngx_pcalloc(http_request_->pool, sizeof(ngx_event_t)));
    client_liveness_detection_timer_->log = http_request_->connection->log;
    client_liveness_detection_timer_->handler =
        client_liveness_detection_handler;
    client_liveness_detection_timer_->data =
        client_liveness_detection_timer_connection_;
  }

  ngx_int_t rc = ngx_http_read_client_request_body(http_request_,
                                                   continue_read_request_body);
  if (rc >= NGX_HTTP_BAD_REQUEST) {
    DEBUG("ngx_http_read_client_request_body failed, rc = %" PRIdPTR ".", rc);
    SendErrorToClient(grpc::Status(grpc::StatusCode::INTERNAL,
                                   "Failed to read request body."));
    return;
  }
}

void NginxHttpFrontend::ContinueReadRequestBody() {
  if (http_request_ == nullptr) {
    return;
  }
  http_request_->read_event_handler = continue_read_request_body;
  if (http_request_->stream == nullptr &&
      (http_request_->connection->read->pending_eof ||
       http_request_->connection->read->eof)) {
    // FIN or RST from client received.
    DEBUG("receive FIN from peer, close the HTTP connection.");
    backend()->Cancel(grpc::Status::CANCELLED);
    ngx_http_finalize_request(http_request_, NGX_DONE);
    return;
  }

  if (!http_request_->reading_body) {
    if (!is_request_half_closed_) {
      is_request_half_closed_ = true;
      DEBUG("http_request has been finished, send request half close.");
      SendRequestToBackend();
    }
    return;
  }

  while (true) {
    ngx_int_t rc = ngx_http_read_unbuffered_request_body(http_request_);
    DEBUG("ngx_http_read_unbuffered_request_body = %" PRIdPTR ".", rc);
    if (http_request_->request_body->bufs == nullptr) {
      return;
    }
    if (rc == NGX_AGAIN) {
      DEBUG("request has not been finished yet, request_length = %lli.",
            static_cast<long long>(http_request_->request_length));
      bool sent = SendRequestToBackend();
      if (is_response_status_sent_) {
        return;
      }
      if (!sent) {
        DEBUG("no enough data to decode, continue when more data comes.");
        http_request_->read_event_handler = continue_read_request_body;
        continue;
      }
      DEBUG("message sent to backend, continue when finish sending.");
      http_request_->read_event_handler = ngx_http_request_empty_handler;
      return;
    }
    if (rc == NGX_OK) {
      if (!is_request_half_closed_) {
        is_request_half_closed_ = true;
        DEBUG(
            "ngx_http_read_unbuffered_request_body returns NGX_OK, send "
            "request "
            "half close.");
        SendRequestToBackend();
        if (is_response_status_sent_) {
          return;
        }
      }
      http_request_->request_body->bufs = nullptr;
      return;
    }
  }
}

void NginxHttpFrontend::SendResponseMessageToClient(Response *response) {
  if (response->message() != nullptr) {
    std::vector<Slice> transcoded_message;
    ByteBuffer buffer(response->message()->data(), response->message()->size());
    DEBUG("Sends response message, size: %" PRIdPTR ".", buffer.Length());
    encoder_->Encode(&buffer, &transcoded_message);
    if (!transcoded_message.empty()) {
      ngx_chain_t *output = ngx_alloc_chain_link(http_request_->pool);
      if (output == nullptr) {
        ERROR("Failed to allocate response buffer for GRPC response message.");
      }
      output->buf = nullptr;
      output->next = nullptr;
      for (Slice &slice : transcoded_message) {
        ngx_buf_t *buffer =
            reinterpret_cast<ngx_buf_t *>(ngx_calloc_buf(http_request_->pool));
        uint8_t *data = reinterpret_cast<uint8_t *>(
            ngx_palloc(http_request_->pool, slice.size()));
        memcpy(data, slice.begin(), slice.size());
        buffer->start = data;
        buffer->pos = buffer->start;
        buffer->end = data + slice.size();
        buffer->last = buffer->end;
        buffer->temporary = 1;
        ngx_chain_t *last_chain = ngx_chain_seek_to_last(output);
        if (last_chain->buf == nullptr) {
          last_chain->buf = buffer;
          last_chain->next = nullptr;
        } else {
          last_chain->next = ngx_alloc_chain_link(http_request_->pool);
          last_chain->next->buf = buffer;
          last_chain->next->next = nullptr;
        }
      }
      ngx_chain_seek_to_last(output)->buf->flush = 1;
      if (ngx_http_output_filter(http_request_, output) == NGX_AGAIN) {
        http_request_->write_event_handler = continue_write_response;
      }
      if (ngx_handle_write_event(http_request_->connection->write, 0) !=
          NGX_OK) {
        ngx_http_finalize_request(http_request_,
                                  NGX_HTTP_INTERNAL_SERVER_ERROR);
      }
    }
  }
}

void NginxHttpFrontend::SendResponseStatusToClient(Response *response) {
  if (response->status() == nullptr) {
    return;
  }
  if (is_response_status_sent_) {
    return;
  }
  is_response_status_sent_ = true;

  StopClientLivenessDetection();

  std::vector<Slice> trancoded_status;
  if (request_protocol_ == Protocol::GRPC) {
    SendResponseTrailersToClient(response);
  } else {
    encoder_->EncodeStatus(*response->status(), response->trailers(),
                           &trancoded_status);
  }
  ngx_chain_t *output = ngx_alloc_chain_link(http_request_->pool);
  if (output == nullptr) {
    ERROR("Failed to allocate response buffer for GRPC response message.");
    ngx_abort();
  }
  output->buf = nullptr;
  output->next = nullptr;
  for (Slice &slice : trancoded_status) {
    ngx_buf_t *buffer =
        reinterpret_cast<ngx_buf_t *>(ngx_calloc_buf(http_request_->pool));
    uint8_t *data = reinterpret_cast<uint8_t *>(
        ngx_palloc(http_request_->pool, slice.size()));
    memcpy(data, slice.begin(), slice.size());
    buffer->start = data;
    buffer->pos = buffer->start;
    buffer->end = data + slice.size();
    buffer->last = buffer->end;
    buffer->temporary = 1;
    ngx_chain_t *last_chain = ngx_chain_seek_to_last(output);
    if (last_chain->buf == nullptr) {
      last_chain->buf = buffer;
      last_chain->next = nullptr;
    } else {
      last_chain->next = ngx_alloc_chain_link(http_request_->pool);
      last_chain->next->buf = buffer;
      last_chain->next->next = nullptr;
    }
  }

  if (output->buf != nullptr) {
    ngx_chain_t *last = ngx_chain_seek_to_last(output);
    if (last->buf == nullptr) {
      last->buf =
          reinterpret_cast<ngx_buf_t *>(ngx_calloc_buf(http_request_->pool));
    }
    last->buf->flush = 1;
    last->buf->last_buf = 1;
    ngx_http_output_filter(http_request_, output);
  } else {
    ngx_http_send_special(http_request_, NGX_HTTP_LAST);
  }
  ngx_http_finalize_request(http_request_, NGX_OK);
}

void NginxHttpFrontend::Send(std::unique_ptr<Response> response) {
  if (http_request_ == nullptr || http_request_->connection == nullptr ||
      http_request_->connection->destroyed) {
    // The HTTP request/connection has been terminated, do nothing.
    return;
  }
  SendResponseHeadersToClient(response.get());
  SendResponseMessageToClient(response.get());
  SendResponseStatusToClient(response.get());
}

void NginxHttpFrontend::AddRequestInitialMetadataOnce(
    const std::unique_ptr<Request> &request) {
  if (!is_request_init_metadata_sent_) {
    is_request_init_metadata_sent_ = true;
    request->set_headers(std::unique_ptr<Headers>(new Headers()));
    ngx_list_part_t *part;
    ngx_table_elt_t *h;
    part = &http_request_->headers_in.headers.part;
    while (part != nullptr) {
      h = static_cast<ngx_table_elt_t *>(part->elts);
      for (ngx_uint_t i = 0; i < part->nelts; i++) {
        request->headers()->push_back(Header(
            std::string(reinterpret_cast<char *>(h[i].key.data), h[i].key.len),
            string_ref(reinterpret_cast<char *>(h[i].value.data),
                       h[i].value.len)));
      }
      part = part->next;
    }
  }
}

Status NginxHttpFrontend::DecodeRequestBody() {
  ngx_http_request_body_t *request_body = http_request_->request_body;
  ngx_chain_t *buffers = request_body->bufs;
  if (buffers != nullptr) {
    while (buffers) {
      ngx_buf_t *buffer = buffers->buf;
      if (buffer->in_file) {
        if (buffer->file_pos >= buffer->file_last) {
          buffers = buffers->next;
          continue;
        }
        grpc_slice slice =
            grpc_slice_malloc(buffer->file_last - buffer->file_pos);
        ssize_t size = ngx_read_file(buffer->file, GRPC_SLICE_START_PTR(slice),
                                     GRPC_SLICE_LENGTH(slice), 0);
        GPR_ASSERT(size >= 0 &&
                   static_cast<size_t>(size) == GRPC_SLICE_LENGTH(slice));
        decoder_->Append(Slice(slice, Slice::STEAL_REF));
        buffer->file_pos = buffer->file_last;
      } else {
        if (buffer->pos >= buffer->last) {
          buffers = buffers->next;
          continue;
        }
        decoder_->Append(Slice(
            grpc_slice_from_copied_buffer(reinterpret_cast<char *>(buffer->pos),
                                          buffer->last - buffer->pos),
            Slice::STEAL_REF));
        buffer->pos = buffer->last;
      }
    }
    request_body->bufs = nullptr;
    request_body->busy = nullptr;
  }
  return decoder_->Decode();
}

void NginxHttpFrontend::AddRequestMessage(
    const std::unique_ptr<Request> &request) {
  std::unique_ptr<ByteBuffer> message = std::move(decoder_->results()->front());
  decoder_->results()->pop_front();
  std::vector<Slice> slices;
  message->Dump(&slices);
  request->set_message(std::unique_ptr<Message>(new Message()));
  for (auto &slice : slices) {
    request->message()->push_back(slice);
  }
}

bool NginxHttpFrontend::SendRequestToBackend() {
  std::unique_ptr<Request> request(new Request());
  AddRequestInitialMetadataOnce(request);
  Status status = DecodeRequestBody();
  if (!status.ok()) {
    // Terminate backend.

    // Send back response.
    SendErrorToClient(status);
    return false;
  }

  if (!decoder_->results()->empty()) {
    AddRequestMessage(request);
  }

  if (decoder_->results()->empty() && is_request_half_closed_) {
    request->set_final(is_request_half_closed_);
    is_request_half_closed_sent_ = true;
  }

  if (request->empty()) {
    return false;
  }

  backend()->Send(
      std::move(request),
      BindTo(this, this, &NginxHttpFrontend::SendRequestToBackendDone));
  return true;
}

void NginxHttpFrontend::SendRequestToBackendDone(bool result) {
  if (http_request_ == nullptr) {
    // Nginx HTTP request has been terminated, do nothing.
    return;
  }

  if (!result) {
    // GRPC failed, the status will come later, return here.
    return;
  }

  if (!decoder_->results()->empty()) {
    // Have pending request messages.
    SendRequestToBackend();
    return;
  }

  if (!is_request_half_closed_) {
    ContinueReadRequestBody();
    return;
  }
}

void NginxHttpFrontend::SendResponseHeadersToClient(Response *response) {
  if (is_response_http_headers_sent_) {
    return;
  }
  is_response_http_headers_sent_ = true;

  http_request_->headers_out.status = NGX_HTTP_OK;
  if (response != nullptr && response->headers() != nullptr) {
    for (auto &header : *response->headers()) {
      if (header.first == kContentLength || header.first == kContentType ||
          header.first == kContentTransferEncoding) {
        continue;
      }
      AddHTTPHeader(http_request_, header.first, header.second);
    }
  }
  switch (response_protocol_) {
    case GRPC:
      AddHTTPHeader(http_request_, kGrpcEncoding, kGrpcEncoding_Identity);
      AddHTTPHeader(http_request_, kContentType, kContentTypeGrpc);
      AddHTTPHeader(http_request_, kGrpcAcceptEncoding,
                    kGrpcAcceptEncoding_AcceptAll);
      break;
    case GRPC_WEB:
      AddHTTPHeader(http_request_, kContentType, kContentTypeGrpcWeb);
      break;
    case GRPC_WEB_TEXT:
      AddHTTPHeader(http_request_, kContentType, kContentTypeGrpcWebText);
      break;
    case JSON_STREAM_BODY:
      AddHTTPHeader(http_request_, kContentType, kContentTypeJson);
      break;
    case PROTO:
      AddHTTPHeader(http_request_, kContentType, kContentTypeProto);
      break;
    case PROTO_STREAM_BODY:
      AddHTTPHeader(http_request_, kContentType, kContentTypeStreamBody);
      break;
    case B64_PROTO:
      AddHTTPHeader(http_request_, kContentType, kContentTypeProto);
      AddHTTPHeader(http_request_, kContentTransferEncoding,
                    kContentTransferEncoding_Base64);
      break;
    case B64_PROTO_STREAM_BODY:
      AddHTTPHeader(http_request_, kContentType, kContentTypeStreamBody);
      AddHTTPHeader(http_request_, kContentTransferEncoding,
                    kContentTransferEncoding_Base64);
      break;
    default: {
      // Intended to skip.
    }
  }
  ngx_int_t rc = ngx_http_send_header(http_request_);
  if (rc != NGX_OK) {
    ERROR("Failed to send HTTP response headers via nginx, rc = %" PRIdPTR ".",
          rc);
  }

  // Enable keepalive timer.
  if ((response_protocol_ == Protocol::B64_PROTO_STREAM_BODY ||
       response_protocol_ == Protocol::PROTO_STREAM_BODY) &&
      client_liveness_detection_interval_ > 0) {
    ngx_event_add_timer(client_liveness_detection_timer_,
                        client_liveness_detection_interval_);
  }
}

void NginxHttpFrontend::SendResponseTrailersToClient(Response *response) {
  http_request_->headers_out.status = NGX_HTTP_OK;
  http_request_->expect_trailers = 1;
  if (response != nullptr) {
    AddHTTPTrailer(
        http_request_, kGrpcStatus,
        string_ref(std::to_string(response->status()->error_code())));
    if (!response->status()->error_message().empty()) {
      AddHTTPTrailer(http_request_, kGrpcMessage,
                     response->status()->error_message());
    }
    if (response->trailers()) {
      for (auto &trailer : *response->trailers()) {
        AddHTTPTrailer(http_request_, trailer.first, trailer.second);
      }
    }
  }
}

void NginxHttpFrontend::SendErrorToClient(const grpc::Status &status) {
  backend()->Cancel(status);
}

void NginxHttpFrontend::WriteToNginxResponse(uint8_t *data, size_t size) {
  ngx_chain_t *output = ngx_alloc_chain_link(http_request_->pool);
  if (output == nullptr) {
    ERROR("Failed to allocate response buffer for keep alive message.");
  }
  output->buf = nullptr;
  output->next = nullptr;
  ngx_buf_t *buffer =
      reinterpret_cast<ngx_buf_t *>(ngx_calloc_buf(http_request_->pool));
  buffer->start = data;
  buffer->pos = buffer->start;
  buffer->end = data + size;
  buffer->last = buffer->end;
  buffer->temporary = 1;
  ngx_chain_t *last_chain = ngx_chain_seek_to_last(output);
  if (last_chain->buf == nullptr) {
    last_chain->buf = buffer;
    last_chain->next = nullptr;
  } else {
    last_chain->next = ngx_alloc_chain_link(http_request_->pool);
    last_chain->next->buf = buffer;
    last_chain->next->next = nullptr;
  }
  ngx_chain_seek_to_last(output)->buf->flush = 1;
  if (ngx_http_output_filter(http_request_, output) == NGX_AGAIN) {
    http_request_->write_event_handler = continue_write_response;
  }
  if (ngx_handle_write_event(http_request_->connection->write, 0) != NGX_OK) {
    ngx_http_finalize_request(http_request_, NGX_HTTP_INTERNAL_SERVER_ERROR);
  }
}  // namespace gateway

void NginxHttpFrontend::OnClientLivenessDetectionEvent(ngx_event_t *event) {
  if (http_request_ == nullptr) {
    // The HTTP request has been finalized.
    return;
  }

  if (response_protocol_ == Protocol::PROTO_STREAM_BODY) {
    uint8_t *data =
        reinterpret_cast<uint8_t *>(ngx_palloc(http_request_->pool, 2));
    *data = 0b01111010;
    *(data + 1) = 0;
    WriteToNginxResponse(data, 2);
  } else if (request_protocol_ == Protocol::B64_PROTO_STREAM_BODY) {
    uint8_t *data =
        reinterpret_cast<uint8_t *>(ngx_palloc(http_request_->pool, 4));
    *data = 'e';
    *(data + 1) = 'g';
    *(data + 2) = 'E';
    *(data + 3) = 'A';
    WriteToNginxResponse(data, 4);
  }

  if (client_liveness_detection_interval_ > 0) {
    ngx_event_add_timer(client_liveness_detection_timer_,
                        client_liveness_detection_interval_);
  }
}

void NginxHttpFrontend::StopClientLivenessDetection() {
  if (client_liveness_detection_timer_ != nullptr) {
    if (client_liveness_detection_timer_->timer_set) {
      ngx_event_del_timer(client_liveness_detection_timer_);
    }
    client_liveness_detection_timer_ = nullptr;
  }
}
}  // namespace gateway
}  // namespace grpc
