Integrated changes on content negociation library

This commit is contained in:
2013-09-20 15:04:50 +02:00
40 changed files with 1006 additions and 1133 deletions

View File

@@ -1,79 +0,0 @@
#!/usr/bin/env ruby
# Niklaus Giger, 15.01.2011
# Small ruby-script run all tests using ec (the Eiffel compiler)
# we assumen that ec outputs everything in english!
# For the command line options look at
# http://docs.eiffel.com/book/eiffelstudio/eiffelstudio-command-line-options
# we use often the -batch open.
#
# TODO: Fix problems when compiling takes too long and/or there
# are ec process lingering around from a previous failed build
require 'tempfile'
require 'fileutils'
# Override system command.
# run command. if not successful, complain and exit with error
def system(cmd)
puts cmd
res = Kernel.system(cmd)
if !res
puts "Failed running: #{cmd}"
exit 2
end
end
def runTestForProject(where)
if !File.directory?(where)
puts "Directory #{where} does not exist"
exit 2
end
# create a temporary file with input for the
# interactive mode of ec
commands2run=<<EOF
T
E
q
EOF
file = Tempfile.new('commands2run')
file.puts commands2run
file.close
Dir.chdir(where)
# First we have to remove old compilation
FileUtils.rm_rf("EIFGENs")
# compile the library
cmd = "ec -config library/emime-safe.ecf -target emime -batch -c_compile"
res = system(cmd)
# compile the test
cmd = "ec -config test/test-safe.ecf -target test -batch -c_compile"
res = system(cmd)
logFile = "#{__FILE__}.log"
sleep 1
cmd = "ec -config test/test-safe.ecf -target test -batch -loop 1>#{logFile} 2>#{__FILE__}.auto_test_output <#{file.path}"
res = system(cmd)
m= nil
IO.readlines(logFile).each{
|line|
m = /(\d+) tests total \((\d+) executed, (\d+) failing, (\d+) unresolved/.match(line)
break if m
}
puts
if m[3].to_i == 0 and m[4].to_i == 0 then
puts "#{m[1]} tests completed successfully"
else
puts "Failures while running #{m[1]} failed. #{m[2]} executed #{m[3]} failures #{m[4]} unresolved"
exit 2
end
end
runTestForProject(Dir.pwd)

View File

@@ -1,45 +0,0 @@
note
description: "Summary description for {CHARACTER_ENCODING_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
CHARACTER_ENCODING_VARIANT_RESULTS
feature
character_type : detachable STRING
set_character_type ( a_character_type: STRING)
do
character_type := a_character_type
ensure
set_character_type : a_character_type ~ character_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept-Charset"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,44 +0,0 @@
note
description: "Summary description for {COMPRESSION_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
COMPRESSION_VARIANT_RESULTS
feature
compression_type : detachable STRING
set_compression_type ( a_compression_type: STRING)
do
compression_type := a_compression_type
ensure
set_compression_type : a_compression_type ~ compression_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept-Encoding"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,216 +0,0 @@
note
description: "Summary description for {CONNEG_SERVER_SIDE}. Utility class to support Server Side Content Negotiation "
author: ""
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
class
CONNEG_SERVER_SIDE
inherit
SHARED_CONNEG
REFACTORING_HELPER
create
make
feature -- Initialization
make ( a_mime: STRING; a_language : STRING; a_charset :STRING; an_encoding: STRING)
do
set_mime_default (a_mime)
set_language_default (a_language)
set_charset_default (a_charset)
set_encoding_defautl (an_encoding)
end
feature -- Server Side Defaults Formats
mime_default : STRING
set_mime_default ( a_mime: STRING)
-- set the mime_default with `a_mime'
do
mime_default := a_mime
ensure
set_mime_default: a_mime ~ mime_default
end
language_default : STRING
set_language_default (a_language : STRING)
-- set the language_default with `a_language'
do
language_default := a_language
ensure
set_language : a_language ~ language_default
end
charset_default : STRING
set_charset_default (a_charset : STRING)
-- set the charset_default with `a_charset'
do
charset_default := a_charset
ensure
set_charset : a_charset ~ charset_default
end
encoding_default : STRING
set_encoding_defautl (an_encoding : STRING)
do
encoding_default := an_encoding
ensure
set_encoding : an_encoding ~ encoding_default
end
feature -- Media Type Negotiation
media_type_preference ( mime_types_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : MEDIA_TYPE_VARIANT_RESULTS
-- mime_types_supported represent media types supported by the server.
-- header represent the Accept header, ie, the client preferences.
-- Return which media type to use for representaion in a response, if the server support
-- one media type, or empty in other case.
-- Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1
local
mime_match: STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (TRUE)
Result.set_media_type (mime_default)
else
-- select the best match, server support, client preferences
mime_match := mime.best_match (mime_types_supported, header)
if mime_match.is_empty then
-- The server does not support any of the media types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (mime_types_supported)
else
-- Set the best match
Result.set_media_type(mime_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Encoding Negotiation
charset_preference (server_charset_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : CHARACTER_ENCODING_VARIANT_RESULTS
-- server_charset_supported represent a list of charset supported by the server.
-- header represent the Accept-Charset header, ie, the client preferences.
-- Return which Charset to use in a response, if the server support
-- one Charset, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
local
charset_match : STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept-Charset header, ie the header is empty, in this case use default charset encoding
-- (UTF-8)
Result.set_acceptable (TRUE)
Result.set_character_type (charset_default)
else
-- select the best match, server support, client preferences
charset_match := common.best_match (server_charset_supported, header)
if charset_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (server_charset_supported)
else
-- Set the best match
Result.set_character_type(charset_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Compression Negotiation
encoding_preference (server_encoding_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : COMPRESSION_VARIANT_RESULTS
-- server_encoding_supported represent a list of encoding supported by the server.
-- header represent the Accept-Encoding header, ie, the client preferences.
-- Return which Encoding to use in a response, if the server support
-- one Encoding, or empty in other case.
-- Representation: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
local
compression_match : STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept-Encoding header, ie the header is empty, in this case do not compress representations
Result.set_acceptable (TRUE)
Result.set_compression_type (encoding_default)
else
-- select the best match, server support, client preferences
compression_match := common.best_match (server_encoding_supported, header)
if compression_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (server_encoding_supported)
else
-- Set the best match
Result.set_compression_type(compression_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Language Negotiation
language_preference (server_language_supported : LIST[STRING]; header: detachable READABLE_STRING_8) : LANGUAGE_VARIANT_RESULTS
-- server_language_supported represent a list of languages supported by the server.
-- header represent the Accept-Language header, ie, the client preferences.
-- Return which Language to use in a response, if the server support
-- one Language, or empty in other case.
-- Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
local
language_match: STRING
do
create Result
if header = Void or else header.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (TRUE)
Result.set_language_type (language_default)
else
-- select the best match, server support, client preferences
language_match := language.best_match (server_language_supported, header)
if language_match.is_empty then
-- The server does not support any of the media types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (server_language_supported)
else
-- Set the best match
Result.set_language_type(language_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Apache Conneg Algorithm
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,352 +0,0 @@
note
description: "Summary description for {LANGUAGE_PARSE}."
author: ""
date: "$Date$"
revision: "$Revision$"
description : "Language Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4"
class
LANGUAGE_PARSE
inherit
REFACTORING_HELPER
feature -- Parser
parse_mime_type (a_mime_type: STRING): LANGUAGE_RESULTS
-- Parses a mime-type into its component parts.
-- For example, the media range 'application/xhtml;q=0.5' would get parsed
-- into:
-- ('application', 'xhtml', {'q', '0.5'})
local
l_parts: LIST [STRING]
p: STRING
sub_parts: LIST [STRING]
i: INTEGER
l_full_type: STRING
l_types: LIST [STRING]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_mime_type.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
--Java URLConnection class sends an Accept header that includes a
--single "*" - Turn it into a legal wildcard.
l_full_type := trim (l_parts[1])
if l_full_type.same_string ("*") then
l_full_type := "*"
end
l_types := l_full_type.split ('-')
if l_types.count = 1 then
Result.set_type (trim (l_types[1]))
else
Result.set_type (trim (l_types[1]))
Result.set_sub_type (trim (l_types[2]))
end
end
parse_media_range (a_range: STRING): LANGUAGE_RESULTS
-- Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
-- For example, the media range 'application/*;q=0.5' would get parsed into:
-- ('application', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_mime_type (a_range)
if attached Result.item ("q") as q then
if
q.is_double and then
attached {REAL_64} q.to_double as r and then
(r >= 0.0 and r <= 1.0)
then
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
end
fitness_and_quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [LANGUAGE_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: LANGUAGE_RESULTS
range: LANGUAGE_RESULTS
keys: LIST [STRING]
param_matches: INTEGER
element: detachable STRING
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_media_range (a_mime_type)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if
attached target.type as l_target_type
then
from
parsed_ranges.start
until
parsed_ranges.after
loop
range := parsed_ranges.item_for_iteration
if
(
attached range.type as l_range_type and then
(l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))
)
then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if
not element.same_string ("q") and then
range.has_key (element) and then
(attached target.item (element) as t_item and attached range.item (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if (
attached range.sub_type as l_range_sub_type and then attached target.sub_type as l_target_sub_type and then
(l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))
) then
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [LANGUAGE_RESULTS]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parseMediaRange(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality() except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
Result := fitness_and_quality_parsed (a_mime_type, parsed_ranges).quality
end
quality (a_mime_type: STRING; ranges: STRING): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [STRING]
res : ARRAYED_LIST [LANGUAGE_RESULTS]
p_res : LANGUAGE_RESULTS
do
l_ranges := ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := parse_media_range (l_ranges.item_for_iteration)
res.put_left (p_res)
l_ranges.forth
end
Result := quality_parsed (a_mime_type, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [LANGUAGE_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: LANGUAGE_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: STRING
do
l_res := header.split (',')
create {ARRAYED_LIST [LANGUAGE_RESULTS]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := parse_media_range (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
feature {NONE} -- Implementation
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,46 +0,0 @@
note
description: "Summary description for {LANGUAGE_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_VARIANT_RESULTS
feature
language_type : detachable STRING
set_language_type ( a_language_type: STRING)
do
language_type := a_language_type
ensure
set_language_type : a_language_type ~ language_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept-Language"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,47 +0,0 @@
note
description: "Summary description for {MEDIA_TYPE_VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
MEDIA_TYPE_VARIANT_RESULTS
feature
media_type : detachable STRING
set_media_type ( a_media_type: STRING)
do
media_type := a_media_type
ensure
set_media_type : a_media_type ~ media_type
end
variant_header : detachable STRING
set_variant_header
do
variant_header := "Accept"
end
supported_variants : detachable LIST[STRING]
set_supported_variants (a_supported : LIST[STRING])
do
supported_variants := a_supported
ensure
set_supported_variants : supported_variants = a_supported
end
is_acceptable : BOOLEAN
set_acceptable ( acceptable : BOOLEAN)
do
is_acceptable := acceptable
ensure
is_acceptable = acceptable
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,58 +0,0 @@
note
description: "Summary description for {VARIANT_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
VARIANT_RESULTS
feature -- Mime, Language, Charset and Encoding Results
mime_result : detachable STRING
set_mime_result ( a_mime: STRING)
-- set the mime_result with `a_mime'
do
mime_result := a_mime
ensure
set_mime_result: a_mime ~ mime_result
end
language_result : detachable STRING
set_language_result (a_language : STRING)
-- set the language_result with `a_language'
do
language_result := a_language
ensure
set_language : a_language ~ language_result
end
charset_result : detachable STRING
set_charset_result (a_charset : STRING)
-- set the charset_result with `a_charset'
do
charset_result := a_charset
ensure
set_charset : a_charset ~ charset_result
end
encoding_result : detachable STRING
set_encoding_defautl (an_encoding : STRING)
do
encoding_result := an_encoding
ensure
set_encoding : an_encoding ~ encoding_result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,4 +0,0 @@
*~
EIFGEN* # ignore all files in the EIFGENs/ directory

View File

@@ -12,7 +12,7 @@
<assertions precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\http\http-safe.ecf"/>
<library name="http" location="..\http\http-safe.ecf" readonly="false"/>
<cluster name="conneg" location=".\src\" recursive="true"/>
</target>
</system>

View File

@@ -0,0 +1,230 @@
note
description: "Summary description for {CONNEG_SERVER_SIDE}. Utility class to support Server Side Content Negotiation "
date: "$Date$"
revision: "$Revision$"
description: "[
Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.1
Server-driven Negotiation : If the selection of the best representation for a response is made by an algorithm located at the server,
it is called server-driven negotiation. Selection is based on the available representations of the response (the dimensions over which it can vary; e.g. language, content-coding, etc.)
and the contents of particular header fields in the request message or on other information pertaining to the request (such as the network address of the client).
Server-driven negotiation is advantageous when the algorithm for selecting from among the available representations is difficult to describe to the user agent,
or when the server desires to send its "best guess" to the client along with the first response (hoping to avoid the round-trip delay of a subsequent request if the "best guess" is good enough for the user).
In order to improve the server's guess, the user agent MAY include request header fields (Accept, Accept-Language, Accept-Encoding, etc.) which describe its preferences for such a response.
]"
EIS: "name=server driven negotiation", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec12.html#sec12.", "protocol=uri"
class
CONNEG_SERVER_SIDE
inherit
SHARED_CONNEG
REFACTORING_HELPER
create
make
feature -- Initialization
make (a_mime: READABLE_STRING_8; a_language: READABLE_STRING_8; a_charset: READABLE_STRING_8; a_encoding: READABLE_STRING_8)
do
set_mime_default (a_mime)
set_language_default (a_language)
set_charset_default (a_charset)
set_encoding_default (a_encoding)
ensure
mime_default_set: mime = a_mime
language_default_set: language_default = a_language
charset_default_set: charset_default = a_charset
encoding_default_set: encoding_default = a_encoding
end
feature -- AccessServer Side Defaults Formats
mime_default: READABLE_STRING_8
-- Media type which is acceptable for the response.
language_default: READABLE_STRING_8
-- Natural language that is preferred as a response to the request.
charset_default: READABLE_STRING_8
-- Character set that is acceptable for the response.
encoding_default: READABLE_STRING_8
-- Content-coding that is acceptable in the response.
feature -- Change Element
set_mime_default (a_mime: READABLE_STRING_8)
-- Set the mime_default with `a_mime'
do
mime_default := a_mime
ensure
mime_default_set: a_mime = mime_default
end
set_language_default (a_language: READABLE_STRING_8)
-- Set the language_default with `a_language'
do
language_default := a_language
ensure
language_default_set: a_language = language_default
end
set_charset_default (a_charset: READABLE_STRING_8)
-- Set the charset_default with `a_charset'
do
charset_default := a_charset
ensure
charset_default_set: a_charset = charset_default
end
set_encoding_default (a_encoding: READABLE_STRING_8)
do
encoding_default := a_encoding
ensure
encoding_default_set: a_encoding = encoding_default
end
feature -- Media Type Negotiation
media_type_preference (a_mime_types_supported: LIST [READABLE_STRING_8]; a_header: detachable READABLE_STRING_8): MEDIA_TYPE_VARIANT_RESULTS
-- `a_mime_types_supported' represent media types supported by the server.
-- `a_header represent' the Accept header, ie, the client preferences.
-- Return which media type to use for representation in a response, if the server supports
-- the requested media type, or empty in other case.
note
EIS: "name=media type", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1", "protocol=uri"
local
l_mime_match: READABLE_STRING_8
do
create Result
if a_header = Void or else a_header.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (TRUE)
Result.set_type (mime_default)
else
-- select the best match, server support, client preferences
l_mime_match := mime.best_match (a_mime_types_supported, a_header)
if l_mime_match.is_empty then
-- The server does not support any of the media types preferred by the client
Result.set_acceptable (False)
Result.set_supported_variants (a_mime_types_supported)
else
-- Set the best match
Result.set_type (l_mime_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Encoding Negotiation
charset_preference (a_server_charset_supported: LIST [READABLE_STRING_8]; a_header: detachable READABLE_STRING_8): CHARACTER_ENCODING_VARIANT_RESULTS
-- `a_server_charset_supported' represent a list of character sets supported by the server.
-- `a_header' represents the Accept-Charset header, ie, the client preferences.
-- Return which Charset to use in a response, if the server supports
-- the requested Charset, or empty in other case.
note
EIS: "name=charset", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2", "protocol=uri"
local
l_charset_match: READABLE_STRING_8
do
create Result
if a_header = Void or else a_header.is_empty then
-- the request has no Accept-Charset header, ie the header is empty, in this case use default charset encoding
Result.set_acceptable (TRUE)
Result.set_type (charset_default)
else
-- select the best match, server support, client preferences
l_charset_match := common.best_match (a_server_charset_supported, a_header)
if l_charset_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (a_server_charset_supported)
else
-- Set the best match
Result.set_type (l_charset_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Compression Negotiation
encoding_preference (a_server_encoding_supported: LIST [READABLE_STRING_8]; a_header: detachable READABLE_STRING_8): COMPRESSION_VARIANT_RESULTS
-- `a_server_encoding_supported' represent a list of encoding supported by the server.
-- `a_header' represent the Accept-Encoding header, ie, the client preferences.
-- Return which Encoding to use in a response, if the server supports
-- the requested Encoding, or empty in other case.
note
EIS: "name=encoding", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
local
l_compression_match: READABLE_STRING_8
do
create Result
if a_header = Void or else a_header.is_empty then
-- the request has no Accept-Encoding header, ie the header is empty, in this case do not compress representations
Result.set_acceptable (TRUE)
Result.set_type (encoding_default)
else
-- select the best match, server support, client preferences
l_compression_match := common.best_match (a_server_encoding_supported, a_header)
if l_compression_match.is_empty then
-- The server does not support any of the compression types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (a_server_encoding_supported)
else
-- Set the best match
Result.set_type (l_compression_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
feature -- Language Negotiation
language_preference (a_server_language_supported: LIST [READABLE_STRING_8]; a_header: detachable READABLE_STRING_8): LANGUAGE_VARIANT_RESULTS
-- `a_server_language_supported' represent a list of languages supported by the server.
-- `a_header' represent the Accept-Language header, ie, the client preferences.
-- Return which Language to use in a response, if the server supports
-- the requested Language, or empty in other case.
note
EIS: "name=language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
local
l_language_match: READABLE_STRING_8
do
create Result
if a_header = Void or else a_header.is_empty then
-- the request has no Accept header, ie the header is empty, in this case we use the default format
Result.set_acceptable (TRUE)
Result.set_type (language_default)
else
-- select the best match, server support, client preferences
l_language_match := language.best_match (a_server_language_supported, a_header)
if l_language_match.is_empty then
-- The server does not support any of the media types prefered by the client
Result.set_acceptable (False)
Result.set_supported_variants (a_server_language_supported)
else
-- Set the best match
Result.set_type (l_language_match)
Result.set_acceptable (True)
Result.set_variant_header
end
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,34 +1,39 @@
note
description: "COMMON_ACCEPT_HEADER_PARSER, this class allows to parse Accept-Charset and Accept-Encoding headers"
author: ""
description: "[
COMMON_ACCEPT_HEADER_PARSER, this class allows to parse Accept-Charset and Accept-Encoding headers
]"
date: "$Date$"
revision: "$Revision$"
description : "[
Charset Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2
Encoding Reference : http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3
]"
EIS: "name=Charset", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2", "protocol=uri"
EIS: "name=Encoding", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
class
COMMON_ACCEPT_HEADER_PARSER
inherit {NONE}
MIME_TYPE_PARSER_UTILITIES
feature -- Parser
parse_common (header: STRING): COMMON_RESULTS
parse_common (header: READABLE_STRING_8): COMMON_RESULTS
-- Parses `header' charset/encoding into its component parts.
-- For example, the charset 'iso-8889-5' would get parsed
-- into:
-- ('iso-8889-5', {'q':'1.0'})
local
l_parts: LIST [STRING]
sub_parts: LIST [STRING]
p: STRING
l_parts: LIST [READABLE_STRING_8]
sub_parts: LIST [READABLE_STRING_8]
p: READABLE_STRING_8
i: INTEGER
l_header: STRING
l_header: READABLE_STRING_8
do
create Result.make
l_parts := header.split (';')
if l_parts.count = 1 then
Result.put ("1.0", "q")
Result.put ("1.0", "q")
else
from
i := 1
@@ -38,17 +43,16 @@ feature -- Parser
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
Result.put (trim (sub_parts [2]), trim (sub_parts [1]))
end
i := i + 1
end
end
l_header := trim (l_parts[1])
l_header := trim (l_parts [1])
Result.set_field (trim (l_header))
end
fitness_and_quality_parsed (a_field: STRING; parsed_charsets: LIST [COMMON_RESULTS]): FITNESS_AND_QUALITY
fitness_and_quality_parsed (a_field: READABLE_STRING_8; parsed_charsets: LIST [COMMON_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given charset/encoding against a list of charsets/encodings
-- that have already been parsed by parse_common. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
@@ -60,12 +64,12 @@ feature -- Parser
best_fit_q: REAL_64
target: COMMON_RESULTS
range: COMMON_RESULTS
element: detachable STRING
element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_common(a_field)
target := parse_common (a_field)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
@@ -76,9 +80,7 @@ feature -- Parser
else
target_q := 1.0
end
if attached target.field as l_target_field
then
if attached target.field as l_target_field then
from
parsed_charsets.start
until
@@ -86,7 +88,7 @@ feature -- Parser
loop
range := parsed_charsets.item_for_iteration
if attached range.field as l_range_common then
if l_target_field.same_string (l_range_common) or l_target_field.same_string ("*") or l_range_common.same_string ("*") then
if l_target_field.same_string (l_range_common) or l_target_field.same_string ("*") or l_range_common.same_string ("*") then
if l_range_common.same_string (l_target_field) then
l_fitness := 100
else
@@ -109,8 +111,7 @@ feature -- Parser
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_field: STRING; parsed_common: LIST [COMMON_RESULTS]): REAL_64
quality_parsed (a_field: READABLE_STRING_8; parsed_common: LIST [COMMON_RESULTS]): REAL_64
-- Find the best match for a given charset/encoding against a list of charsets/encodings that
-- have already been parsed by parse_charsets(). Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
@@ -119,14 +120,13 @@ feature -- Parser
Result := fitness_and_quality_parsed (a_field, parsed_common).quality
end
quality (a_field: STRING; commons: STRING): REAL_64
quality (a_field: READABLE_STRING_8; commons: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a charset/encoding when compared against the
-- a list of charsets/encodings/
local
l_commons : LIST [STRING]
res : ARRAYED_LIST [COMMON_RESULTS]
p_res : COMMON_RESULTS
l_commons: LIST [READABLE_STRING_8]
res: ARRAYED_LIST [COMMON_RESULTS]
p_res: COMMON_RESULTS
do
l_commons := commons.split (',')
from
@@ -142,19 +142,17 @@ feature -- Parser
Result := quality_parsed (a_field, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
best_match (supported: LIST [READABLE_STRING_8]; header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the accept with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [COMMON_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
l_res: LIST [READABLE_STRING_8]
p_res: COMMON_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
do
l_res := header.split (',')
create {ARRAYED_LIST [COMMON_RESULTS]} l_header_results.make (l_res.count)
from
l_res.start
until
@@ -164,9 +162,7 @@ feature -- Parser
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (supported.count)
from
supported.start
until
@@ -201,12 +197,16 @@ feature -- Parser
end
weighted_matches.forth
end
check weighted_matches.item = fitness_and_quality end
check
weighted_matches.item = fitness_and_quality
end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check first_one > fitness_and_quality end
check
first_one > fitness_and_quality
end
weighted_matches.remove
end
end
@@ -228,14 +228,16 @@ feature -- Parser
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (l_field) then
--| Found
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
else
check has_field: False end
check
has_field: False
end
end
l_header_results.forth
end
@@ -250,33 +252,8 @@ feature -- Parser
end
end
feature -- Util
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,312 @@
note
description: "[
{LANGUAGE_PARSE} is encharge to parse language tags defined as follow:
Accept-Language = "Accept-Language" ":"
1#( language-range [ ";" "q" "=" qvalue ] )
language-range = ( ( 1*8ALPHA *( "-" 1*8ALPHA ) ) | "*" )
Example:
Accept-Language: da, en-gb;q=0.8, en;q=0.7
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name=Accept-Language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
class
LANGUAGE_PARSE
inherit {NONE}
MIME_TYPE_PARSER_UTILITIES
REFACTORING_HELPER
feature -- Parser
parse_language (a_accept_language: READABLE_STRING_8): LANGUAGE_RESULTS
-- Parses `a_accept_language' request-header field into its component parts.
-- For example, the language range 'en-gb;q=0.8' would get parsed
-- into:
-- ('en-gb', {'q':'0.8',})
local
l_parts: LIST [READABLE_STRING_8]
p: READABLE_STRING_8
sub_parts: LIST [READABLE_STRING_8]
i: INTEGER
l_full_type: READABLE_STRING_8
l_types: LIST [READABLE_STRING_8]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_accept_language.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts [2]), trim (sub_parts [1]))
end
i := i + 1
end
l_full_type := trim (l_parts [1])
if l_full_type.same_string ("*") then
l_full_type := "*"
end
l_types := l_full_type.split ('-')
if l_types.count = 1 then
Result.set_type (trim (l_types [1]))
else
Result.set_type (trim (l_types [1]))
Result.set_sub_type (trim (l_types [2]))
end
end
parse_language_range (a_language_range: READABLE_STRING_8): LANGUAGE_RESULTS
-- Languages-ranges are languages with wild-cards and a 'q' quality parameter.
-- For example, the language range ('en-* ;q=0.5') would get parsed into:
-- ('en', '*', {'q', '0.5'})
-- In addition this function also guarantees that there is a value for 'q'
-- in the params dictionary, filling it in with a proper default if
-- necessary.
do
fixme ("Improve the code!!!")
Result := parse_language (a_language_range)
if attached Result.item ("q") as q then
if q.is_double and then attached {REAL_64} q.to_double as r and then (r >= 0.0 and r <= 1.0) then
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
else
Result.put ("1.0", "q")
end
end
fitness_and_quality_parsed (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [LANGUAGE_RESULTS]): FITNESS_AND_QUALITY
-- Find the best match for a given `a_language' against a list of language ranges `a_parsed_ranges'
-- that have already been parsed by parse_language_range.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: LANGUAGE_RESULTS
range: LANGUAGE_RESULTS
keys: LIST [READABLE_STRING_8]
param_matches: INTEGER
element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_language_range (a_language)
if attached target.item ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
elseif target_q > 1.0 then
target_q := 1.0
end
else
target_q := 1.0
end
if attached target.type as l_target_type then
from
a_parsed_ranges.start
until
a_parsed_ranges.after
loop
range := a_parsed_ranges.item_for_iteration
if (attached range.type as l_range_type and then (l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))) then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if not element.same_string ("q") and then range.has_key (element) and then (attached target.item (element) as t_item and attached range.item (element) as r_item) and then t_item.same_string (r_item) then
param_matches := param_matches + 1
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
l_fitness := 0
end
if (attached range.sub_type as l_range_sub_type and then attached target.sub_type as l_target_sub_type and then (l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))) then
if l_range_sub_type.same_string (l_target_sub_type) then
l_fitness := l_fitness + 10
end
end
l_fitness := l_fitness + param_matches
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
best_fit_q := 0.0
end
end
end
a_parsed_ranges.forth
end
end
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_language: READABLE_STRING_8; a_parsed_ranges: LIST [LANGUAGE_RESULTS]): REAL_64
-- Find the best match for a given `a_language' against a list of ranges `parsed_ranges' that
-- have already been parsed by parse_language_range. Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality except that 'a_parsed_ranges' must be a list
-- of parsed language ranges.
do
Result := fitness_and_quality_parsed (a_language, a_parsed_ranges).quality
end
quality (a_language: READABLE_STRING_8; a_ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a `a_language' when compared against the
-- language range in `a_ranges'.
local
l_ranges: LIST [READABLE_STRING_8]
res: ARRAYED_LIST [LANGUAGE_RESULTS]
p_res: LANGUAGE_RESULTS
do
l_ranges := a_ranges.split (',')
from
create res.make (10);
l_ranges.start
until
l_ranges.after
loop
p_res := parse_language_range (l_ranges.item_for_iteration)
res.put_left (p_res)
l_ranges.forth
end
Result := quality_parsed (a_language, res)
end
best_match (a_supported: LIST [READABLE_STRING_8]; a_header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the `language' with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [LANGUAGE_RESULTS]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [READABLE_STRING_8]
p_res: LANGUAGE_RESULTS
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: READABLE_STRING_8
do
l_res := a_header.split (',')
create {ARRAYED_LIST [LANGUAGE_RESULTS]} l_header_results.make (l_res.count)
fixme ("Extract method!!!")
from
l_res.start
until
l_res.after
loop
p_res := parse_language_range (l_res.item_for_iteration)
l_header_results.force (p_res)
l_res.forth
end
create {ARRAYED_LIST [FITNESS_AND_QUALITY]} weighted_matches.make (a_supported.count)
from
a_supported.start
until
a_supported.after
loop
fitness_and_quality := fitness_and_quality_parsed (a_supported.item_for_iteration, l_header_results)
fitness_and_quality.set_mime_type (mime_type (a_supported.item_for_iteration))
weighted_matches.force (fitness_and_quality)
a_supported.forth
end
--| Keep only top quality+fitness types
from
weighted_matches.start
first_one := weighted_matches.item
weighted_matches.forth
until
weighted_matches.after
loop
fitness_and_quality := weighted_matches.item
if first_one < fitness_and_quality then
first_one := fitness_and_quality
if not weighted_matches.isfirst then
from
weighted_matches.back
until
weighted_matches.before
loop
weighted_matches.remove
weighted_matches.back
end
weighted_matches.forth
end
check
weighted_matches.item = fitness_and_quality
end
weighted_matches.forth
elseif first_one.is_equal (fitness_and_quality) then
weighted_matches.forth
else
check
first_one > fitness_and_quality
end
weighted_matches.remove
end
end
if first_one /= Void and then first_one.quality /= 0.0 then
if weighted_matches.count = 1 then
Result := first_one.mime_type
else
from
fitness_and_quality := Void
l_header_results.start
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
from
weighted_matches.start
until
weighted_matches.after or fitness_and_quality /= Void
loop
fitness_and_quality := weighted_matches.item
if fitness_and_quality.mime_type.same_string (s) then
--| Found
else
fitness_and_quality := Void
weighted_matches.forth
end
end
l_header_results.forth
end
if fitness_and_quality /= Void then
Result := fitness_and_quality.mime_type
else
Result := first_one.mime_type
end
end
else
Result := ""
end
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,58 +1,45 @@
note
description: "Summary description for {MIME_PARSE}."
author: ""
description: "[
{MIME_PARSE}. is encharge to parse Accept request-header field defined as follow:
Accept = "Accept" ":"
#( media-range [ accept-params ] )
media-range = ( "*/*"
| ( type "/" "*" )
| ( type "/" subtype )
) *( ";" parameter )
accept-params = ";" "q" "=" qvalue *( accept-extension )
accept-extension = ";" token [ "=" ( token | quoted-string ) ]
Example:
Accept: text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c
]"
date: "$Date$"
revision: "$Revision$"
description : "Accept Reference: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1"
EIS: "name=Accept", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1", "protocol=uri"
class
MIME_PARSE
inherit
inherit {NONE}
MIME_TYPE_PARSER_UTILITIES
REFACTORING_HELPER
feature -- Parser
parse_mime_type (a_mime_type: STRING): PARSE_RESULTS
parse_mime_type (a_mime_type: READABLE_STRING_8): HTTP_MEDIA_TYPE
-- Parses a mime-type into its component parts.
-- For example, the media range 'application/xhtml;q=0.5' would get parsed
-- into:
-- ('application', 'xhtml', {'q', '0.5'})
local
l_parts: LIST [STRING]
p: STRING
sub_parts: LIST [STRING]
i: INTEGER
l_full_type: STRING
l_types: LIST [STRING]
do
fixme ("Improve code!!!")
create Result.make
l_parts := a_mime_type.split (';')
from
i := 1
until
i > l_parts.count
loop
p := l_parts.at (i)
sub_parts := p.split ('=')
if sub_parts.count = 2 then
Result.put (trim (sub_parts[2]), trim (sub_parts[1]))
end
i := i + 1
end
--Java URLConnection class sends an Accept header that includes a
--single "*" - Turn it into a legal wildcard.
l_full_type := trim (l_parts[1])
if l_full_type.same_string ("*") then
l_full_type := "*/*"
end
l_types := l_full_type.split ('/')
Result.set_type (trim (l_types[1]))
Result.set_sub_type (trim (l_types[2]))
create Result.make_from_string (a_mime_type)
end
parse_media_range (a_range: STRING): PARSE_RESULTS
parse_media_range (a_range: READABLE_STRING_8): HTTP_MEDIA_TYPE
-- Media-ranges are mime-types with wild-cards and a 'q' quality parameter.
-- For example, the media range 'application/*;q=0.5' would get parsed into:
-- ('application', '*', {'q', '0.5'})
@@ -62,7 +49,7 @@ feature -- Parser
do
fixme ("Improve the code!!!")
Result := parse_mime_type (a_range)
if attached Result.item ("q") as q then
if attached Result.parameter ("q") as q then
if
q.is_double and then
attached {REAL_64} q.to_double as r and then
@@ -71,38 +58,34 @@ feature -- Parser
--| Keep current value
if q.same_string ("1") then
--| Use 1.0 formatting
Result.put ("1.0", "q")
Result.add_parameter ("q", "1.0")
end
else
Result.put ("1.0", "q")
Result.add_parameter ("q", "1.0")
end
else
Result.put ("1.0", "q")
Result.add_parameter ("q", "1.0")
end
end
fitness_and_quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [PARSE_RESULTS]): FITNESS_AND_QUALITY
fitness_and_quality_parsed (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): FITNESS_AND_QUALITY
-- Find the best match for a given mimeType against a list of media_ranges
-- that have already been parsed by parse_media_range. Returns a
-- tuple of the fitness value and the value of the 'q' quality parameter of
-- the best match, or (-1, 0) if no match was found. Just as for
-- quality_parsed(), 'parsed_ranges' must be a list of parsed media ranges.
-- that have already been parsed by parse_media_range.
local
best_fitness: INTEGER
target_q: REAL_64
best_fit_q: REAL_64
target: PARSE_RESULTS
range: PARSE_RESULTS
keys: LIST [STRING]
target: HTTP_MEDIA_TYPE
range: HTTP_MEDIA_TYPE
param_matches: INTEGER
element: detachable STRING
element: detachable READABLE_STRING_8
l_fitness: INTEGER
do
best_fitness := -1
best_fit_q := 0.0
target := parse_media_range (a_mime_type)
if attached target.item ("q") as q and then q.is_double then
if attached target.parameter ("q") as q and then q.is_double then
target_q := q.to_double
if target_q < 0.0 then
target_q := 0.0
@@ -115,7 +98,7 @@ feature -- Parser
if
attached target.type as l_target_type and
attached target.sub_type as l_target_sub_type
attached target.subtype as l_target_sub_type
then
from
parsed_ranges.start
@@ -129,29 +112,29 @@ feature -- Parser
(l_target_type.same_string (l_range_type) or l_range_type.same_string ("*") or l_target_type.same_string ("*"))
) and
(
attached range.sub_type as l_range_sub_type and then
attached range.subtype as l_range_sub_type and then
(l_target_sub_type.same_string (l_range_sub_type) or l_range_sub_type.same_string ("*") or l_target_sub_type.same_string ("*"))
)
then
from
param_matches := 0
keys := target.keys
keys.start
until
keys.after
loop
element := keys.item_for_iteration
if
not element.same_string ("q") and then
range.has_key (element) and then
(attached target.item (element) as t_item and attached range.item (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
if attached target.parameters as l_keys then
from
param_matches := 0
l_keys.start
until
l_keys.after
loop
element := l_keys.key_for_iteration
if
not element.same_string ("q") and then
range.has_parameter (element) and then
(attached target.parameter (element) as t_item and attached range.parameter (element) as r_item) and then
t_item.same_string (r_item)
then
param_matches := param_matches + 1
end
l_keys.forth
end
keys.forth
end
if l_range_type.same_string (l_target_type) then
l_fitness := 100
else
@@ -166,7 +149,7 @@ feature -- Parser
if l_fitness > best_fitness then
best_fitness := l_fitness
element := range.item ("q")
element := range.parameter ("q")
if element /= Void then
best_fit_q := element.to_double.min (target_q)
else
@@ -180,23 +163,23 @@ feature -- Parser
create Result.make (best_fitness, best_fit_q)
end
quality_parsed (a_mime_type: STRING; parsed_ranges: LIST [PARSE_RESULTS]): REAL_64
quality_parsed (a_mime_type: READABLE_STRING_8; parsed_ranges: LIST [HTTP_MEDIA_TYPE]): REAL_64
-- Find the best match for a given mime-type against a list of ranges that
-- have already been parsed by parseMediaRange(). Returns the 'q' quality
-- have already been parsed by parse_media_range. Returns the 'q' quality
-- parameter of the best match, 0 if no match was found. This function
-- bahaves the same as quality() except that 'parsed_ranges' must be a list
-- bahaves the same as quality except that 'parsed_ranges' must be a list
-- of parsed media ranges.
do
Result := fitness_and_quality_parsed (a_mime_type, parsed_ranges).quality
end
quality (a_mime_type: STRING; ranges: STRING): REAL_64
quality (a_mime_type: READABLE_STRING_8; ranges: READABLE_STRING_8): REAL_64
-- Returns the quality 'q' of a mime-type when compared against the
-- mediaRanges in ranges.
local
l_ranges : LIST [STRING]
res : ARRAYED_LIST [PARSE_RESULTS]
p_res : PARSE_RESULTS
l_ranges : LIST [READABLE_STRING_8]
res : ARRAYED_LIST [HTTP_MEDIA_TYPE]
p_res : HTTP_MEDIA_TYPE
do
l_ranges := ranges.split (',')
from
@@ -212,18 +195,18 @@ feature -- Parser
Result := quality_parsed (a_mime_type, res)
end
best_match (supported: LIST [STRING]; header: STRING): STRING
best_match (supported: LIST [READABLE_STRING_8]; header: READABLE_STRING_8): READABLE_STRING_8
-- Choose the mime-type with the highest fitness score and quality ('q') from a list of candidates.
local
l_header_results: LIST [PARSE_RESULTS]
l_header_results: LIST [HTTP_MEDIA_TYPE]
weighted_matches: LIST [FITNESS_AND_QUALITY]
l_res: LIST [STRING]
p_res: PARSE_RESULTS
l_res: LIST [READABLE_STRING_8]
p_res: HTTP_MEDIA_TYPE
fitness_and_quality, first_one: detachable FITNESS_AND_QUALITY
s: STRING
s: READABLE_STRING_8
do
l_res := header.split (',')
create {ARRAYED_LIST [PARSE_RESULTS]} l_header_results.make (l_res.count)
create {ARRAYED_LIST [HTTP_MEDIA_TYPE]} l_header_results.make (l_res.count)
fixme("Extract method!!!")
from
@@ -290,7 +273,7 @@ feature -- Parser
until
l_header_results.after or fitness_and_quality /= Void
loop
s := l_header_results.item.mime_type
s := l_header_results.item.simple_type
from
weighted_matches.start
until
@@ -317,33 +300,7 @@ feature -- Parser
end
end
feature {NONE} -- Implementation
mime_type (s: STRING): STRING
local
p: INTEGER
do
p := s.index_of (';', 1)
if p > 0 then
Result := trim (s.substring (1, p - 1))
else
Result := trim (s.string)
end
end
trim (a_string: STRING): STRING
-- trim whitespace from the beginning and end of a string
require
valid_argument : a_string /= Void
do
a_string.left_adjust
a_string.right_justify
Result := a_string
ensure
result_same_as_argument: a_string = Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,43 @@
note
description: "{MIME_TYPE_PARSER_UTILITIES}."
date: "$Date$"
revision: "$Revision$"
class
MIME_TYPE_PARSER_UTILITIES
feature {NONE} -- Implementation
mime_type (a_str: READABLE_STRING_8): READABLE_STRING_8
-- `s' with any trailing parameters stripped
local
p: INTEGER
do
p := a_str.index_of (';', 1)
if p > 0 then
Result := trim (a_str.substring (1, p - 1))
else
Result := trim (a_str.string)
end
end
trim (a_string: READABLE_STRING_8): READABLE_STRING_8
-- trim whitespace from the beginning and end of a string
-- `a_string'
require
valid_argument : a_string /= Void
local
l_result: STRING
do
l_result := a_string.as_string_8
l_result.left_adjust
l_result.right_adjust
Result := l_result
ensure
result_same_as_argument: Result.same_string_general (a_string)
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,12 +1,13 @@
note
description: "Summary description for {COMMON_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
COMMON_RESULTS
inherit
ANY
redefine
out
@@ -31,7 +32,6 @@ feature -- Access
field: detachable STRING
item (a_key: STRING): detachable STRING
-- Item associated with `a_key', if present
-- otherwise default value of type `STRING'
@@ -57,15 +57,14 @@ feature -- Access
feature -- Element change
set_field (a_field: STRING)
-- Set type with `a_charset'
-- Set type with `a_charset'
do
field := a_field
ensure
field_assigned: field ~ field
field_set: attached field as l_field implies l_field = a_field
end
put (new: STRING; key: STRING)
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
@@ -90,7 +89,6 @@ feature -- Status Report
Result.append_string ("'" + t + "',")
end
Result.append_string (" {")
from
params.start
until
@@ -113,7 +111,10 @@ feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
;
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,12 +1,13 @@
note
description: "Summary description for {LANGUAGE_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_RESULTS
inherit
ANY
redefine
out
@@ -61,7 +62,7 @@ feature -- Access
feature -- Element change
set_type (a_type: STRING)
-- Set type with `a_type'
-- Set type with `a_type'
do
type := a_type
if attached sub_type as st then
@@ -74,7 +75,7 @@ feature -- Element change
end
set_sub_type (a_sub_type: STRING)
-- Set sub_type with `a_sub_type
-- Set sub_type with `a_sub_type
do
sub_type := a_sub_type
if attached type as t then
@@ -86,7 +87,7 @@ feature -- Element change
sub_type_assigned: sub_type ~ a_sub_type
end
put (new: STRING; key: STRING)
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
@@ -114,7 +115,6 @@ feature -- Status Report
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
from
params.start
until
@@ -137,7 +137,10 @@ feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
;
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,6 +1,5 @@
note
description: "Summary description for {PARSE_RESULTS}."
author: ""
date: "$Date$"
revision: "$Revision$"
@@ -8,6 +7,7 @@ class
PARSE_RESULTS
inherit
ANY
redefine
out
@@ -62,7 +62,7 @@ feature -- Access
feature -- Element change
set_type (a_type: STRING)
-- Set type with `a_type'
-- Set type with `a_type'
do
type := a_type
if attached sub_type as st then
@@ -75,7 +75,7 @@ feature -- Element change
end
set_sub_type (a_sub_type: STRING)
-- Set sub_type with `a_sub_type
-- Set sub_type with `a_sub_type
do
sub_type := a_sub_type
if attached type as t then
@@ -87,7 +87,7 @@ feature -- Element change
sub_type_assigned: sub_type ~ a_sub_type
end
put (new: STRING; key: STRING)
put (new: STRING; key: STRING)
-- Insert `new' with `key' if there is no other item
-- associated with the same key. If present, replace
-- the old value with `new'
@@ -115,7 +115,6 @@ feature -- Status Report
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
from
params.start
until
@@ -138,7 +137,10 @@ feature {NONE} -- Implementation
params: HASH_TABLE [STRING, STRING]
--dictionary of all the parameters for the media range
;note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
;
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -1,5 +1,5 @@
note
description: "Summary description for {SHARED_MIME}."
description: "Summary description for {SHARED_CONNEG}."
date: "$Date$"
revision: "$Revision$"
@@ -8,23 +8,24 @@ class
feature
mime: MIME_PARSE
Mime: MIME_PARSE
once
create Result
end
common: COMMON_ACCEPT_HEADER_PARSER
-- Charset and Encoding
Common: COMMON_ACCEPT_HEADER_PARSER
-- Charset and Encoding
once
create Result
end
language: LANGUAGE_PARSE
Language: LANGUAGE_PARSE
once
create Result
end
note
copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others"
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,33 @@
note
description: "[
{CHARACTER_ENCODING_VARIANT_RESULTS}
Represent the character sets results between client preferences and character sets variants supported by the server.
If the server is unable to supports the requested Accept-Charset values, the server can build
a response with the list of supported character sets.
]"
date: "$Date$"
revision: "$Revision$"
class
CHARACTER_ENCODING_VARIANT_RESULTS
inherit
VARIANT_RESULTS
feature -- Change Element
set_variant_header
-- Set variant header as `Accept-Charset'
do
variant_header := "Accept-Charset"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,32 @@
note
description: "[
{COMPRESSION_VARIANT_RESULTS}
Represent the compression results between client preferences and ccompression variants supported by the server.
If the server is unable to supports the requested Accept-Encoding values, the server can build
a response with the list of supported encodings/compressions
]"
date: "$Date$"
revision: "$Revision$"
EIS: "name= Compression", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
class
COMPRESSION_VARIANT_RESULTS
inherit
VARIANT_RESULTS
feature -- Change Element
set_variant_header
-- Set variant_header as `Accept-Encoding'
do
variant_header := "Accept-Encoding"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,33 @@
note
description: "[
{LANGUAGE_VARIANT_RESULTS}.
Represent the language results between client preferences and language variants supported by the server.
If the server is unable to supports the requested Accept-Language values, the server can build
a response with the list of supported languages
]"
date: "$Date$"
revision: "$Revision$"
class
LANGUAGE_VARIANT_RESULTS
inherit
VARIANT_RESULTS
feature -- Change Element
set_variant_header
-- Set variant header as 'Accept-Language'
do
variant_header := "Accept-Language"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,32 @@
note
description: "[
{MEDIA_TYPE_VARIANT_RESULTS}.
Represent the media type results between client preferences and media type variants supported by the server..
If the server is unable to supports the requested Accept values, the server can build
a response with the list of supported representations
]"
date: "$Date$"
revision: "$Revision$"
class
MEDIA_TYPE_VARIANT_RESULTS
inherit
VARIANT_RESULTS
feature -- Change Element
set_variant_header
-- Set variant header as `Accept'
do
variant_header := "Accept"
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -0,0 +1,83 @@
note
description: "Generic {VARIANT_RESULTS}.with common functionality to most header variants.."
date: "$Date$"
revision: "$Revision$"
deferred class
VARIANT_RESULTS
feature -- Access
variant_header: detachable READABLE_STRING_8
-- Name of variant header to be added to the Vary header of the response
supported_variants: detachable LIST [READABLE_STRING_8]
-- Set of supported variants for the response
is_acceptable: BOOLEAN
-- is the current variant accepted?
type: detachable READABLE_STRING_8
-- the type could be: media type, language, chracter_sets and encoding.
feature {NONE} -- Implementation
accept_headers_set: ARRAY[READABLE_STRING_8]
-- Set of valid accept headers headers
note
EIS:"name=Accept", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.1", "protocol=uri"
EIS:"name=Accept-Charset", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.2", "protocol=uri"
EIS:"name=Accept-Encoding", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.3", "protocol=uri"
EIS:"name=Accept-Language", "src=http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4", "protocol=uri"
once
Result:= <<"Accept","Accept-Language","Accept-Encoding","Accept-Charset">>
Result.compare_objects
end
feature -- Status_Report
is_valid_header (a_header: READABLE_STRING_8): BOOLEAN
-- is `a_header' a valid accept header?
do
Result := accept_headers_set.has (a_header)
end
feature -- Change Element
set_type (a_type: READABLE_STRING_8)
-- Set `type' as `a_type'
do
type := a_type
ensure
type_set: attached type as l_type implies l_type = a_type
end
set_acceptable (acceptable: BOOLEAN)
-- Set `is_acceptable' with `acceptable'
do
is_acceptable := acceptable
ensure
is_acceptable_set: is_acceptable = acceptable
end
set_variant_header
-- Set variant header
deferred
ensure
is_valid_header_set : attached variant_header as l_header implies is_valid_header (l_header)
end
set_supported_variants (a_supported: LIST [READABLE_STRING_8])
-- Set `supported variants' with `a_supported'
do
supported_variants := a_supported
ensure
set_supported_variants: attached supported_variants as l_supported_variants implies l_supported_variants = a_supported
end
note
copyright: "2011-2013, Javier Velilla, Jocelyn Fiat, Eiffel Software and others"
license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)"
end

View File

@@ -78,10 +78,10 @@ feature {NONE} -- Initialization
print (language.best_match (accept.split (','), "da"))
print (language.best_match (accept.split (','), "en-*"))
print ("%N"+language.parse_media_range ("da").out)
print ("%N"+language.parse_media_range ("en-gb;q=0.8").out)
print ("%N"+language.parse_media_range ("en;q=0.7").out)
print ("%N"+language.parse_media_range ("en-*").out)
print ("%N"+language.parse_language_range ("da").out)
print ("%N"+language.parse_language_range ("en-gb;q=0.8").out)
print ("%N"+language.parse_language_range ("en;q=0.7").out)
print ("%N"+language.parse_language_range ("en-*").out)
end
end

View File

@@ -36,13 +36,13 @@ feature -- Test routines
assert ("Same Value at 1",mime_types_supported.at (1).is_equal (media_variants.supported_variants.at (1)))
assert ("Same count",mime_types_supported.count = media_variants.supported_variants.count)
assert ("Variant header is void",media_variants.variant_header = Void)
assert ("Media type is void",media_variants.media_type = Void)
assert ("Media type is void",media_variants.type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept:
media_variants := conneg.media_type_preference (mime_types_supported, "")
assert ("Expected Acceptable", media_variants.is_acceptable)
assert ("Variants is dettached",media_variants.supported_variants = Void)
assert ("Mime is defaul", conneg.mime_default.is_equal (media_variants.media_type))
assert ("Mime is defaul", conneg.mime_default.is_equal (media_variants.type))
assert ("Variant header", media_variants.variant_header = Void)
--Scenario 3, the server select the best match, and set the vary header
@@ -50,7 +50,7 @@ feature -- Test routines
assert ("Expected Acceptable", media_variants.is_acceptable)
assert ("Variants is dettached",media_variants.supported_variants = Void)
assert ("Variant Header", media_variants.variant_header.is_equal ("Accept"))
assert ("Media Type is application/json", media_variants.media_type.is_equal ("application/json"))
assert ("Media Type is application/json", media_variants.type.is_equal ("application/json"))
end
@@ -70,14 +70,14 @@ feature -- Test routines
assert ("Same Value at 1",charset_supported.at (1).is_equal (charset_variants.supported_variants.at (1)))
assert ("Same count",charset_supported.count = charset_variants.supported_variants.count)
assert ("Variant header is void",charset_variants.variant_header = Void)
assert ("Character type is void",charset_variants.character_type = Void)
assert ("Character type is void",charset_variants.type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Charset:
charset_variants := conneg.charset_preference (charset_supported, "")
assert ("Expected Acceptable", charset_variants.is_acceptable)
assert ("Variants is dettached",charset_variants.supported_variants = Void)
assert ("Charset is defaul", conneg.charset_default.is_equal (charset_variants.character_type))
assert ("Charset is defaul", conneg.charset_default.is_equal (charset_variants.type))
assert ("Variant header", charset_variants.variant_header = Void)
@@ -86,7 +86,7 @@ feature -- Test routines
assert ("Expected Acceptable", charset_variants.is_acceptable)
assert ("Variants is dettached",charset_variants.supported_variants = Void)
assert ("Variant Header", charset_variants.variant_header.is_equal ("Accept-Charset"))
assert ("Character Type is iso-8859-5", charset_variants.character_type.is_equal ("iso-8859-5"))
assert ("Character Type is iso-8859-5", charset_variants.type.is_equal ("iso-8859-5"))
end
test_compression_negotiation
@@ -103,26 +103,26 @@ feature -- Test routines
assert ("Same Value at 1",compression_supported.at (1).is_equal (compression_variants.supported_variants.at (1)))
assert ("Same count",compression_supported.count = compression_variants.supported_variants.count)
assert ("Variant header is void",compression_variants.variant_header = Void)
assert ("Compression type is void",compression_variants.compression_type = Void)
assert ("Compression type is void",compression_variants.type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Encoding
compression_variants := conneg.encoding_preference (compression_supported, "")
assert ("Expected Acceptable", compression_variants.is_acceptable)
assert ("Variants is dettached",compression_variants.supported_variants = Void)
assert ("Compression is defaul", conneg.encoding_default.is_equal (compression_variants.compression_type))
assert ("Compression is defaul", conneg.encoding_default.is_equal (compression_variants.type))
assert ("Variant header", compression_variants.variant_header = Void)
--Scenario 3, the server select the best match, and set the vary header
l_compression := "gzip"
compression_supported := l_compression.split(',')
conneg.set_encoding_defautl ("gzip")
conneg.set_encoding_default("gzip")
compression_variants := conneg.encoding_preference (compression_supported, "compress,gzip;q=0.7")
assert ("Expected Acceptable", compression_variants.is_acceptable)
assert ("Variants is dettached",compression_variants.supported_variants = Void)
assert ("Variant Header", compression_variants.variant_header.is_equal ("Accept-Encoding"))
assert ("Encoding Type is gzip", compression_variants.compression_type.is_equal ("gzip"))
assert ("Encoding Type is gzip", compression_variants.type.is_equal ("gzip"))
end
@@ -141,14 +141,14 @@ feature -- Test routines
assert ("Same Value at 1",languages_supported.at (1).is_equal (language_variants.supported_variants.at (1)))
assert ("Same count",languages_supported.count = language_variants.supported_variants.count)
assert ("Variant header is void",language_variants.variant_header = Void)
assert ("Language type is void",language_variants.language_type = Void)
assert ("Language type is void",language_variants.type = Void)
-- Scenario 2, the client doesnt send values in the header, Accept-Language:
language_variants := conneg.language_preference (languages_supported, "")
assert ("Expected Acceptable", language_variants.is_acceptable)
assert ("Variants is dettached",language_variants.supported_variants = Void)
assert ("Language is defaul", conneg.language_default.is_equal (language_variants.language_type))
assert ("Language is defaul", conneg.language_default.is_equal (language_variants.type))
assert ("Variant header", language_variants.variant_header = Void)
@@ -157,7 +157,7 @@ feature -- Test routines
assert ("Expected Acceptable", language_variants.is_acceptable)
assert ("Variants is dettached",language_variants.supported_variants = Void)
assert ("Variant Header", language_variants.variant_header.is_equal ("Accept-Language"))
assert ("Language Type is fr", language_variants.language_type.is_equal ("fr"))
assert ("Language Type is fr", language_variants.type.is_equal ("fr"))
end

View File

@@ -25,10 +25,10 @@ feature -- Test routines
test_parse_media_range
do
assert ("Expected ('da', {'q':'1.0',})", parser.parse_media_range ("da").out.same_string ("('da', {'q':'1.0',})"));
assert ("Expected ('en', 'gb', {'q':'0.8',})", parser.parse_media_range ("en-gb;q=0.8").out.same_string ("('en', 'gb', {'q':'0.8',})"));
assert ("Expected ('en', {'q':'0.7',})", parser.parse_media_range ("en;q=0.7").out.same_string ("('en', {'q':'0.7',})"));
assert ("Expected ('en', '*', {'q':'1.0',})", parser.parse_media_range ("en-*").out.same_string ("('en', '*', {'q':'1.0',})"));
assert ("Expected ('da', {'q':'1.0',})", parser.parse_language_range ("da").out.same_string ("('da', {'q':'1.0',})"));
assert ("Expected ('en', 'gb', {'q':'0.8',})", parser.parse_language_range ("en-gb;q=0.8").out.same_string ("('en', 'gb', {'q':'0.8',})"));
assert ("Expected ('en', {'q':'0.7',})", parser.parse_language_range ("en;q=0.7").out.same_string ("('en', {'q':'0.7',})"));
assert ("Expected ('en', '*', {'q':'1.0',})", parser.parse_language_range ("en-*").out.same_string ("('en', '*', {'q':'1.0',})"));
end

View File

@@ -24,19 +24,45 @@ feature {NONE} -- Events
create parser
end
feature -- Helper
format (a_mediatype: HTTP_MEDIA_TYPE): STRING
-- Representation of the current object
do
create Result.make_from_string ("(")
if attached a_mediatype.type as t then
Result.append_string ("'" + t + "',")
end
if attached a_mediatype.subtype as st then
Result.append_string (" '" + st + "',")
end
Result.append_string (" {")
if attached a_mediatype.parameters as l_params then
from
l_params.start
until
l_params.after
loop
Result.append ("'" + l_params.key_for_iteration + "':'" + l_params.item_for_iteration + "',");
l_params.forth
end
end
Result.append ("})")
end
feature -- Test routines
test_parse_media_range
do
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml;q=1").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.parse_media_range("application/xml;q=1")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml;q=").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", parser.parse_media_range("application/xml ; q=").out.same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", parser.parse_media_range("application/xml ; q=1;b=other").out.same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", parser.parse_media_range("application/xml ; q=2;b=other").out.same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.parse_media_range("application/xml")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.parse_media_range("application/xml;q=")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1',})", format (parser.parse_media_range("application/xml ; q=")).same_string("('application', 'xml', {'q':'1.0',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", format (parser.parse_media_range("application/xml ; q=1;b=other")).same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
assert ("Expected ('application', 'xml', {'q':'1','b':'other',})", format (parser.parse_media_range("application/xml ; q=2;b=other")).same_string("('application', 'xml', {'q':'1.0','b':'other',})") )
-- Accept header that includes *
assert ("Expected ('*', '*', {'q':'.2',})", parser.parse_media_range(" *; q=.2").out.same_string("('*', '*', {'q':'.2',})"))
assert ("Expected ('*', '*', {'q':'.2',})", format (parser.parse_media_range(" *; q=.2")).same_string("('*', '*', {'q':'.2',})"))
end

View File

@@ -12,6 +12,7 @@
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base-safe.ecf"/>
<library name="http" location="..\..\http\http-safe.ecf" readonly="false"/>
<library name="conneg" location="..\conneg-safe.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing-safe.ecf"/>
<cluster name="test" location=".\" recursive="true"/>

View File

@@ -12,6 +12,7 @@
<assertions precondition="true" postcondition="true" check="true" invariant="true" loop="true" supplier_precondition="true"/>
</option>
<library name="base" location="$ISE_LIBRARY\library\base\base.ecf"/>
<library name="http" location="..\..\http\http.ecf" readonly="false"/>
<library name="conneg" location="..\conneg.ecf"/>
<library name="testing" location="$ISE_LIBRARY\library\testing\testing.ecf"/>
<cluster name="test" location=".\" recursive="true"/>

View File

@@ -100,13 +100,11 @@ feature {NONE} -- Initialization
t.right_adjust
type := t
if t.same_string ("*") then
-- Flexible parser allowing "*" even if this is not really a valid media-type
-- let's interpret it as "*/*"
subtype := "*"
--| Accept *; should be */*
else
has_error := True
subtype := "*"
end
subtype := "*"
else
subtype := t.substring (p + 1, t.count)
type := t