
私は最近ウェブサイト
backgrounddating.comを立ち上げ、Habrahabrでそれについて
書いた 。 もちろん、このプロジェクトの実装の技術的な詳細についてはすでに話しましたが、インターネット上のこのトピックに関するドキュメント(ロシア語と英語の両方)がまだ非常に少ないので、サイトの機能の1つについて個別に書きたいと思います。 そこで、2人のユーザー間のリアルタイムチャットについて説明します。 タスクは、すべてのユーザーが他のユーザーにメッセージを送信できることであり、メッセージの受信者がこれらのユーザーとチャットしている場合、彼はすぐに着信メッセージを見ました(そうでなければ、彼は後でメッセージを読むことができます:つまり、チャットが開かれると、それはロードされます最近の投稿の履歴)。
ユーザーが一緒に通信するだけでなく、任意の数のグループで通信できるようにする場合、これはほぼ基本的に実行できます。実際、説明した実装は、このような機能拡張のために設計されています。
これがこれを実装する唯一の方法ではないことをすぐに明確にします。 別の非同期Webサーバー(たとえば、node.js)を使用できます。別のメッセージキューを使用できます(または、このオプションの機能が適している場合はまったく
使用し
ないでください。Webサーバーの同じワーカーが常に同じチャネルのユーザーと通信します)。 私は、このオプションが最良であると主張することさえしません(しかし、この場合、それは最もうまくいきました)。 最終的に、Webソケットをサポートしない古いブラウザー(およびこれらはほとんどすべてのIEバージョンなど)の松葉杖(ロングポーリング、Flash)はまったく考慮しません。また、既にあるブラウザーからの接続も検討しません。 WebSocketプロトコルをサポートしますが、標準バージョン(
RFC 6455 )ではなく、廃止されたバージョンの1つをサポートします。 ドラフト76(別名hixie-76)の古いバージョンのサポートを有効にする方法については、Tornadoの
ドキュメントを参照してください。
それにもかかわらず、私たちは確かに言うことができます-この方法はうまく機能し、1つのプロジェクトではなく、多くのプロジェクトで説明されています(説明された実装方法は長い間使用されていますが、まだ多くの情報はありません)。 たとえば、Background Datingを実行しているサーバーは、現在Linodeの最も若いVPS(512 MiBのメモリー)ですが、プロセッサーの負荷は20〜40%を超えず、RAM使用率は約30%でした。 さらに、リソースは主にgunicorn(Djangoを実行するWebサーバー)とPostgreSQLによって使用されます。 しかし、Tornadoが高速で動作するだけでなく、
C10k (すでにHabrahabrに搭載されていた)に対処するのに十分なほど軽量であることは秘密ではないため、驚くべきことはまったくありません。
したがって、
Django 1.4.2、
Tornado 2.4、
Redis 2.6.5、および
PostgreSQL 9.2.1を使用します(実際、別のリレーショナルDBMSを使用できます-Djangoには多くの異なるバックエンドがあります)。 Redisに接続するには、標準のPython用
redis-pyクライアントと
brükva (Tornadoで使用する非同期Redisクライアント)が使用されます。 これらすべてをサーバーにデプロイするには、
haproxyと
nginxを使用し、
gunicorn Webサーバーを使用して
実稼働環境で Djangoプロジェクトを起動し、
SupervisorがDjangoとTornadoのWebサーバーの起動を制御します。
理論的な知識が不足していると感じた場合(たとえば、非同期のノンブロッキングWebサーバーとは何か、メッセージキューなどがよくわからない場合)、最初に関連するドキュメントを読み、質問がある場合は、コメント(または私に
メール )。 Ivan Sagalaevの優れたPython
フォーラムのトピック(1、2、3、4、5、6、7)に関するトピックと、「同期フレームワークと非同期フレームワークのリンク(例としてdjangoを使用)」の
スライドにも注意を払うことをお勧めします別のPythonの第一人者であるMikhail Korobovが
DevConf 2011で読みました。
問題のソリューションのソースは、この記事
とGitHubの両方に
あります 。
Djangoのセットアップ
Djangoプロジェクトを作成し、表示されるディレクトリに移動します。
django-admin.py startproject myproject
cd myproject /
次に、myproject / settings.pyファイルを編集します。
まず、Redisバックエンドを使用してユーザーセッションに関する情報を保存するようにすぐに切り替えることをお勧めします。 これはすべてのプロジェクトで行う価値があります(何らかの理由でRedisを使用できない場合や、多くのユーザーが計画していない場合を除きます)。 これを行うには、
django-redis-sessionsをインストールし(pip install django-redis-sessions)、設定を追加するだけです:
SESSION_ENGINE = 'redis_sessions.session'
おめでとうございます。サイトにクエリを送信するときにデータベースへのクエリの数を1つ減らしただけです(Redisはほぼ即座にクエリに応答します)。 :)
ところで、このような設定は、gitignoreに追加する別のファイル(通常はlocal_settings.py)に保存するのが最適です。
そこで、たとえば(SECRET_KEY)などのプロジェクトの秘密キーとデータベース設定を転送できます。 ここでのポイントは、まず、特定のサーバーに固有の設定(ローカルデータベース、キャッシュバックエンド、セッションバックエンド、デバッグモード設定)をlocal_settings.pyに保存し、次にGit(または別のバージョン管理システム)に保存できることですキーとパスワードに関する情報は保存されません。
したがって、このような設定をlocal_settings.pyに追加するには、settings.pyの最後に次を追加するだけです。
try: from local_settings import * except ImportError: pass
次に、データベース(DATABASES)を設定し、テンプレートディレクトリ、静的ファイルのディレクトリ、APIキーを指定することを忘れないでください(Tornadoは非同期でリクエストをDjangoに送信し、Djangoはこのメッセージをデータベースに保存します)。リクエストを送信する必要があります。
そのため、サイトを別のサーバーにデプロイするとき(またはプロジェクトをFSの別の場所に配置するとき)、テンプレートおよび静的ファイルのディレクトリへのパスを変更する必要はありません。上記の設定に以下を追加して、起動時にプロジェクトの場所を決定するのが最善です:
import os PROJECT_ROOT = os.path.abspath(os.path.dirname(__file__))
そして、それに応じて、PROJECT_ROOTに基づいて他のパスを取得します。
静的ファイル:
STATICFILES_DIRS = ( os.path.join(PROJECT_ROOT, "static"), )
テンプレート:
TEMPLATE_DIRS = ( os.path.join(PROJECT_ROOT, "templates"), )
APIキーとアドレス:
API_KEY = '$0m3-U/\/1qu3-K3Y' SEND_MESSAGE_API_URL = 'http://127.0.0.1:8000/messages/send_message_api'
キーを生成するには、コンソールで次の簡単な行を使用できます(ところで、絵文字はそこからあなたを見ます)。
</ dev / urandom tr -dc _A-Zaz-0-9 | head -c $ {1:-32}; echo;
これで、myprojectディレクトリ(settings.pyとlocal_settings.pyが配置されている場所)に静的ディレクトリとテンプレートを作成し、チャットアプリケーションの作成を開始します。
チャット(ジャンゴ)
新しいアプリケーションを追加します。
python manage.py startapp privatemessages
モデルを記述します(privatemessages / models.py):
from django.db import models from django.db.models.signals import post_save from django.contrib.auth.models import User
複雑なことは何もありません-メッセージとスレッド(スレッド)があります。 2人のユーザーごとにスレッドが作成され、すべてのメッセージはこのスレッドに関連付けられます。 新しいメッセージが作成されると、対応するスレッドの最後のメッセージの日付と時刻が更新されます。
ここで、アプリケーションをsettings.pyのINSTALLED_APPSに追加し、モデルをデータベースと同期します。
python manage.py syncdb
privatemessages / views.pyを開いて4つのビューを書きましょう。
- send_message_view -Djangoを介してメッセージを送信します(たとえば、これが2人の間の最初のメッセージである場合)
- send_message_api_view -Tornadoを介してメッセージを送信します
- messages_view-対話者のリストを表示します(最後のメッセージの日時の降順でソートされます)
- chat_view-チャット用(Djangoはデータベースに既にあるメッセージを含むページを返すだけで、ブラウザはWSを介してTornadoサーバーに接続し、ページをリロードせずにHTTPを追加せずにリアルタイムで新しいメッセージを送受信しますリクエスト)
そして、これがprivatemessages / utils.pyです:
import json import redis from django.utils import dateformat from privatemessages.models import Message def json_response(obj): """ This function takes a Python object (a dictionary or a list) as an argument and returns an HttpResponse object containing the data from the object exported into the JSON format. """ return HttpResponse(json.dumps(obj), content_type="application/json") def send_message(thread_id, sender_id, message_text, sender_name=None): """ This function takes Thread object id (first argument), sender id (second argument), message text (third argument) and can also take sender's name. It creates a new Message object and increases the values stored in Redis that represent the total number of messages for the thread and the number of this thread's messages sent from this specific user. If a sender's name is passed, it also publishes the message in the thread's channel in Redis (otherwise it is assumed that the message was already published in the channel). """ message = Message() message.text = message_text message.thread_id = thread_id message.sender_id = sender_id message.save() thread_id = str(thread_id) sender_id = str(sender_id) r = redis.StrictRedis() if sender_name: r.publish("".join(["thread_", thread_id, "_messages"]), json.dumps({ "timestamp": dateformat.format(message.datetime, 'U'), "sender": sender_name, "text": message_text, })) for key in ("total_messages", "".join(["from_", sender_id])): r.hincrby( "".join(["thread_", thread_id, "_messages"]), key, 1 )
タイムゾーンの操作に関するDjango
ドキュメントの tz.activate()について読むことができます。 ブラウザはタイムゾーンに関する情報を提供しないため(言語に関する情報など)、クライアント側でタイムゾーンと呼ばれるCookieを作成する必要があります-これにより、Djangoはタイムゾーンの各メッセージの日付と時刻を表示できるようになります瞬間はユーザーです。
クライアント側でタイムゾーンを計算するには、
jstzライブラリを使用します(myproject / staticディレクトリに
jstz.min.jsを置くことを忘れないでください)。 ユーザーのタイムゾーンを決定できる他のソリューションがあります:たとえば、GeoIPデータベースに基づいて最も可能性の高いタイムゾーンを取得したり、ユーザーのコンピューターの現地時間をサーバーの現地時間と比較したりすることができます(サーバーで時刻が正しく設定されていることがわかっているため)-そのようなソリューションシステムで完全に異なるタイムゾーンが選択されているが、タイムゾーンに対応するように手動で時間が転送された場合、ユーザーの実際のタイムゾーンを見つけることができます。
しかし、この場合、ユーザーが間違ったタイムゾーンセットを持っている場合、おそらく時間の不正確さが彼に合っていると仮定しましょう(そうでなければ、設定を変更する追加のリマインダーになります)。
また、
pytzをインストールする必要があることに注意してください(pip install pytz)-これは、Olsonデータベース(
IANAタイムゾーンデータベース )の形式の文字列からタイムゾーンを取得するために必要です。
send_messages関数がデータベースに新しいメッセージを追加すると、Redisのこのスレッドのメッセージ数でハッシュも更新されます。 このハッシュでは、2つのキーが増分されます。1つはスレッド内のメッセージの総数を表し、もう1つはこのユーザーからのメッセージの数を表します。 これらのキーは、メッセージの合計数、および受信メッセージと送信メッセージの数を表示するときに使用されます(受信数は、単に合計数から送信数を引いたものです)。
ユーザーがチャットを開き、ブラウザーがTornadoサーバーとのWS接続を開くと、TornadoサーバーはRedisのスレッドチャンネルにサブスクライブし、新しいメッセージが表示されるとすぐにユーザーに送信します。
RedisのPub / Sub実装(SUBSCRIBE、UNSUBSCRIBE、およびPUBLISHコマンド)に慣れていない場合は、Redisの
ドキュメントで読むことができ
ます 。
send_messages関数がsend_message_viewを呼び出し、特に送信者の名前を渡すと、関数はRedisのこのスレッドのチャネルでメッセージを公開します。 send_message_api_viewの場合、送信者の名前は送信されず、send_message_api_viewを呼び出す前であっても、メッセージはこのスレッドのチャネルに既に送信されていると想定されます、チャンネルでの公開後)。
ここでprivatemessages / urls.pyファイルを作成し、Djangoメソッドのurlpatternsを設定します。
from django.conf.urls import patterns, url urlpatterns = patterns('privatemessages.views', url(r'^send_message/$', 'send_message_view'), url(r'^send_message_api/(?P<thread_id>\d+)/$', 'send_message_api_view'), url(r'^chat/(?P<thread_id>\d+)/$', 'chat_view'), url(r'^$', 'messages_view'), )
そしてもちろん、
ルートURLconf (myproject / urls.py)にそれらを含めます:
from django.conf.urls import patterns, include, url
テンプレートディレクトリに、chat.htmlとprivate_messages.htmlを配置する必要があります。 ベーステンプレートbase.htmlも追加します。
base.html
<!DOCTYPE html> <html> <head> <link rel="stylesheet" type="text/css" href="/static/privatemessages.css"> <script type="text/javascript" src="http://yandex.st/jquery/1.8.3/jquery.min.js"></script> <script type="text/javascript" src="/static/jstz.min.js"></script> <script type="text/javascript" src="/static/privatemessages.js"></script> {% block head %}{% endblock %} <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>{% block title %} {% endblock title %}</title> </head> <body> {% block content %}{% endblock content %} </body> </html>
chat.html
{% extends "base.html" %} {% block title %}{{ partner.username }}{% endblock %} {% block head %} <script type="text/javascript"> $(document).ready(function() { activate_chat({{ thread_id }}, "{{ user.username }}", { "total": {{ messages_total }}, "sent": {{ messages_sent }}, "received": {{ messages_received }} }); }); </script> {% endblock %} {% block content %} {% load pluralize %} <div class="chat"> <div class="partner"> <p class="name">{{ partner.username }}</p> <p class="messages"><span class="total">{{ messages_total }}</span> {{ messages_total|rupluralize:",," }} (<span class="received">{{ messages_received }}</span> , <span class="sent">{{ messages_sent }}</span> )</p> </div> <div class="conversation"> {% for message in thread_messages reversed %} <div class="message"> {% if message.sender == user %}<p class="author we"><span class="datetime">{{ message.datetime|date:"dmY H:i:s" }}</span> {{ user.username }}:</p>{% else %}<p class="author partner"><span class="datetime">{{ message.datetime|date:"dmY H:i:s" }}</span> {{ partner.username }}:</p>{% endif %} <p class="message">{{ message.text|linebreaksbr }}</p> </div> {% endfor %} </div> <form class="message_form"> <div class="compose"> <textarea rows="1" cols="30" id="message_textarea"></textarea> </div> <div class="send"> <button class="btn" type="button"></button> <p> Ctrl + Enter.</p> </div> </form> </div> {% endblock content %}
private_messages.html
{% extends "base.html" %} {% block content %} {% load pluralize %} <div class="private_messages"> <h1></h1> <div class="partners"> {% for thread in threads %} <p><a href="{% url privatemessages.views.chat_view thread.id %}">{{ thread.partner.username }} ({{ thread.total_messages|default_if_none:"0" }} {{ thread.total_messages|rupluralize:",," }})</a></p> {% empty %} <p> .</p> {% endfor %} </div> <h1> </h1> <form action="{% url privatemessages.views.send_message_view %}" method="post" class="new_message"> {% csrf_token %} <p class="name"><input name="recipient_name" placeholder=" "></p> <p><textarea name="message" placeholder=""></textarea></p> <p><input type="submit" value=""></p> </form> </div> {% endblock content %}
__init__.pyファイルとpluralize.pyファイルを使用してprivatemessages / templatetagsディレクトリを作成します。
privatemessages / templatetags / pluralize.py(
フィルター作成者-V @ s3K):
from django import template register = template.Library() @register.filter def rupluralize(value, arg): args = arg.split(",") try: number = abs(int(value)) except TypeError: number = 0 a = number % 10 b = number % 100 if (a == 1) and (b != 11): return args[0] elif (a >= 2) and (a <= 4) and ((b < 10) or (b >= 20)): return args[1] else: return args[2]
スタイルを追加します(myproject / static / privatemessages.css):
html, body { height: 100%; margin: 0; } body { font-family: Geneva, Arial, Helvetica, sans-serif; font-size: 14px; color: #000; background: #fff; } textarea, form p.name input { border: 1px #d4d4d4 solid; } form.new_message p { margin: 4px 0; } form.new_message p.name input, form.new_message textarea { width: 300px; } form.new_message textarea { height: 100px; } textarea:focus, input.name:focus { border-color: #cacaca; -webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 3px rgba(150, 150, 150, 0.5); -moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 3px rgba(150, 150, 150, 0.5); box-shadow: 0 1px 1px rgba(0, 0, 0, 0.075) inset, 0 0 3px rgba(150, 150, 150, 0.5); } div.private_messages { padding: 10px; } div.private_messages h1 { margin-top: 0; } div.private_messages div.partners { margin-bottom: 30px; } div.chat { height: 100%; min-height: 400px; min-width: 600px; position: relative; background-color: #e0e0e0; } div.chat div.partner { height: 50px; padding: 5px; } div.chat div.partner p { margin: 0; } div.chat div.partner p.name { font-weight: bold; margin-bottom: 3px; } div.chat div.conversation { position: absolute; top: 50px; left: 5px; right: 5px; bottom: 140px; overflow: auto; padding: 0 5px; border-style: solid; border-color: #eee; border-width: 10px 0; background-color: #fff; -webkit-border-radius: 7px; -moz-border-radius: 7px; border-radius: 7px; } div.chat div.conversation div.message { padding: 5px 0; } div.chat div.conversation div.message p { margin: 0; } div.chat div.conversation div.message p.author.partner { color: #002c64; } div.chat div.conversation div.message p.author.we { color: #216300; } div.chat div.conversation div.message p.author span.datetime { font-size: 12px; } div.chat form.message_form { position: absolute; margin: 0; left: 0; right: 0; bottom: 5px; height: 130px; } div.chat form.message_form div.outdated_browser_message { margin: 10px 5px; } div.chat form.message_form div.compose { float: left; height: 100%; width: 80%; padding: 0 5px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } div.chat form.message_form div.compose textarea { width: 100%; height: 100%; margin: 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; resize: none; } div.chat form.message_form div.send { float: left; height: 100%; width: 20%; min-width: 100px; padding-right: 5px; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } div.chat form.message_form div.send p { margin-top: 5px; color: #333; } div.chat form.message_form div.send button { width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; }
JavaScript(myproject / static / privatemessages.js):
function getCookie(name) { var cookieValue = null; if (document.cookie && document.cookie != '') { var cookies = document.cookie.split(';'); for (var i = 0; i < cookies.length; i++) { var cookie = jQuery.trim(cookies[i]);
サーバーに展開するときは、Tornadoサーバーを使用できるアドレスを指定することを忘れないでください(または、別の変数に入れることもできます)。
チャット(竜巻)
新しいTornadoアプリケーションを作成し、privatemessages / tornadoapp.pyに保存します。
import datetime import json import time import urllib import brukva import tornado.web import tornado.websocket import tornado.ioloop import tornado.httpclient from django.conf import settings from django.utils.importlib import import_module session_engine = import_module(settings.SESSION_ENGINE) from django.contrib.auth.models import User from privatemessages.models import Thread c = brukva.Client() c.connect() class MainHandler(tornado.web.RequestHandler): def get(self): self.set_header('Content-Type', 'text/plain') self.write('Hello. :)') class MessagesHandler(tornado.websocket.WebSocketHandler): def __init__(self, *args, **kwargs): super(MessagesHandler, self).__init__(*args, **kwargs) self.client = brukva.Client() self.client.connect() def open(self, thread_id): session_key = self.get_cookie(settings.SESSION_COOKIE_NAME) session = session_engine.SessionStore(session_key) try: self.user_id = session["_auth_user_id"] self.sender_name = User.objects.get(id=self.user_id).username except (KeyError, User.DoesNotExist): self.close() return if not Thread.objects.filter( id=thread_id, participants__id=self.user_id ).exists(): self.close() return self.channel = "".join(['thread_', thread_id,'_messages']) self.client.subscribe(self.channel) self.thread_id = thread_id self.client.listen(self.show_new_message) def handle_request(self, response): pass def on_message(self, message): if not message: return if len(message) > 10000: return c.publish(self.channel, json.dumps({ "timestamp": int(time.time()), "sender": self.sender_name, "text": message, })) http_client = tornado.httpclient.AsyncHTTPClient() request = tornado.httpclient.HTTPRequest( "".join([ settings.SEND_MESSAGE_API_URL, "/", self.thread_id, "/" ]), method="POST", body=urllib.urlencode({ "message": message.encode("utf-8"), "api_key": settings.API_KEY, "sender_id": self.user_id, }) ) http_client.fetch(request, self.handle_request) def show_new_message(self, result): self.write_message(str(result.body)) def on_close(self): try: self.client.unsubscribe(self.channel) except AttributeError: pass def check(): if self.client.connection.in_progress: tornado.ioloop.IOLoop.instance().add_timeout( datetime.timedelta(0.00001), check ) else: self.client.disconnect() tornado.ioloop.IOLoop.instance().add_timeout( datetime.timedelta(0.00001), check ) application = tornado.web.Application([ (r"/", MainHandler), (r'/(?P<thread_id>\d+)/', MessagesHandler), ])
このアプリケーションのほとんどすべては、tornado.ioloopを使用して非同期的に実行されます。 例外は、セッション識別子によるユーザーの承認(およびデータベースからユーザーの名前を取得)です。 これはブロッキング操作です。つまり、現時点では、このTornadoサーバーに接続している他のユーザーは待機します。 必要な場合は簡単に変更できますが、第一に、それが必要になるという事実ではなく、第二に、これが正しい決定かどうかを考えてください。 この機会に、Friendfeed(Tornadoの開発者)のメンバーが複数回話しています。 たとえば、Ben Darnellの投稿からの引用です。
Friendfeedは、標準の同期MySQLdbモジュール(http://bret.appspot.com/entry/how-friendfeed-uses-mysql)でmysqlを使用します。 Friendfeedの哲学は、高速で制御可能なもの(つまり、memcacheとデータベース-およびデータベースが高速でない場合、クエリの方法を再考する)については、複雑さの価値がないため、同期的に呼び出す必要があるということです。コールバックの管理。 非同期コードは、何もできない方法で低速になる可能性のあるもの(外部APIなど)や、要求を無期限に中断する(長いポーリング)場合に使用します。 adispのようなライブラリは、非同期インターフェイスの使用をはるかに簡単にするため、バランスをいくらかシフトしますが、ジェネレーターコルーチンは通常の関数呼び出しのように構成できないという問題がまだあります。
つまり、大まかに言って、データベースが遅い場合は、サーバーが負荷に対処できないことを意味し、非同期起動はここではあまり役に立ちません。
または、データベースの構造が間違っているか、クエリの速度が遅すぎる可能性があります。この場合、問題を修正して症状を軽減しない方がはるかに便利です。トルネードアプリケーションの起動は、管理チームにとって非常に便利です。この場合、ORMおよびその他すべてにアクセスするためにDjangoワークスペースを構成する必要はありません。管理チームを非常に簡単にします。 privatemessagesディレクトリに管理ディレクトリを作成し、その中にコマンドディレクトリを作成します。__init__.pyファイルを使用して、必ずprivatemessages / managementおよびprivatemessages / management /コマンドに追加してください(誰かが突然使用された理由が突然わからない場合は、ドキュメントを参照してください)。ここでprivatemessages / management / commands / starttornadoapp.pyファイルを作成します: import signal import time import tornado.httpserver import tornado.ioloop from django.core.management.base import BaseCommand, CommandError from privatemessages.tornadoapp import application class Command(BaseCommand): args = '[port_number]' help = 'Starts the Tornado application for message handling.' def sig_handler(self, sig, frame): """Catch signal and init callback""" tornado.ioloop.IOLoop.instance().add_callback(self.shutdown) def shutdown(self): """Stop server and add callback to stop i/o loop""" self.http_server.stop() io_loop = tornado.ioloop.IOLoop.instance() io_loop.add_timeout(time.time() + 2, io_loop.stop) def handle(self, *args, **options): if len(args) == 1: try: port = int(args[0]) except ValueError: raise CommandError('Invalid port number specified') else: port = 8888 self.http_server = tornado.httpserver.HTTPServer(application) self.http_server.listen(port, address="127.0.0.1")
シグナルを介してプロセスの最後に何かを行う必要がある場合(たとえば、プロセスが最後にCtrl + Cを介して受信するSIGINTシグナルを介して)、これは対応するハンドラーで設定できます。このトピックの詳細については、こちらをご覧ください。確認する
ここで、プロジェクトのルートディレクトリ(myprojectおよびprivatemessagesディレクトリ、manage.pyファイルを含む)に移動し、Django開発サーバーとTornadoサーバーを起動します。python manage.py runserver
python manage.py starttornadoapp
これらのコマンドは、ターミナルエミュレータのさまざまなタブ、またはさまざまな画面タブで実行できます(たとえば、SSHを介してサーバーに接続し、別のSSHセッションを開くのが気に入らない場合)。これで、ポート8000でDjango開発サーバーが実行され、ポート8888でTornadoサーバーが実行されています。次のページで対話者のリストを確認できます。http : //127.0.0.1 : 8000 / messages/そこから、既存のチャットの1つを開くか、新しいチャットを作成できます(適切なフォームを介して最初のメッセージを送信します)。Djangoサイトにログインする必要があることに注意してください。ドキュメントの承認の書き方を参照してください。怠け者の解決策は簡単です管理インターフェイスを有効にして、ログインします。展開
サーバーでアプリケーションを実行するために、次のソフトウェアを使用できます。- haproxy-ロードバランサー、ポート80で動作します
- nginxのは、可能性 -ポート8100上で実行されます、gunicornのための静的ファイル+リバースプロキシを配布します
- スーパーバイザー -Tornadoサーバーをポート8000-8003(4プロセス)で起動し、Djangoサーバー(gunicornを使用できます)をポート8150で起動します
まず、スーパーバイザーを構成します。これを行うには(Ubuntuリポジトリのスーパーバイザーの例を使用)、/ etc / supervisor / conf.dにdjango.confおよびtornadoapp.confファイルを作成します。スーパーバイザがこれらのファイルを読み取ることを確認します(includeセクションは、行ファイル= /etc/supervisor/conf.d/*.confでメイン構成ファイルに存在する必要があります)。django.conf[プログラム:django]
コマンド= gunicorn_django --workers 4 -b 127.0.0.1:8150
ディレクトリ= / home / yourusername / myproject
ユーザー= yourusername
autostart = true
自動再起動= true
tornadoapp.confprocess_name = tornado-%(process_num)s
ユーザー= yourusername
ディレクトリ= / home / yourusername / myproject
コマンド= python manage.py starttornadoapp%(process_num)s
#numprocsを増やして、異なるポートで複数のプロセスを実行します。
#チャットデモは実際にはその構成では機能しないことに注意してください
#すべてのリスナーが1つのプロセスにあると想定しているため。
numprocs = 4
numprocs_start = 8000
autostart = true
自動再起動= true
次に、supervisorを実行し、supervisorctl statusコマンドの出力を確認します。5つのプロセスすべて(gunicornは単一のプロセスとして表示されます)がRUNNING状態を表示し、アップタイムがスーパーバイザーが起動してから経過した時間と一致することを確認してください。また、DjangoサーバーとTornadoサーバーの両方が機能していることを確認することをお勧めします。これを行うには、サーバーへのSSHトンネルを開いて、ローカルアドレスで選択したポートにアクセスしながら、ブラウザーでサイトを開くだけです。たとえば、次のとおりです。ssh -L 8000:localhost:8150 someverycoolserver.com
次に、ブラウザで127.0.0.1:8000を開きます(SSHサーバーは、ネットワーク上の127.0.0.1:8150にリクエストを送信します-確認したいWebサーバーが実行されています)。同様に、Tornadoサーバーを確認できます。ポート8000-8003にあります。DjangoサーバーとTornadoサーバーが実行されている場合は、nginxとhaproxyを設定するだけです。nginxの構成ファイルの例:サーバー{
listen 127.0.0.1:8100;
server_name someverycoolserver.com;
#/は常にアップストリームに渡されるため、セキュリティ上の問題はありません
root / home / yourusername / myproject / myproject / static /;
##圧縮
#src:http://www.ruby-forum.com/topic/141251
#src:http://wiki.brightbox.co.uk/docs:nginx
gzip on;
gzip_http_version 1.0;
gzip_comp_level 2;
gzip_proxied any;
gzip_min_length 1100;
gzip_buffers 16 8k;
gzip_types text/plain text/html text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;
# Some version of IE 6 don't handle compression well on some mime-types, so just disable for them
gzip_disable "MSIE [1-6].(?!.*SV1)";
# Set a vary header so downstream proxies don't send cached gzipped content to IE6
gzip_vary on;
## /Compression
location /static/admin/ {
# this changes depending on your python version
root /usr/local/lib/python2.7/dist-packages/django/contrib/admin/;
}
location /robots.txt {
alias /home/yourusername/myproject/myproject/robots.txt;
}
location /favicon.ico {
alias /home/yourusername/myproject/myproject/img/favicon.ico;
expires 3d;
}
location /static/ {
root /home/yourusername/myproject/myproject/;
expires 3d;
}
場所/ {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $ remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_connect_timeout 10;
proxy_read_timeout 10;
proxy_pass http://localhost:8150/;
}
}
ここでは、3日間の静的ファイルのキャッシュが選択されていることに注意してください。そこに何も変更しないことを確信している場合(ただし、通常は変更する必要があります)、またはすべての静的ファイルに対していわゆる静的ファイルのバージョン管理手法を使用する場合(通常、これは静的ファイルのクエリでは、クエリ文字列がアドレスに追加され、サーバーがファイルがディスク上で変更されたことを認識すると、クエリ文字列が変更されます)。特に、これは、たとえばdjango-assetsのように、CSSとJSの自動結合とミミフィケーション(ミニフィケーション)を使用する場合に最適です。haproxy設定ファイルの例:グローバル
maxconn 10000#合計最大接続数。これはulimitに依存しています
nbproc 2
デフォルト
モードhttp
オプション再ディスパッチ
maxconn 2000
contimeout 5000
clitimeout 50000
srvtimeout 50000
option httpclose
frontend all 0.0.0.0:80
timeout client 86400000
acl is_chat hdr_beg(host) -i chat
use_backend socket_backend if is_chat
default_backend www_backend
backend www_backend
option forwardfor # This sets X-Forwarded-For
timeout server 30000
timeout connect 4000
server server1 localhost:8100
backend socket_backend
balance roundrobin
option forwardfor # This sets X-Forwarded-For
no option httpclose # To match the `Connection` header for the websocket protocol rev. 76
option http-server-close
option http-pretend-keepalive
timeout queue 5000
timeout server 86400000
timeout connect 86400000
server server1 localhost:8000 weight 1 maxconn 5000 check
server server2 localhost:8001 weight 1 maxconn 5000 check
サーバーserver2 localhost:8002重み1 maxconn 5000チェック
サーバーserver2 localhost:8003重み1 maxconn 5000チェック
ここでは、チャットでホストが開始するすべてのリクエストをlocalhost:8000、localhost:8001、localhost:8002、およびlocalhost:8003にリダイレクトし、他のすべてのリクエストをlocalhost:8100にリダイレクトすることを示します。開始後、haproxyは使用可能なすべてのIPv4アドレスのポート80で動作します。IPv6をサポートする場合(およびホスティング/プロバイダーがサポートしている場合、これは素晴らしいアイデアです)、バインド構成パラメーターをIPv6アドレスとフロントエンドセクションのコロンを介して80番目のポートに追加します。, IPv6- (, , AAAA- DNS), . , . — IPv6,
DNS whitelisting .
, , — IPv6, 1—2 ( ).
ちなみに、Tornadoサーバーは別のポートではなく、別のサブドメインで使用できます。一部の企業には、代替ポート(80ではなく、443ではない)へのアクセスを単にブロックする非常に奇妙なシステム管理者がいます。また、Hostヘッダーではなく、Upgradeヘッダーの存在によってバックエンドを選択するようにhaproxyを構成することもできます-Django とTornadoの両方が同じドメイン(サブドメインなし)で動作することがわかりますが、WS-接続はTornadoサーバーと開き、残りはすべてDjangoサーバーと開きます。ご覧のとおり、一般に、すべてが非常に単純です。新しいトピックを理解するには、ドキュメントを読むだけで十分です。ドキュメントにすべてが記載されていない場合は、ソース(DjangoとTornadoの場合、ソースから多くのことを学ぶのが最も簡単です)、フォーラム、テーマ別レポートの資料を読んでください。興味深いプロジェクトを改善して作業してください!皆さんに幸運を祈り、プログラミングをお楽しみください。