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
93
94
95
96
|
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.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.swaggerUINew
}
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.version" -> version.getOrElse("<unknown>"),
// open tracing semantic tags
"span.kind" -> "server",
"service" -> name,
"http.url" -> ctx.request.uri.toString,
"http.method" -> ctx.request.method.value,
"peer.hostname" -> ctx.request.uri.authority.host.toString,
// google's tracing console provides extra search features if we define these tags
"/http/path" -> ctx.request.uri.path.toString,
"/http/method" -> ctx.request.method.value.toString,
"/http/url" -> ctx.request.uri.toString,
"/http/user_agent" -> ctx.request.header[`User-Agent`].map(_.value).getOrElse("<unknown>")
)
val parent = ctx.request.header[Traceparent].map { header =>
header.spanContext -> CausalRelation.Child
}
reporter
.traceWithOptionalParentAsync(s"http_handle_rpc", tags, parent) { spanContext =>
val header = Traceparent(spanContext)
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
)
)
)
}
|