syntax = "proto3";

package envoy.service.ext_proc.v3;

import "envoy/config/core/v3/base.proto";
import "envoy/extensions/filters/http/ext_proc/v3/processing_mode.proto";
import "envoy/type/v3/http_status.proto";

import "google/protobuf/struct.proto";

import "xds/annotations/v3/status.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.service.ext_proc.v3";
option java_outer_classname = "ExternalProcessorProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/service/ext_proc/v3;ext_procv3";
option java_generic_services = true;
option (udpa.annotations.file_status).package_version_status = ACTIVE;
option (xds.annotations.v3.file_status).work_in_progress = true;

// [#protodoc-title: External Processing Service]

// A service that can access and modify HTTP requests and responses
// as part of a filter chain.
// The overall external processing protocol works like this:
//
// 1. Envoy sends to the service information about the HTTP request.
// 2. The service sends back a ProcessingResponse message that directs Envoy
//    to either stop processing, continue without it, or send it the
//    next chunk of the message body.
// 3. If so requested, Envoy sends the server chunks of the message body,
//    or the entire body at once. In either case, the server sends back
//    a ProcessingResponse after each message it receives.
// 4. If so requested, Envoy sends the server the HTTP trailers,
//    and the server sends back a ProcessingResponse.
// 5. At this point, request processing is done, and we pick up again
//    at step 1 when Envoy receives a response from the upstream server.
// 6. At any point above, if the server closes the gRPC stream cleanly,
//    then Envoy proceeds without consulting the server.
// 7. At any point above, if the server closes the gRPC stream with an error,
//    then Envoy returns a 500 error to the client, unless the filter
//    was configured to ignore errors.
//
// In other words, the process is a request/response conversation, but
// using a gRPC stream to make it easier for the server to
// maintain state.

service ExternalProcessor {
  // This begins the bidirectional stream that Envoy will use to
  // give the server control over what the filter does. The actual
  // protocol is described by the ProcessingRequest and ProcessingResponse
  // messages below.
  rpc Process(stream ProcessingRequest) returns (stream ProcessingResponse) {
  }
}

// This represents the different types of messages that Envoy can send
// to an external processing server.
// [#next-free-field: 8]
message ProcessingRequest {
  // Specify whether the filter that sent this request is running in synchronous
  // or asynchronous mode. The choice of synchronous or asynchronous mode
  // can be set in the filter configuration, and defaults to false.
  //
  // * A value of "false" indicates that the server must respond
  //   to this message by either sending back a matching ProcessingResponse message,
  //   or by closing the stream.
  // * A value of "true" indicates that the server must not respond to this
  //   message, although it may still close the stream to indicate that no more messages
  //   are needed.
  //
  bool async_mode = 1;

  // Each request message will include one of the following sub-messages. Which
  // ones are set for a particular HTTP request/response depend on the
  // processing mode.
  oneof request {
    option (validate.required) = true;

    // Information about the HTTP request headers, as well as peer info and additional
    // properties. Unless "async_mode" is true, the server must send back a
    // HeaderResponse message, an ImmediateResponse message, or close the stream.
    HttpHeaders request_headers = 2;

    // Information about the HTTP response headers, as well as peer info and additional
    // properties. Unless "async_mode" is true, the server must send back a
    // HeaderResponse message or close the stream.
    HttpHeaders response_headers = 3;

    // A chunk of the HTTP request body. Unless "async_mode" is true, the server must send back
    // a BodyResponse message, an ImmediateResponse message, or close the stream.
    HttpBody request_body = 4;

    // A chunk of the HTTP request body. Unless "async_mode" is true, the server must send back
    // a BodyResponse message or close the stream.
    HttpBody response_body = 5;

    // The HTTP trailers for the request path. Unless "async_mode" is true, the server
    // must send back a TrailerResponse message or close the stream.
    //
    // This message is only sent if the trailers processing mode is set to "SEND".
    // If there are no trailers on the original downstream request, then this message
    // will only be sent (with empty trailers waiting to be populated) if the
    // processing mode is set before the request headers are sent, such as
    // in the filter configuration.
    HttpTrailers request_trailers = 6;

    // The HTTP trailers for the response path. Unless "async_mode" is true, the server
    // must send back a TrailerResponse message or close the stream.
    //
    // This message is only sent if the trailers processing mode is set to "SEND".
    // If there are no trailers on the original downstream request, then this message
    // will only be sent (with empty trailers waiting to be populated) if the
    // processing mode is set before the request headers are sent, such as
    // in the filter configuration.
    HttpTrailers response_trailers = 7;
  }
}

