common/app/contentapi/http.scala (69 lines of code) (raw):

package contentapi import java.net.{InetAddress, URI} import java.util.concurrent.TimeoutException import common.ContentApiMetrics.{ContentApi404Metric, ContentApiErrorMetric, ContentApiRequestsMetric} import common.{ContentApiMetrics, GuLogging} import conf.Configuration import conf.Configuration.contentApi.capiPreviewCredentials import play.api.libs.ws.WSClient import com.gu.contentapi.client.IAMSigner import scala.concurrent.{ExecutionContext, Future} import scala.util.{Failure, Success, Try} case class Response(body: Array[Byte], status: Int, statusText: String) /** CAPI preview uses IAM authorization. The signer generates AWS sig4v signed headers based on the request and the capi * credentials */ object PreviewSigner { def apply() = new IAMSigner(capiPreviewCredentials, common.Environment.awsRegion) } trait HttpClient { def GET(url: String, headers: Iterable[(String, String)]): Future[Response] } class CapiHttpClient(wsClient: WSClient)(implicit executionContext: ExecutionContext) extends HttpClient with GuLogging { import java.lang.System.currentTimeMillis val signer: Option[IAMSigner] = None private def addAuthHeaders(headers: Iterable[(String, String)], url: String): Iterable[(String, String)] = signer.fold(headers)(_.addIAMHeaders(headers.toMap, URI.create(url))) def GET(url: String, headers: Iterable[(String, String)]): Future[Response] = { // append with a & as there are always params in there already val urlWithDebugInfo = s"$url&${RequestDebugInfo.debugParams}" val contentApiTimeout = Configuration.contentApi.timeout val start = currentTimeMillis val baseRequest = wsClient.url(urlWithDebugInfo) val headersWithAuth = addAuthHeaders(headers, urlWithDebugInfo) val response = baseRequest.withHttpHeaders(headersWithAuth.toSeq: _*).withRequestTimeout(contentApiTimeout).get() // record metrics response.foreach((f) => { ContentApiRequestsMetric.increment() }) response.foreach { case r if r.status == 404 => ContentApi404Metric.increment() case r if r.status == 200 => ContentApiMetrics.HttpLatencyTimingMetric.recordDuration(currentTimeMillis - start) case _ => } response.failed.foreach { case e: TimeoutException => log.warn(s"Content API TimeoutException for $url in ${currentTimeMillis - start}: $e") ContentApiMetrics.HttpTimeoutCountMetric.increment() case e: Exception => log.warn(s"Content API client exception for $url in ${currentTimeMillis - start}: $e") } response onComplete { case Success(r) if r.status >= 500 => ContentApiErrorMetric.increment() case Failure(_) => ContentApiErrorMetric.increment() case _ => } response map { wsResponse => Response(wsResponse.bodyAsBytes.toArray, wsResponse.status, wsResponse.statusText) } } } private object RequestDebugInfo { import java.net.URLEncoder.encode private lazy val host: String = Try(InetAddress.getLocalHost.getCanonicalHostName).getOrElse("unable-to-determine-host") private lazy val stage: String = Configuration.environment.stage private lazy val project: String = Configuration.environment.app lazy val debugParams = Seq( s"ngw-host=${encode(host, "UTF-8")}", s"ngw-stage=${encode(stage, "UTF-8")}", s"ngw-project=${encode(project, "UTF-8")}", ).mkString("&") }