ぐらめぬ・ぜぷつぇんのはてダ

2008/11/24以降のメインブログはこちらになります。 : http://www.glamenv-septzen.net/

本はてなダイアリにはコメント・トラックバックを受け付ける記事を公開します。

2008-11-11

[]Jakarta Commons FileUpload の 1.1 で、RFC1867見ながら作ったmultipart/form-dataを送りつけたらFileUploadExceptionで怒られた。




The client might send back the following data:

Content-type: multipart/form-data, boundary=AaB03x

--AaB03x

...

--AaB03x--

http://www.ietf.org/rfc/rfc1867.txt

これをそのままコピペして適当にHTTPのPOSTデータ頭にくっつけて送るプログラムを動かしてみたのだけれど。鯖側のサンプルWebアプリがJakartaCommonsのFileUpload-1.1を使ってて、標題のExceptionで怒られる。

FileUploadException : the request was rejected because no multipart boundary was found

・・・おかしい。

使い慣れたPHPでも受信用のアプリを組んでいて、こちらでは正常に$_FILESに入ってきてる。

で、パケットをよく見てみると・・・

Content-type: multipart/form-data, boundary=AaB03x
                                 ^  Σ(゚д゚) 

!?・・・これ、";"の間違いじゃないの!?Googleでフレーズ検索しても、出てくるのは";"バージョンばっかなんですが・・・。

まさかと思い、Commons-FileUpload 1.1 のソースを落としてみました。見るべきポイントはFileUploadBaseクラスのgetBoundary()メソッドと見当が付き、見てみると。

