All Downloads are FREE. Search and download functionalities are using the official Maven repository.

next.events.capture.scala Maven / Gradle / Ivy

package otoroshi.next.events

import akka.util.ByteString
import org.joda.time.DateTime
import otoroshi.env.Env
import otoroshi.events.AnalyticEvent
import otoroshi.next.models.NgRoute
import otoroshi.next.plugins.api.{NgPluginHttpRequest, NgPluginHttpResponse}
import otoroshi.next.utils.JsonHelpers
import otoroshi.security.IdGenerator
import otoroshi.utils.TypedMap
import otoroshi.utils.syntax.implicits.BetterJsReadable
import play.api.libs.json.{JsValue, Json}
import play.api.mvc.RequestHeader

object TrafficCaptureEvent {
  val strippedHeaders =
    Seq("Remote-Address", "Timeout-Access", "Raw-Request-URI", "Tls-Session-Info").map(_.toLowerCase())
}

case class TrafficCaptureEvent(
    route: NgRoute,
    request: RequestHeader,
    backendRequest: NgPluginHttpRequest,
    backendResponse: NgPluginHttpResponse,
    response: NgPluginHttpResponse,
    responseChunks: ByteString,
    attrs: TypedMap
) extends AnalyticEvent {

  override def `@service`: String            = route.name
  override def `@serviceId`: String          = route.id
  def `@id`: String                          = IdGenerator.uuid
  def `@timestamp`: org.joda.time.DateTime   = timestamp
  def `@type`: String                        = "TrafficCaptureEvent"
  override def fromOrigin: Option[String]    = None
  override def fromUserAgent: Option[String] = None

  val timestamp = DateTime.now()

  def toGoReplayFormat(
      captureRequest: Boolean,
      captureResponse: Boolean,
      preferBackendRequest: Boolean,
      preferBackendResponse: Boolean
  ): String = {
    var event             = Seq.empty[String]
    val responseChunkUtf8 = responseChunks.utf8String
    if (captureRequest && !preferBackendRequest) {
      val requestEvent = Seq(
        s"1 ${attrs.get(otoroshi.plugins.Keys.SnowFlakeKey).getOrElse("").padTo(24, "0").mkString("")} ${attrs.get(otoroshi.plugins.Keys.RequestStartKey).map(_.toString).getOrElse("").padTo(19, "0").mkString("")} 0",
        System.lineSeparator(),
        s"${request.method.toUpperCase()} ${request.uri} ${request.version}",
        System.lineSeparator(),
        request.headers.toSimpleMap
          .filterNot { case (key, _) =>
            TrafficCaptureEvent.strippedHeaders.contains(key.toLowerCase)
          }
          .map { case (key, value) =>
            s"${key}: ${value}${System.lineSeparator()}"
          }
          .mkString(""),
        System.lineSeparator(),
        attrs.get(otoroshi.plugins.Keys.CaptureRequestBodyKey).map(_.utf8String).getOrElse(""),
        System.lineSeparator(),
        s"🐵🙈🙉",
        System.lineSeparator()
      )
      event = event ++ requestEvent
    }
    if (captureRequest && preferBackendRequest) {
      val requestEvent = Seq(
        s"1 ${attrs.get(otoroshi.plugins.Keys.SnowFlakeKey).getOrElse("").padTo(24, "0").mkString("")} ${attrs.get(otoroshi.plugins.Keys.RequestStartKey).map(_.toString).getOrElse("").padTo(19, "0").mkString("")} 0",
        System.lineSeparator(),
        s"${backendRequest.method.toUpperCase()} ${backendRequest.uri} ${backendRequest.version}",
        System.lineSeparator(),
        backendRequest.headers
          .filterNot { case (key, _) =>
            TrafficCaptureEvent.strippedHeaders.contains(key.toLowerCase)
          }
          .map { case (key, value) =>
            s"${key}: ${value}${System.lineSeparator()}"
          }
          .mkString(""),
        System.lineSeparator(),
        attrs
          .get(otoroshi.plugins.Keys.CaptureRequestBodyKey)
          .map(_.utf8String)
          .getOrElse(""), // TODO: backendRequestChunks.utf8String,
        System.lineSeparator(),
        s"🐵🙈🙉",
        System.lineSeparator()
      )
      event = event ++ requestEvent
    }
    if (captureResponse && !preferBackendResponse) {
      val responseEvent = Seq(
        s"2 ${attrs.get(otoroshi.plugins.Keys.SnowFlakeKey).getOrElse("").padTo(24, "0").mkString("")} ${System.currentTimeMillis().toString.padTo(19, "0").mkString("")} 0", // still dont know what 0 means
        System.lineSeparator(),
        s"${request.version} ${response.status} ${response.statusText}",
        System.lineSeparator(),
        response.headers
          .map { case (key, value) =>
            s"${key}: ${value}${System.lineSeparator()}"
          }
          .mkString(""),
        System.lineSeparator(),
        responseChunks.utf8String,
        System.lineSeparator(),
        s"🐵🙈🙉",
        System.lineSeparator()
      )
      event = event ++ responseEvent
    }
    if (captureResponse && preferBackendResponse) {
      val responseEvent = Seq(
        s"2 ${attrs.get(otoroshi.plugins.Keys.SnowFlakeKey).getOrElse("").padTo(24, "0").mkString("")} ${System.currentTimeMillis().toString.padTo(19, "0").mkString("")} 0", // still dont know what 0 means
        System.lineSeparator(),
        s"${request.version} ${backendResponse.status} ${backendResponse.statusText}",
        System.lineSeparator(),
        backendResponse.headers
          .map { case (key, value) =>
            s"${key}: ${value}${System.lineSeparator()}"
          }
          .mkString(""),
        System.lineSeparator(),
        responseChunkUtf8,                                                                                                                                                    // TODO: backendResponseChunks.utf8String,
        System.lineSeparator(),
        s"🐵🙈🙉",
        System.lineSeparator()
      )
      event = event ++ responseEvent
    }
    event.mkString("")
    // val path = Paths.get("./capture.gor")
    // if (!path.toFile.exists()) path.toFile.createNewFile()
    // Files.write(
    //   path,
    //   event.mkString("").getBytes(),
    //   StandardOpenOption.APPEND
    // )
  }

  override def toJson(implicit env: Env): JsValue = {
    val inputBody         = attrs.get(otoroshi.plugins.Keys.CaptureRequestBodyKey).map(_.utf8String).getOrElse("")
    val id                = attrs.get(otoroshi.plugins.Keys.SnowFlakeKey).getOrElse("").padTo(24, "0").mkString("")
    val responseChunkUtf8 = responseChunks.utf8String
    Json.obj(
      "@id"              -> `@id`,
      "@timestamp"       -> play.api.libs.json.JodaWrites.JodaDateTimeNumberWrites.writes(timestamp),
      "@type"            -> "TrafficCaptureEvent",
      "@product"         -> "otoroshi",
      "@serviceId"       -> `@serviceId`,
      "@service"         -> `@service`,
      "@env"             -> "prod",
      "route"            -> Json.obj(
        "id"   -> route.id,
        "name" -> route.name
      ),
      "request"          -> (JsonHelpers.requestToJson(request).asObject ++ Json.obj(
        "id"     -> id,
        "int_id" -> request.id,
        "body"   -> inputBody
      )),
      "backend_request"  -> (backendRequest.json.asObject - "backend" ++ Json.obj(
        "id"     -> id,
        "int_id" -> request.id,
        "body"   -> inputBody // TODO: inputBackendBody,
      )),
      "backend_response" -> (backendResponse.json.asObject ++ Json.obj(
        "id"           -> id,
        "status_txt"   -> backendResponse.statusText,
        "http_version" -> request.version,
        "body"         -> responseChunkUtf8 // TODO: outputBackendBody
      )),
      "response"         -> (response.json.asObject ++ Json.obj(
        "id"           -> id,
        "status_txt"   -> response.statusText,
        "http_version" -> request.version,
        "body"         -> responseChunkUtf8
      ))
    )
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy