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

io.gatling.http.engine.tx.HttpTxExecutor.scala Maven / Gradle / Ivy

There is a newer version: 3.13.1
Show newest version
/*
 * Copyright 2011-2024 GatlingCorp (https://gatling.io)
 *
 * 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.engine.tx

import io.gatling.core.CoreComponents
import io.gatling.core.controller.throttle.Throttler
import io.gatling.core.util.NameGen
import io.gatling.http.cache.{ ContentCacheEntry, HttpCaches, SslContextSupport }
import io.gatling.http.client.HttpListener
import io.gatling.http.client.util.Pair
import io.gatling.http.engine.{ GatlingHttpListener, HttpEngine }
import io.gatling.http.engine.response._
import io.gatling.http.fetch.ResourceFetcher
import io.gatling.http.protocol.HttpProtocol
import io.gatling.http.response.HttpFailure

import com.typesafe.scalalogging.StrictLogging

final class HttpTxExecutor(
    coreComponents: CoreComponents,
    httpEngine: HttpEngine,
    httpCaches: HttpCaches,
    defaultStatsProcessor: DefaultStatsProcessor,
    httpProtocol: HttpProtocol
) extends NameGen
    with StrictLogging {
  import coreComponents._

  private val resourceFetcher = new ResourceFetcher(coreComponents, httpCaches, httpProtocol, httpTxExecutor = this)

  private def executeWithCache(origTx: HttpTx)(f: HttpTx => Unit): Unit = {
    val tx = httpCaches.applyPermanentRedirect(origTx)
    val clientRequest = tx.request.clientRequest
    val uri = clientRequest.getUri

    httpCaches.contentCacheEntry(tx.session, clientRequest) match {
      case None | Some(ContentCacheEntry(None, _, _)) =>
        f(tx)

      case Some(ContentCacheEntry(Some(expire), _, _)) if clock.nowMillis > expire =>
        val newTx = tx.copy(session = httpCaches.clearContentCache(tx.session, clientRequest))
        f(newTx)

      case _ =>
        resourceFetcher.newResourceAggregatorForCachedPage(tx) match {
          case Some(aggregator) =>
            logger.debug(
              s"Fetching resources of cached page request=${tx.request.requestName} uri=$uri: scenario=${tx.session.scenario}, userId=${tx.session.userId}"
            )
            aggregator.start(tx.session)

          case _ =>
            logger.debug(s"Skipping cached request=${tx.request.requestName} uri=$uri: scenario=${tx.session.scenario}, userId=${tx.session.userId}")
            tx.resourceTx match {
              case Some(resourceTx) => resourceTx.aggregator.onCachedResource(resourceTx, tx)
              case _                => tx.next ! tx.session
            }
        }
    }
  }

  private def executeHttp2WithCache(origTxs: Iterable[HttpTx])(f: Iterable[HttpTx] => Unit): Unit = {
    val cached = scala.collection.mutable.ListBuffer[HttpTx]()
    val nonCached = scala.collection.mutable.ListBuffer[HttpTx]()
    var session = origTxs.head.session

    origTxs.foreach { tx =>
      val updatedTx = httpCaches.applyPermanentRedirect(tx)
      val txCacheEntry = httpCaches.contentCacheEntry(updatedTx.session, updatedTx.request.clientRequest)
      txCacheEntry match {
        case None | Some(ContentCacheEntry(None, _, _)) =>
          nonCached += updatedTx

        case Some(ContentCacheEntry(Some(expire), _, _)) if clock.nowMillis > expire =>
          val requestToClean = updatedTx.request.clientRequest
          session = httpCaches.clearContentCache(session, requestToClean)
          nonCached += updatedTx

        case _ =>
          cached += updatedTx
      }
    }

    f(nonCached.map(_.copy(session = session)))

    cached.map(_.copy(session = session)).foreach { tx =>
      val uri = tx.request.clientRequest.getUri
      resourceFetcher.newResourceAggregatorForCachedPage(tx) match {
        case Some(aggregator) =>
          logger
            .debug(
              s"Fetching resources of cached page request=${tx.request.requestName} uri=$uri: scenario=${tx.session.scenario}, userId=${tx.session.userId}"
            )
          aggregator.start(tx.session)

        case _ =>
          logger.debug(s"Skipping cached request=${tx.request.requestName} uri=$uri: scenario=${tx.session.scenario}, userId=${tx.session.userId}")
          tx.resourceTx match {
            case Some(resourceTx) => resourceTx.aggregator.onCachedResource(resourceTx, tx)
            case _                =>
          }
      }
    }
  }

  def execute(origTx: HttpTx): Unit =
    execute(origTx, responseProcessorFactory)

  def execute(origTxs: Iterable[HttpTx]): Unit =
    execute(origTxs, responseProcessorFactory)

  def execute(origTx: HttpTx, responseProcessorFactory: HttpTx => ResponseProcessor): Unit =
    executeWithCache(origTx) { tx =>
      if (tx.redirectCount >= tx.request.requestConfig.httpProtocol.responsePart.maxRedirects) {
        val now = clock.nowMillis
        // [e]
        //
        // [e]
        responseProcessorFactory(tx).onComplete(
          HttpFailure(
            request = tx.request.clientRequest,
            startTimestamp = now,
            endTimestamp = now,
            errorMessage = s"Too many redirects, max is ${tx.request.requestConfig.httpProtocol.responsePart.maxRedirects}"
          )
        )
      } else {
        logger.debug(
          s"Sending request=${tx.request.requestName} uri=${tx.request.clientRequest.getUri}: scenario=${tx.session.scenario}, userId=${tx.session.userId}"
        )

        val clientRequest = tx.request.clientRequest
        val clientId = tx.session.userId
        val shared = tx.request.requestConfig.httpProtocol.enginePart.shareConnections
        val listener = new GatlingHttpListener(tx, coreComponents.clock, responseProcessorFactory(tx))
        val userSslContexts = SslContextSupport.sslContexts(tx.session)

        throttler match {
          case Some(th) if tx.request.requestConfig.throttled =>
            th ! Throttler.Command.ThrottledRequest(
              tx.session.scenario,
              () => httpEngine.executeRequest(clientRequest, clientId, shared, tx.session.eventLoop, listener, userSslContexts)
            )
          case _ =>
            httpEngine.executeRequest(clientRequest, clientId, shared, tx.session.eventLoop, listener, userSslContexts)
        }
      }
    }

  def execute(origTxs: Iterable[HttpTx], responseProcessorFactory: HttpTx => ResponseProcessor): Unit =
    executeHttp2WithCache(origTxs) { txs =>
      val headTx = txs.head
      txs.foreach(tx =>
        logger.debug(
          s"Sending request=${tx.request.requestName} uri=${tx.request.clientRequest.getUri} scenario=${tx.session.scenario}, userId=${tx.session.userId}"
        )
      )
      val requestsAndListeners = txs.map { tx =>
        val listener: HttpListener = new GatlingHttpListener(tx, coreComponents.clock, responseProcessorFactory(tx))
        new Pair(tx.request.clientRequest, listener)
      }
      val clientId = headTx.session.userId
      val shared = headTx.request.requestConfig.httpProtocol.enginePart.shareConnections
      val userSslContexts = SslContextSupport.sslContexts(headTx.session)

      throttler match {
        case Some(th) if txs.head.request.requestConfig.throttled =>
          th ! Throttler.Command.ThrottledRequest(
            headTx.session.scenario,
            () => httpEngine.executeHttp2Requests(requestsAndListeners, clientId, shared, headTx.session.eventLoop, userSslContexts)
          )
        case _ =>
          httpEngine.executeHttp2Requests(requestsAndListeners, clientId, shared, headTx.session.eventLoop, userSslContexts)
      }
    }

  private val responseProcessorFactory: HttpTx => ResponseProcessor = tx =>
    tx.resourceTx match {
      case Some(resourceTx) => newResourceResponseProcessor(tx, resourceTx)
      case _                => newRootResponseProcessor(tx)
    }

  private def newRootResponseProcessor(tx: HttpTx): ResponseProcessor =
    new DefaultResponseProcessor(
      tx,
      sessionProcessor = new RootSessionProcessor(
        tx.silent,
        tx.request.clientRequest,
        tx.request.requestConfig.checks,
        httpCaches,
        httpProtocol
      ),
      statsProcessor = statsProcessor(tx),
      nextExecutor = new RootNextExecutor(tx, resourceFetcher, this),
      configuration.core.charset
    )

  private def newResourceResponseProcessor(tx: HttpTx, resourceTx: ResourceTx): ResponseProcessor =
    new DefaultResponseProcessor(
      tx = tx.copy(session = resourceTx.aggregator.currentSession),
      sessionProcessor = new ResourceSessionProcessor(
        tx.silent,
        tx.request.clientRequest,
        tx.request.requestConfig.checks,
        httpCaches,
        httpProtocol
      ),
      statsProcessor = statsProcessor(tx),
      nextExecutor = new ResourceNextExecutor(tx, resourceTx),
      configuration.core.charset
    )

  def statsProcessor(tx: HttpTx): StatsProcessor =
    if (tx.silent) NoopStatsProcessor else defaultStatsProcessor
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy