Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update App Engine Websockets sample. #378

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 44 additions & 16 deletions appengine/websockets/README.md
Original file line number Diff line number Diff line change
@@ -1,25 +1,53 @@
# Node.js websockets sample for Google App Engine

This sample demonstrates how to use websockets on
[Google App Engine Flexible Environment](https://cloud.google.com/appengine) with Node.js.
[Google App Engine Flexible Environment][appengine] with Node.js.

__Note:__ Secure WebSockets are currently not supported by App Engine Flexible Environment.
WebSockets will only work if you load your page over HTTP (not HTTPS).
* [Setup](#setup)
* [Running locally](#running-locally)
* [Deploying to App Engine](#deploying-to-app-engine)
* [Running the tests](#running-the-tests)

To use Secure WebSockets now, you can launch a VM on Google Compute Engine using
a custom image where you have added SSL support for WebSockets.
## Setup

Refer to the [appengine/README.md](../README.md) file for instructions on
running and deploying.
Before you can run or deploy the sample, you need to do the following:

## Setup
1. Refer to the [appengine/README.md][readme] file for instructions on
running and deploying.
1. Install dependencies:

With `npm`:

npm install

or with `yarn`:

yarn install

## Running locally

With `npm`:

npm start

or with `yarn`:

yarn start

## Deploying to App Engine

With `npm`:

npm run deploy

or with `yarn`:

yarn run deploy

## Running the tests

Before you can run or deploy the sample, you will need to create a new firewall
rule to allow traffic on port 65080. This port will be used for websocket
connections. You can do this with the
[Google Cloud SDK](https://cloud.google.com/sdk) with the following command:
See [Contributing][contributing].

gcloud compute firewall-rules create default-allow-websockets \
--allow tcp:65080 \
--target-tags websocket \
--description "Allow websocket traffic on port 65080"
[appengine]: https://cloud.google.com/appengine/docs/flexible/nodejs
[readme]: ../README.md
[contributing]: https://github.com/GoogleCloudPlatform/nodejs-docs-samples/blob/master/CONTRIBUTING.md
75 changes: 16 additions & 59 deletions appengine/websockets/app.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2016, Google, Inc.
* Copyright 2017, Google, Inc.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
Expand All @@ -13,73 +13,30 @@
* limitations under the License.
*/

// [START app]
'use strict';

const http = require('http');
const express = require('express');
const request = require('request');

const app = express();
// [START appengine_websockets_app]
const app = require('express')();
app.set('view engine', 'pug');

// Use express-ws to enable web sockets.
require('express-ws')(app);
const server = require('http').Server(app);
const io = require('socket.io')(server);

// A simple echo service.
app.ws('/echo', (ws) => {
ws.on('message', (msg) => {
ws.send(msg);
});
app.get('/', (req, res) => {
res.render('index.pug');
});

app.get('/', (req, res) => {
getExternalIp((err, externalIp) => {
if (err) {
res.status(500).send(err.message).end();
return;
}
res.render('index.pug', { externalIp: externalIp }).end();
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
io.emit('chat message', msg);
});
});

// [START external_ip]
// In order to use websockets on App Engine, you need to connect directly to
// application instance using the instance's public external IP. This IP can
// be obtained from the metadata server.
const METADATA_NETWORK_INTERFACE_URL = 'http://metadata/computeMetadata/v1/' +
'/instance/network-interfaces/0/access-configs/0/external-ip';

function getExternalIp (cb) {
const options = {
url: METADATA_NETWORK_INTERFACE_URL,
headers: {
'Metadata-Flavor': 'Google'
}
};

request(options, (err, resp, body) => {
if (err || resp.statusCode !== 200) {
console.log('Error while talking to metadata server, assuming localhost');
cb(null, 'localhost');
return;
}
cb(null, body);
if (module === require.main) {
const PORT = process.env.PORT || 8080;
server.listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
}
// [END external_ip]

// Start the websocket server
const wsServer = app.listen('65080', () => {
console.log('Websocket server listening on port %s', wsServer.address().port);
});

// Additionally listen for non-websocket connections on the default App Engine
// port 8080. Using http.createServer will skip express-ws's logic to upgrade
// websocket connections.
const PORT = process.env.PORT || 8080;
http.createServer(app).listen(PORT, () => {
console.log(`App listening on port ${PORT}`);
console.log('Press Ctrl+C to quit.');
});
// [END app]
// [END appengine_websockets_app]
12 changes: 3 additions & 9 deletions appengine/websockets/app.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,8 @@

# [START app_yaml]
runtime: nodejs
vm: true
env: flex

# [START network]
# This ensures clients will be able to connect via websockets to any instance of
# this app using websockets
network:
forwarded_ports:
- 65080
instance_tag: websocket
# [END network]
manual_scaling:
instances: 1
# [END app_yaml]
27 changes: 22 additions & 5 deletions appengine/websockets/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,29 @@
"node": ">=4.3.2"
},
"scripts": {
"start": "node app.js"
"deploy": "gcloud app deploy",
"start": "node app.js",
"lint": "samples lint",
"pretest": "npm run lint",
"system-test": "samples test app",
"test": "npm run system-test",
"e2e-test": "samples test deploy"
},
"dependencies": {
"express": "4.14.1",
"express-ws": "2.0.0",
"pug": "2.0.0-beta6",
"request": "2.79.0"
"express": "4.15.2",
"pug": "2.0.0-rc.1",
"socket.io": "1.7.4"
},
"devDependencies": {
"@google-cloud/nodejs-repo-tools": "1.4.12"
},
"cloud-repo-tools": {
"test": {
"app": {
"msg": "messages"
}
},
"requiresKeyFile": true,
"requiresProjectId": true
}
}
97 changes: 34 additions & 63 deletions appengine/websockets/views/index.pug
Original file line number Diff line number Diff line change
@@ -1,70 +1,41 @@
doctype html
html(lang="en")
head
title Websockets Echo
title Socket.IO chat on App Engine
meta(charset="utf-8")
//- [START form]
body(data-external-ip=externalIp)
p Echo demo
form(id="echo-form")
textarea(id="echo-text", placeholder="Enter some text...")
button(type="submit") Send

div
p Response:
pre(id="echo-response")

div
p Status:
pre(id="echo-status")
//- [END form]

script(src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js")
style.
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
//- [START appengine_websockets_form]
body
ul(id="messages")
form(action="")
input(id="m" autocomplete="off")
button Send
//- [END appengine_websockets_form]

script(src="/socket.io/socket.io.js")
script(src="https://code.jquery.com/jquery-1.11.1.js")
script.
// [START js]
$(function() {
/* The external ip is determined by app.js and passed into the template. */
var webSocketHost = location.protocol === 'https:' ? 'wss://' : 'ws://';
var externalIp = $('body').data('external-ip');
var webSocketUri = webSocketHost + externalIp + ':65080/echo';

/* Get elements from the page */
var form = $('#echo-form');
var textarea = $('#echo-text');
var output = $('#echo-response');
var status = $('#echo-status');

/* Helper to keep an activity log on the page. */
function log(text){
status.text(status.text() + text + '\n');
}

/* Establish the WebSocket connection and register event handlers. */
var websocket = new WebSocket(webSocketUri);

websocket.onopen = function() {
log('Connected');
};

websocket.onclose = function() {
log('Closed');
};

websocket.onmessage = function(e) {
log('Message received');
output.text(e.data);
};

websocket.onerror = function(e) {
log('Error (see console)');
console.log(e);
};

/* Handle form submission and send a message to the websocket. */
form.submit(function(e) {
e.preventDefault();
var data = textarea.val();
websocket.send(data);
// [START appengine_websockets_js]
$(function () {
var socket = io();
$('form').submit(function(){
console.log($('#m').val());
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
console.log(msg);
$('#messages').append($('<li>').text(msg));
window.scrollTo(0, document.body.scrollHeight);
});
});
// [END js]
// [END appengine_websockets_js]
Loading