2004-08-06

Encoding in JavaServer Pages

Encoding is an important issue in software i18n. Iranian developers have spent enormous energy in tackling encoding problems, ever since computer programming entered to this country. Fortunately, Java has a marvellous support for various encodings, including Unicode and its variants. Regarding the encoding issue in JSP applications, these things should be taken in mind:

Encoding of the JSP Page Itself

The best solution for this problem is to write JSP documents, i.e., JSP pages that are standard XML documents. The XML standard has an efficient mechanism for determining the document encoding:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <!-- Page content... --></jsp:root>

Encoding of the Response

By setting the content type in the following manner, we can declare the encoding of the response that is sent to the client:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <jsp:directive.page contentType="text/html; charset=UTF-8" />
  <!-- Page content... --></jsp:root>

But there is a problem if you use the fmt:setLocale tag to set the locale -- the tag handler will automatically assign a (presumably) appropriate encoding for the response. This is by no means apprpriate, since we have no locale for the Persian language. On the other hand, the current implementation of Apache JSTL project assigns the ISO-8859-6 encoding to Arabic locales (like ar_SA). This encoding is not sufficient for Persian language content. The following seems to solve the problem satisfatorily:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <!-- Page content... --></jsp:root>

Encoding of the Request

Setting the character encoding of the HTTP request is essential for proper interpretation of form data. This can most easily be done by using a JSTL tag:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <!-- Page content... --></jsp:root>

Encoding of Database Connections

If you are using pure java JDBC relational database management systems and drivers, such as IDB, HSQLDB, and PostgreSQL, you will not have any problems regarding the character encoding of the database connection. But if you are using the sun.jdbc.odbc.JdbcOdbcDriver driver, there may be some problems.

However, in recent versions of the JDBC-ODBC bridge, there is a way to set the connection charset:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:scriptlet><![CDATA[
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Properties p = new Properties();
    p.put("charSet", "windows-1256");
    p.put("user", "ghasem");
    p.put("password", "100");
    Connection c = DriverManager.getConnection("jdbc:odbc:MyTestDb", p);
    // ...
  ]]></jsp:scriptlet>
  <!-- Page content... --></jsp:root>

Of course, it would be much better to use this DataSource instead:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
  xmlns:sql="http://java.sun.com/jsp/jstl/sql">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:useBean scope="page" id="ds" class="sun.jdbc.odbc.ee.DataSource" />
  <c:set target="${ds}" property="databaseName" value="MyTestDb" />
  <c:set target="${ds}" property="user" value="ghasem" />
  <c:set target="${ds}" property="password" value="11" />
  <c:set target="${ds}" property="charSet" value="windows-1256" />
  <sql:setDataSource scope="page" var="source" dataSource="${ds}" />
  <sql:query scope="page" var="res" dataSource="${source}"><![CDATA[
    SELECT author, title, url FROM Books WHERE author like '%مطهري%'
  ]]></sql:query>
  <!-- Page content... --></jsp:root>

Note:

The following software have been used for creating and testing these code snippets (earlier versions may have some discrepancies):

  1. Java 2 Standard Edition, Software development Kit, version 1.5.0 beta 31
  2. Tomcat, version 5.0.6
  3. Servlet 2.4 and JSP 2.0 (these are distributed together with the above-mentioned Tomcat release)
  4. JSTL version 1.1

Surely, much more can be said about JSP. These code fragments are only guides to show the ways we can set encoding in JSP applications. Each of the technologies mentioned above (servlets, JSP, JDBC, XML, JSTL, etc.) warrants a much deeper explanation. I may write some more notes about these technologies in the future.

رمزگذاري در صفحات سرور جاوا

رمزگذاري (encoding) يكي از مهم‌ترين مسايل در بين‌المللي‌سازي برنامه‌ها است, و در طول ساليان اخير, مقدار زيادي از تلاش برنامه‌نويسان ايراني را به خود مشغول كرده است. خوشبختانه, جاوا بهترين پشتيباني را براي ارائه‌ي برنامه‌ها با رمزگذاري‌هاي معتبر (مثلاً يونيكد) دارد. در مورد رمزگذاري صفحات سرور جاوا چند نكته حايز اهميت است:

رمزگذاري خود صفحه‌ي JSP

بهتر است از سند JSP استفاده كنيم, يعني صفحاتي كه سند XML هستند. در اين صورت, رمزگذاري در ابتداي پرونده مشخص مي‌شود:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <!-- متن صفحه... --></jsp:root>

رمزگذاري پاسخ

براي تعيين رمزگذاري صفحه‌اي كه به دست مشتري مي‌رسد, نوع محتوا را تعيين مي‌كنيم:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page">
  <jsp:directive.page contentType="text/html; charset=UTF-8" />
  <!-- متن صفحه... --></jsp:root>

مسئله اين است كه اگر از برگه‌ي (يعني tag) تعيين محل fmt:setLocale استفاده كنيد, خودبخود رمزگذاري پاسخ را مطابق محل (locale) داده شده تعيين خواهد كرد. از آنجا كه براي فارسي در جاوا محلي تعريف نشده است, و ممكن است خيلي سيستم‌ها محل پيش‌فرض را عربي (مثلاً ar_SA) تعريف كرده باشند, در صورت تعيين اين محل, رمزگذاري صفحه در پياده‌سازي JSTL كنوني Apache رمزگذاري ISO-8859-6 خواهد بود كه براي فارسي اصلاً مناسب نيست. اگر به صورت زير عمل كنيم, ظاهراً اين مسئله حل مي‌شود:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <!-- متن صفحه... --></jsp:root>

رمزگذاري تقاضا

تعيين اين رمزگذاري در گرفتن داده‌هاي فرم اهميت زيادي دارد. در اين مورد, بهترين كار استفاده از JSTL است:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <!-- متن صفحه... --></jsp:root>

رمزگذاري پايگاه داده‌اي

در گردانه‌هاي JDBC جاواي خالص كه خود پايگاه داده‌اي تشكيل مي‌دهند (خود سرور پايگاه داده‌اي هستند), مانند IDB و HSQLDB و PostgreSQL معمولاً مشكلي خاصي وجود ندارد. ولي وقتي از گردانه‌ي sun.jdbc.odbc.JdbcOdbcDriver استفاده مي‌كنيم, ممكن است دچار مشكل بشويم.

با اين حال, در نسخه‌هاي جديد پل JDBC و ODBC, امكان تعيين رمزگذاري ارتباط به صورت زير وجود دارد:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:scriptlet><![CDATA[
    Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
    Properties p = new Properties();
    p.put("charSet", "windows-1256");
    p.put("user", "ghasem");
    p.put("password", "100");
    Connection c = DriverManager.getConnection("jdbc:odbc:MyTestDb", p);
    // ...
  ]]></jsp:scriptlet>
  <!-- متن صفحه... --></jsp:root>

البته, راه بهتر آن است كه از اين DataSource استفاده شود:

<?xml version="1.0" encoding="windows-1256" ?>
 <jsp:root version="2.0"
  xmlns:jsp="http://java.sun.com/JSP/Page"
  xmlns:c="http://java.sun.com/jsp/jstl/core"
  xmlns:fmt="http://java.sun.com/jsp/jstl/fmt"
  xmlns:sql="http://java.sun.com/jsp/jstl/sql">
  <c:set target="${pageContext.response}" property="characterEncoding" value="UTF-8" />
  <jsp:directive.page contentType="text/html" />
  <fmt:setLocale value="ar-SA" />
  <fmt:requestEncoding value="UTF-8" />
  <jsp:useBean scope="page" id="ds" class="sun.jdbc.odbc.ee.DataSource" />
  <c:set target="${ds}" property="databaseName" value="MyTestDb" />
  <c:set target="${ds}" property="user" value="ghasem" />
  <c:set target="${ds}" property="password" value="11" />
  <c:set target="${ds}" property="charSet" value="windows-1256" />
  <sql:setDataSource scope="page" var="source" dataSource="${ds}" />
  <sql:query scope="page" var="res" dataSource="${source}"><![CDATA[
    SELECT author, title, url FROM Books WHERE author like '%مطهري%'
  ]]></sql:query>
  <!-- متن صفحه... --></jsp:root>

توجه:

براي تهيه‌ي اين متن‌هاي JSP و آزمايش آنها از نرم‌افزارهاي زير استفاده شده است (نسخه‌هاي پايين‌تر ممكن است تفاوت‌هايي داشته باشند):

  1. كيت برنامه‌نويسي جاواي 1.5.0
  2. تامكت, نسخه‌ي 5.0.6
  3. سرولت نسخه‌ي 2.4 و JSP نسخه‌ي 2.0 (در توزيع تامكت فوق موجود است)
  4. JSTL نسخه‌ي 1.1

روشن است كه در باره‌ي صفحات سرور جاوا حرف‌هاي زيادي براي گفتن وجود دارد. متن‌هاي ارائه شده در اينجا صرفاً براي نشان دادن راهكارها و قابليت‌هاي تعيين رمزگذاري بود. در باره‌ي هر كدام از مباحث فوق (سرولت, JSP, كتابخانه‌هاي برگه [مانند JSTL], ارتباط با پايگاه‌هاي داده‌اي [فناوري JDBC], و غيره) مطالب زيادي مي‌توان گفت, كه شايد بعداً به برخي از آنها بپردازم.

No comments: