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

org.http4s.blaze.util.HttpClient.scala Maven / Gradle / Ivy

package org.http4s.blaze.util

import org.http4s.blaze.channel.nio2.ClientChannelFactory
import org.http4s.blaze.pipeline.{Command, LeafBuilder}
import java.nio.ByteBuffer
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration.Duration
import java.net.InetSocketAddress
import org.http4s.blaze.pipeline.stages.http.{SimpleHttpResponse, HttpClientStage, Response}
import javax.net.ssl.{KeyManagerFactory, SSLContext}
import java.security.KeyStore
import org.http4s.blaze.pipeline.stages.SSLStage

/**
 * @author Bryce Anderson
 *         Created on 2/6/14
 */
trait HttpClient {

  private lazy val connManager = new ClientChannelFactory()

  // TODO: is there a better way to make a dummy context? Clients shouldn't need a certificate I would think
  private lazy val sslContext = {
    val ksStream = BogusKeystore.asInputStream()
    val ks = KeyStore.getInstance("JKS")
    ks.load(ksStream, BogusKeystore.getKeyStorePassword)

    val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())
    kmf.init(ks, BogusKeystore.getCertificatePassword)

    val context = SSLContext.getInstance("SSL")

    context.init(kmf.getKeyManagers(), null, null)
    context
  }

  // TODO: the robustness of this method to varying input is highly questionable
  private def parseURL(url: String): (String, Int, String, String) = {
    val uri = java.net.URI.create(if (url.startsWith("http")) url else "http://" + url)

    val port = if (uri.getPort > 0) uri.getPort else (if (uri.getScheme == "http") 80 else 443)

    (uri.getHost,
      port,
      uri.getScheme,
      if (uri.getQuery != null) uri.getPath + "?" + uri.getQuery else uri.getPath
    )
  }

  protected def runReq(method: String,
                     url: String,
                     headers: Seq[(String, String)],
                     body: ByteBuffer,
                     timeout: Duration)(implicit ec: ExecutionContext): Future[Response] = {

    val (host, port, scheme, uri) = parseURL(url)

    val fhead = connManager.connect(new InetSocketAddress(host, port))

    fhead.flatMap { head =>
      val t = new HttpClientStage()

      if (scheme == "https") {
        val eng = sslContext.createSSLEngine()
        eng.setUseClientMode(true)
        LeafBuilder(t).prepend(new SSLStage(eng)).base(head)
      }
      else LeafBuilder(t).base(head)

      head.sendInboundCommand(Command.Connect)
      val f = t.makeRequest(method, host, uri, headers, body)
      // Shutdown our connection
      f.onComplete( _ => t.sendOutboundCommand(Command.Disconnect))

      f
    }
  }

  def GET(url: String, headers: Seq[(String, String)] = Nil, timeout: Duration = Duration.Inf)
         (implicit ec: ExecutionContext = Execution.trampoline): Future[SimpleHttpResponse] = {
    val r = runReq("GET", url, headers, BufferTools.emptyBuffer, timeout)
    r.flatMap {
      case r: SimpleHttpResponse => Future.successful(r)
      case r => Future.failed(new Exception(s"Received invalid response type: ${r.getClass}"))
    }(Execution.directec)
  }
}

object HttpClient extends HttpClient




© 2015 - 2025 Weber Informatics LLC | Privacy Policy