ついったーのStreaming API ChirpUserStreamとWebSocketを組み合わせてみた
最近の流れでは、時代はHTML5なんですかね?
そんなわけで、前にちょっとやってみたJetty7のWebSocketと、ついったーのStreaming API ChirpUserStreamを組み合わせて簡単なWebベースのついったクライアントを書いてみました。
動いているところ:
動作している動画を見てもらえば分かるんですが、Streamingで受信したイベントを上から"落とす"ようなUIにしてみました。
TLをちゃんと読むのではなく、ぼーっとみてるようなコンセプトです。実際に使うならこんなUIしんどいでしょうけどまぁ実験あぷりなんで。
(もしスクリーンショットや動画に自分のPOSTが表示されていて、消して欲しい場合は、お手数ですがコメントお願いします。)
ChirpUserStreamなんで、他の人がふぁぼったりフォローしたりという情報も落ちてきますよ。
ソースはこんなんです。
Server側:
package com.yuroyoro.websocket import java.io.{InputStream, IOException} import java.net.{Authenticator, PasswordAuthentication, URL, HttpURLConnection} import javax.servlet.http._ import org.eclipse.jetty.websocket._ import org.eclipse.jetty.websocket.WebSocket.Outbound class ChirpUserStreamsServlet extends WebSocketServlet { override def doGet(req:HttpServletRequest, res:HttpServletResponse ) = getServletContext.getNamedDispatcher("default").forward(req, res) override def doWebSocketConnect(req:HttpServletRequest, protocol:String ) = new ChirpUserStreamsWebSocket class ChirpUserStreamsWebSocket extends WebSocket { private val chirpUserStreamingURL = "http://chirpstream.twitter.com/2b/user.json" // BASIC認証orz private def setBasicAuth( username:String, passwd:String ) = Authenticator.setDefault( new Authenticator { override def getPasswordAuthentication = new PasswordAuthentication(username, passwd.toCharArray) }) private def connectSteaming( url:String ) = { val urlConn = new URL( url ).openConnection match { case con:HttpURLConnection => { con.connect() con.getResponseCode match { case 200 => { } case c => throw new RuntimeException( "can't connect to %s : StatusCode = %s" format ( url, c)) } con } } urlConn.getInputStream } def consume(in:InputStream)( f:Option[String]=>Unit){ val buf = new Array[Byte](1024) var remains:String = "" try{ // InputStreamから1行読んでfにわたす for(i <- Stream.continually(in.read(buf)).takeWhile(_ != -1)){ val str = remains + new String(buf, 0, i) remains = ( "" /: str){ (s,c) => if( c == '\n'){ f( Some(s) ) "" } else s + c } } } catch{ case e:IOException => } finally{ in.close } } var outbound:Outbound = _ override def onConnect(outbound:Outbound ) = this.outbound = outbound override def onMessage(frame:Byte, data:Array[Byte], offset:Int, length:Int ) = {} override def onMessage(frame:Byte, data:String ) = { // usernameとpasswdが送られてくるので切り出す val m = Map( data split('&') map{ _.split('=') match { case Array(k,v) => (k,v)}} : _* ) m.get("username") foreach { u => m.get("passwd") foreach { p => streaming( frame, u, p ) }} } def streaming( frame:Byte, username:String, passwd:String ) = { setBasicAuth( username, passwd ) // BASIC認証設定する // ChirpUserStreamに接続 val con = connectSteaming( chirpUserStreamingURL ) // JSONをクライアントに送る consume( con ) { o => o match{ case Some( s ) => { println(s) println("----------------------- %s Bytes. -------" format( s.length ) ) outbound.sendMessage( frame , s ) } case None => }} } override def onDisconnect = {} } }
InputStreamから一行JSONを読んだら、consumeメソッドの第2引数の f:Option[String]=>Unit)な関数オブジェクトに渡してます。その関数オブジェクトでクライアントにJSONをプッシュ!してる感じですね。
ChirpUserStreamsは今のところBasic認証のみでOAuthはサポートしてないんだって。
まだちゃんとローンチされてないAPIだからかな。
クライアント:
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>WebSocket Twitter ChirpUserStreams </title> <link href="./style.css" media="screen, projection" rel="stylesheet" type="text/css"> </head> <body> <form id="account"> <span class="title">WebSocket Twitter ChirpUserStreams </span> username:<input name="username" type="text"/> password:<input name="passwd" type="password"/> <input id="start_button" type="submit" value="Start!"/> <input id="stop_button" type="button" value="Stop!"/> </form> <hr/> <section id="content"></section> <script src="http://www.google.com/jsapi"></script> <script>google.load("jquery", "1.4.1")</script> <script> var ws = new WebSocket("ws://localhost:8080/"); var lean = { i:0, next:function(){ var n = this.i * 320; this.i = (this.i + 1) % 5; return n; } } var createUserInfo = function ( json ){ var e = $("<span/>").addClass("user_info") .append( $("<span/>" ).addClass("profile_img") .append($("<img/>" ).attr("src" , json.profile_image_url ) )) .append( $("<a/>" ).addClass("user_name") .attr("href", "http://twitter.com/" + json.screen_name ) .append( json.screen_name + "(" + json.name + ")" )); return e; }; var pushEvent = function( e ) { $(e).hide() $('#content').prepend( e ); $(e).css({ position : "absolute", top : 50, left: 20 + lean.next() }); $(e).show("normal", function(){ $(e).animate({"top" : "+=1200px"},15000, function(){ $(e).hide();$(e).remove();}) } ); }; var start = function(){ if( !ws ){ ws = new WebSocket("ws://localhost:8080/"); } ws.onmessage = function(m) { var json = $.parseJSON( m.data ); if( json.text ){ var e = $("<div/>").addClass("event").addClass("posted") .append( createUserInfo ( json.user ) ) .append( $("<span/>" ).addClass("event_name").append( " : posted" ) ) .append( $("<hr/>" ) ) .append( $("<span/>" ).addClass("message") .append( json.text )); pushEvent( e ); } if( json.event ) { var t = $("<div/>" ).addClass("target") .append( createUserInfo ( json.target ) ); if( json.target_object ) { t.append("<br/>") .append( $("<span/>" ).addClass("message") .append( json.target_object.text )); } var e = $("<div/>").addClass("event").addClass( json.event ) .append( createUserInfo ( json.source) ) .append( $("<span/>" ).addClass("event_name").append( " : " + json.event ) ) .append( $("<hr/>" )) .append( t ); pushEvent( e ); } }; ws.send( $("form").serialize() ); }; var stop = function(){ if( ws ) { ws.close(); ws = null; } }; $('#start_button').click( function(){start();return false;}); $('#account').submit( function(){start();return false;}); $('#stop_button').click( function(){ stop();return false;} ); $(window).unload( stop ); </script> </body>
ws.onmessageに登録したfunctionで、サーバーからプッシュされたJSONをパースして、DOMを組み立ててjQueryでアニメーションして落としてます。
このクライアントの方が作るの大変だったorz
動作するプロジェクトはGitHubに置いてあります。
git clone して、以下のコマンドで起動しますよ(要maven)。
scala-websocket/ChirpUserStreams at master · yuroyoro/scala-websocket · GitHub
mvn clean package scala:run -DaddArgs="target/websocket.chirpuserstreams-1.0/|8080"
参考情報:
Jetty7のWebSocketをScalaから使う - ゆろよろ日記
Page not found | Twitter Developers
Twitterの新しいStreaming API「ChirpUserStreams」がすごすぎる件 - すぎゃーんメモ