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

com.twitter.finagle.memcached.protocol.text.server.ServerDecoder.scala Maven / Gradle / Ivy

There is a newer version: 24.2.0
Show newest version
package com.twitter.finagle.memcached.protocol.text.server

import com.twitter.finagle.memcached.protocol.ClientError
import com.twitter.finagle.memcached.protocol.text._
import com.twitter.finagle.memcached.util.ParserUtils
import com.twitter.io.Buf
import scala.collection.immutable

private[memcached] object ServerDecoder {

  private val NeedMoreData: Null = null

  private def validateStorageCommand(tokens: Seq[Buf]): Unit = {
    if (tokens.size < 5) throw new ClientError("Too few arguments")
    if (tokens.size > 6) throw new ClientError("Too many arguments")
    if (!ParserUtils.isDigits(tokens(4))) throw new ClientError("Bad frame length")
  }
}

/**
 * Decodes Buf-encoded Commands into Commands. Used by the server.
 *
 * @note Class contains mutable state. Not thread-safe.
 */
private[finagle] abstract class ServerDecoder[R >: Null](storageCommands: immutable.Set[Buf])
    extends Decoder[R] {
  import ServerDecoder._

  /** Type that represents a complete cache value */
  protected type Value

  private sealed trait State
  private case object AwaitingCommand extends State
  private case class AwaitingData(valuesSoFar: Seq[Value], tokens: Seq[Buf], bytesNeeded: Int)
      extends State

  private[this] var state: State = AwaitingCommand

  private[this] def needsData(tokens: Seq[Buf]): Int = {
    val commandName = tokens.head
    if (storageCommands.contains(commandName)) {
      validateStorageCommand(tokens)
      val dataLengthAsBuf = tokens(4)
      ParserUtils.bufToInt(dataLengthAsBuf)
    } else -1
  }

  /** Parse a sequence of tokens into a command */
  protected def parseNonStorageCommand(tokens: Seq[Buf]): R

  protected def parseStorageCommand(tokens: Seq[Buf], data: Buf, casUnique: Option[Buf] = None): R

  def decode(buffer: Buf): R =
    state match {
      case AwaitingCommand =>
        val tokens = ParserUtils.splitOnWhitespace(buffer)
        val dataBytes = needsData(tokens)
        if (dataBytes == -1) {
          parseNonStorageCommand(tokens)
        } else {
          // We are waiting for data next
          state = AwaitingData(Nil, tokens, dataBytes)
          NeedMoreData
        }
      case AwaitingData(_, tokens, bytesNeeded) =>
        // The framer should have given us the right sized Buf
        assert(buffer.length == bytesNeeded)
        state = AwaitingCommand

        val commandName = tokens.head
        if (commandName.equals(Buf.Utf8("cas"))) // cas command
          parseStorageCommand(tokens.slice(0, 5), buffer, Some(tokens(5)))
        else // other commands
          parseStorageCommand(tokens, buffer, None)
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy