Skip to content

Commit 62ef48d

Browse files
anguillanneufchenyumic
authored and
chenyumic
committed
Pub/Sub Sample for App Engine Standard (#1606)
* Example of Pub/Sub API Client Lib in GAE Standard * Added Pub/Sub in AppEngine Standard
1 parent f6ca863 commit 62ef48d

File tree

7 files changed

+305
-0
lines changed

7 files changed

+305
-0
lines changed

appengine/standard/pubsub/README.md

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# Python Google Cloud Pub/Sub sample for Google App Engine Standard Environment
2+
3+
[![Open in Cloud Shell][shell_img]][shell_link]
4+
5+
[shell_img]: http://gstatic.com/cloudssh/images/open-btn.png
6+
[shell_link]: https://console.cloud.google.com/cloudshell/open?git_repo=https://github.com/GoogleCloudPlatform/python-docs-samples&page=editor&open_in_editor=appengine/standard/pubsub/README.md
7+
8+
This demonstrates how to send and receive messages using [Google Cloud Pub/Sub](https://cloud.google.com/pubsub) on [Google App Engine Standard Environment](https://cloud.google.com/appengine/docs/standard/).
9+
10+
## Setup
11+
12+
Before you can run or deploy the sample, you will need to do the following:
13+
14+
1. Enable the Cloud Pub/Sub API in the [Google Developers Console](https://console.developers.google.com/project/_/apiui/apiview/pubsub/overview).
15+
16+
2. Create a topic and subscription.
17+
18+
$ gcloud pubsub topics create [your-topic-name]
19+
$ gcloud pubsub subscriptions create [your-subscription-name] \
20+
--topic [your-topic-name] \
21+
--push-endpoint \
22+
https://[your-app-id].appspot.com/_ah/push-handlers/receive_messages/token=[your-token] \
23+
--ack-deadline 30
24+
25+
3. Update the environment variables in ``app.yaml``.
26+
27+
## Running locally
28+
29+
Refer to the [top-level README](../README.md) for instructions on running and deploying.
30+
31+
When running locally, you can use the [Google Cloud SDK](https://cloud.google.com/sdk) to provide authentication to use Google Cloud APIs:
32+
33+
$ gcloud init
34+
35+
Install dependencies, preferably with a virtualenv:
36+
37+
$ virtualenv env
38+
$ source env/bin/activate
39+
$ pip install -r requirements.txt
40+
41+
Then set environment variables before starting your application:
42+
43+
$ export GOOGLE_CLOUD_PROJECT=[your-project-name]
44+
$ export PUBSUB_VERIFICATION_TOKEN=[your-verification-token]
45+
$ export PUBSUB_TOPIC=[your-topic]
46+
$ python main.py
47+
48+
### Simulating push notifications
49+
50+
The application can send messages locally, but it is not able to receive push messages locally. You can, however, simulate a push message by making an HTTP request to the local push notification endpoint. There is an included ``sample_message.json``. You can use
51+
``curl`` or [httpie](https://github.com/jkbrzt/httpie) to POST this:
52+
53+
$ curl -i --data @sample_message.json ":8080/_ah/push-handlers/receive_messages?token=[your-token]"
54+
55+
Or
56+
57+
$ http POST ":8080/_ah/push-handlers/receive_messages?token=[your-token]" < sample_message.json
58+
59+
Response:
60+
61+
HTTP/1.0 200 OK
62+
Content-Length: 2
63+
Content-Type: text/html; charset=utf-8
64+
Date: Mon, 10 Aug 2015 17:52:03 GMT
65+
Server: Werkzeug/0.10.4 Python/2.7.10
66+
67+
OK
68+
69+
After the request completes, you can refresh ``localhost:8080`` and see the message in the list of received messages.
70+
71+
## Running on App Engine
72+
73+
Deploy using `gcloud`:
74+
75+
gcloud app deploy app.yaml
76+
77+
You can now access the application at `https://your-app-id.appspot.com`. You can use the form to submit messages, but it's non-deterministic which instance of your application will receive the notification. You can send multiple messages and refresh the page to see the received message.

appengine/standard/pubsub/app.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
runtime: python27
2+
api_version: 1
3+
threadsafe: yes
4+
5+
handlers:
6+
- url: /
7+
script: main.app
8+
9+
- url: /_ah/push-handlers/.*
10+
script: main.app
11+
login: admin
12+
13+
#[START env]
14+
env_variables:
15+
PUBSUB_TOPIC: your-topic
16+
# This token is used to verify that requests originate from your
17+
# application. It can be any sufficiently random string.
18+
PUBSUB_VERIFICATION_TOKEN: 1234abc
19+
#[END env]

appengine/standard/pubsub/main.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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+
# [START app]
16+
import base64
17+
import json
18+
import logging
19+
import os
20+
21+
from flask import current_app, Flask, render_template, request
22+
from googleapiclient.discovery import build
23+
24+
25+
app = Flask(__name__)
26+
27+
# Configure the following environment variables via app.yaml
28+
# This is used in the push request handler to veirfy that the request came from
29+
# pubsub and originated from a trusted source.
30+
app.config['PUBSUB_VERIFICATION_TOKEN'] = \
31+
os.environ['PUBSUB_VERIFICATION_TOKEN']
32+
app.config['PUBSUB_TOPIC'] = os.environ['PUBSUB_TOPIC']
33+
app.config['GCLOUD_PROJECT'] = os.environ['GOOGLE_CLOUD_PROJECT']
34+
35+
36+
# Global list to storage messages received by this instance.
37+
MESSAGES = []
38+
39+
40+
# [START index]
41+
@app.route('/', methods=['GET', 'POST'])
42+
def index():
43+
if request.method == 'GET':
44+
return render_template('index.html', messages=MESSAGES)
45+
46+
data = request.form.get('payload', 'Example payload').encode('utf-8')
47+
48+
service = build('pubsub', 'v1')
49+
topic_path = 'projects/{project_id}/topics/{topic}'.format(
50+
project_id=app.config['GCLOUD_PROJECT'],
51+
topic=app.config['PUBSUB_TOPIC']
52+
)
53+
service.projects().topics().publish(
54+
topic=topic_path, body={
55+
"messages": [{
56+
"data": base64.b64encode(data)
57+
}]
58+
}).execute()
59+
60+
return 'OK', 200
61+
# [END index]
62+
63+
64+
# [START push]
65+
@app.route('/_ah/push-handlers/receive_messages', methods=['POST'])
66+
def receive_messages_handler():
67+
if (request.args.get('token', '') !=
68+
current_app.config['PUBSUB_VERIFICATION_TOKEN']):
69+
return 'Invalid request', 400
70+
71+
envelope = json.loads(request.data.decode('utf-8'))
72+
payload = base64.b64decode(envelope['message']['data'])
73+
74+
MESSAGES.append(payload)
75+
76+
# Returning any 2xx status indicates successful receipt of the message.
77+
return 'OK', 200
78+
# [END push]
79+
80+
81+
@app.errorhandler(500)
82+
def server_error(e):
83+
logging.exception('An error occurred during a request.')
84+
return """
85+
An internal error occurred: <pre>{}</pre>
86+
See logs for full stacktrace.
87+
""".format(e), 500
88+
89+
90+
if __name__ == '__main__':
91+
# This is used when running locally. Gunicorn is used to run the
92+
# application on Google App Engine. See entrypoint in app.yaml.
93+
app.run(host='127.0.0.1', port=8080, debug=True)
94+
# [END app]
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
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 base64
16+
import json
17+
import os
18+
19+
import pytest
20+
21+
import main
22+
23+
24+
@pytest.fixture
25+
def client():
26+
main.app.testing = True
27+
return main.app.test_client()
28+
29+
30+
def test_index(client):
31+
r = client.get('/')
32+
assert r.status_code == 200
33+
34+
35+
def test_post_index(client):
36+
r = client.post('/', data={'payload': 'Test payload'})
37+
assert r.status_code == 200
38+
39+
40+
def test_push_endpoint(client):
41+
url = '/_ah/push-handlers/receive_messages?token=' + \
42+
os.environ['PUBSUB_VERIFICATION_TOKEN']
43+
44+
r = client.post(
45+
url,
46+
data=json.dumps({
47+
"message": {
48+
"data": base64.b64encode(
49+
u'Test message'.encode('utf-8')
50+
).decode('utf-8')
51+
}
52+
})
53+
)
54+
55+
assert r.status_code == 200
56+
57+
# Make sure the message is visible on the home page.
58+
r = client.get('/')
59+
assert r.status_code == 200
60+
assert 'Test message' in r.data.decode('utf-8')
61+
62+
63+
def test_push_endpoint_errors(client):
64+
# no token
65+
r = client.post('/_ah/push-handlers/receive_messages')
66+
assert r.status_code == 400
67+
68+
# invalid token
69+
r = client.post('/_ah/push-handlers/receive_messages?token=bad')
70+
assert r.status_code == 400
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Flask==0.12.2
2+
google-api-python-client==1.7.3
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"message": {
3+
"data": "SGVsbG8sIFdvcmxkIQ=="
4+
}
5+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
{#
2+
# Copyright 2015 Google Inc. All Rights Reserved.
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>Pub/Sub Python on Google App Engine Flexible Environment</title>
20+
</head>
21+
<body>
22+
<div>
23+
<p>Messages received by this instance:</p>
24+
<ul>
25+
{% for message in messages: %}
26+
<li>{{message}}</li>
27+
{% endfor %}
28+
</ul>
29+
<p><small>Note: because your application is likely running multiple instances, each instance will have a different list of messages.</small></p>
30+
</div>
31+
<!-- [START form] -->
32+
<form method="post">
33+
<textarea name="payload" placeholder="Enter message here"></textarea>
34+
<input type="submit">
35+
</form>
36+
<!-- [END form] -->
37+
</body>
38+
</html>

0 commit comments

Comments
 (0)