1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
package xyz.driver.core
package init
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.{RequestContext, Route, RouteConcatenation}
import spray.json.DefaultJsonProtocol._
import spray.json._
import xyz.driver.core.rest.Swagger
import xyz.driver.core.rest.directives.Directives
import akka.http.scaladsl.model.headers._
import xyz.driver.core.reporting.Reporter.CausalRelation
import xyz.driver.core.reporting.SpanContext
import xyz.driver.core.rest.headers.Traceparent
import scala.collection.JavaConverters._
/** Mixin trait that provides some well-known HTTP endpoints, diagnostic header injection and forwarding,
* and exposes an application-specific route that must be implemented by services.
* @see ProtobufApi
*/
trait HttpApi extends CloudServices with Directives with SprayJsonSupport { self =>
/** Route that handles the application's business logic.
* @group hooks
*/
def applicationRoute: Route
/** Classes with Swagger annotations.
* @group hooks
*/
def swaggerRouteClasses: Set[Class[_]]
private val healthRoute = path("health") {
complete(Map("status" -> "good").toJson)
}
private val versionRoute = path("version") {
complete(Map("name" -> self.name.toJson, "version" -> self.version.toJson).toJson)
}
private lazy val swaggerRoute = {
val generator = new Swagger(
"",
"https" :: "http" :: Nil,
self.version.getOrElse("<unknown>"),
swaggerRouteClasses,
config,
reporter
)
generator.routes ~ generator.swaggerUI
}
private def cors(inner: Route): Route =
cors(
config.getStringList("application.cors.allowedOrigins").asScala.toSet,
xyz.driver.core.rest.AllowedHeaders
)(inner)
private def traced(inner: Route): Route = (ctx: RequestContext) => {
val tags = Map(
"service_name" -> name,
"service_version" -> version.getOrElse("<unknown>"),
"http_path" -> ctx.request.uri.path.toString,
"http_method" -> ctx.request.method.value.toString,
"http_uri" -> ctx.request.uri.toString,
"http_user_agent" -> ctx.request.header[`User-Agent`].map(_.value).getOrElse("<unknown>")
)
val parent = ctx.request.header[Traceparent].map { p =>
SpanContext(p.traceId, p.spanId) -> CausalRelation.Child
}
reporter
.traceWithOptionalParentAsync(s"${ctx.request.method.value.toLowerCase}_${ctx.request.uri.path}", tags, parent) {
sctx =>
val header = Traceparent(sctx.traceId, sctx.spanId)
val withHeader = ctx.withRequest(ctx.request.withHeaders(header))
inner(withHeader)
}
}
/** Extended route. */
override lazy val route: Route = traced(
cors(
RouteConcatenation.concat(
healthRoute,
versionRoute,
swaggerRoute,
applicationRoute
)
)
)
}
|