今日からはじまりです
毎日、覚えたことをここに書いていく。
アプレットで「ファイルを保存」ダイアログを出す
調べたらわかった。わかってみると割と簡単。
FileDialog dialog = new FileDialog(new JFrame(), "ファイルの保存", FileDialog.SAVE); dialog.toFront(); dialog.setVisible(true); // ここでダイアログが出て、閉じるまで待つ。 System.out.println(dialog.getDirectory()); // パス System.out.println(dialog.getFile()); // ファイル名
- 上書きしますか?の確認は自動でやってくれる。
- FileDialogのコンストラクタの第二引数をFileDialog.LOADにすると「ファイルを開く」ダイアログになる。
awtなので別にアプレットに限った事じゃないか。
ていうかアプレットでこれ使おうとすると、ローカルファイルに触れる権限が必要なのでむしろ面倒。
テストのため、オレオレ証明書でjarに署名する方法
ローカルファイルに触れるアプレットは、署名が無いとダメ。権限が無いって怒られる。でも開発中に本物の署名ができるケースもなかなか無い。
そこで、オレオレ証明書による署名をした。今回、Windowsのコマンドプロンプトからやってみた。他のOSでも大差ないと思う。
鍵ペアの生成
C:\>c:\jdk1.5.0_12\bin\keytool -genkey -keyalg rsa -alias test
これで「test」っていう名前(エイリアス)の鍵ペアの生成が始まる。あとは画面に従って、パスワードやら姓名などの情報を入れる。テスト用のオレオレ証明書なので、パスワード以外の情報は適当でいい。パスワードはあとで署名するときに使うので覚えておくこと。
署名する
C:\>c:\jdk1.5.0_12\bin\jarsigner c:\dev\applet.jar test
jarsignerに、署名したいjarのパスと鍵ペアのエイリアスを指定する。
するとキーストアのパスワードを入れろと言われるので、さっきのパスワードを入れる。
終わり
- 権限を与えたいすべてのjarを署名する必要がある。
- このjarを使うと、当然ブラウザは警告を出す。オレオレだから。
- これはあくまでテスト用。本番でオレオレ証明書は使ってはいけない。絶対に。手抜きはダメ。
アプレットに指定したURLの画像を表示
画像をWebから取得してアプレット内に表示したい。
なんか難しそうだと思ったら、めちゃくちゃ簡単だった。
URL url = new URL("http://example.com/image.gif"); ImageIcon icon = new ImageIcon(url); JLabel label = new JLabel(); label.setIcon(icon); label.setSize(icon.getIconWidth(), icon.getIconHeight());
どこにも通信とか描画の処理を書かなくておk。けっこう便利ー。
アプレットからWebサーバへファイルアップロード
アプレットからのファイルアップロード。これはなかなか大変だった。
具体的には、httpでmultipartのPOSTをしたい。複数のファイルや、一緒にテキストデータなんかも一緒にPOSTしたい。
Jakarta Commons HttpClientをアプレットで使うのは難しい
Jakarta Commons HttpClientというコンポーネントがあって、これを使うとhttp通信が手軽に使えるらしい。
これを使って実際に作ってみたけど、ダメだった。
- 自分の作ったjarだけじゃなく、このHttpClientのjarにも署名が必要
- 一緒に使う必要があるJakarta Commons Loggingのバグにより、権限エラーになる
- 日本語ファイル名の文字化け
どれも回避しようと思えばできるんだけど、後でのメンテも含めて面倒になるため、あきらめた。
自分で通信部分を実装
Javaの標準機能だけを使って自分で通信部分を実装した。
String boundary = generateBoundary(); // 接続 URL url = new URL("http://example.com/upload.do"); // 送信先 URLConnection conn = url.openConnection(); conn.setDoOutput(true); conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary); DataOutputStream out = new DataOutputStream(conn.getOutputStream()); // テキストフィールド送信 out.writeBytes("--" + boundary + "\r\n"); out.writeBytes("Content-Disposition: form-data; name=\"text\"\r\n"); out.writeBytes("Content-Type: text/plain; charset=Shift_JIS\r\n\r\n"); out.write("テキスト".getBytes(this.charset)); out.writeBytes("\r\n"); // ファイルフィールド送信 File file = new File("c:\\files\\file.zip"); out.writeBytes("--" + boundary + "\r\n"); out.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\""); out.write(file.getName().getBytes("Shift_JIS")); out.writeBytes("\"\r\n"); out.writeBytes("Content-Type: application/octet-stream\r\n\r\n"); BufferedInputStream in = new BufferedInputStream(new FileInputStream(file)); int buff = 0; while((buff = in.read()) != -1){ out.write(buff); } out.writeBytes("\r\n"); in.close(); // 送信終わり out.writeBytes("--" + boundary + "--"); out.flush(); out.close(); // レスポンスを受信 (これをやらないと通信が完了しない) InputStream stream = conn.getInputStream(); BufferedReader reader = new BufferedReader(new InputStreamReader(stream)); String responseData = null; while((responseData = reader.readLine()) != null){ System.out.print(responseData); } stream.close();
テキストフィールドとファイルフィールドを送信しているところは、連続して何回でも呼べる (multipart のパートとなる)。
あと、最初にboudaryを取得してるところ (generateBoundary) は、以下のような感じで書いた。
private String generateBoundary(){ String chars = "1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_"; Random rand = new Random(); String boundary = ""; for(int i = 0; i < 40; i++){ int r = rand.nextInt(chars.length()); boundary += chars.substring(r, r + 1); } return boundary; }
boundaryっていうのは、multipartのデータを送信するときにデータ(各パート)の区切りを表す文字列。どんな文字列でも良いが、boundary以外のデータに同じ文字列が含まれると、そこで区切られたことになってしまって良くない。
今回は、ランダムに40文字の文字列を生成しただけ。このやり方は、ものすごく運が悪いと、ファイル内のデータに含まれる文字列と重複しちゃうかも知れない。
けど、たぶん相当なレアケースだと思う (確率は計算してないけど) ので、気にしないことにした。
URL#openConnectionによるhttp通信時にBasic認証
Javaの標準機能を使ってhttp通信するときは、URLクラスのopenConnectionメソッドを使って接続するわけだけど、そのときにBasic認証で認証したいときのやりかた。
final String username = "username"; final String password = "p@ssw0rd"; Authenticator.setDefault(new Authenticator(){ @Override protected PasswordAuthentication getPasswordAuthentication(){ return new PasswordAuthentication(username, password.toCharArray()); } });
- AuthenticatorクラスのsetDefaultメソッドを呼ぶ。これはstaticメソッド。
- 引数として、Authenticatorインタフェースを実装したクラスのインスタンスを渡す。
- いちいちクラスを定義してインスタンスをnewして渡すと面倒なので、匿名クラス(無名インナークラス)で渡すのが簡単。
- 匿名クラス内のコードから、外の変数を参照する場合、外の変数はfinalにしておくか、インスタンス変数にしておく必要がある。
これを、openConnectionメソッドを呼ぶ前にやっておく。
これでうまくいったから良いけど、なんでこんな仕様なの?あらかじめstaticメソッドを呼んでおくなんて、微妙すぎると思うんだけど。
アプレットからJavaScriptの関数を呼び出す
例えばアプレットで何かの処理をしていて、それが終わったタイミングで
- JavaScriptのalertを呼んだり
- ページを移動したり
- その他もろもろ
といったような、何かしらのJavaScriptを動かしたいことがある。そんなときのやり方。
JSObject window = JSObject.getWindow(Applet.this); window.eval("hoge()");
これで、アプレットが埋め込まれているhtml側に定義されてるJavaScriptのhoge関数が呼ばれる。いわゆるコールバック。
このhoge関数は、例えばこんな感じ。
function hoge(){ alert('完了しました'); }
上記のようにすればalertされる。ページを移動したいなら、
function hoge(){ location.href = 'http://example.com/complete.html'; }
こんな感じ。まあ普通のJavaScript。これをhtml側に用意しておけば良い。
デザインのカスタマイズ
とりあえず以下のcssだけ書いた。
.body h3{ background: #9fbfbe; color: #ffffff; padding: 0.3em; font-size: 120%; } .body h3 a{ color: #ffffff; } .body h3 a.sectioncategory{ color: #ffffff; } .body h3 a .sanchor{ color: #ffffff; } .body .section p { margin-bottom: 1.2em; }
- 記事の見出しを目立つようにした
- 段落と段落の間のすきまを広くした
デザインをカスタマイズするのは楽しい。