What is

PyLadies Tokyo

Chat System?

@maaya8585   石田真彩

Flow


Web

今回のシステムフローを確認しよう

  • サインインからチャット文字の出力とサインアウトまで
  • 使われている技術(抜粋)

Tornado is 何?

  • Tornado とは
  • Tornadoでhello world!
  • 今回の利用パターン


Tornado
 

(おまけ)  WebSocket is 何?

  • WebSocketざっくり概要
  • 今回の利用パターン 

Attention!

これからお話しする内容は

なるべくわかりやすいように簡単に簡単に書いています。

デキるエンジニアさんから見ると

「そこ違うだろ!」みたいなお叱りを受けるかもしれません。

が、今回はそこはお口にチャックでお願いします。

Attention2!

  • この後、何回も同じ話をします。少しづつ言い方を変えて同じ話を何回もします。今回覚えて欲しいことは以下のとおり!
    • 普段私たちが使っているwebサイトは色々な技術が入り乱れて初めて実現されていること
      • 表示(html / css / javascript / Cookie ...)
      • バックエンド(Python / Java / C++ ...)
      • 通信(http / https / WebSocket ...)
    • ソースが読めなければ英語読みしてしまいましょう!

Girls Tech Fes!

https://github.com/PyLadiesTokyo/girls-tech-fes

ソースをダウンロードしよう!

出来る人はローカルへクローンしてみよう!

何言ってるのかわかんないよ!ってひとはzipダウンロードしてみよう!

[Flow] Screen 

Access!

Access!

Typing!

Typing!

Typing!

Signout!

[Flow] Technology 

Javascript

Websocket

Tornado

 

 

html

http

http

Cookie

[Flow] Technology(detail) 

Websocket

Tornado

html

http

http

入力フォームでデータを受け取る

データをget送信

  • Webサーバでデータ受信
  • 画面表示や遷移に必要な処理を実施

Tornado

 

 

html

Javascript

Websocketとhttpを通じて

チャット文字列の送受を行う。

表示はJavascriptがhtmlを作成して実現

Cookie

[Tornado]What is Tornado ?

Javascript

何かしら

処理

いっぱい

html

http

http

Cookie

[Tornado]What is Webxxx ?

WebServer

httpという通信システムを使って、

ブラウザに画像や文字を

世界中に届けられるようにするもの

[Tornado]HelloWorld!

pip install tornado 

今回の

ファイル構成

はこれ!

[Tornado]HelloWorld!

1. 入力ページを表示し

2. 入力文字列をキャッチして

3. 遷移先ページ情報をクレクレ

4. 前のページの情報を表示する!

[Tornado]HelloWorld!

class Application(tornado.web.Application):
    def __init__(self):
        handlers = [
            (r"/", MainHandler),          #--------①
            (r"/send/message", SendMessageHandler)
        ]

        tornado.web.Application.__init__(self, handlers)
                #--------②

①handlers : 画面に遷移する時のパス名の決定と処理の指定

              (r"[URL]", [Handlerの名前])

②Tornadoに「この流れ(台本)でよろしく!」とお知らせ

まずはどんなページを用意したのか、

遷移前にどんな処理をさせたいのかを設定する!

[Tornado]HelloWorld!

class MainHandler(tornado.web.RequestHandler):
    def get(self):   #----③
        self.render("templates/index.html")

③httpのgetリクエストを受け取り、

​templatesディレクトリ内の

index.htmlを

ユーザーに返却するという指示

最初の接続でやりたいことを設定する!

[Tornado]HelloWorld!

class SendMessageHandler(tornado.web.RequestHandler):
    def post(self):
        send_message = self.get_argument('text_message')     
                       #-----④
        self.render(
            "templates/result.html",
            message=send_message
        )

④httpのpostリクエストを受け取り、

​前のページで打った文字列を

result.htmlと共にmessageという名前で

ユーザーに返却するという指示

決定ボタンを押した後にやりたいことを設定する!

[Tornado]HelloWorld!

def main():
    app = Application()
    app.listen(8888)

    print("接続可能だよ!")
    tornado.ioloop.IOLoop.instance().start()     
                                                           #------⑤

⑤httpの接続を受け入れる

回線番号(ポート番号)を指定し、

