gaeのchannel apiの練習に、ソーシャルブラウザブラウザを作ってみる
http://www.synaesthesia.jp/googleAppEngine/channelAPI.php
http://libro.tuyano.com/index3?id=921002
基本は、iframeをつかったブラウザ内ブラウザ
ただし、このアプリを同時に見ている人数などが分かる機能をchanel apiを使って実装
チャット機能もつけよう
eclipseに google app engineのSKDとgoogleプラグインを入れて、新規webアプリケーションプロジェクトを作る
iframeを使ったブラウザ内ブラウザ部分をつくる
index.html
iframe要素と、そこに表示するページのURLをいれるURL欄、入力されたURLのページを表示する表示ボタンの3つをつくる。
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> <html> <head> <meta http-equiv="content-type" content="text/html; charset=UTF-8"> <title>ブラウザ内ブラウザ</title> </head> <body> <h1>ブラウザ内ブラウザ</h1> <input type="text" placeholder="URL" id="urlInput" ></input> <input type="button" id="goToURLButton" value="go"></input> <br /> <iframe id="webFrame" src="" height=480 width=600> この部分は iframe 対応のブラウザで見てください。 </iframe> <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script> <script src="script/index.js"></script> </body> </html>
index.js
ボタンが押されたときにちゃんとページを表示するようにする。
jqueryを使う。
$(document).ready(function(){ $("#goToURLButton").click(function(){ $("#webFrame").attr("src",$("#urlInput").val()); }); });
これは動いた
サーバ側のChannelServiceのところを実装していく
次に、
VisitPageServlet.java
そのページに訪れたときサーブレット
http://libro.tuyano.com/index3?id=921002&page=2
ここを参考に
public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { req.setCharacterEncoding("utf-8"); resp.setContentType("text/plain;charset=utf-8"); ChannelService channelService = ChannelServiceFactory.getChannelService(); String channelKey = req.getParameter("channelKey"); String token = channelService.createChannel(channelKey); resp.getWriter().println(token); } public void doPost(HttpServletRequest req, HttpServletResponse resp){ ChannelService channelService = ChannelServiceFactory.getChannelService(); String channelKey = req.getParameter("channelKey"); String requestURI = req.getParameter("url"); if(urls.containsKey(requestURI)){ urls.put(requestURI, urls.get(requestURI) + 1); }else{ urls.put(requestURI, new Long(1)); } channelService.sendMessage(new ChannelMessage( channelKey, urls.get(requestURI).toString() + " user(s) visit" )); }
javascriptも変更
var constants = { key:"test", token:"", path:"visit" }; var socket = null; $(document).ready(function(){ //初期化 init(); //goボタンが押されたら $("#goToURLButton").click(function(){ var targetUrl = $("#urlInput").val(); $("#webFrame").attr("src", targetUrl); var xhr = new XMLHttpRequest(); var url = targetUrl; xhr.open('POST',constants.path + '?channelKey=' +constants.key + '&url=' + url, true); xhr.send(); }); }); //初期化 function init(){ //iframeにwebページを表示 $("#webFrame").attr("src",$("#urlInput").val()); //キーを送ってトークンを受け取る var xhr = new XMLHttpRequest(); xhr.open('GET', constants.path + '?channelKey=' + constants.key , true); xhr.onreadystatechange = function(){ if (xhr.readyState == 4 && xhr.status == 200){ var token = xhr.responseText; constants.token = token; openChannel(); } }; xhr.send(); } //ソケットを開く function openChannel(){ var channel = new goog.appengine.Channel( constants.token); socket = channel.open(); //ソケット関連コールバック設定 //ソケットを開いたら socket.onopen = function(){ } //メッセージを受け取ったら socket.onmessage = function(resp){ $("#result").append("<div>" + resp.data + "</div>"); } }
htmlには、以下を追加
<script src='/_ah/channel/jsapi'></script>
ここまでやって気づいたが、ブラウザを複数開いて同じページにアクセスしたら、サーブレットは違うインスタンスになってるらしい。
これでは、あるページの訪問数をカウントできない。
とりあえず、あるページへのアクセス人数を数えるために、datastoreにデータを入れることにする。
Data storeはリレーショナルデータベースじゃないらしいけど..
参考
http://appengine.keicode.com/gae/datastore.php
http://techbooster.jpn.org/andriod/9667/
http://xawa99.blogspot.jp/2012/08/GAE-DataStore.html
http://www.intepro.co.jp/gaej/datastore/creatinggettinganddeletingdata.html
http://learntogoogleit.com/post/65411773811/counting-entities-in-the-app-engine-datastore
こっちは低レベルAPI
http://tech.topgate.co.jp/gijutsu-shiryou/datastore-lowlevelapi#TOC--6
いろいろ方法があるようだが、JDOを使おう
JDOのリファレンスと、低レベルAPIのリファレンスが混ざってて、非常に調べにくい。
最終的な形
VisitPageServlet
@SuppressWarnings("serial") public class VisitPageServlet extends HttpServlet { private HashMap<String, Long> urls = new HashMap<String, Long>(); public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException { //チャンネル作る ChannelService channelService = ChannelServiceFactory.getChannelService(); String channelKey = req.getParameter("channelKey"); String token = channelService.createChannel(channelKey); //ビジターを作る //Key key = KeyFactory.createKey(Visitor.class.getSimpleName(),""); Visit visit = new Visit(); //visitor.setKey(key); PersistenceManager pm = PMF.get().getPersistenceManager(); try { // オブジェクトの格納 pm.makePersistent(visit); } finally { pm.close(); } //キーの取得 Key key = visit.getKey(); String visitKey = Long.valueOf(key.getId()).toString(); //返す値を作る HashMap<String, String> array = new HashMap<String,String>(); array.put("token", token); array.put("visitKey", visitKey); String text = JSON.encode(array); req.setCharacterEncoding("utf-8"); resp.setContentType("text/plain;charset=utf-8"); resp.getWriter().println(text); } public void doPost(HttpServletRequest req, HttpServletResponse resp){ ChannelService channelService = ChannelServiceFactory.getChannelService(); String channelKey = req.getParameter("channelKey"); String requestURI = req.getParameter("url"); String action = req.getParameter("action"); String visitKey = req.getParameter("visitKey"); //Key key = KeyFactory.createKey(Visit.class.getSimpleName(), visitKey); PersistenceManager pm = PMF.get().getPersistenceManager(); //DBからビジターを出す Visit visit = (Visit) pm.getObjectById(Visit.class,Long.valueOf(visitKey)); if (action.equals("visit")){ visit.setUrl(requestURI); //ビジターを数える javax.jdo.Query query = pm.newQuery(Visit.class); query.setFilter("url == '"+requestURI + "'"); query.setResult("count(this)"); Long countResult = (Long) query.execute(); //返す値を作る HashMap<String, String> array = new HashMap<String,String>(); array.put("message", countResult.toString() + " user(s) visit" + requestURI); array.put("url", requestURI); String message = JSON.encode(array); channelService.sendMessage(new ChannelMessage( channelKey, message )); } if ( action.equals("close")) { //ビジターを消す pm.deletePersistent(visit); //ビジターを数える javax.jdo.Query query = pm.newQuery(Visit.class); query.setFilter("url == '"+requestURI + "'"); query.setResult("count(this)"); Long countResult = (Long) query.execute(); //返す値を作る HashMap<String, String> array = new HashMap<String,String>(); array.put("message", "a user leave from " + requestURI + " " + countResult + " remain(s)."); array.put("url", requestURI); array.put("action", action); String message = JSON.encode(array); channelService.sendMessage(new ChannelMessage( channelKey, message )); } } }
訪問を表すデータのクラス
@PersistenceCapable public class Visit { @PrimaryKey @Persistent( valueStrategy = IdGeneratorStrategy.IDENTITY ) private Key key; @Persistent private String url; public Visit() { } public Key getKey() { return key; } public void setKey(Key key) { this.key = key; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } }
クライアントサイドのjavascript
var constants = { key:"test", token:"", path:"visit", visitKey:"" }; var targetUrl = ""; var socket = null; $(document).ready(function(){ //初期化 init(); //goボタンが押されたら $("#goToURLButton").click(function(){ targetUrl = $("#urlInput").val(); $("#webFrame").attr("src", targetUrl); var xhr = new XMLHttpRequest(); var url = targetUrl; var path = constants.path + '?channelKey=' +constants.key + '&url=' + url + '&action=' + "visit" + "&visitKey=" + constants.visitKey xhr.open('POST',path, true); xhr.send(); }); $("#closeSocketButton").click(function(){ closeSocket(); }); }); //初期化 function init(){ //iframeにwebページを表示 $("#webFrame").attr("src",$("#urlInput").val()); //キーを送ってトークンを受け取る var xhr = new XMLHttpRequest(); xhr.open('GET', constants.path + '?channelKey=' + constants.key , true); xhr.onreadystatechange = function(){ if (xhr.readyState == 4 && xhr.status == 200){ var obj = JSON.parse(xhr.responseText); constants.visitKey = obj.visitKey; //キーを受け取る constants.token = obj.token;; openChannel(); } }; xhr.send(); } //ソケットを開く function openChannel(){ var channel = new goog.appengine.Channel( constants.token); socket = channel.open(); //ソケット関連コールバック設定 //ソケットを開いたら socket.onopen = function(){ } //ソケットを閉じたら socket.onclose = function(){ } //メッセージを受け取ったら socket.onmessage = function(resp){ var obj = JSON.parse(resp.data); if (obj.url == targetUrl){ //同じURLを訪問中の人だけ $("#result").append("<div>" + obj.message + "</div>"); } } } function closeSocket(){ var xhr = new XMLHttpRequest(); var url = targetUrl; var path = constants.path + '?channelKey=' +constants.key + '&url=' + url + '&action=' + "close" + "&visitKey=" + constants.visitKey; xhr.open('POST',path, true); xhr.send(); //$("#result").append("<div>closed</div>"); //socket.close(); }
何とか、datastoreに見ているURLを入れて参照できるようになった。
地味に訪問データを消すボタンもつけた。
ホントはこれはブラウザを閉じたときにするべき処理。
今度は、違うページを見ている結果も表示されている。
つまり、チャンネルの表示側を絞ることができない。
チャンネル自体違うのを使えという話か。
キーを変えればいいんだ。
上では、強引にクライアントサイドで同じURLを見てる人の訪問情報以外をはじいてる。
でもこれじゃその分の通信が無駄になる。
これは今後の課題