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

io.gatling.http.ahc.HttpEngine.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2011-2014 eBusiness Information, Groupe Excilys (www.ebusinessinformation.fr)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * 		http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gatling.http.ahc

import java.util.concurrent.{ Executors, ThreadFactory }

import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory
import org.jboss.netty.logging.{ InternalLoggerFactory, Slf4JLoggerFactory }
import org.jboss.netty.util.HashedWheelTimer

import com.ning.http.client.{ AsyncHttpClient, AsyncHttpClientConfig, Request }
import com.ning.http.client.providers.netty.{ NettyAsyncHttpProviderConfig, NettyConnectionsPool }
import com.ning.http.client.websocket.WebSocketUpgradeHandler
import com.typesafe.scalalogging.slf4j.StrictLogging

import akka.actor.ActorRef
import io.gatling.core.ConfigurationConstants._
import io.gatling.core.akka.AkkaDefaults
import io.gatling.core.config.GatlingConfiguration.configuration
import io.gatling.core.controller.{ Controller, ThrottledRequest }
import io.gatling.core.session.{ Session, SessionPrivateAttributes }
import io.gatling.core.util.TimeHelper.nowMillis
import io.gatling.http.action.ws.{ OnFailedOpen, WebSocketListener }
import io.gatling.http.check.HttpCheck
import io.gatling.http.config.HttpProtocol
import io.gatling.http.request.{ ExtraInfoExtractor, HttpRequest }
import io.gatling.http.response.ResponseBuilderFactory
import io.gatling.http.util.SSLHelper.{ RichAsyncHttpClientConfigBuilder, newKeyManagers, newTrustManagers }

case class HttpTx(session: Session,
                  request: Request,
                  requestName: String,
                  checks: List[HttpCheck],
                  responseBuilderFactory: ResponseBuilderFactory,
                  protocol: HttpProtocol,
                  next: ActorRef,
                  followRedirect: Boolean,
                  maxRedirects: Option[Int],
                  throttled: Boolean,
                  silent: Boolean,
                  explicitResources: Seq[HttpRequest],
                  extraInfoExtractor: Option[ExtraInfoExtractor],
                  resourceFetching: Boolean = false,
                  redirectCount: Int = 0)

case class WebSocketTx(session: Session,
                       request: Request,
                       requestName: String,
                       protocol: HttpProtocol,
                       next: ActorRef,
                       reconnectCount: Int = 0)

object HttpEngine extends AkkaDefaults with StrictLogging {

  private var _instance: Option[HttpEngine] = None

  def start() {
    if (!_instance.isDefined) {
      val client = new HttpEngine
      _instance = Some(client)
      system.registerOnTermination(_instance = None)
    }
  }

  def instance: HttpEngine = _instance match {
    case Some(engine) => engine
    case _            => throw new UnsupportedOperationException("HTTP engine hasn't been started")
  }
}

class HttpEngine extends AkkaDefaults with StrictLogging {

  val applicationThreadPool = Executors.newCachedThreadPool(new ThreadFactory {
    override def newThread(r: Runnable) = {
      val t = new Thread(r, "Netty Thread")
      t.setDaemon(true)
      t
    }
  })

  val hashedWheelTimer = new HashedWheelTimer
  hashedWheelTimer.start()

  // set up Netty LoggerFactory for slf4j instead of default JDK
  InternalLoggerFactory.setDefaultFactory(new Slf4JLoggerFactory)

  val connectionsPool = new NettyConnectionsPool(configuration.http.ahc.maximumConnectionsTotal,
    configuration.http.ahc.maximumConnectionsPerHost,
    configuration.http.ahc.idleConnectionInPoolTimeOutInMs,
    configuration.http.ahc.maxConnectionLifeTimeInMs,
    configuration.http.ahc.allowSslConnectionPool,
    hashedWheelTimer)

  val nettyConfig = {
    val numWorkers = configuration.http.ahc.ioThreadMultiplier * Runtime.getRuntime.availableProcessors
    val socketChannelFactory = new NioClientSocketChannelFactory(Executors.newCachedThreadPool, applicationThreadPool, numWorkers)
    system.registerOnTermination(socketChannelFactory.releaseExternalResources())
    val nettyConfig = new NettyAsyncHttpProviderConfig
    nettyConfig.addProperty(NettyAsyncHttpProviderConfig.SOCKET_CHANNEL_FACTORY, socketChannelFactory)
    nettyConfig.setHashedWheelTimer(hashedWheelTimer)
    nettyConfig
  }