// For every ProcessingRequest received by the server with the "async_mode" field
// set to false, the server must send back exactly one ProcessingResponse message.
// [#next-free-field: 10]
message ProcessingResponse {
  oneof response {
    option (validate.required) = true;

    // The server must send back this message in response to a message with the
    // "request_headers" field set.
    HeadersResponse request_headers = 1;

    // The server must send back this message in response to a message with the
    // "response_headers" field set.
    HeadersResponse response_headers = 2;

    // The server must send back this message in response to a message with
    // the "request_body" field set.
    BodyResponse request_body = 3;

    // The server must send back this message in response to a message with
    // the "response_body" field set.
    BodyResponse response_body = 4;

    // The server must send back this message in response to a message with
    // the "request_trailers" field set.
    TrailersResponse request_trailers = 5;

    // The server must send back this message in response to a message with
    // the "response_trailers" field set.
    TrailersResponse response_trailers = 6;

    // If specified, attempt to create a locally generated response, send it
    // downstream, and stop processing additional filters and ignore any
    // additional messages received from the remote server for this request or
    // response. If a response has already started -- for example, if this
    // message is sent response to a "response_body" message -- then
    // this will either ship the reply directly to the downstream codec,
    // or reset the stream.
    ImmediateResponse immediate_response = 7;
  }

  // [#not-implemented-hide:]
  // Optional metadata that will be emitted as dynamic metadata to be consumed by the next
  // filter. This metadata will be placed in the namespace "envoy.filters.http.ext_proc".
  google.protobuf.Struct dynamic_metadata = 8;

  // Override how parts of the HTTP request and response are processed
  // for the duration of this particular request/response only. Servers
  // may use this to intelligently control how requests are processed
  // based on the headers and other metadata that they see.
  envoy.extensions.filters.http.ext_proc.v3.ProcessingMode mode_override = 9;
}

// The following are messages that are sent to the server.

// This message is sent to the external server when the HTTP request and responses
// are first received.
message HttpHeaders {
  // The HTTP request headers. All header keys will be
  // lower-cased, because HTTP header keys are case-insensitive.
  config.core.v3.HeaderMap headers = 1;

  // [#not-implemented-hide:]
  // The values of properties selected by the "request_attributes"
  // or "response_attributes" list in the configuration. Each entry
  // in the list is populated
  // from the standard :ref:`attributes <arch_overview_attributes>`
  // supported across Envoy.
  map<string, google.protobuf.Struct> attributes = 2;

  // If true, then there is no message body associated with this
  // request or response.
  bool end_of_stream = 3;
}

// This message contains the message body that Envoy sends to the external server.
message HttpBody {
  bytes body = 1;

  bool end_of_stream = 2;
}

// This message contains the trailers.
message HttpTrailers {
  config.core.v3.HeaderMap trailers = 1;
}

// The following are messages that may be sent back by the server.

// This message must be sent in response to an HttpHeaders message.
message HeadersResponse {
  CommonResponse response = 1;
}

// This message must be sent in response to an HttpTrailers message.
message TrailersResponse {
  // Instructions on how to manipulate the trailers
  HeaderMutation header_mutation = 1;
}

// This message must be sent in response to an HttpBody message.
message BodyResponse {
  CommonResponse response = 1;
}

// This message contains common fields between header and body responses.
// [#next-free-field: 6]
message CommonResponse {
  enum ResponseStatus {
    // Apply the mutation instructions in this message to the
    // request or response, and then continue processing the filter
    // stream as normal. This is the default.
    CONTINUE = 0;

    // Apply the specified header mutation, replace the body with the body
    // specified in the body mutation (if present), and do not send any
    // further messages for this request or response even if the processing
    // mode is configured to do so.
    //
    // When used in response to a request_headers or response_headers message,
    // this status makes it possible to either completely replace the body
    // while discarding the original body, or to add a body to a message that
    // formerly did not have one.
    //
    // In other words, this response makes it possible to turn an HTTP GET
    // into a POST, PUT, or PATCH.
    CONTINUE_AND_REPLACE = 1;
  }

  // If set, provide additional direction on how the Envoy proxy should
  // handle the rest of the HTTP filter chain.
  ResponseStatus status = 1 [(validate.rules).enum = {defined_only: true}];

  // Instructions on how to manipulate the headers. When responding to an
  // HttpBody request, header mutations will only take effect if
  // the current processing mode for the body is BUFFERED.
  HeaderMutation header_mutation = 2;

  // Replace the body of the last message sent to the remote server on this
  // stream. If responding to an HttpBody request, simply replace or clear
  // the body chunk that was sent with that request. Body mutations only take
  // effect in response to "body" messages and are ignored otherwise.
  BodyMutation body_mutation = 3;

  // [#not-implemented-hide:]
  // Add new trailers to the message. This may be used when responding to either a
  // HttpHeaders or HttpBody message, but only if this message is returned
  // along with the CONTINUE_AND_REPLACE status.
  config.core.v3.HeaderMap trailers = 4;

  // Clear the route cache for the current request.
  // This is necessary if the remote server
  // modified headers that are used to calculate the route.
  bool clear_route_cache = 5;
}

// This message causes the filter to attempt to create a locally
// generated response, send it  downstream, stop processing
// additional filters, and ignore any additional messages received
// from the remote server for this request or response. If a response
// has already started, then  this will either ship the reply directly
// to the downstream codec, or reset the stream.
// [#next-free-field: 6]
message ImmediateResponse {
  // The response code to return
  type.v3.HttpStatus status = 1 [(validate.rules).message = {required: true}];

  // Apply changes to the default headers, which will include content-type.
  HeaderMutation headers = 2;

  // The message body to return with the response which is sent using the
  // text/plain content type, or encoded in the grpc-message header.
  string body = 3;

  // If set, then include a gRPC status trailer.
  GrpcStatus grpc_status = 4;

  // A string detailing why this local reply was sent, which may be included
  // in log and debug output (e.g. this populates the %RESPONSE_CODE_DETAILS%
  // command operator field for use in access logging).
  string details = 5;
}

// This message specifies a gRPC status for an ImmediateResponse message.
message GrpcStatus {
  // The actual gRPC status
  uint32 status = 1;
}

// Change HTTP headers or trailers by appending, replacing, or removing
// headers.
message HeaderMutation {
  // Add or replace HTTP headers. Attempts to set the value of
  // any "x-envoy" header, and attempts to set the ":method",
  // ":authority", ":scheme", or "host" headers will be ignored.
  repeated config.core.v3.HeaderValueOption set_headers = 1;

  // Remove these HTTP headers. Attempts to remove system headers --
  // any header starting with ":", plus "host" -- will be ignored.
  repeated string remove_headers = 2;
}

// Replace the entire message body chunk received in the corresponding
// HttpBody message with this new body, or clear the body.
message BodyMutation {
  oneof mutation {
    // The entire body to replace
    bytes body = 1;

    // Clear the corresponding body chunk
    bool clear_body = 2;
  }
}
