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

io.gatling.http.action.ws.WebSocketActor.scala Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2011-2014 eBusiness Information, Groupe Excilys (www.excilys.com)
 * Copyright 2012 Gilt Groupe, Inc. (www.gilt.com)
 *
 * 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.action.ws

import scala.collection.mutable

import com.ning.http.client.websocket.WebSocket

import akka.actor.ActorRef
import io.gatling.core.akka.BaseActor
import io.gatling.core.result.message.{ KO, OK, Status }
import io.gatling.core.result.writer.DataWriterClient
import io.gatling.core.session.Session
import io.gatling.core.util.TimeHelper.nowMillis
import io.gatling.http.ahc.{ HttpEngine, WebSocketTx }

class WebSocketActor(wsName: String) extends BaseActor with DataWriterClient {

  private def logRequest(session: Session, requestName: String, status: Status, started: Long, ended: Long, errorMessage: Option[String] = None) {
    writeRequestData(
      session,
      requestName,
      started,
      ended,
      ended,
      ended,
      status,
      errorMessage)
  }

  def sendTextMessage(webSocket: WebSocket, requestName: String, message: String, session: Session) {
    val started = nowMillis
    webSocket.sendTextMessage(message)
    logRequest(session, requestName, OK, started, nowMillis)
  }

  def sendBinaryMessage(webSocket: WebSocket, requestName: String, message: Array[Byte], session: Session) {
    val started = nowMillis
    webSocket.sendMessage(message)
    logRequest(session, requestName, OK, started, nowMillis)
  }

  def webSocketOpenState(webSocket: WebSocket, tx: WebSocketTx): Receive = {
    case OnMessage(message) =>
      // TODO deal with messages
      logger.debug(s"Received message on websocket '$wsName':$message")

    case SendTextMessage(requestName, message, next, session) =>
      sendTextMessage(webSocket, requestName, message, session)
      next ! session

    case SendBinaryMessage(requestName, message, next, session) =>
      sendBinaryMessage(webSocket, requestName, message, session)
      next ! session

    case Close(requestName, next, session) =>
      val started = nowMillis
      webSocket.close()
      logRequest(session, requestName, OK, started, nowMillis)
      next ! session.remove(wsName)
      context.become(closingState)

    case OnUnexpectedClose | OnClose =>
      if (tx.protocol.wsPart.reconnect)
        if (tx.protocol.wsPart.maxReconnects.map(_ > tx.reconnectCount).getOrElse(true))
          context.become(disconnectedState(mutable.Queue.empty[WebSocketMessage], tx))
        else
          context.become(pendingErrorMessageState(s"Websocket '$wsName' was unexpectedly closed and max reconnect reached"))

      else
        context.become(pendingErrorMessageState(s"Websocket '$wsName' was unexpectedly closed"))

    case OnError(t) =>
      context.become(pendingErrorMessageState(s"Websocket '$wsName' gave an error: '${t.getMessage}'"))
  }

  def closingState: Receive = {
    case OnClose => context.stop(self)
  }

  def disconnectedState(pendingSendMessages: mutable.Queue[WebSocketMessage], tx: WebSocketTx): Receive = {

    case message: WebSocketMessage =>
      // reconnect on first client message tentative
      HttpEngine.instance.startWebSocketTransaction(tx.copy(reconnectCount = tx.reconnectCount + 1), self)

      context.become(reconnectingState(pendingSendMessages += message))
  }

  def reconnectingState(pendingSendMessages: mutable.Queue[WebSocketMessage]): Receive = {

    case message: WebSocketMessage =>
      pendingSendMessages += message

    case OnOpen(tx, webSocket, started, ended) =>
      // send all pending messages
      pendingSendMessages.foreach { self ! _ }

      context.become(webSocketOpenState(webSocket, tx))

    case OnFailedOpen(tx, message, _, _) =>

      val error = s"Websocket '$wsName' failed to reconnect: $message"

      // send all pending messages
      pendingSendMessages.foreach { self ! _ }

      context.become(pendingErrorMessageState(error))
  }

  def pendingErrorMessageState(error: String): Receive = {

      def flushPendingError(requestName: String, next: ActorRef, session: Session) {
        val now = nowMillis
        logRequest(session, requestName, KO, now, now, Some(error))
        next ! session.markAsFailed.remove(wsName)
        context.stop(self)
      }

    {
      case SendTextMessage(requestName, _, next, session) =>
        flushPendingError(requestName, next, session)

      case SendBinaryMessage(requestName, _, next, session) =>
        flushPendingError(requestName, next, session)

      case Close(requestName, next, session) =>
        flushPendingError(requestName, next, session)
    }
  }

  def receive = {
    case OnOpen(tx, webSocket, started, ended) =>
      import tx._
      logRequest(session, requestName, OK, started, ended)
      next ! session.set(wsName, self)
      context.become(webSocketOpenState(webSocket, tx))

    case OnFailedOpen(tx, message, started, ended) =>
      import tx._
      logger.warn(s"Websocket '$wsName' failed to open: $message")
      logRequest(session, requestName, KO, started, ended, Some(message))
      next ! session.markAsFailed
      context.stop(self)
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy