├── templates ├── login.html └── chat.html ├── start.sh ├── requirements.txt ├── README.md ├── Dockerfile ├── docker-compose.yml ├── app.py ├── LICENSE └── .gitignore /templates/login.html: -------------------------------------------------------------------------------- 1 |
User: -------------------------------------------------------------------------------- /start.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | gunicorn -k gevent -b 0.0.0.0:5000 --chdir /app app:app -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | click==6.7 2 | Flask==0.12.2 3 | gevent==1.2.2 4 | greenlet==0.4.12 5 | gunicorn==19.7.1 6 | itsdangerous==0.24 7 | Jinja2==2.9.6 8 | MarkupSafe==1.0 9 | redis==2.10.6 10 | Werkzeug==0.12.2 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask Realtime Chat 2 | 3 | ## What 4 | This is a simple example using Flask and Redis Pub/Sub for a realtime chat application. 5 | 6 | 7 | ## Running 8 | Run the follow command (you need Docker installed): 9 | 10 | `docker-compse run --build` 11 | 12 | Go to `http://localhost:5000` 13 | 14 | 15 | 16 | Enjoy :) 17 | 18 | 19 | 20 | License: [BSD 3-clause](LICENSE) -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.6.2-alpine3.6 2 | 3 | RUN mkdir /app 4 | 5 | COPY . /app 6 | 7 | RUN apk add -U --no-cache --virtual=build-dependencies \ 8 | gcc linux-headers musl musl-dev 9 | 10 | RUN cd /app \ 11 | && pip install -r requirements.txt --no-cache-dir \ 12 | && apk del build-dependencies \ 13 | && rm -rf /var/cache/apk/* \ 14 | && mv /app/start.sh /start \ 15 | && chmod +x /start 16 | 17 | WORKDIR /app 18 | 19 | CMD [ "/start" ] -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | ###################################### 4 | # Applications Container 5 | ###################################### 6 | app: 7 | container_name: flask-chat 8 | build: 9 | context: . 10 | volumes: 11 | - .:/app 12 | links: 13 | - redis 14 | ports: 15 | - 5000:5000 16 | ###################################### 17 | # Redis Container 18 | ###################################### 19 | redis: 20 | container_name: redis 21 | image: redis:4.0.1-alpine 22 | ports: 23 | - 6379:6379 -------------------------------------------------------------------------------- /templates/chat.html: -------------------------------------------------------------------------------- 1 | 2 | chat 3 | 4 | 5 |

Hi, {{ user }}!

6 |

Message:

7 |

 8 | 


--------------------------------------------------------------------------------
/app.py:
--------------------------------------------------------------------------------
 1 | import datetime
 2 | import redis
 3 | from flask import (
 4 |     Flask,
 5 |     Response,
 6 |     redirect,
 7 |     render_template,
 8 |     request,
 9 |     session,
10 | )
11 | 
12 | app = Flask(__name__)
13 | app.secret_key = 'asdf'
14 | r = redis.StrictRedis('redis', 6379, 0, charset='utf-8', decode_responses=True)
15 | 
16 | 
17 | def event_stream():
18 |     pubsub = r.pubsub(ignore_subscribe_messages=True)
19 |     pubsub.subscribe('chat')
20 |     # TODO: handle client disconnection.
21 |     for message in pubsub.listen():
22 |         yield 'data: %s\n\n' % message['data']
23 | 
24 | 
25 | @app.route('/login', methods=['GET', 'POST'])
26 | def login():
27 |     if request.method == 'POST':
28 |         session['user'] = request.form['user']
29 |         return redirect('/')
30 |     return render_template('login.html')
31 | 
32 | 
33 | @app.route('/post', methods=['POST'])
34 | def post():
35 |     message = request.form['message']
36 |     user = session.get('user', 'anonymous')
37 |     now = datetime.datetime.now().replace(microsecond=0).time()
38 |     r.publish('chat', '[%s] %s: %s' % (now.isoformat(), user, message))
39 |     return Response(status=204)
40 | 
41 | 
42 | @app.route('/stream')
43 | def stream():
44 |     return Response(event_stream(), mimetype="text/event-stream")
45 | 
46 | 
47 | @app.route('/')
48 | def home():
49 |     if 'user' not in session:
50 |         return redirect('/login')
51 |     return render_template('chat.html', user=session['user'])
52 | 
53 | 
54 | if __name__ == '__main__':
55 |     app.run()
56 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
 1 | BSD 3-Clause License
 2 | 
 3 | Copyright (c) 2017, Juliano Petronetto
 4 | All rights reserved.
 5 | 
 6 | Redistribution and use in source and binary forms, with or without
 7 | modification, are permitted provided that the following conditions are met:
 8 | 
 9 | * Redistributions of source code must retain the above copyright notice, this
