IEだけjQueryの$.ajax()が実行エラー

↓の話でした。

http://civic.xrea.jp/2006/10/12/jquery-ie/

今回はSAStruts使ってるプロダクトだったので、AJAXで呼んでる先の処理でこんな感じにContent-typeのencodingを書き換えました。

@Execute(validator = false)
public String hoge() {
	try {
		RequestUtil.getRequest().setCharacterEncoding("shift_jis");
		ResponseUtil.write("OK");
	} catch (Exception e) {
		e.printStackTrace();
		ResponseUtil.write("NG");
	}
	return null;
}

au京セラ端末でのcheckbox問題

auの京セラ端末(他にもあるかもしれませんが)だと以下のようなformでis_checkedの値が受け取れなくてトラブりました。

<form name="example_form" method="post" action="/example/hoge/">
<textarea name="body" rows="3" cols="20"></textarea><br/>
<input name="is_checked" type="checkbox" />京セラ端末<br/>
<input type="submit" value="じっこー" />
</form>

is_chechedの値が"foo"だったらチェックされてる、みたいな判定をサーバサイドでやる感じで対応しました。

<input name="is_checked" type="checkbox" value="foo" />京セラ端末<br/>

java.net.URL#openStream()はclient timeoutしない

JavaのSocket周りでは割とよくある感じですが、以下のコードだとconnectもreadもデフォルトで無限に待ち続ける挙動をします。

BufferedImage image = ImageIO.read(new URL(url).openStream());

Javadocをみると以下の通り。

Opens a connection to this URL and returns an InputStream for reading from that connection. This method is a shorthand for:
     openConnection().getInputStream()

実際、jdk1.6u18のソースを見るとopenConnection()してすぐにgetInputStream()しています。

public final InputStream openStream() throws java.io.IOException {
	return openConnection().getInputStream();
}

以下は適切にtimeoutさせる実装例です。URL#openStream()を使うのはやめてopenConnection()で取得したURLConnectionオブジェクトのsetterでタイムアウト値を設定します。

URLConnection conn = new URL(url).openConnection();
conn.setReadTimeout(1000);
conn.setConnectTimeout(1000);
conn.connect();
BufferedImage image = ImageIO.read(conn.getInputStream());

connect、readともに1000ミリ秒でSocketTimeoutExceptionを投げるようになります。

svn client 1.5以上でdoesn't match expected UUID

svn: Repository UUID 'XXXXXXXXXXXXXXXXXXXXXX' doesn't match expected UUID 'YYYYYYYYYYYYYYYYYYYYY'

1.5系のsubversion clientで発生するようですが、以下のような状態になってしまうと基本的には「chekoutし直すしかない」という事のようです。
とはいえ、毎度毎度そんなのもやっていられないので、試しに.svn/entriesに'XXXXXXXXXXXXXXXXXXXXXX'が記録されているのを思い切って行ごと削除→svn updateとかしてみると手元では事象の解消を確認できました(1度だけですが)。

1.4系のclientでは無視している項目のようなので問題なさそうですが、あくまで体験談レベルということで。。

StringBuffer or StringBuilder?

StringBuilderがStringBufferよりどれほど高速か、実際に少しパフォーマンス比較をしてみました。

useStringBuffer : 141 milsec.
useStringBuilder : 125 milsec.
useStringBuffer : 156 milsec.
useStringBuilder : 125 milsec.
useStringBuffer : 141 milsec.
useStringBuilder : 109 milsec.
useStringBuffer : 157 milsec.
useStringBuilder : 109 milsec.
useStringBuffer : 156 milsec.
useStringBuilder : 110 milsec.
useStringBuffer : 140 milsec.
useStringBuilder : 125 milsec.
useStringBuffer : 141 milsec.
useStringBuilder : 109 milsec.
useStringBuffer : 141 milsec.
useStringBuilder : 109 milsec.
useStringBuffer : 141 milsec.
useStringBuilder : 110 milsec.
useStringBuffer : 140 milsec.
useStringBuilder : 110 milsec.