protected byte[] getBoundary(String contentType) {
    // ...
    Map params = parser.parse(contentType, ';');
                                            ^   Σ(゚д゚lll)

やられた・・・。

ちなみに、","が認識されるのは 1.2.1 になってからのようです。気になって、1.1からの最新リリース迄("1.1", "1.1.1", "1.2", "1.2.1")のソースコードをDLして確認したのですが、1.2.1になってようやく

Map params = parser.parse(contentType, new char[] {';', ','});

となっています。*1

というわけで、Commons-FileUploadの1.2.1未満をWebアプリで使っている場合、プログラム組んで手動でHTTPヘッダ組み立ててRFC1867で送信する時は、";"を使わないと駄目ですよー、という話でした。

*1:ソースコードで確認したまでで、すみません、実際に動かして確認までは取ってないです。

2008-10-21

[]ブラウザ上にPCのローカルファイルをドラッグ&ドロップする技法のメモ

お仕事の関連で調べる事になった。で、ブラウザ上で動くので多分Appletで良いのかな?ということでGoogleに「Java Applet Drag and Drop」、さらにPC上のローカルファイルなので「Desktop File」を付け足す。

Java Applet Drag and Drop Desktop File - Google 検索

ざっと眺めたところ、商用アプリとして取り込んで弄くり回してリリースできる無償のライセンス形態のサンプルは以下に絞れた。

いずれのサンプルも、以下のポイントが共通。

  • DropTargetListenerを実装したクラスを用意して、DropTargetDropEventを補足する。
  • DataFlavorをチェックする。Windowsであれば"DataFlavor.javaFileListFlavor"というのを上手く使える?
  • jarファイルに署名する。自己署名だとブラウザが警告を表示する。

1. senior software Engg.

2. Open Source Drag & Drop Upload Java Applet for Websites | Panyasan’s Random Musings

3. OKLab DnDUploader ドラッグアンドドロップ・ファイルアップローダ Java Applet

1. はドロップされたファイルをzipにまとめてPUTメソッドでサーバーに送信するexample。

2. は、1. を更に微調整+カスタマイズしたもの。 1.と2.はAPL2.0。

3. はアップロード+ダウンロードの両方に対応してアプリとしてまとめられている。日本語ファイルのUP&DOWNなどいろいろ工夫されているっぽい。ライセンスについては2008/10/20時点で改造は可能。OSS系のライセンスを適用するとしてもBSD系になるだろう、とのこと。

Q.ライセンスはどうなっていますか?

現在特にライセンスの指定はしていません。当然、無償・無保証でソフトウェアが使え、ソースコードの改変を行っていただいてかまいません。

Q.将来オープンソース系のライセンスになりますか?またGPLになりますか?

検討中です。オープンソースライセンスは、ソフトウェア開発者が免責や法律を調査するコストを減らしてくれるため一定のメリットがあると思います。ですが、ライセンスが乱立しているため、開発者、ユーザ共にどのライセンスであるか、調べるコストが発生しています。訴訟等の問題が発生しない限り、無償・無保証ですが好きに使っていただけるように、現在はライセンスの付加を考えていません。またこのようなスタンスなのでライセンスを付加する場合は、GPLではなくBSD系になると思います。

OKLab DnDUploader ドラッグアンドドロップ・ファイルアップローダ Java Applet

ちなみに 1. のAntスクリプトですが、Antの1.6.5で動かすとsignjarタスクのところで

The <signjar> type doesn't support the "preservelastmodified" attribute.

と出ちゃいました。Antの1.7.1に上げたところ、signjarは正常に動作しましたので注意。

他にも、日本語ファイルは大丈夫かーとか、フォルダをDandDされたらどうするーとかありますが、そこは未調査。

全然別の意味で興味を惹かれたのが 3. ではAppletは当然Java、んでサーバー側がPerlCGIで組まれていた点。しかもPerl5の時代の"use utf8;"なPerlコード。

なんつーか、2008年時点でのWeb開発者というのは一つの言語しか知らないようでは非常に苦しいのだなぁとつくづく思った。あとAnt知らないとやっぱキツイ。だってサンプルコードはAntでビルドするようになってるのが本当、多くなってきてるから。

特にサンプルプログラムでサーバー側をさくっとやっつけたい時はやはりスクリプト言語を使えるとすごい楽だなぁ、と。

2008-09-19

[]Echoサーバ・クライアントのサンプル集とThreadButterをCodeReposにUPしました。


ソケットで電文をやり取りするシステムに関わる機会が多くて、その度に対向器を作る為に「えーっと、JavaでEchoサーバの実装は・・・」とWebで調べます。

というわけで、Javaのソケットで対向器を作る時のベースとなるEchoサーバ・クライアントのサンプル集をUPらせて頂きました。

http://coderepos.org/share/browser/lang/java/echo_samples

"act1" - "act6"位までは相当古いです。2004年・・・って新人の時か。2004/12頃に実装してみた時のですね。この時点ではBufferedReaderやBufferedWriter使って一行単位で読み書きするのしか作れませんでした。

"act7"は先日、見よう見まねで java.nio.channels 使って非同期入出力でサーバ側を実装してみた例です。これでようやく、サーバ側での「READ TIME OUT」を実装できました。


"ThreadButter"というのは、某所の仕事絡みで単純なHTTPのPOSTメソッドを大量に投げる必要が生じた時に作ったツールで、複数のスレッドを使って同時に多重実行する為のフレーム・・・のつもりなのですがいかんせんJavaのThreadについて理解が浅いです。

POSTデータをCSVで用意しておき、それを複数のスレッドに振り分けて一斉にPOSTを開始する・・・みたいな使い方です。

格好良く言うと、WebAPIに負荷をかける為のツールでした。

勿体ないのでUPしておきます。

2008-09-17

[][][] Eclipse 3.2 で Subclipse 1.4.x を入れたらNLPackが動かなくなった。

今時Eclipse3.2って、何年前だよと言われそうですが。2005年のEclipse3.1の時点で同様の現象が発生しているようです。

Eclipse3.1.1に言語パックを入れた環境に、Subclipseを入れて再起動すると英語に戻ってしまいます。その後、「eclipse -clean」による起動や、言語パックの入れ直しをしても英語のままです。

[Eclipse

下記にあるように"-nl"オプションを使って無理矢理"ja_JP"とかを指定しても英語になってしまう。*1

localeの設定は、実は、

eclipse.exe -nl "en_US"

というコマンドラインオプションでできることを発見。これでメニューが全部英語になって、和訳や誤訳などに惑わされないですみます。

Leo’s Chronicle: [Eclipse] Subclipse plug-in用のlocale設定

Eclipseのpluginを入れる時に、NLPackがおかしくなるのは以前にも遭遇した事があります。はっきり言っていらつきます。只でさえEclipseってzipの時点で数十M - 百何MBで、展開するのも時間がかかります。またEclipse3.2.1の日本語が入ったNLPackもzipで51MBもありました(SDK用のNLPack)。

これでPluginの入れる順序がおかしいからと何度も展開したり消したりするなんてやってられません。

そもそもpluginの管理もイケテ無い。Eclipseの本体のfeaturesとかpluginsに突っ込むなんて、キモチワルイ。

・・・と思っていたら、こんな記事を見つけました。

Eclipse環境を管理する

・・・これですよ!

これを読めば、あるPluginをEclipse本体とは別のディレクトリ階層に分離して入れる事ができ、しかも特定のPluginをピンポイントでdisableにすることも出来るようになります!!

もっと早く読んでおけば良かった!!

で、試行錯誤しながら弄っている内に、ちゃんとSubclipseとNLPackをEclipse本体から分離してセットアップし、しかも一度もNLPackがおかしくならずにそのまま動作しました。

やっぱり、本体ディレクトリにごちゃごちゃ入れると駄目ですね。

大雑把な流れを示します。

1. Eclipseの本体を展開します。

C:\eclipse3.2\
    configurations\
    features\
    plugins\
    readme\
    ...
    eclipse.exe

2. NLPackのplugin用のディレクトリを作成し、NLPackを展開します。

C:\eclipse.extensions\
    3.2.1_nlpack1\
        eclipse\
            features\
            plugins\
            .eclipseextension

".eclipseextension"の中身ですが、実はこの前にプロパティエディタをchange locationして入れてみていて、その時自動生成された.eclipseextensionをそのまま流用してます。前掲のIBMのサイトにも解説されています。

そしてここのfeaturesとpluginsディレクトリにNLPackの中身をコピーしておきます。

3. linksディレクトリを使ってpluginの探索パスにNLPack用のディレクトリを追加する。

Eclipse本体ディレクトリに"links"ディレクトリを作成します。

C:\eclipse3.2\
    links\

この中に "eclipse3_2_1_nlpack1.link" というテキストファイルを用意して(多分ファイル名は末尾.linkであれば何でも良いはずです)、以下の内容を書いておきます。

path=C:\\eclipse.extensions\\3.2.1_nlpack1

pathに先ほど作成したNLPack用のディレクトリを指定します。円記号が二重になっていますが、前掲のIBMの資料によるとこれで良いようです。

4. "-clean" オプション付きでeclipseを実行する。

これで日本語化されました。

5. プロパティエディタ5.0.0が動かなかったので4.8.2に簡単に戻せた。

今回は手動でディレクトリを用意したりプラグインを展開したりlinksファイルを用意したりしていますが、ディレクトリだけ用意しておけば、Eclipseのインストーラでpluginとかを特定のディレクトリにインストールできるように元からなっています。

で、プロパティエディタ を検索→インストールさせたら 5.0.0 が入りました。

が、これがNullPointerとか吐いて動かない。

しょうがないので、ここまで書いた方法で

C:\eclipse.extensions\propedit_482

の下に eclipse\featrues とか eclipse\plugins とか作成し、その中に以前はちゃんとEclipse3.2上で動いていた バージョン4.8.2 のzipを展開。で、Eclipseの構成管理で追加し、5.0.0の方はdisableにする。

これでバージョン4.8.2の方のプロパティエディタがちゃんと動くようになりました。

6. SysdeoのtomcatPluginだけはこれじゃだめだった。

調子に乗ってsysdeoのtomcatPluginもこの手法で入れようとしたのですが、何故か認識されず・・・。やむなくこれだけ、Eclipseの本体ディレクトリに入れました・・・。

*1:下記サイトの記事の本意は、en_USで動作させる事にあるようです

2008-09-09

[]JavaとEBCDIC覚え書き

EBCDIC - Wikipedia

そうか、8バイト全部使ってるのか・・・。でしかも、日本語を取り扱う為にベンダ毎に空き領域を弄っているらしい。

文字コード - Wikipedia

メインフレームやオフコンの世界では、Webでは聞いた事もない文字コードが飛び交っている。

no title

いつまで有効か分からないけど、例えばIBMのAS400でのJava開発時におけるfile.encodingのマッピング

第9章 日本語文字コード変換機能の使用方法

HP-UXでのiconvにおける、文字コードについて。

DAL?データ・アプリケーション|インフォメーション / プレスリリース|BEA WebLogic Integration向けにJavaベースの「AnyTran」を販売開始

no title

Javaでレガシーシステムと接続するにあたり、やはり文字コードの変換がネックになるようで、商用でメインフレームやオフコン系の文字コード・フォーマット変換をしてくれるライブラリというかパッケージも出てるようだ。

「日本語を含むEBCDIC用のCharsetProviderは?」(1) Java Solution − @IT

@IT会議室にも載ってるね・・・。

[]java.nio.Buffer系の使い方が分からないので簡単なサンプルを作ってみた。

事情によりJDK1.5で確認。

元ネタはここ:Buffering « File Input Output « Java

まずは作ったサンプルの全体像。

package test.nio2;

import java.nio.CharBuffer;

public class BufferDemo2
{
	public static void dumpCharBuffer(CharBuffer b)
	{
		char c = '\0';
		while (b.hasRemaining()) {
			c = b.get();
			if (c != 0) {
				System.out.print("[" + c + "]: ");
			} else {
				System.out.print("[]: ");
			}
			BufferUtil.dump(b);
		}
	}
	
	public static void main(String[] args)
	{
		CharBuffer cb = CharBuffer.allocate(64);
		BufferUtil.dump(cb);
		cb.limit(16);
		BufferUtil.dump(cb);
		cb.put("UsingBuffers".toCharArray());
		BufferUtil.dump(cb);
		cb.rewind();
		BufferUtil.dump(cb);
		dumpCharBuffer(cb);
		System.out.println("-------------------------");
		try {
			cb.put("Foo".toCharArray());
			BufferUtil.dump(cb);
		} catch (java.nio.BufferOverflowException ignored) {
			System.out.println("Buffer Overflow!!");
		}
		System.out.println("-------------------------");
		cb.position(5);
		cb.mark();
		BufferUtil.dump(cb);
		cb.put("Foo".toCharArray());
		BufferUtil.dump(cb);
		cb.reset();
		BufferUtil.dump(cb);
		dumpCharBuffer(cb);
		System.out.println("-------------------------");
		cb.clear();
		BufferUtil.dump(cb);
		cb.put("BarBuz".toCharArray());
		cb.flip();
		BufferUtil.dump(cb);
		dumpCharBuffer(cb);
		System.out.println("-------------------------");
	}
}
BufferUtil.dump(cb);

というのが頻出しますが、要はjava.nio.Bufferのcapacity, limit, position, remainingを整形して標準出力する自作メソッドです。

capacity[64],limit[64],position[0],remaining[64]

こんな感じに整形して出力します。

細かい解説は後回しにして、まずは実行結果の全体を示します。

capacity[64],limit[64],position[0],remaining[64]
capacity[64],limit[16],position[0],remaining[16]
capacity[64],limit[16],position[12],remaining[4]
capacity[64],limit[16],position[0],remaining[16]
[U]: capacity[64],limit[16],position[1],remaining[15]
[s]: capacity[64],limit[16],position[2],remaining[14]
[i]: capacity[64],limit[16],position[3],remaining[13]
[n]: capacity[64],limit[16],position[4],remaining[12]
[g]: capacity[64],limit[16],position[5],remaining[11]
[B]: capacity[64],limit[16],position[6],remaining[10]
[u]: capacity[64],limit[16],position[7],remaining[9]
[f]: capacity[64],limit[16],position[8],remaining[8]
[f]: capacity[64],limit[16],position[9],remaining[7]
[e]: capacity[64],limit[16],position[10],remaining[6]
[r]: capacity[64],limit[16],position[11],remaining[5]
[s]: capacity[64],limit[16],position[12],remaining[4]
[]: capacity[64],limit[16],position[13],remaining[3]
[]: capacity[64],limit[16],position[14],remaining[2]
[]: capacity[64],limit[16],position[15],remaining[1]
[]: capacity[64],limit[16],position[16],remaining[0]
-------------------------
Buffer Overflow!!
-------------------------
capacity[64],limit[16],position[5],remaining[11]
capacity[64],limit[16],position[8],remaining[8]
capacity[64],limit[16],position[5],remaining[11]
[F]: capacity[64],limit[16],position[6],remaining[10]
[o]: capacity[64],limit[16],position[7],remaining[9]
[o]: capacity[64],limit[16],position[8],remaining[8]
[f]: capacity[64],limit[16],position[9],remaining[7]
[e]: capacity[64],limit[16],position[10],remaining[6]
[r]: capacity[64],limit[16],position[11],remaining[5]
[s]: capacity[64],limit[16],position[12],remaining[4]
[]: capacity[64],limit[16],position[13],remaining[3]
[]: capacity[64],limit[16],position[14],remaining[2]
[]: capacity[64],limit[16],position[15],remaining[1]
[]: capacity[64],limit[16],position[16],remaining[0]
-------------------------
capacity[64],limit[64],position[0],remaining[64]
capacity[64],limit[6],position[0],remaining[6]
[B]: capacity[64],limit[6],position[1],remaining[5]
[a]: capacity[64],limit[6],position[2],remaining[4]
[r]: capacity[64],limit[6],position[3],remaining[3]
[B]: capacity[64],limit[6],position[4],remaining[2]
[u]: capacity[64],limit[6],position[5],remaining[1]
[z]: capacity[64],limit[6],position[6],remaining[0]
-------------------------

では、エッセンス部分を個別に見ていきます。dumpCharBuffer()は解説不要ですよね?現在位置から1文字get()して(現在位置は1つずれます)、remainingがある間ぐるぐる1文字ずつ出力していくだけです。BufferUtil.dump()とか、dumpCharBuffer()は以降省略したコードで見ていきます。

初期化時点でのcapacityとかlimit、またput()後のposition, rewind()動作の確認

まずCharBufferの初期化部分を見てみます。

// (1) 64バイト分のcharバッファを確保する。
CharBuffer cb = CharBuffer.allocate(64);

// (2) put/getは16char分に制限する。
cb.limit(16);

// (3) charデータを現在のpositionから追記する。
cb.put("UsingBuffers".toCharArray());

// (4) positionを0にしてmarkも破棄する。
cb.rewind();

この結果を見てみます。

capacity[64],limit[64],position[0],remaining[64]
... (1)の時点です。capacity = limit ですね。

capacity[64],limit[16],position[0],remaining[16]
... (2)でlimit(16)をした時点です。limitが16になってます。また、remaining = limitです。

capacity[64],limit[16],position[12],remaining[4]
... (3) で、put()した時点です。positionが12に増え、remainingは limit - positionになります。

capacity[64],limit[16],position[0],remaining[16]
... (4) rewind()するとlimitの値はそのままで、positionを0に戻します。

... ↓で、この状態でぐるぐる回すとバッファの先頭から順に表示されていきます。
[U]: capacity[64],limit[16],position[1],remaining[15]
[s]: capacity[64],limit[16],position[2],remaining[14]
[i]: capacity[64],limit[16],position[3],remaining[13]
[n]: capacity[64],limit[16],position[4],remaining[12]
[g]: capacity[64],limit[16],position[5],remaining[11]
[B]: capacity[64],limit[16],position[6],remaining[10]
[u]: capacity[64],limit[16],position[7],remaining[9]
[f]: capacity[64],limit[16],position[8],remaining[8]
[f]: capacity[64],limit[16],position[9],remaining[7]
[e]: capacity[64],limit[16],position[10],remaining[6]
[r]: capacity[64],limit[16],position[11],remaining[5]
[s]: capacity[64],limit[16],position[12],remaining[4]
... バッファの内容はデフォルトで0ですね。
[]: capacity[64],limit[16],position[13],remaining[3]
[]: capacity[64],limit[16],position[14],remaining[2]
[]: capacity[64],limit[16],position[15],remaining[1]
[]: capacity[64],limit[16],position[16],remaining[0]
-------------------------

position = limitの状態でput()したらjava.nio.BufferOverflowException発生

try {
    // この段階でposition = limitになるため、
    // これ以上のputはBufferOverflowExceptionを発生させる。 
    cb.put("Foo".toCharArray());
} catch (java.nio.BufferOverflowException ignored) {
    System.out.println("Buffer Overflow!!");
}

出力:

Buffer Overflow!!
-------------------------

position()で読み書き位置を設定、mark()とreset()の挙動確認。

// (5)-a 次の読み書き位置を0始まりの5に設定
cb.position(5);
// (5)-b reset()用に現在位置をマーキング
cb.mark();

// (6) 現在位置から"Foo"をバッファに書き込む。
cb.put("Foo".toCharArray());

// (7) 最後にmarkした位置までpositionを戻す。
cb.reset();
capacity[64],limit[16],position[5],remaining[11]
... (5)-a,b 終了後。positionが指定したとおりになっています。
ちょうど"Using"の"g"の位置が"4"文字目になるので、
positionが5と言う事はその次の"B"に次にput()する1文字目が書き込まれます。

capacity[64],limit[16],position[8],remaining[8]
... (6) 終了時点です。3文字分positionが前進しています。

capacity[64],limit[16],position[5],remaining[11]
... (7) の時点です。positionがput()する前の位置に戻ってます。

... ↓この時点でぐるぐる回すと、現在位置がちょうど"F"になるのでそこから表示されます。
[F]: capacity[64],limit[16],position[6],remaining[10]
[o]: capacity[64],limit[16],position[7],remaining[9]
[o]: capacity[64],limit[16],position[8],remaining[8]
[f]: capacity[64],limit[16],position[9],remaining[7]
[e]: capacity[64],limit[16],position[10],remaining[6]
[r]: capacity[64],limit[16],position[11],remaining[5]
[s]: capacity[64],limit[16],position[12],remaining[4]
[]: capacity[64],limit[16],position[13],remaining[3]
[]: capacity[64],limit[16],position[14],remaining[2]
[]: capacity[64],limit[16],position[15],remaining[1]
[]: capacity[64],limit[16],position[16],remaining[0]
-------------------------

ちなみにこの段階でrewind()してぐるぐる回すと、当然"UsingFoofers"という表示になります。実験してみて下さい。

clear()とflip()の挙動確認

このバッファをフリップ (反転) します。リミットは現在位置の値に設定され、現在位置を表す値は 0 に設定されます。マークが定義されている場合、そのマークは破棄されます。

チャネル読み込み操作 (put) のあと、このメソッドを呼び出してチャネル書き込み操作 (相対「get」) の準備を行います。

Oracle Technology Network for Java Developers | Oracle Technology Network | Oracle

flip()というのが上記javadocの解説からは全くイメージが湧かなかったので使ってみました。

// (8) limitをcapacityに合わせ、positionも0に戻す。
cb.clear();

// "BarBuz"と先頭から書き込み。
cb.put("BarBuz".toCharArray());

// (9) 書き込んだ所までをlimitにして(ここがrewind()と異なる所)、
// positionを0に戻す。
cb.flip();
capacity[64],limit[64],position[0],remaining[64]
... (8)の時点。確かにcapacity = limitとなり、positionも0になっている。

capacity[64],limit[6],position[0],remaining[6]
... (9)の時点。"BarBuz"まで書き込んだ時点で、positionは0始まりの"6"になる。
そこがlimitに設定され、positionは0に巻き戻される。

... ↓そこからぐるぐるすれば、"ちょうどputされた分だけ"getできる。
[B]: capacity[64],limit[6],position[1],remaining[5]
[a]: capacity[64],limit[6],position[2],remaining[4]
[r]: capacity[64],limit[6],position[3],remaining[3]
[B]: capacity[64],limit[6],position[4],remaining[2]
[u]: capacity[64],limit[6],position[5],remaining[1]
[z]: capacity[64],limit[6],position[6],remaining[0]
-------------------------

flip()というのは、Bufferに書き込まれた分だけちょうど取得したい時に呼んでおく、という使い方が出来るようです。

まとめと雑感

Cとかだと、配列を用意していろんな「今どこ」を管理する必要があるのですが、JavaですとBufferでclear()とかflip()とかrewind()を用いる事で、その辺りを手計算する必要がないように省力化してくれている・・・という感じを受けました。

そもそもいきなりjava.nio.Bufferに手を出したのは、channelを使ったソケット入出力のexamplesでByteBufferやCharBufferのflip()やrewind()が出てきて、「これ、何?」と全然分からなかったのが原因です。

確かにソケット入出力では(C言語でも)バッファに対して何バイト分read/writeして云々というのが必要です。

java.nio.Bufferのメソッドを上手く使うと、その当たりを「数字の手計算無しに」できるのがメリットかなぁ・・・と思いました。

慣れてくれば意外と重宝するクラス群ではないでしょーか・・・。