Permalink
Browse files

Merge pull request #1 from benblank/matching

Add "best match" methods for each header
  • Loading branch information...
2 parents d2d3fb9 + d1a0b89 commit e4d9f2a80ba4a265d9ad37d26e603b52e3a1381c @niclashoyer committed May 10, 2012
Showing with 108 additions and 3 deletions.
  1. +7 −0 README.md
  2. +101 −3 src/Accept.coffee
View
7 README.md
@@ -75,3 +75,10 @@ A request from a browser on `http://localhost:3000` would print out
languages: [ 'de-de', 'de', 'en-us', 'en' ],
ranges: undefined }
```
+
+For each header present in the request, there is also a `getBestMatch` method which will find the highest quality match amongst the supplied candidates.
+
+```coffeescript
+encoding = (req.accept.encodings?.getBestMatch ["gzip", "deflate"]) ? "identity"
+mediaType = req.accept.types?.getBestMatch ["text/html", "application/json" ,"application/xml"]
+```
View
104 src/Accept.coffee
@@ -67,7 +67,7 @@ parseStandard = (obj) ->
obj.value
# Parse custom `Accept` header field.
-parseHeaderField = (str, map, sort) ->
+parseHeaderField = (str, map, sort, match) ->
if not str?
return
@@ -82,10 +82,13 @@ parseHeaderField = (str, map, sort) ->
objects.sort sort
+ Object.defineProperty objects, "getBestMatch"
+ value: match ? getBestMatch
+
# Parse `Accept` header field.
parseAccept = (str) ->
str = str ? '*/*'
- parseHeaderField str, parseMediaType, sortMediaType
+ parseHeaderField str, parseMediaType, sortMediaType, getBestMediaMatch
# ## Sort functions
@@ -116,13 +119,108 @@ sortMediaType = (a, b) ->
return -1
0
+# ## Match functions
+
+# Get the best full-string match.
+getBestMatch = (candidates) ->
+ acceptable = (accepted for accepted in this when accepted in candidates)
+ acceptable[0] ? (candidates[0] if "*" in this)
+
+# Get the best language match, as per RFC 2616 section 14.4.
+getBestLanguageMatch = (candidates) ->
+ acceptable = ({value: candidate, q: -1, length: 0} for candidate in candidates)
+
+ for candidate in acceptable
+ value = candidate.value + "-"
+
+ for accepted, i in this
+ if (value.indexOf accepted + "-") is 0
+ length = (accepted.split "-").length
+
+ if length > candidate.length
+ candidate.q = i
+ candidate.length = length
+
+ acceptable.sort (a, b) ->
+ # Sort q = -1 to the bottom
+ if a.q is -1 and b.q isnt -1
+ return 1
+ if a.q isnt -1 and b.q is -1
+ return -1
+
+ # "q" comes from an array index, so sort 0 to the top.
+ if a.q > b.q
+ return 1
+ if a.q < b.q
+ return -1
+
+ # If all else is equal, longer matches are better.
+ if a.length < b.length
+ return 1
+ if a.length > b.length
+ return -1
+ 0
+
+ if acceptable[0].q isnt -1
+ acceptable[0].value
+ else
+ candidates[0] if "*" in this
+
+# Get the best media-type match, as per RFC 2616 section 14.1.
+getBestMediaMatch = (candidates) ->
+ acceptable = (parseMediaType parseParams candidate for candidate in candidates)
+
+ for candidate, i in acceptable
+ candidate.index = i
+ candidate.quality = 0
+ candidate.prec = -1
+
+ for accepted in this
+ prec = -1
+
+ if accepted.type is candidate.type
+ if accepted.subtype is candidate.subtype
+ prec = 2
+
+ for param, value of accepted.params when param isnt "q"
+ if candidate.params[param] is value
+ prec++
+ else if accepted.subtype is "*"
+ prec = 1
+ # If type matches, but subtype does not and isn't a wildcard, leave the precedence at -1.
+ else if accepted.type is "*" and accepted.subtype is "*"
+ # Lower than the precedence of any non-wildcard match, but higher than that of a non-match.
+ prec = 0
+
+ if prec > candidate.prec
+ candidate.prec = prec
+ candidate.quality = accepted.quality
+
+ acceptable.sort (a, b) ->
+ if a.quality < b.quality
+ return 1
+ if a.quality > b.quality
+ return -1
+ if a.prec < b.prec
+ return 1
+ if a.prec > b.prec
+ return 1
+
+ # If all else is equal, prefer earlier candidates.
+ if a.index > b.index
+ return -1
+ if a.index < b.index
+ return 1
+
+ candidates[acceptable[0].index] if acceptable[0].quality
+
# Build middleware with parsers for several accept header fields.
middleware = (req, res, next) ->
req.accept =
types: parseAccept req.headers.accept
charsets: parseHeaderField req.headers['accept-charset']
encodings: parseHeaderField req.headers['accept-encoding']
- languages: parseHeaderField req.headers['accept-language']
+ languages: (parseHeaderField req.headers['accept-language'], null, null, getBestLanguageMatch)
ranges: parseHeaderField req.headers['accept-ranges']
next()

0 comments on commit e4d9f2a

Please sign in to comment.