diff --git a/library/protocol/CONNEG/README.md b/library/protocol/CONNEG/README.md index c368f653..0a130463 100644 --- a/library/protocol/CONNEG/README.md +++ b/library/protocol/CONNEG/README.md @@ -3,3 +3,124 @@ where there are multiple representations available. Using this labrary you can retrieve the best Variant for media type, language preference, enconding and compression. The library is based on eMIME Eiffel MIME library based on Joe Gregorio code + +Take into account that the library is under development so is expected that the API chanherge. + +The library contains utilities that deal with content negotiation (server driven negotiation).This utility class +is based on ideas taken from the Book Restful WebServices Cookbook + +The class CONNEG_SERVER_SIDE contains several features that helps to write different type of negotiations (media type, language, +charset and compression). +So for each of the following questions, you will have a corresponding method to help in the solution. + +How to implement Media type negotiation? +Hint: Use CONNEG_SERVER_SIDE.media_type_preference + +How to implement Language Negotiation? +Hint: Use CONNEG_SERVER_SIDE.language_preference + + +How to implement Character encoding Negotiation? +Hint: Use CONNEG_SERVER_SIDE.charset_preference + +How to implement Compression Negotiation? +Hint: Use CONNEG_SERVER_SIDE.encoding_preference + +There is also a test case where you can check how to use this class. + +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 interface + CONNEG_SERVER_SIDE + +create + make + +feature -- Initialization + + make (a_mime: STRING_8; a_language: STRING_8; a_charset: STRING_8; an_encoding: STRING_8) + +feature -- Compression Negotiation + + encoding_preference (server_encoding_supported: LIST [STRING_8]; header: 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 + +feature -- Encoding Negotiation + + charset_preference (server_charset_supported: LIST [STRING_8]; header: 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 + +feature -- Language Negotiation + + language_preference (server_language_supported: LIST [STRING_8]; header: 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 + +feature -- Media Type Negotiation + + media_type_preference (mime_types_supported: LIST [STRING_8]; header: 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 + +feature -- Server Side Defaults Formats + + charset_default: STRING_8 + + encoding_default: STRING_8 + + language_default: STRING_8 + + mime_default: STRING_8 + + set_charset_default (a_charset: STRING_8) + -- set the charset_default with `a_charset' + ensure + set_charset: a_charset ~ charset_default + + set_encoding_defautl (an_encoding: STRING_8) + ensure + set_encoding: an_encoding ~ encoding_default + + set_language_default (a_language: STRING_8) + -- set the language_default with `a_language' + ensure + set_language: a_language ~ language_default + + set_mime_default (a_mime: STRING_8) + -- set the mime_default with `a_mime' + ensure + set_mime_default: a_mime ~ mime_default + +note + copyright: "2011-2011, Javier Velilla, Jocelyn Fiat and others" + license: "Eiffel Forum License v2 (see http://www.eiffel.com/licensing/forum.txt)" + +end -- class CONNEG_SERVER_SIDE + diff --git a/library/protocol/CONNEG/src/character_encoding_variant_results.e b/library/protocol/CONNEG/src/character_encoding_variant_results.e new file mode 100644 index 00000000..2be13cc0 --- /dev/null +++ b/library/protocol/CONNEG/src/character_encoding_variant_results.e @@ -0,0 +1,45 @@ +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 diff --git a/library/protocol/CONNEG/src/compression_variant_results.e b/library/protocol/CONNEG/src/compression_variant_results.e new file mode 100644 index 00000000..333cd351 --- /dev/null +++ b/library/protocol/CONNEG/src/compression_variant_results.e @@ -0,0 +1,44 @@ +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 diff --git a/library/protocol/CONNEG/src/conneg_server_side.e b/library/protocol/CONNEG/src/conneg_server_side.e index 02e2cf73..cca5a6b6 100644 --- a/library/protocol/CONNEG/src/conneg_server_side.e +++ b/library/protocol/CONNEG/src/conneg_server_side.e @@ -109,29 +109,71 @@ feature -- Media Type Negotiation feature -- Encoding Negotiation - charset_preference (server_charset_supported : LIST[STRING]; header: STRING) : STRING + charset_preference (server_charset_supported : LIST[STRING]; header: STRING) : 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 - Result := common.best_match (server_charset_supported, header) + create Result + if 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: STRING) : STRING + encoding_preference (server_encoding_supported : LIST[STRING]; header: STRING) : 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 - Result := common.best_match (server_encoding_supported, header) + create Result + if 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: STRING) : LANGUAGE_VARIANT_RESULTS @@ -164,6 +206,8 @@ feature -- Language Negotiation 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)" diff --git a/library/protocol/CONNEG/test/conneg_server_side_test.e b/library/protocol/CONNEG/test/conneg_server_side_test.e index 8a966f4e..e9d181f4 100644 --- a/library/protocol/CONNEG/test/conneg_server_side_test.e +++ b/library/protocol/CONNEG/test/conneg_server_side_test.e @@ -17,7 +17,7 @@ feature {NONE} -- Events on_prepare -- Called after all initializations in `default_create'. do - create conneg.make ("application/json", "es", "UTF8", "") + create conneg.make ("application/json", "es", "UTF-8", "") -- set default values end @@ -56,6 +56,77 @@ feature -- Test routines + test_charset_negotiation + local + charset_variants : CHARACTER_ENCODING_VARIANT_RESULTS + charset_supported : LIST [STRING] + l_charset : STRING + do + -- Scenario 1, the server side does not support client preferences + l_charset := "UTF-8, iso-8859-5" + charset_supported := l_charset.split(',') + charset_variants := conneg.charset_preference (charset_supported, "unicode-1-1") + assert ("Expected Not Acceptable", not charset_variants.is_acceptable) + 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) + + + -- 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 ("Variant header", charset_variants.variant_header = Void) + + + --Scenario 3, the server select the best match, and set the vary header + charset_variants := conneg.charset_preference (charset_supported, "unicode-1-1, UTF-8;q=0.3, iso-8859-5") + 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")) + end + + test_compression_negotiation + local + compression_variants : COMPRESSION_VARIANT_RESULTS + compression_supported : LIST [STRING] + l_compression : STRING + do + -- Scenario 1, the server side does not support client preferences + l_compression := "" + compression_supported := l_compression.split(',') + compression_variants := conneg.encoding_preference (compression_supported, "gzip") + assert ("Expected Not Acceptable", not compression_variants.is_acceptable) + 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) + + + -- 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 ("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") + 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")) + end + + + test_language_negotiation local language_variants : LANGUAGE_VARIANT_RESULTS @@ -90,6 +161,7 @@ feature -- Test routines end + feature -- Implementation conneg : CONNEG_SERVER_SIDE end