Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit a45d3b0

Browse filesBrowse files
authored
Add Flex websockets sample (GoogleCloudPlatform#1894)
1 parent 11b5ad3 commit a45d3b0
Copy full SHA for a45d3b0

File tree

Expand file treeCollapse file tree

6 files changed

+257
-0
lines changed
Filter options
Expand file treeCollapse file tree

6 files changed

+257
-0
lines changed
+11Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Python websockets sample for Google App Engine Flexible Environment
2+
3+
This sample demonstrates how to use websockets on [Google App Engine Flexible Environment](https://cloud.google.com/appengine).
4+
5+
## Running locally
6+
7+
Refer to the [top-level README](../README.md) for instructions on running and deploying.
8+
9+
To run locally, you need to use gunicorn with the ``flask_socket`` worker:
10+
11+
$ gunicorn -b 127.0.0.1:8080 -k flask_sockets.worker main:app
+22Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
runtime: python
2+
env: flex
3+
4+
# Use a special gunicorn worker class to support websockets.
5+
entrypoint: gunicorn -b :$PORT -k flask_sockets.worker main:app
6+
7+
runtime_config:
8+
python_version: 3
9+
10+
# Use only a single instance, so that this local-memory-only chat app will work
11+
# consistently with multiple users. To work across multiple instances, an
12+
# extra-instance messaging system or data store would be needed.
13+
manual_scaling:
14+
instances: 1
15+
16+
17+
# For applications which can take advantage of session affinity
18+
# (where the load balancer will attempt to route multiple connections from
19+
# the same user to the same App Engine instance), uncomment the folowing:
20+
21+
# network:
22+
# session_affinity: true

‎appengine/flexible/websockets/main.py

Copy file name to clipboard
+53Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import print_function
16+
17+
# [START gae_flex_websockets_app]
18+
from flask import Flask, render_template
19+
from flask_sockets import Sockets
20+
21+
22+
app = Flask(__name__)
23+
sockets = Sockets(app)
24+
25+
26+
@sockets.route('/chat')
27+
def chat_socket(ws):
28+
while not ws.closed:
29+
message = ws.receive()
30+
if message is None: # message is "None" if the client has closed.
31+
continue
32+
# Send the message to all clients connected to this webserver
33+
# process. (To support multiple processes or instances, an
34+
# extra-instance storage or messaging system would be required.)
35+
clients = ws.handler.server.clients.values()
36+
for client in clients:
37+
client.ws.send(message)
38+
# [END gae_flex_websockets_app]
39+
40+
41+
@app.route('/')
42+
def index():
43+
return render_template('index.html')
44+
45+
46+
if __name__ == '__main__':
47+
print("""
48+
This can not be run directly because the Flask development server does not
49+
support web sockets. Instead, use gunicorn:
50+
51+
gunicorn -b 127.0.0.1:8080 -k flask_sockets.worker main:app
52+
53+
""")
+71Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# Copyright 2018 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import socket
16+
import subprocess
17+
18+
import pytest
19+
import requests
20+
from retrying import retry
21+
import websocket
22+
23+
24+
@pytest.fixture(scope='module')
25+
def server():
26+
"""Provides the address of a test HTTP/websocket server.
27+
The test server is automatically created before
28+
a test and destroyed at the end.
29+
"""
30+
# Ask the OS to allocate a port.
31+
sock = socket.socket()
32+
sock.bind(('127.0.0.1', 0))
33+
port = sock.getsockname()[1]
34+
35+
# Free the port and pass it to a subprocess.
36+
sock.close()
37+
38+
bind_to = '127.0.0.1:{}'.format(port)
39+
server = subprocess.Popen(
40+
['gunicorn', '-b', bind_to, '-k' 'flask_sockets.worker', 'main:app'])
41+
42+
# Wait until the server responds before proceeding.
43+
@retry(wait_fixed=50, stop_max_delay=5000)
44+
def check_server(url):
45+
requests.get(url)
46+
47+
check_server('http://{}/'.format(bind_to))
48+
49+
yield bind_to
50+
51+
server.kill()
52+
53+
54+
def test_http(server):
55+
result = requests.get('http://{}/'.format(server))
56+
assert 'Python Websockets Chat' in result.text
57+
58+
59+
def test_websocket(server):
60+
url = 'ws://{}/chat'.format(server)
61+
ws_one = websocket.WebSocket()
62+
ws_one.connect(url)
63+
64+
ws_two = websocket.WebSocket()
65+
ws_two.connect(url)
66+
67+
message = 'Hello, World'
68+
ws_one.send(message)
69+
70+
assert ws_one.recv() == message
71+
assert ws_two.recv() == message
+4Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Flask==0.12.2
2+
Flask-Sockets==0.2.1
3+
gunicorn==19.7.1
4+
requests==2.14.2
+96Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
{#
2+
# Copyright 2018 Google LLC
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#}
16+
<!doctype html>
17+
<html>
18+
<head>
19+
<title>Google App Engine Flexible Environment - Python Websockets Chat</title>
20+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
21+
</head>
22+
<body>
23+
24+
<!-- [START gae_flex_websockets_form] -->
25+
<p>Chat demo</p>
26+
<form id="chat-form">
27+
<textarea id="chat-text" placeholder="Enter some text..."></textarea>
28+
<button type="submit">Send</button>
29+
</form>
30+
31+
<div>
32+
<p>Messages:</p>
33+
<ul id="chat-response"></ul>
34+
</div>
35+
36+
<div>
37+
<p>Status:</p>
38+
<ul id="chat-status"></ul>
39+
</div>
40+
<!-- [END gae_flex_websockets_form] -->
41+
42+
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
43+
<script>
44+
// [START gae_flex_websockets_js]
45+
$(function() {
46+
/* If the main page is served via https, the WebSocket must be served via
47+
"wss" (WebSocket Secure) */
48+
var scheme = window.location.protocol == "https:" ? 'wss://' : 'ws://';
49+
var webSocketUri = scheme
50+
+ window.location.hostname
51+
+ (location.port ? ':'+location.port: '')
52+
+ '/chat';
53+
54+
/* Get elements from the page */
55+
var form = $('#chat-form');
56+
var textarea = $('#chat-text');
57+
var output = $('#chat-response');
58+
var status = $('#chat-status');
59+
60+
/* Helper to keep an activity log on the page. */
61+
function log(text){
62+
status.append($('<li>').text(text))
63+
}
64+
65+
/* Establish the WebSocket connection and register event handlers. */
66+
var websocket = new WebSocket(webSocketUri);
67+
68+
websocket.onopen = function() {
69+
log('Connected');
70+
};
71+
72+
websocket.onclose = function() {
73+
log('Closed');
74+
};
75+
76+
websocket.onmessage = function(e) {
77+
log('Message received');
78+
output.append($('<li>').text(e.data));
79+
};
80+
81+
websocket.onerror = function(e) {
82+
log('Error (see console)');
83+
console.log(e);
84+
};
85+
86+
/* Handle form submission and send a message to the websocket. */
87+
form.submit(function(e) {
88+
e.preventDefault();
89+
var data = textarea.val();
90+
websocket.send(data);
91+
});
92+
});
93+
// [END gae_flex_websockets_js]
94+
</script>
95+
</body>
96+
</html>

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.