本日、2010年12月にリリース1.4で登場したChannel APIに関するGoogle App Engineの新しい記事をご紹介します。 この瞬間から、 ポーリングを使用せずに、サーバーからクライアントにメッセージを直接送信し、メッセージを送信することが可能になりました。
そのため、Google App Engineでチャットを実装するのは非常に簡単になりました。 実装プロセスは、カットの下で説明されています。
http://chat-channelapi.appspot.com/で デモを見ることができます 。
プロジェクトコードはここからダウンロードできます (元の記事のコードはこちらです )。
このアプリケーションでは、ユーザーとプログラムの間にチャネルを作成するために、次の図に示す手順を実行する必要があります。
手順:
1)アプリケーションはチャネルID(チャネルID)とトークンを作成し、クライアントに送信します
2)クライアントはトークンを使用して、チャネルをリッスンするソケットを開きます
3)クライアント2は、一意のチャネルIDとともにチャットメッセージをアプリケーションに送信します
4)アプリケーションは、ソケットを介してチャネルをリッスンするすべてのクライアントにメッセージを送信します。 このために、各クライアントのチャネルIDが使用されます。
すべての手順を説明する前に、プログラムに参加するデータベースのエンティティを可能な限り簡略化したことに注意してください。 ユーザーモデルとメッセージの2つを作成しました。
class OnlineUser (db . Model):
nick = db . StringProperty(default = "" )
channel_id = db . StringProperty(default = "" )
class Message (db . Model):
text = db . StringProperty(default = "" )
user = db . ReferenceProperty(User)
このコードは、 GAEユーティリティライブラリに実装されているセッションメカニズムも使用します。 しかし、セッションにあまり注意を払ってはいけません。
ステップ1
このステップでは、チャットアプリケーションがチャネルIDとトークンを作成し、クライアントに送信します。 このステップのコードは非常に簡単です。 Channel APIをインポートすることを忘れないでください:
from google.appengine.api import channel
その後、各ユーザーに一意のIDを生成するハンドラーを作成します(uuidモジュールのuuid4()関数を使用します)。 次のハンドラーはそれを行い、データをテンプレートにクライアントに渡します。
class ChatHandler (webapp . RequestHandler):
def get ( self ):
self . redirect( '/' )
def post ( self ):
# http://gaeutilities.appspot.com/
self . session = Session()
#
nick = self . request . get( 'nick' )
if not nick:
self . redirect( '/' )
# ,
user = OnlineUser . all() . filter( 'nick =' , nick) . get()
if user:
self . session[ 'error' ] = 'That nickname is taken'
self . redirect( '/' )
return
else :
self . session[ 'error' ] = ''
# id Channel API
channel_id = str (uuid . uuid4())
chat_token = channel . create_channel(channel_id)
#
user = OnlineUser(nick = nick,channel_id = channel_id)
user . put()
# 100
messages = Message . all() . order( 'date' ) . fetch( 1000 )
#
template_vars = { 'nick' :nick, 'messages' :messages, 'channel_id' :channel_id, 'chat_token' :chat_token}
temp = os . path . join(os . path . dirname(__file__), 'templates/chat.html' )
outstr = template . render(temp, template_vars)
self . response . out . write(outstr)
この記事を過負荷にしないために、ここではテンプレートコードを提供しません。 githubで見ることができます。
ステップ2
これで、クライアントはトークンを抽出してソケットを開く必要があります。 jQueryを使用してjavascriptコードを削減します。 以下にコードを示します。
var chat_token = $( '#channel_api_params' ).attr( 'chat_token' );
var channel = new goog.appengine.Channel(chat_token);
var socket = channel.open();
socket.onopen = function (){
};
socket.onmessage = function (m){
var data = $.parseJSON(m.data);
$( '#center' ).append(data[ 'html' ]);
$( '#center' ).animate({scrollTop : $( "#center" ).attr( "scrollHeight" )}, 500 );
};
socket.onerror = function (err){
alert( "Error => " + err.description);
};
socket.onclose = function (){
alert( "channel closed" );
};
ステップ3
このステップで、クライアント2はインターフェースを介してチャットにメッセージを送信します。 これを行うには、テキストボックスとメッセージを送信するボタンを作成します。 メッセージは、jQueryを使用して実装する単純なリスナーを使用してJavaScriptコードで送信されます。 代わりにjavsctiptライブラリを使用することも、XMLHttpRequestオブジェクトを使用することもできます。 アプリケーションでクライアントを正しく識別するには、一意のクライアントチャネルIDを送信する必要があることに注意してください。
$( '#send' ).click( function (){
var text = $( '#text' ).val();
var nick = $( '#nick' ).attr( 'value' );
var channel_id = $( '#channel_api_params' ).attr( 'channel_id' );
$.ajax({
url : '/newMessage/' ,
type : 'POST' ,
data : {
text : text,
nick : nick,
channel_id : channel_id,
},
success : function (data){
},
complete : function (){
}
});
});
ステップ4
クライアントからメッセージを受信するには、すべてのクライアントにメッセージを送信する新しいハンドラーを実装する必要があります。
class NewMessageHandler (webapp . RequestHandler):
def post ( self ):
#
text = self . request . get( 'text' )
channel_id = self . request . get( 'channel_id' )
q = db . GqlQuery( "SELECT * FROM OnlineUser WHERE channel_id = :1" , channel_id)
nick = q . fetch( 1 )[ 0 ] . nick
date = datetime . datetime . now()
#
message = Message(user = nick,text = strip_tags(text), date = date, date_string = date . strftime( "%H:%M:%S" ))
message . put()
#
messages = [message]
template_vars = { 'messages' :messages}
temp = os . path . join(os . path . dirname(__file__), 'templates/messages.html' )
outstr = template . render(temp, template_vars)
channel_msg = json . dumps({ 'success' : True , "html" :outstr})
#
users = OnlineUser . all() . fetch( 100 )
for user in users:
channel . send_message(user . channel_id, channel_msg)
オプションの手順
この段階で、元の記事は終了しましたが、次のタスクに関連するコードにいくつか変更を加えたかったのです。 元の記事では、チャットに入るとユーザー名がブロックされ、このニックネームを使用してログインすることはできません。 この制限を削除します。 これを行うには、キーの有効期限が切れた後、データベースからユーザーデータを削除します。 キーは、2時間が経過するか、クライアントがソケットでclose()関数を呼び出すまで有効期限が切れます。 次に、/ _ah / channel / disconnect /に登録されたハンドラーが呼び出されます。 このようなハンドラーを作成します。
class ChannelDisconnectHandler (webapp . RequestHandler):
def post ( self ):
channel_id = self . request . get( 'from' )
q = OnlineUser . all() . filter( 'channel_id =' , channel_id)
users = q . fetch( 1000 )
db . delete(users)
JavaScriptコードで、ユーザーがこのページを離れたときに発生するイベントの処理を追加します。
$( window ).unload( function (){
socket.close();
});
次の状況を処理するために残ります。 ユーザーがチャットに入ったが、すぐにウィンドウを閉じた場合、チャンネルは開きません。 これは、ユーザーレコードがデータベースにあるという状況につながりますが、チャネルが閉じていないという事実のために削除されません。 ユーザーデータモデルを変更します。
class OnlineUser (db . Model):
nick = db . StringProperty(default = "" )
channel_id = db . StringProperty(default = "" )
creation_date = db . DateTimeProperty(auto_now_add = True )
opened_socket = db . BooleanProperty(default = False )
これで、レコード(creation_date)を作成する時間と、チャネルのオープン(opened_socket)についてクライアントから確認が来たかどうかに関する情報が得られました。 サーバー側でchannel.open()を呼び出してクライアント側でチャネルを開くと、/ _ah / channel / connected /に登録されているハンドラーが呼び出されます。 このハンドラーは、チャネルのオープンを確認するためにユーザーを公開します。
class ChannelConnectHandler (webapp . RequestHandler):
def post ( self ):
channel_id = self . request . get( 'from' )
q = OnlineUser . all() . filter( 'channel_id =' , channel_id)
user = q . fetch( 1 )[ 0 ]
user . opened_socket = True
user . put()
このコードは、識別のためにチャネルIDをサーバーに送信します。 ハンドラーは次のとおりです。
class RegisterOpenSocketHandler (webapp . RequestHandler):
def post ( self ):
channel_id = self . request . get( 'channel_id' )
q = OnlineUser . all() . filter( 'channel_id =' , channel_id)
user = q . fetch( 1 )[ 0 ]
user . opened_socket = True
user . put()
最後の手順は、cronハンドラーの使用を開始することです。これにより、OnlineUserユーザーモデルから、チャネルのオープンを確認するすべてのエントリが選択され、現在のチャネルからの作成時間が120秒以上になります。
class ClearDBHandler (webapp . RequestHandler):
def get ( self ):
q = OnlineUser . all() . filter( 'opened_socket =' , False )
users = q . fetch( 1000 )
for user in users:
if ((datetime . datetime . now() - user . creation_date) . seconds > 120 ):
db . delete(user)
最後に
簡単なチャットアプリケーションを構築することができました。 オリジナルの記事の4つのステップでChannel APIを表示できます。コードを追加すると、アプリケーションがより完全に見えるようになります。
PSアプリケーションの実際の作業では、htmlタグを除外してメッセージをフィルタリングする必要があることが示されました。 これを行うには、djangoフレームワークからstrip_tags関数をインポートします。
from django.utils.html import strip_tags
新しいメッセージハンドラ(NewMessageHandler)で、新しいメッセージを作成するためのコードを次のように置き換えます。
message = Message(user = nick,text = strip_tags(text), date = date, date_string = date . strftime( "%H:%M:%S" ))