  val defaultAhcConfig = {
    val ahcConfigBuilder = new AsyncHttpClientConfig.Builder()
      .setAllowPoolingConnection(configuration.http.ahc.allowPoolingConnection)
      .setAllowSslConnectionPool(configuration.http.ahc.allowSslConnectionPool)
      .setCompressionEnabled(configuration.http.ahc.compressionEnabled)
      .setConnectionTimeoutInMs(configuration.http.ahc.connectionTimeOut)
      .setIdleConnectionInPoolTimeoutInMs(configuration.http.ahc.idleConnectionInPoolTimeOutInMs)
      .setIdleConnectionTimeoutInMs(configuration.http.ahc.idleConnectionTimeOutInMs)
      .setIOThreadMultiplier(configuration.http.ahc.ioThreadMultiplier)
      .setMaximumConnectionsPerHost(configuration.http.ahc.maximumConnectionsPerHost)
      .setMaximumConnectionsTotal(configuration.http.ahc.maximumConnectionsTotal)
      .setMaxRequestRetry(configuration.http.ahc.maxRetry)
      .setRequestTimeoutInMs(configuration.http.ahc.requestTimeOutInMs)
      .setUseProxyProperties(configuration.http.ahc.useProxyProperties)
      .setUserAgent("Gatling/2.0")
      .setUseRawUrl(configuration.http.ahc.useRawUrl)
      .setExecutorService(applicationThreadPool)
      .setAsyncHttpClientProviderConfig(nettyConfig)
      .setConnectionsPool(connectionsPool)
      .setWebSocketIdleTimeoutInMs(configuration.http.ahc.webSocketIdleTimeoutInMs)
      .setUseRelativeURIsWithSSLProxies(configuration.http.ahc.useRelativeURIsWithSSLProxies)
      .setTimeConverter(JodaTimeConverter)

    val trustManagers = configuration.http.ssl.trustStore
      .map(config => newTrustManagers(config.storeType, config.file, config.password, config.algorithm))

    val keyManagers = configuration.http.ssl.keyStore
      .map(config => newKeyManagers(config.storeType, config.file, config.password, config.algorithm))

    if (trustManagers.isDefined || keyManagers.isDefined)
      ahcConfigBuilder.setSSLContext(trustManagers, keyManagers)

    ahcConfigBuilder.build
  }

  def newAHC(session: Session): AsyncHttpClient = newAHC(Some(session))

  def newAHC(session: Option[Session]) = {
    val ahcConfig = session.flatMap { session =>

      val trustManagers = for {
        file <- session(CONF_HTTP_SSL_TRUST_STORE_FILE).asOption[String]
        password <- session(CONF_HTTP_SSL_TRUST_STORE_PASSWORD).asOption[String]
        storeType = session(CONF_HTTP_SSL_TRUST_STORE_TYPE).asOption[String]
        algorithm = session(CONF_HTTP_SSL_TRUST_STORE_ALGORITHM).asOption[String]
      } yield newTrustManagers(storeType, file, password, algorithm)

      val keyManagers = for {
        file <- session(CONF_HTTP_SSL_KEY_STORE_FILE).asOption[String]
        password <- session(CONF_HTTP_SSL_KEY_STORE_PASSWORD).asOption[String]
        storeType = session(CONF_HTTP_SSL_KEY_STORE_TYPE).asOption[String]
        algorithm = session(CONF_HTTP_SSL_KEY_STORE_ALGORITHM).asOption[String]
      } yield newKeyManagers(storeType, file, password, algorithm)

      trustManagers.orElse(keyManagers).map { _ =>
        logger.info(s"Setting a custom SSLContext for user ${session.userId}")
        new AsyncHttpClientConfig.Builder(defaultAhcConfig).setSSLContext(trustManagers, keyManagers).build
      }

    }.getOrElse(defaultAhcConfig)

    val client = new AsyncHttpClient(ahcConfig)
    system.registerOnTermination(client.close())
    system.registerOnTermination(hashedWheelTimer.stop)
    client
  }

  lazy val defaultAHC = newAHC(None)

  val ahcAttributeName = SessionPrivateAttributes.privateAttributePrefix + "http.ahc"

  def httpClient(session: Session, protocol: HttpProtocol): (Session, AsyncHttpClient) = {
    if (protocol.enginePart.shareClient)
      (session, defaultAHC)
    else
      session(ahcAttributeName).asOption[AsyncHttpClient] match {
        case Some(client) => (session, client)
        case _ =>
          val httpClient = newAHC(session)
          (session.set(ahcAttributeName, httpClient), httpClient)
      }
  }

  def startHttpTransaction(tx: HttpTx) {

    val (newTx, client) = {
      val (newSession, client) = httpClient(tx.session, tx.protocol)
      (tx.copy(session = newSession), client)
    }

    if (tx.throttled)
      Controller ! ThrottledRequest(tx.session.scenarioName, () => client.executeRequest(newTx.request, new AsyncHandler(newTx)))
    else
      client.executeRequest(newTx.request, new AsyncHandler(newTx))
  }

  def startWebSocketTransaction(tx: WebSocketTx, wsActor: ActorRef) {
    val (newTx, client) = {
      val (newSession, client) = httpClient(tx.session, tx.protocol)
      (tx.copy(session = newSession), client)
    }

    val now = nowMillis
    try {
      val listener = new WebSocketListener(newTx, wsActor, now)

      val handler = new WebSocketUpgradeHandler.Builder().addWebSocketListener(listener).build
      client.executeRequest(newTx.request, handler)

    } catch {
      case e: Exception =>
        wsActor ! OnFailedOpen(newTx, e.getMessage, now, nowMillis)
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy