Archive for 12月, 2011
svn statusの結果で
? lib ? classes M .classpath M src/java/com/coltware/serene/httpmail/imap/IMAP4Action.java M src/java/com/coltware/serene/mail/MailResponseStatus.java M src/java/com/coltware/serene/mail/IMAP4ResponseMessage.java ? src/java/com/coltware/serene/imap/data/FolderName.java M src/java/com/coltware/serene/imap/data/IMAP4Folder.java ? src/java/com/coltware/serene/imap/codec/IMAP4SelectDecorder.java ? src/java/com/coltware/serene/imap/codec/UTF7Decoder.java M src/java/com/coltware/serene/imap/codec/IMAP4CodecUtil.java M src/java/com/coltware/serene/imap/codec/IMAP4ListDecorder.java M src/java/com/coltware/serene/imap/client/IMAP4Session.java ? src/java/com/coltware/serene/imap/command/IMAP4CloseCommand.java ? src/java/com/coltware/serene/imap/command/IMAP4FolderListCommand.java M src/java/com/coltware/serene/imap/command/IMAP4OpenCommand.java M src/java/com/coltware/serene/imap/command/IMAP4CommandMessage.java
ってな感じで出力されるものの、ここから、まだ登録されていないjavaファイルだけを追加(svn add)したい。
この場合には、
svn status | grep "^?" | grep java | cut -c8- | xargs svn add
ってな感じ。
svn statusの結果をgrepで好きにフィルタしていき、
cutで8文字(8byteかな?)のところから最後まで取得し、
xargsでぐるぐるまわせばよい
今回は、JBOSS Nettyを使って、HTTPストリーミングを実現させる方法を紹介します。
HTTPストリーミングとはなんぞや?
ということですが、要はレスポンスが何度も分割されて出力されるだけです。
chunkedという形式で結果をかえせば、それがストリーミングという訳です。
たとえば、どんなところで使えるかと言えば、
チャットシステムなんかを構築する際に、受けるデータ(他人のコメント)を、
HTTPストリーミングという形で受ければ、つまり、PUSH配信ということが実現できるという事です。
どうやら、私はやらないし、つかわないのでよくわかりませんが、
twitterのStreaming APIみたいな感じ(まちがっていたらすいません。)のPUSH配信を実現する感じです。
では、nettyでのhandlerのサンプルを乗せますと、
package com.coltware.chat;
import java.util.Set;
import org.jboss.netty.buffer.ChannelBuffer;
import org.jboss.netty.buffer.ChannelBuffers;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.ChannelHandlerContext;
import org.jboss.netty.channel.ChannelStateEvent;
import org.jboss.netty.channel.MessageEvent;
import org.jboss.netty.channel.SimpleChannelUpstreamHandler;
import org.jboss.netty.handler.codec.http.DefaultHttpChunk;
import org.jboss.netty.handler.codec.http.DefaultHttpResponse;
import org.jboss.netty.handler.codec.http.HttpChunk;
import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;
import org.jboss.netty.handler.codec.http.HttpResponse;
import org.jboss.netty.handler.codec.http.HttpResponseStatus;
import org.jboss.netty.handler.codec.http.HttpVersion;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ChatHandler extends SimpleChannelUpstreamHandler {
private static final Logger log = LoggerFactory.getLogger(ChatHandler.class);
public ChatHandler(){
}
@Override
public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
throws Exception {
log.debug("channelOpen");
super.channelOpen(ctx, e);
}
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)
throws Exception {
Object ret = e.getMessage();
if(ret instanceof HttpRequest){
final HttpRequest request = (HttpRequest)ret;
Set<string> headers = request.getHeaderNames();
for(String name:headers){
log.debug("header[" + name + "]=>[" + request.getHeader(name) + "]");
}
HttpResponse response = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
response.setChunked(true);
response.setHeader(HttpHeaders.Names.CONTENT_TYPE, "application/octet-stream");
response.setHeader(HttpHeaders.Names.TRANSFER_ENCODING,HttpHeaders.Values.CHUNKED);
log.debug("response" + response);
e.getChannel().write(response);
this.writeStringChunk(e.getChannel(), "this is 1st chunk data");
this.writeStringChunk(e.getChannel(), "this is 2nd chunk data");
e.getChannel().write(HttpChunk.LAST_CHUNK).addListener(ChannelFutureListener.CLOSE);
}
else{
super.messageReceived(ctx, e);
}
}
private void writeStringChunk(Channel channel,String data){
ChannelBuffer chunkContent = ChannelBuffers.dynamicBuffer(channel.getConfig().getBufferFactory());
chunkContent.writeBytes(data.getBytes());
HttpChunk chunk = new DefaultHttpChunk(chunkContent);
channel.write(chunk);
}
}
これをサーバとして動かし、実際にHTTPを使ってアクセスすると
HTTP/1.1 200 OK Content-Type: application/octet-stream Transfer-Encoding: chunked 16 this is 1st chunk data 16 this is 2nd chunk data 0
とこんな感じの結果になります。
この例では、1つの処理の中で2つのHttpChunkを返して、そのまま閉じてしまいましたが、
たとえば、Chatなどのシステムを作る場合には、
接続を閉じず(63行目を実行せず)、そのまま、全体の接続を他のところでとっておき、
それぞれに、このChunkでくぎられたデータを出力していけば、チャットシステムの機能が簡単に作れるという訳です。
ただし、これをActionScriptのURLStreamをつかってChunkごとにデータをキチンととれないかと、
調べてみましたが、どうやらそれができないようです。
chunkedの形式ではなく、
this is 1st chunk datathis is 2nd chunk data
のようにしかデータが取得できないので、データ自体に1つのデータの区切りを作るための仕掛けを作っておいた方が良さそうです。
ちょっと、ニュースから私なりに気になる部分を抜き出します。
ニュースの元ネタは
「ITC、アップルに有利な決定–HTCによる1件の特許侵害を認定」
何気なく読んでいたのですが、ここでの内容はめちゃくちゃソフトウェアを作っている人は関係があるなーと思った次第です。
ちょっと、記事の抜粋ですが、
「争点になっている米国特許番号5946647は1999年に取得したもので、「電話番号や郵便番号、電子メールアドレス、名前」など、「認識可能な構造を持つ」識別データを対象としている。また同特許によると、「パーシングプロセス」によって、「適切なアクション」を実行することが可能になるという。」
とあります。
つまり、メーラ等あつかう自由フォーマットの中で自動的にリンクがでるあれです。
私がつくったメーラだって、URLがあれば、自動的にリンクくらい出します。
それが、特許侵害かもしれないという訳です。
もう少し、このあたりを調べてみることにしたら、
http://www.anlyznews.com/2011/10/apple5946647.html
http://www.techvisor.jp/blog/archives/1844
が、参考になりました。
これをどうやって回避するのでしょうか?
HTMLに変換したあとに、ブラウザ機能で回避が可能とか?
まあ、私も特許を出願したことがあります(出願だけですが、金がかかるかかる・・・)が、
特許というのは目的アイデアではなく、その実装手順に対して(すくなくとも日本では)なので、
何らかの回避方法はありそうですが、ちょっと面倒ですね。
昔の、ワンクリック特許とか、ダート特許を思い出します。
どうせ、メールを見た人は、それが住所だか、電話番号だかわかっているので、
その文字周辺をクリックしたら、そのあたりを中心に該当を探し出し、自動的にポップアップをだすってのはだめですかね。
あらかじめ、使えるアクションを登録できるとか?
こんなかんじを、
タッチに対応すればスマホでも使いやすいんじゃないかな?
少なくとも、この特許は侵害しないとおもうし・・・
それに、スマホなら他に自由にアプリと連携もさせたいとおもうので・・・
Macでairxzipで圧縮した0Byteのファイルを含むzipファイルがunzipできない問題を修正し、
Version 1.0.1としてリリースしました。
(あまり、変わっていないので、0.0.1だけバージョンをあげました。)
ちなみに、airxzipでは1.0でも解凍はできます。
それ以外は特にないです。
ダウンロードはこちらからどうぞ。
JavaのRESTful(JAX-RS)実装であるjerseyの中身をみていることにした。
というのも、あのPathパラメータの実装が欲しいと思ったからです。
具体的には、JAX-RSの仕様では
@Path("/customer/{first}-{last}")
public void foo(
@PathParam("first") String firstName,
@PathParam("last") String lastName){
}
という表記をすると、
http://xxxxxx/customer/shinnosuke-nohara
のようなURLにアクセスしたときに、
foo(“shinnosuke”,”nohara”)
のメソッドを自動的に起動できる。
ドキュメントチックにRESTのURLのパターン仕様が記述できるので、
ある意味、わかりやすい。
このような仕様を今度作成するnettyで実現するRESTにも部分的に取り込もうと思う。
そこで、Jerseyではこの部分をどのようにやっているのかをみて、
まずはそこだけ借りてしまおうということです。
で、調べたのが、
com.sun.jersey.api.uri.UriTemplate
というクラス。これで、URLの正規表現と実際のパスからパラメータを抜き出すことができます。
では簡単な例。
public class PathPatternSample {
/**
* @param args
*/
public static void main(String[] args) {
checkPath("/foo/{arg1}/{arg2}/{num:[0-9]{3}}","/foo/test1/test2/999");
checkPath("/customer/{firstname}-{lastname}","/customer/shinnosuke-nohara");
}
public static void checkPath(String path,String input){
UriTemplate template = new UriTemplate(path);
Map<string , String> values = new HashMap</string><string , String>();
System.out.println("======>" + template.getTemplate() + "< ======");
if(template.match(input, values)){
for(String key: values.keySet()){
System.out.println(key + "=>[" + values.get(key) + "]");
}
}
}
}
これで、実行結果は
======>/foo/{arg1}/{arg2}/{num:[0-9]{3}}< ======
num=>[999]
arg2=>[test2]
arg1=>[test1]
======>/customer/{firstname}-{lastname}< ======
lastname=>[nohara]
firstname=>[shinnosuke]
となります。
これで、javaのアノテーションと組み合わせれば、
まずは完成といったところです。
最近は、Flexを離れJBoss Nettyにはまっています。
Nettyとは、簡単に言えば(比較的)簡単にTCP/IPのサーバが作れるというものです。
その際に、接続処理や、切断処理、データのエンコード、デコードなどを簡単にできて、
なおかつ、大量の接続に耐えられるようにしたフレームワークです。
個人的には、構造を意識するとinetd上にアプリを作っている感じと、
Apacheのモジュールを作っている感じもあるのですが、
Javaゆえの簡易さも感じます。
WebSocketなどのサーバを作る場合の例や、HTTPのプロキシサーバの例など結構あるようです。
今、私はSMTPのProxyサーバを作っています。
受付は、HTTPですが、それをNettyを使ってSMTPのプロトコルに変換し、
メールを送信するというものです。
POP3/IMAPなどもやりたいのですが、
まずは、比較的簡単そうなSMTPです。
まあ、これはServletを使ったほうが簡単そうですが、
Nettyを使って、Sevletでは難しい機能も追加しやすくなります。
(一方で、難しいことも出てきますが・・・)
たとえば、送信時のリアルタイム承認機能などです。
送信時に、あらかじめ決められた承認者に対してPUSH配信を使って、
承認が得られるまで配信を保留などの場合などができます。
(送信者側の接続は切ってしまうこともできます。)
これは、あらかじめ他のアプリなどで接続を維持しておく必要がありますが、
その接続が維持できている人に対してポップアップなどを出しておくこともできます。
後は、メールではないものの、ECなどをやっているときにやりたかったのが、
先着XXX名様限定。
(別に、販売で無くてもいいのですが・・)
接続数を限定で、50名とかにして、それ以上は待機接続として待たせておいて、
購入をあきらめたら、先着順に新たな接続を許可するなどです。
(純粋な)HTTPを使っていると、どうしてもこのような処理はできません。
サーバからは、ブラウザが落ちている(あきらめた)のか、ただ見ているだけ(作業中)なのかがわからないためです。
それ以外にも、いろいろな応用範囲がありそうです。
Nettyの機能などはあまり、日本語で参考資料もないので、
気づいたことでも書いていければよいなと思っています。
NettyをAndroidとPC(というか通常のJAVA)で動かそうと試行錯誤しています。
結局、Androidもしかり、AIRでもしかり、
画面があることが前提のプログラミングは、非常に画面を意識した非同期処理(やスレッド)を意識しなくてはならず、
さらに、WEBサービスの一部であったりするので、大変です。
そこに、WEBプログラムとの親和性まで求められてしまいます。
これでは、難易度はさらに上がり、とても、普通はやってられない。
というのが私なりの結論です。
これを手っ取り早くやるなら、組み込みWEBサーバを通じてすべての処理をしてしまうのが一番よいのではないか?
と思うのです。
でも、組み込みWEBサーバと言っても、Servletなどの仕様は大きすぎますし、
一方で、純粋なTCP/IP (Socket通信)の操作もしたくなります。
私がこのブログ内で作ってきた、GravityMailなども、
正直、AIRだけでは無理があると思いますし、
クライアントだけで動かせる利点もどこまで意味があるのかも。
ただ、「クライアントアプリがある」というのは、
ちょっと、聞いた限りでは結構価値がありそうです。
なので、まずはNettyでhttpのサーバを作り込み、
それから、imapやpopのプロトコルをhttpのRESTの形に変換すればいいのかなという感じです。
そうすれば、AIRのNative Extensionを使わなくても、
Javaの制限が許せる限り、結構応用範囲が広そうな気がしています。
この時点で、iOSがだめになってしまうのですが・・・・
という本を買って読んでみることにしました。
といっても、私は会計やファイナンスに興味があったから読んでみることにした訳ではありません。
理由は、2つあります。
1つめは、たまたま
ドコモ「1人負け」はなぜ止まらないのかという記事を読んだことでした。
ちなみに、この記事は、2011/4/7の記事です。
ここで、ドコモと、KDDI、ソフトバンクの実際の契約数シェアと、
この記事の筆者が展開している理論上のシェアというものがありまして、
そこで、理論と実際で異なるのがソフトバンクとドコモで、
そのことから、ソフトバンクはドコモを標的にしている。
という持論を持ち出しています。
私が面白いと思ったのは、何となくわかりきったことをある理論のもとに証明しようとしているところです。
こういうのって意外と難しいと思います。
なので、その基本には何があるのかを知りたいと思ったところ、
上記、本があるとのことなので、数学を通じて、別の知識をみてみたいなとおもったのでした。
本の中身をみても、独特の雰囲気があり勉強になる気がしました。
そして、もう一つ。
この方が、私の出身である、「栃木県小山市」なのです。
この間も、実家に帰ったばかりですし、自分の出身地となればより親しみがわきます。
ちなみに、この本とは全然関係がなく栃木県小山市つながりなのですが、
ソフトエージェンシーという会社は
MySQLのつながりで会社をしったのですが、こちらも栃木県小山市なのです。
どんなところなのかすごく気になります。
しかも、実家から歩いていけるところにあるので、
なんか、近くまでいく機会がないものかと思っている次第です。
Airxzipで0Byteのファイルを圧縮すると、
Mac OSの標準的にインストールされているzipコマンドで解凍できないというバグがありました。
今回、このためソースを修正しました。
ただし、どうやらWindowsでは問題ないようです。
(改めて、この修正をアーカイブとしてアップしようと思います。)
さて、原因ですが、簡単にいえば0Byteのファイルをinflateしようとして、エラーになっているようです。
ようは、0Byteなのだから、圧縮形式にしなければよい訳です。
これをコマンド等を使って確認してみると
unzip airxzip.zip
Archive: airxzip.zip
inflating: blank
error: invalid compressed data to inflate
こんな感じにエラーになってしまいます。
ちなみに、unzip -v airxzip.zip
とすると
Length Method Size Ratio Date Time CRC-32 Name
——– —— ——- —– —- —- —— —-
0 Defl:N 0 0% 12-02-11 01:07 00000000 blank
——– ——- — ——-
0 0 0% 1 file
のように表示されます。
しかし、macのzipコマンドで圧縮したものは、
Length Method Size Ratio Date Time CRC-32 Name
——– —— ——- —– —- —- —— —-
0 Stored 0 0% 12-02-11 01:07 00000000 b1
——– ——- — ——-
0 0 0% 1 file
のように、macでzipしたものは圧縮されていない(Method = Stored )のがわかります。
したがって、airxzipも0ByteのデータはMethodで圧縮形式ではないことにして、
アーカイブにするようにしました。
これで、Mac OS XとWindowsでは解凍できることも確認できました。