TornadoのWebサーバを起動します。

今まで作った全部の処理を起動するスイッチを作ります

[Tornado]HelloWorld!

<form action="/send/message" method="post">
            {% module xsrf_form_html() %}    #--------★
            <p>文字列を次のページへ文字列を送付する簡単なページ遷移デモをします</p>
            <input type="text" name="text_message"/>
            <input type="submit" value="go!"/>
</form>

★handlersに指定した処理の中の

どの処理を実行させたいのかを

form actionに記述。httpの送信方法は

form methodへ。

module xsrf_form_html()は

Tornadoでデータを受け取る呪文

表示画面を作ります(index.html抜粋)

[Tornado]HelloWorld!

    <div id="output">
        <p>入力した文字列は</p>
        <p>{{ message }}</p>
        <p>です!</p>
    </div>

    {% from datetime import datetime %}
    <div id="date">
        {{ datetime.now() }}
    </div>

{{変数名もしくは関数名}}

と記述することで、Tornadoから送付されたデータやPython関数を表示利用することができます

表示画面を作ります(result.html抜粋)

Demo?

[Tornado]PyLadies Chat

では、

Girls Tech Fes で使った

PyLadies Tokyo Chat のソースを見てみましょう

 

[Tornado]PyLadies Chat

def __init__(self):
        # URLの指定
        handlers = [
            (ur"/", MainHandler),  #--- Sign In ページ表示
            (ur"/name/inputname", InputNameHandler),  #--- 表示名確定処理
            (ur"/name/signup", SignUpHandler),  #--- チャットページ表示
            (ur"/name/signout", SignOutHandler),  #--- サインアウト処理
            (ur"/chatsocket", ChatSocketHandler),  #--- チャット文字列授受ができるようにする処理
        ]
        settings = dict(
            cookie_secret=u"__pyladies_girls_tech_fes_demo_cookie__",  #--- Cookie名
            template_path=os.path.join(os.path.dirname(__file__), u"templates"),
            static_path=os.path.join(os.path.dirname(__file__), u"static"),

            login_url=u"/name/inputname",  #--- Cookieにデータが存在しなかった場合の処理
            autoescape=u"xhtml_escape",
            xsrf_cookies=True,
            debug=options.debug,
        )
        tornado.web.Application.__init__(self, handlers, **settings)

[Tornado]PyLadies Chat

class MainHandler(BaseHandler):
    # Sign in ページ遷移
    @tornado.web.authenticated
    def get(self):
        self.render(u"index.html", messages=ChatSocketHandler.cache)

class BaseHandler(tornado.web.RequestHandler):
    # ページ遷移処理共通。
    def get_current_user(self):
        username = self.get_secure_cookie(u"chatdemo_user")

        # cookieが値を返すとパスできる
        # 何もないとlogin_urlへリダイレクトされる 
        #           →login_urlの指定がなければloginなしのシステムとなる
        if not username:
            return None
        return tornado.escape.utf8(username)

[Tornado]PyLadies Chat

class MainHandler(BaseHandler):
    # Sign in ページ遷移
    @tornado.web.authenticated
    def get(self):
        self.render(u"index.html", messages=ChatSocketHandler.cache)

class BaseHandler(tornado.web.RequestHandler):
    # ページ遷移処理共通。
    def get_current_user(self):
        username = self.get_secure_cookie(u"chatdemo_user")

        # cookieが値を返すとパスできる
        # 何もないとlogin_urlへリダイレクトされる 
        #           →login_urlの指定がなければloginなしのシステムとなる
        if not username:
            return None
        return tornado.escape.utf8(username)

[Tornado]PyLadies Chat

# ログインの代わりに名前の入力してもらう
class InputNameHandler(BaseHandler):
    def get(self):
        self.render(u"inputname.html")

# Cookie削除処理
class SignOutHandler(BaseHandler):
    def get(self):
        self.clear_cookie(u"chatdemo_user")
        self.write(u"登録していたユーザ名を消去しました。
                                          もう一度はじめからやり直しをお願いします。")

[Tornado]PyLadies Chat

# 入力した名前をCookieにセット。チャットページで使えるようにする。
class SignUpHandler(BaseHandler):
    def get(self):
        self.render(u"login.html")

    # <form action="/name/setname" method="post"> の記述により
    # inputname.htmlから最初に呼ばれるdefはこちら。
    def post(self):
        logging.debug(u"xsrf_cookie:" + self.get_argument(u"_xsrf", None))

        self.check_xsrf_cookie()       #--- Cookieデータの確認
        username = self.get_argument(u"username")
        logging.debug(u'サインアップメソッドで {0} の名前を受け取りました'.format(username))

        if username:
            self.set_secure_cookie(u"chatdemo_user", tornado.escape.utf8(username))
            self.redirect(u"/")
        else:
            self.set_status = 403
            self.set_header('Content-Type', 'text/html; charset="utf-8"')
            self.finish(
                '名前が入力されていません。最初からやり直して下さい。')

[Tornado]PyLadies Chat

最後はChatSocketHandler

[Tornado]PyLadies Chat

その前に!

WebSocketについて

すごくざっくり知識を!

[Tornado]What is Webxxx ?

  • Webサーバとユーザの間で一度接続が確立できたら、(明示的に切断しない限り)データのやり取りを実施し続けることができる

WebSocket

 

  • WebSocketで接続が確立しているサーバとすべてのユーザは同じデータを共有し、リアルタイムで送受信できる

[Tornado]What is Webxxx ?

Web

サーバ

での

処理

http

http

[Tornado]What is Webxxx ?

一般のデータ送受

都度通信通路

の取り直し!

Web

サーバ

での

処理

http

http

[Tornado]What is Webxxx ?

WebSocket

[Tornado]PyLadies Chat

#WebSocketを使ってチャット通信を行う
class ChatSocketHandler(tornado.websocket.WebSocketHandler):
    waiters = set()
    cache = []
    cache_size = 200

    def open(self):    #---接続オープン
        ChatSocketHandler.waiters.add(self)

    def on_close(self):    #----接続クローズ
        ChatSocketHandler.waiters.remove(self)

[Tornado]PyLadies Chat

    @classmethod
    def update_cache(cls, chat):      
        #---新しい文字列を今まで溜めていたデータにマージする
        cls.cache.append(chat)
        if len(cls.cache) > cls.cache_size:    
            #---保存上限値内に情報量が収まっているかチェック
            cls.cache = cls.cache[-cls.cache_size:]

    @classmethod
    def send_updates(cls, chat):     
        #---新しい文字列をWebSocket接続されているユーザ全員に送付!
        logging.info(u"sending message to %d waiters", len(cls.waiters))
        print chat
        for waiter in cls.waiters:
            try:
                waiter.write_message(chat)
            except:
                logging.error(u"Error sending message", exc_info=True)    
               #--- 異常があった場合の処理

ChatSocketHandler つづき

[Tornado]PyLadies Chat

    # 表示メッセージの整形
    def on_message(self, message):
        logging.info(u"got message %r", message)
        parsed = tornado.escape.json_decode(message)    #----文字コードの整形
       #----ユーザ名をCookieから取得
        username = self.get_secure_cookie(u"chatdemo_user")    

        # htmlに表示させたい文字列をそれぞれ用意する
        chat = {
            u"id": unicode(uuid.uuid4()),
            u"from": unicode(username, encoding=u'utf-8'),  
            u"body": parsed[u"body"],
            }
        # htmlセット
        chat[u"html"] = tornado.escape.to_basestring(
            self.render_string(u"message.html", message=chat))

        # 新しい文字列が追加されたらマージして
        # WebSocket接続している全ユーザへ送付
        ChatSocketHandler.update_cache(chat)
        ChatSocketHandler.send_updates(chat)

ChatSocketHandler つづき

M a T o M e !!!

  • Tornado はWebサーバ機能を含むPythonでできたフレームワークである
  • Handlerと呼ばれる処理を利用して、画面遷移処理や内部の細かい動きの制御を実施する
  • PyLadies Tokyo Chatツールは大きく分けて以下技術で構成されている
    • Tornado
    • WebSocket
    • http通信
    • htmlなど表示系言語

Fin.

@maaya8585   

20150406_PyLadiesChatSystem

By maaya ishida

20150406_PyLadiesChatSystem

What's that PyLadies Tokyo Chat System?

  • 2,649