|
Play Framework/Scala example source code file (MediaRange.scala)
The MediaRange.scala Play Framework example source code
/*
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
*/
package play.api.http
import play.api.Play
import scala.util.parsing.input.CharSequenceReader
import scala.util.parsing.combinator.Parsers
import scala.collection.BitSet
import play.api.http.MediaRange.MediaRangeParser
/**
* A media type as defined by RFC 2616 3.7.
*
* @param mediaType The media type
* @param mediaSubType The media sub type
* @param parameters The parameters
*/
case class MediaType(mediaType: String, mediaSubType: String, parameters: Seq[(String, Option[String])]) {
override def toString = {
mediaType + "/" + mediaSubType + parameters.map { param =>
"; " + param._1 + param._2.map { value =>
if (MediaRangeParser.token(new CharSequenceReader(value)).next.atEnd) {
"=" + value
} else {
"=\"" + value.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\\\"") + "\""
}
}.getOrElse("")
}.mkString("")
}
}
/**
* A media range as defined by RFC 2616 14.1
*
* @param mediaType The media type
* @param mediaSubType The media sub type
* @param parameters The parameters
* @param qValue The Q value
* @param acceptExtensions The accept extensions
*/
class MediaRange(mediaType: String,
mediaSubType: String,
parameters: Seq[(String, Option[String])],
val qValue: Option[BigDecimal],
val acceptExtensions: Seq[(String, Option[String])]) extends MediaType(mediaType, mediaSubType, parameters) {
/**
* @return true if `mimeType` matches this media type, otherwise false
*/
def accepts(mimeType: String): Boolean =
(mediaType + "/" + mediaSubType).equalsIgnoreCase(mimeType) ||
(mediaSubType == "*" && mediaType.equalsIgnoreCase(mimeType.takeWhile(_ != '/'))) ||
(mediaType == "*" && mediaSubType == "*")
override def toString = {
new MediaType(mediaType, mediaSubType,
parameters ++ qValue.map(q => ("q", Some(q.toString()))).toSeq ++ acceptExtensions).toString
}
}
object MediaType {
/**
* Function and extractor object for parsing media types.
*/
object parse {
import MediaRange.MediaRangeParser
def unapply(mediaType: String): Option[MediaType] = apply(mediaType)
def apply(mediaType: String): Option[MediaType] = {
MediaRangeParser.mediaType(new CharSequenceReader(mediaType)) match {
case MediaRangeParser.Success(mt: MediaType, next) => {
if (!next.atEnd) {
Play.logger.debug("Unable to parse part of media type '" + next.source + "'")
}
Some(mt)
}
case MediaRangeParser.NoSuccess(err, next) => {
Play.logger.debug("Unable to parse media type '" + next.source + "'")
None
}
}
}
}
}
object MediaRange {
/**
* Function and extractor object for parsing media ranges.
*/
object parse {
def apply(mediaRanges: String): Seq[MediaRange] = {
MediaRangeParser(new CharSequenceReader(mediaRanges)) match {
case MediaRangeParser.Success(mrs: List[MediaRange], next) =>
if (next.atEnd) {
Play.logger.debug("Unable to parse part of media range header '" + next.source + "'")
}
mrs.sorted
case MediaRangeParser.NoSuccess(err, _) =>
Play.logger.debug("Unable to parse media range header '" + mediaRanges + "': " + err)
Nil
}
}
}
/**
* Ordering for MediaRanges, in order of highest priority to lowest priority.
*
* The reason it is highest to lowest instead of lowest to highest is to ensure sorting is stable, so if two media
* ranges have the same ordering weight, they will not change order.
*
* Ordering rules for MediaRanges:
*
* First compare by qValue, default to 1. Higher means higher priority.
* Then compare the media type. If they are not the same, then the least specific (ie, if one is *) has a lower
* priority, otherwise if they have same priority.
* Then compare the sub media type. If they are the same, the one with the more parameters has a higher priority.
* Otherwise the least specific has the lower priority, otherwise they have the same priority.
*/
implicit val ordering = new Ordering[play.api.http.MediaRange] {
def compareQValues(x: Option[BigDecimal], y: Option[BigDecimal]) = {
if (x.isEmpty && y.isEmpty) 0
else if (x.isEmpty) 1
else if (y.isEmpty) -1
else x.get.compare(y.get)
}
def compare(a: play.api.http.MediaRange, b: play.api.http.MediaRange) = {
val qCompare = compareQValues(a.qValue, b.qValue)
if (qCompare != 0) -qCompare
else if (a.mediaType == b.mediaType) {
if (a.mediaSubType == b.mediaSubType) b.parameters.size - a.parameters.size
else if (a.mediaSubType == "*") 1
else if (b.mediaSubType == "*") -1
else 0
} else if (a.mediaType == "*") 1
else if (b.mediaType == "*") -1
else 0
}
}
/**
* Parser for media ranges.
*
* Parses based on RFC2616 section 14.1, and 3.7.
*
* Unfortunately the specs are quite ambiguous, leaving much to our discretion.
*/
private[http] object MediaRangeParser extends Parsers {
val separatorChars = "()<>@,;:\\\"/[]?={} \t"
val separatorBitSet = BitSet(separatorChars.toCharArray.map(_.toInt): _*)
type Elem = Char
val any = acceptIf(_ => true)(_ => "Expected any character")
val end = not(any)
/*
* RFC 2616 section 2.2
*
* These patterns are translated directly using the same naming
*/
val ctl = acceptIf { c =>
(c >= 0 && c <= 0x1F) || c == 0x7F
}(_ => "Expected a control character")
val char = acceptIf(_ < 0x80)(_ => "Expected an ascii character")
val text = not(ctl) ~> any
val separators = {
acceptIf(c => separatorBitSet(c))(_ => "Expected one of " + separatorChars)
}
val token = rep1(not(separators | ctl) ~> any) ^^ charSeqToString
def badPart(p: Char => Boolean, msg: => String) = rep1(acceptIf(p)(ignoreErrors)) ^^ {
case chars =>
Play.logger.debug(msg + ": " + charSeqToString(chars))
None
}
val badParameter = badPart(c => c != ',' && c != ';', "Bad media type parameter")
val badMediaType = badPart(c => c != ',', "Bad media type")
def tolerant[T](p: Parser[T], bad: Parser[Option[T]]) = p.map(Some.apply) | bad
// The spec is really vague about what a quotedPair means. We're going to assume that it's just to quote quotes,
// which means all we have to do for the result of it is ignore the slash.
val quotedPair = '\\' ~> char
val qdtext = not('"') ~> text
val quotedString = '"' ~> rep(quotedPair | qdtext) <~ '"' ^^ charSeqToString
/*
* RFC 2616 section 3.7
*/
val parameter = token ~ opt('=' ~> (token | quotedString)) <~ rep(' ') ^^ {
case name ~ value => name -> value
}
// Either it's a valid parameter followed immediately by the end, a comma or a semicolon, or it's a bad parameter
val tolerantParameter = tolerant(parameter <~ guard(end | ';' | ','), badParameter)
val parameters = rep(';' ~> rep(' ') ~> tolerantParameter <~ rep(' '))
val mediaType: Parser[MediaType] = (token <~ '/') ~ (token <~ rep(' ')) ~ parameters ^^ {
case mainType ~ subType ~ ps => MediaType(mainType, subType, ps.flatten)
}
/*
* RFC 2616 section 14.1
*
* Only difference between this and the spec is it treats * as valid.
*/
// Some clients think that '*' is a valid media range. Spec says it isn't, but it's used widely enough that we
// need to support it.
val mediaRange = (mediaType | ('*' ~> parameters.map(ps => MediaType("*", "*", ps.flatten)))) ^^ { mediaType =>
val (params, rest) = mediaType.parameters.span(_._1 != "q")
val (qValueStr, acceptParams) = rest match {
case q :: ps => (q._2, ps)
case _ => (None, Nil)
}
val qValue = qValueStr.flatMap { q =>
try {
val qbd = BigDecimal(q)
if (qbd > 1) {
Play.logger.debug("Invalid q value: " + q)
None
} else {
Some(BigDecimal(q))
}
} catch {
case _: NumberFormatException =>
Play.logger.debug("Invalid q value: " + q)
None
}
}
new MediaRange(mediaType.mediaType, mediaType.mediaSubType, params, qValue, acceptParams)
}
// Either it's a valid media range followed immediately by the end or a comma, or it's a bad media type
val tolerantMediaRange = tolerant(mediaRange <~ guard(end | ','), badMediaType)
val mediaRanges = rep1sep(tolerantMediaRange, ',' ~ rep(' ')).map(_.flatten)
def apply(in: Input): ParseResult[List[MediaRange]] = mediaRanges(in)
def ignoreErrors(c: Char) = ""
def charSeqToString(chars: Seq[Char]) = new String(chars.toArray)
}
}
Other Play Framework source code examplesHere is a short list of links related to this Play Framework MediaRange.scala source code file: |
| ... this post is sponsored by my books ... | |
#1 New Release! |
FP Best Seller |
Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.
A percentage of advertising revenue from
pages under the /java/jwarehouse
URI on this website is
paid back to open source projects.