Java を使うWeb アプリの文字コードを Shift_JIS から UTF-8 に変更する場合の考慮点

# 概要
昔からあるWebアプリケーションのHTML文字コード設定をUTF-8に変える時のポイントをまとめてみる。

# 前提
- Webページの文字コード設定をShift_JISからUTF-8へ変更する
- 利用したソフトウェアは以下
  - [OpenJDK](https://adoptopenjdk.net/index.html) 11
  - [WebSphere Liberty](https://developer.ibm.com/wasdev/downloads/#asset/runtimes-wlp-webProfile8) 19.0.0.3
  - [Db2](https://hub.docker.com/_/db2-developer-c-edition) 11.1.4.4
  - Chrome
- Java, Liberty, ChromeはmacOS、Db2はコンテナで稼働させる

# Db2環境構築

env_list という名前で以下の内容のファイルを作っておく

```bash
LICENSE=accept
DB2INSTANCE=db2inst1
DB2INST1_PASSWORD=password
DBNAME=testdb
BLU=false
ENABLE_ORACLE_COMPATIBILITY=false
UPDATEAVAIL=NO
TO_CREATE_SAMPLEDB=false
REPODB=false
IS_OSXFS=false
PERSISTENT_HOME=true
HADR_ENABLED=false
ETCD_ENDPOINT=
ETCD_USERNAME=
ETCD_PASSWORD=
```

```bash
# Docker環境の構築
brew install docker docker-machine
docker-machine create --driver virtualbox default
eval $(docker-machine env)
docker-machine ip

# Db2コンテナの稼働
docker login
docker pull store/ibmcorp/db2_developer_c:11.1.4.4-x86_64
docker run -h db2server --name db2server --detach --privileged=true -p 50000:50000 -p 55000:55000 --env-file env_list store/ibmcorp/db2_developer_c:11.1.4.4-x86_64
docker exec -it db2server bash

# ibm-943コードページのデータベース作成
su - db2inst1
db2 create db ibm943db using codeset ibm-943 territory jp collate using identity
```


# HTMLの変更箇所
- [HTML5仕様](https://www.w3.org/TR/html/document-metadata.html#specifying-the-documents-character-encoding)には文字コードにはUTF-8を指定するべきとの記載あり
- その他のモダンなWebページの書き方は[HTML5 Boilerplate](https://html5boilerplate.com/)や[Nu HTML Checker](https://validator.w3.org/nu/about.html)を使うと便利

```HTML
<!doctype html>
  <html lang="en">
  <head>
    <meta charset="utf-8">
    <title>title</title>
  </head>
  <body>
    ...
```

# JSPの変更箇所
HTTPレスポンスのcontent-typeとJSPファイル自体の文字コードは以下のように指定する

```HTML
<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8" %>
```

# WebページをUTF-8に変えると何が起きるか
ここからが本題。Webページを変更した時にサーバーサイドではどんな対処が必要なのかまとめてみる。

## ブラウザからサーバーへ送信されるデータの文字参照が変わる
ユーザーが入力した文字によっては、ブラウザによって数値文字参照へ変換されて送信される。Shift_JISを利用する場合と、UTF-8を利用する場合で数値文字参照へ変換される文字が異なる。例えば、😀という絵文字はWebページがShift_JISの場合、`&#128512;`に変換されるが、UTF-8の場合は😀がそのまま送信される。以下の例では、¢、𠮷などで数値文字参照が利用される。

|ユーザーの入力|Unicodeコードポイント|Formエンコードデータ(Shift_JIS)の場合|Formエンコードデータ(UTF-8)の場合|備考|
|---|---|---|---|---|
|a|U+0061|a|a||
|&|U+0026|&(%26)|&(%26)||
|¢|U+00A2|`&#162;`(%26%23162%3B)|¢(%C2%A2)|CENT SIGN. Shift_JIS に存在しない文字|
|￠|U+FFE0|￠(%81%91)|￠(%EF%BF%A0)|FULLWIDTH CENT SIGN|
|—|U+2014|`&#8212;`(%26%238212%3B)|—(%E2%80%94)|EM DASH|
|―|U+2015|―(%81%5C)|―(%E2%80%95)|HORIZONTAL BAR|
|－|U+FF0D|－(%81%7C)|－(%EF%BC%8D)|FULLWIDTH HYPHEN-MINUS|
|−|U+2212|−(%81%7C)|−(%E2%88%92)|MINUS SIGN(上記も%81%7Cとなっていることに注意)|
|∥|U+2225|∥(%81a)|∥(%E2%88%A5)|PARALLEL TO|
|‖|U+2016|`&#8214;`(%26%238214%3B)|‖(%E2%80%96)|DOUBLE VERTICAL LINE|
|～|U+FF5E|～(%81%60)|～(%EF%BD%9E)|FULLWIDTH TILDE|
|〜|U+301C|`&#12316;`(%26%2312316%3B)|〜(%E3%80%9C)|WAVE DASH|
|￤|U+FFE4|￤(%FAU)|￤(%EF%BF%A4)|FULLWIDTH BROKEN BAR|
|¦|U+00A6|`&#166;`(%26%23166%3B)|¦(%C2%A6)|BROKEN BAR|
|①|U+2460|①(%87%40)|(%E2%91%A0)||
|㈱|U+3231|㈱(%87%8A)|㈱(%E3%88%B1)||
|あ|U+3042|あ(%82%A0)|あ(%E3%81%82)||
|ｱ|U+FF71|ｱ(%B1)|ｱ(%EF%BD%B1)|半角カタカナ|
|髙|U+9AD9|髙(%FB%FC)|髙(%E9%AB%99)|IBM 拡張漢字(JIS 第 1〜4 水準外)|
|㐂|U+3402|`&#13314;`(%26%2313314%3B)|㐂(%E3%90%82)|JIS 第 3 水準|
|彅|U+5F45|彅(%FAg)|彅(%E5%BD%85)|JIS 第 3 水準|
|繫|U+7E6B|`&#32363;`(%26%2332363%3B)|繫(%E7%B9%AB)|JIS 第 3 水準.JIS X 0213 へ 2004 年に追加|
|葛|U+845B|葛(%8A%8B)|葛(%E8%91%9B)||
|葛 󠄀|U+845B U+E0100|`葛&#23128512;`(%8A%8B%26%23917760%3B)|葛+異体字セレクタ(%E8%91%9B%F3%A0%84%80)|異体字セレクタを利用|
|パ|U+30D1|パ(%83p)|パ(%E3%83%91)||
|パ|U+30CF U+309A|`ハ&#12442;`(%83n%26%2312442%3B)|ハ+半濁点(%E3%83%8F%E3%82%9A)|結合文字|
|😀|U+1F600|`&#128512;`(%26%23128512%3B)|😀(%F0%9F%98%80)|サロゲートペア|
|𠮷|U+20BB7|`&#134071;`(%26%23134071%3B)|𠮷(%F0%A0%AE%B7)|サロゲートペア|

## DBとの通信時に変換エラーが発生する
WebページがUTF-8の場合、数値文字参照ではなく文字そのものがブラウザからサーバーへ送信される。サーバーサイドではJDBCドライバーがDBのコードセットに応じて変換を行う。文字によってはSELECTのタイミングで CharConversionException(MalformedInputException) が発生する。

```Java
// スタックトレースの最後の部分のみ抜粋
Caused by: com.ibm.db2.jcc.am.SqlException: [jcc][t4][1065][12306][4.25.13] java.io.CharConversionException をキャッチしました。 詳しくは、添付の Throwable を参照してください。 ERRORCODE=-4220, SQLSTATE=null
at com.ibm.db2.jcc.am.b6.a(b6.java:794)
at com.ibm.db2.jcc.am.b6.a(b6.java:66)
// ・・・中略・・・
Caused by: java.nio.charset.MalformedInputException: Input length = 1
at java.nio.charset.CoderResult.throwException(CoderResult.java:274)
at com.ibm.db2.jcc.am.x.a(x.java:52)
at com.ibm.db2.jcc.am.bh.a(bh.java:2952)
... 50 more
```

`-Ddb2.jcc.charsetDecoderEncoder=3`というオプションを付与すると、この例外発生を回避できる。変換エラーが発生した文字は、REPLACEMENT CHARACTER(\uFFFD�)へ自動変換される
- https://www-01.ibm.com/support/docview.wss?uid=swg22005262
- https://www-01.ibm.com/support/docview.wss?uid=swg21973226

なおCENT SIGN(U+00A2)は、`-Ddb2.jcc.charsetDecoderEncoder=3`の設定有無に関わらず単純に文字が消える。ある文字がそれぞれの文字コードに含まれているかはこちらのサイトで確認できる（今回の場合はCENT SIGNはデータベースが利用しているcp943には含まれていないことがわかる）

U+00A2
http://www.fileformat.info/info/unicode/char/00a2/charset_support.htm

上記の表の文字(a,&,¢,￠,—,―,－,−,∥,‖,～,〜,￤,¦,①,㈱,あ,ｱ,髙,㐂,彅,繫,葛,葛󠄀,パ,パ,😀,𠮷)をWebページから入力し、DBへ格納して再度Webページへ表示した結果は以下の通り。

UTF-8を利用する場合、―(U+2015)などの一部の文字で文字化けが発生する。`-Ddb2.jcc.ccsid943Mapping=2`をJVMオプションに付与することで変換先を変更できるが、文字化けを完全に解消することはできない。Unicode上の複数のコードポイントをibm-943上では単一のコードに割り当てていることから避けることができない。WebページがShift_JISの場合に文字化けが発生していないように見えるが、前述の通り数値文字参照が利用されているためである。
- https://www-01.ibm.com/support/docview.wss?uid=jpn1J1008522
- https://www-01.ibm.com/support/docview.wss?uid=jpn1J1011940

|ユーザーの入力|Unicodeコードポイント|DBへINSERTしてSELECTした結果(WebページがShift_JISの場合)|DBへINSERTしてSELECTした結果(WebページがUTF-8、`-Ddb2.jcc.ccsid943Mapping=2`なしの場合)|DBへINSERTしてSELECTした結果(WebページがUTF-8、`-Ddb2.jcc.ccsid943Mapping=2`ありの場合)|
|---|---|---|---|---|
|a|U+0061|a|a|a|
|&|U+0026|&|&|&|
|¢|U+00A2|数値文字参照として扱われる|文字が消える|文字が消える|
|￠|U+FFE0|￠|￠|￠|
|—|U+2014|—|—|― (HORIZONTAL BAR, U+2015)へ化ける|
|―|U+2015|―|— (EM DASH, U+2014)へ化ける|―|
|－|U+FF0D|－|− (MINUS SIGN, U+2212)へ化ける|－|
|−|U+2212|−|−|－ (FULLWIDTH HYPHEN-MINUS, U+FF0D)へ化ける|
|∥|U+2225|∥|‖ (DOUBLE VERTICAL LINE, U+2016)へ化ける|∥|
|‖|U+2016|‖|‖|∥ (PARALLEL TO, U+2225)へ化ける|
|～|U+FF5E|～|〜 (WAVE DASH, U+301C)へ化ける|～|
|〜|U+301C|〜|〜|～ (FULLWIDTH TILDE, U+FF5E)へ化ける|
|￤|U+FFE4|￤|¦(BROKEN BAR, U+00A6)へ化ける|￤|
|¦|U+00A6|¦|¦|￤ (FULLWIDTH BROKEN BAR, U+FFE4)へ化ける|
|①|U+2460|①|①|①|
|㈱|U+3231|㈱|㈱|㈱|
|あ|U+3042|あ|あ|あ|
|ｱ|U+FF71|ｱ|ｱ|ｱ|
|髙|U+9AD9|髙|髙|髙|
|㐂|U+3402|数値文字参照として扱われる|��(REPLACEMENT CHARACTER, U+FFFD)へ化ける|��(REPLACEMENT CHARACTER, U+FFFD)へ化ける|
|彅|U+5F45|彅|彅|彅|
|繫|U+7E6B|数値文字参照として扱われる|繋(U+7E4B)へ化ける|繋(U+7E4B)へ化ける|
|葛|U+845B|葛|葛へ化ける|葛へ化ける|
|葛 󠄀|U+845B U+E0100|葛&#917760;(一部が数値文字参照となる)|葛 �� へ化ける|葛 �� へ化ける|
|パ|U+30D1|パ|パ|パ|
|パ|U+30CF U+309A|ハ&#12442;(一部が数値文字参照となる)|ハ �� へ化ける|ハ �� へ化ける|
|😀|U+1F600|数値文字参照として扱われる|�� へ化ける|�� へ化ける|
|𠮷|U+20BB7|数値文字参照として扱われる|� へ化ける|� へ化ける|

# 参考
- https://qiita.com/yuba/items/83a81a0de09a8d8655f1
- https://blog.jxck.io/entries/2017-03-02/unicode-in-javascript.html