10 |   list of conditions and the following disclaimer.
11 | 
12 | * Redistributions in binary form must reproduce the above copyright notice,
13 |   this list of conditions and the following disclaimer in the documentation
14 |   and/or other materials provided with the distribution.
15 | 
16 | * Neither the name of the copyright holder nor the names of its
17 |   contributors may be used to endorse or promote products derived from
18 |   this software without specific prior written permission.
19 | 
20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
  1 | 
  2 | # Created by https://www.gitignore.io/api/venv,code,pydev,python,pycharm,jupyternotebook
  3 | 
  4 | ### Code ###
  5 | # Visual Studio Code - https://code.visualstudio.com/
  6 | .settings/
  7 | .vscode/
  8 | tsconfig.json
  9 | jsconfig.json
 10 | 
 11 | ### JupyterNotebook ###
 12 | .ipynb_checkpoints
 13 | */.ipynb_checkpoints/*
 14 | 
 15 | # Remove previous ipynb_checkpoints
 16 | #   git rm -r .ipynb_checkpoints/
 17 | #
 18 | ### PyCharm ###
 19 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
 20 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
 21 | 
 22 | # User-specific stuff:
 23 | .idea/**/workspace.xml
 24 | .idea/**/tasks.xml
 25 | .idea/dictionaries
 26 | 
 27 | # Sensitive or high-churn files:
 28 | .idea/**/dataSources/
 29 | .idea/**/dataSources.ids
 30 | .idea/**/dataSources.xml
 31 | .idea/**/dataSources.local.xml
 32 | .idea/**/sqlDataSources.xml
 33 | .idea/**/dynamic.xml
 34 | .idea/**/uiDesigner.xml
 35 | 
 36 | # Gradle:
 37 | .idea/**/gradle.xml
 38 | .idea/**/libraries
 39 | 
 40 | # CMake
 41 | cmake-build-debug/
 42 | 
 43 | # Mongo Explorer plugin:
 44 | .idea/**/mongoSettings.xml
 45 | 
 46 | ## File-based project format:
 47 | *.iws
 48 | 
 49 | ## Plugin-specific files:
 50 | 
 51 | # IntelliJ
 52 | /out/
 53 | 
 54 | # mpeltonen/sbt-idea plugin
 55 | .idea_modules/
 56 | 
 57 | # JIRA plugin
 58 | atlassian-ide-plugin.xml
 59 | 
 60 | # Cursive Clojure plugin
 61 | .idea/replstate.xml
 62 | 
 63 | # Crashlytics plugin (for Android Studio and IntelliJ)
 64 | com_crashlytics_export_strings.xml
 65 | crashlytics.properties
 66 | crashlytics-build.properties
 67 | fabric.properties
 68 | 
 69 | ### PyCharm Patch ###
 70 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
 71 | 
 72 | # *.iml
 73 | # modules.xml
 74 | # .idea/misc.xml
 75 | # *.ipr
 76 | 
 77 | # Sonarlint plugin
 78 | .idea/sonarlint
 79 | 
 80 | ### pydev ###
 81 | .pydevproject
 82 | 
 83 | ### Python ###
 84 | # Byte-compiled / optimized / DLL files
 85 | __pycache__/
 86 | *.py[cod]
 87 | *$py.class
 88 | 
 89 | # C extensions
 90 | *.so
 91 | 
 92 | # Distribution / packaging
 93 | .Python
 94 | build/
 95 | develop-eggs/
 96 | dist/
 97 | downloads/
 98 | eggs/
 99 | .eggs/
100 | lib/
101 | lib64/
102 | parts/
103 | sdist/
104 | var/
105 | wheels/
106 | *.egg-info/
107 | .installed.cfg
108 | *.egg
109 | 
110 | # PyInstaller
111 | #  Usually these files are written by a python script from a template
112 | #  before PyInstaller builds the exe, so as to inject date/other infos into it.
113 | *.manifest
114 | *.spec
115 | 
116 | # Installer logs
117 | pip-log.txt
118 | pip-delete-this-directory.txt
119 | 
120 | # Unit test / coverage reports
121 | htmlcov/
122 | .tox/
123 | .coverage
124 | .coverage.*
125 | .cache
126 | nosetests.xml
127 | coverage.xml
128 | *.cover
129 | .hypothesis/
130 | 
131 | # Translations
132 | *.mo
133 | *.pot
134 | 
135 | # Django stuff:
136 | *.log
137 | local_settings.py
138 | 
139 | # Flask stuff:
140 | instance/
141 | .webassets-cache
142 | 
143 | # Scrapy stuff:
144 | .scrapy
145 | 
146 | # Sphinx documentation
147 | docs/_build/
148 | 
149 | # PyBuilder
150 | target/
151 | 
152 | # Jupyter Notebook
153 | 
154 | # pyenv
155 | .python-version
156 | 
157 | # celery beat schedule file
158 | celerybeat-schedule
159 | 
160 | # SageMath parsed files
161 | *.sage.py
162 | 
163 | # Environments
164 | .env
165 | .venv
166 | env/
167 | venv/
168 | ENV/
169 | env.bak/
170 | venv.bak/
171 | 
172 | # Spyder project settings
173 | .spyderproject
174 | .spyproject
175 | 
176 | # Rope project settings
177 | .ropeproject
178 | 
179 | # mkdocs documentation
180 | /site
181 | 
182 | # mypy
183 | .mypy_cache/
184 | 
185 | ### venv ###
186 | # Linux
187 | bin/ 
188 | include/
189 | 
190 | # Window
191 | Include/
192 | Lib/
193 | Scripts/
194 | 
195 | # Shared
196 | pyvenv.cfg
197 | 
198 | 
199 | # End of https://www.gitignore.io/api/venv,code,pydev,python,pycharm,jupyternotebook
200 | 
201 | redis


--------------------------------------------------------------------------------