確かに速いです。が、原理主義者になるほどの差でもないので、リファクタリング時に見つけたら類似見直し、というくらいがちょうどいいかもしれません。

テストコードは以下です。

package example;
import jp.sourceforge.javacpt.ComparativePerformanceTest;
import jp.sourceforge.javacpt.ComparativePerformanceTestHelper;
public class SBPerformanceTest
{
	public static void main(String[] args) throws Exception
	{
		ComparativePerformanceTest test = ComparativePerformanceTestHelper.initialize(
				1000, SBPerformanceTest.class);
		ComparativePerformanceTestHelper.invoke(test, "useStringBuffer");
		ComparativePerformanceTestHelper.invoke(test, "useStringBuilder");
	}

	public static String useStringBuffer()
	{
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < 1000; i++)
		{
			sb.append("hoge");
		}
		return sb.toString();
	}

	public static String useStringBuilder()
	{
		StringBuilder sb = new StringBuilder();
		for (int i = 0; i < 1000; i++)
		{
			sb.append("hoge");
		}
		return sb.toString();
	}
}

例のごとく、以下のツールを使いました。

http://sourceforge.jp/projects/java-cpt/wiki/FrontPage

SJIS環境からのクエリ文字列に対するURLDecoderでの文字化け

モバイルなどのShift_JIS環境から投げられたGETパラメータのクエリをjava.net.URLDecoder.decode(String, String)でdecodeしようとすると文字化けしてしまう場合があります。

これはURLDecoder.decodeにバグがあるというわけではなく、URLEncoder.encodeでencodeされたものを正しくdecodeするという仕様であるためです。

具体的に「http://example.com/?q=%82Q」のようなリファラからクエリ文字列を抽出するケースを考えてみます。

String arg = "%82Q";
System.out.println(URLDecoder.decode(arg, "Shift_JIS"));

上記の出力結果は文字化けを起こしてしまいます。「Q」の部分は本来「%51」のようにencodeされた状態であるべきところですが、ASCIIコード内の1byte文字としてdecodeされた状態になっているためです。これはリファラを設定してくる各サイトの実装依存なので制御できませんから、受け手側での対応が必要になってきます。

このクエリを正しくdecodeする方法として、本来の「%82%51」に戻してからdecodeするという処理の仕方が考えられます。

無数にパターンが増えていく類のものではないので、個人的には以下のような泥臭い対応でよいのではないかと考えています。

String result = getValidQueryString(arg);
System.out.println(result);
private static final Map<String, String> queryStringURLEncodeMapping = new HashMap<String, String>();
static {
	queryStringURLEncodeMapping.put("\"", "%22");
	queryStringURLEncodeMapping.put("#", "%23");
	queryStringURLEncodeMapping.put("$", "%24");
	・・・(中略)・・・
	queryStringURLEncodeMapping.put("|", "%7C");
	queryStringURLEncodeMapping.put("}", "%7D");
	queryStringURLEncodeMapping.put("~", "%7E");
}

public static String getValidQueryString(String queryString) {
	String[] tmp = queryString.split("%");
	StringBuffer sb = new StringBuffer();
	sb.append(tmp[0]);
	for(int i =1; i< tmp.length; i ++) {
		if ( tmp[i].length() <= 0 ) continue;
		if ( tmp[i].length() > 2 ) {
			sb.append("%" + tmp[i].substring(0, 2));
			String surplus = tmp[i].substring(2);
			String[] targets = surplus.split("");
			for(String each : targets) {
				if ( each != null && ! each.equals("") ) {
        				sb.append(queryStringURLEncodeMapping.get(each));
				}
			}
		} else {
			sb.append("%" + tmp[i]);
		}
	}
	return sb.toString().replaceAll("%", queryStringURLEncodeMapping.get("%"));
}