-
Notifications
You must be signed in to change notification settings - Fork 2.9k
Pusher on App Engine sample #798
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
Changes from all commits
52f59ca
8e479d8
3112453
15466da
75395ea
609231b
bdff334
5aa731d
8ae81bb
707888b
5b956b8
88f00f4
3414941
a3967b5
bcf423f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
# Pusher sample for Google App Engine | ||
|
||
This sample demonstrates how to use the [Pusher][pusher] on [Google App Engine][ae-docs]. | ||
Pusher enables you to create public / private channels with presence information for real time messaging. | ||
This application demonstrates presence channels in Pusher using chat rooms. | ||
All users joining the chat room are authenticated using the `/authorize` endpoint. | ||
All users currently in the chat room receive updates of users joining / leaving the room. | ||
[Java HTTP library](https://github.com/pusher/pusher-http-java) is used for publishing messages to the channel | ||
and the [JS Websocket library](https://github.com/pusher/pusher-js) is used for subscribing. | ||
|
||
[pusher]: https://pusher.com | ||
[ae-docs]: https://cloud.google.com/appengine/docs/java/ | ||
|
||
## Setup | ||
|
||
Install the [Google Cloud SDK](https://cloud.google.com/sdk/) and run: | ||
``` | ||
gcloud init | ||
``` | ||
If this is your first time creating an App engine application: | ||
``` | ||
gcloud app create | ||
``` | ||
|
||
#### Setup Pusher | ||
|
||
- Create a [Pusher] application and note down the `app_id`, `app_key`, `app_secret` and the cluster. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is [Pusher] enough? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, that seems to work. |
||
- Update [appengine-web.xml](src/main/webapp/WEB-INF/appengine-web.xml) with these credentials. | ||
|
||
## Running locally | ||
|
||
``` | ||
mvn clean appengine:run | ||
``` | ||
|
||
Access [http://localhost:8080](http://localhost:8080) via the browser, login and join the chat room. | ||
The chat window will contain a link you can use to join the room as a different user in another browser. | ||
You should now be able to view both the users within the chat application window and send messages to one another. | ||
|
||
## Deploying | ||
|
||
- Deploy the application to the project | ||
``` | ||
mvn clean appengine:deploy | ||
|
||
``` | ||
Access `https://YOUR_PROJECT_ID.appspot.com` |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
<!-- | ||
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 | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
--> | ||
<project> | ||
<modelVersion>4.0.0</modelVersion> | ||
<packaging>war</packaging> | ||
<version>1.0-SNAPSHOT</version> | ||
<groupId>com.example.appengine</groupId> | ||
<artifactId>appengine-pusher-chat</artifactId> | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. parent pom? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ping There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd missed that, just added. |
||
<parent> | ||
<groupId>com.google.cloud</groupId> | ||
<artifactId>appengine-doc-samples</artifactId> | ||
<version>1.0.0</version> | ||
<relativePath>..</relativePath> | ||
</parent> | ||
|
||
<properties> | ||
<maven.compiler.source>1.7</maven.compiler.source> | ||
<maven.compiler.target>1.7</maven.compiler.target> | ||
</properties> | ||
|
||
<dependencies> | ||
<dependency> | ||
<groupId>com.pusher</groupId> | ||
<artifactId>pusher-http-java</artifactId> | ||
<version>1.0.0</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.guava</groupId> | ||
<artifactId>guava</artifactId> | ||
<version>20.0</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 22? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. for support with 1.7, 20.0 is the last version we can use. |
||
</dependency> | ||
<dependency> | ||
<groupId>com.fasterxml.jackson.core</groupId> | ||
<artifactId>jackson-databind</artifactId> | ||
<version>2.8.8</version> | ||
</dependency> | ||
<dependency> | ||
<groupId>javax.servlet</groupId> | ||
<artifactId>servlet-api</artifactId> | ||
<version>2.5</version> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 3.1 and target GAE Std J8? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As discussed offline |
||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>com.google.appengine</groupId> | ||
<artifactId>appengine-api-1.0-sdk</artifactId> | ||
<version>1.9.54</version> | ||
</dependency> | ||
</dependencies> | ||
|
||
<build> | ||
<!-- for hot reload of the web application --> | ||
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory> | ||
<plugins> | ||
<plugin> | ||
<groupId>com.google.cloud.tools</groupId> | ||
<artifactId>appengine-maven-plugin</artifactId> | ||
<version>1.3.1</version> | ||
</plugin> | ||
</plugins> | ||
</build> | ||
</project> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
/* | ||
* 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 | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.example.appengine.pusher; | ||
|
||
import com.google.appengine.api.users.User; | ||
import com.google.appengine.api.users.UserServiceFactory; | ||
import com.google.common.io.CharStreams; | ||
import com.pusher.rest.Pusher; | ||
import com.pusher.rest.data.PresenceUser; | ||
import java.io.IOException; | ||
import java.io.UnsupportedEncodingException; | ||
import java.net.URLDecoder; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
import javax.servlet.http.HttpServlet; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
|
||
/** | ||
* Authorization endpoint that is automatically triggered on `Pusher.subscribe` for private, | ||
* presence channels. Successful authentication returns valid authorization token with user | ||
* information. | ||
* | ||
* @see <a href="https://pusher.com/docs/authenticating_users">Pusher Authentication Docs</a> | ||
*/ | ||
// [START pusher_authorize] | ||
public class AuthorizeServlet extends HttpServlet { | ||
|
||
@Override | ||
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { | ||
|
||
// Instantiate a pusher connection | ||
Pusher pusher = PusherService.getDefaultInstance(); | ||
// Get current logged in user credentials | ||
User user = UserServiceFactory.getUserService().getCurrentUser(); | ||
|
||
// redirect to homepage if user is not authorized | ||
if (user == null) { | ||
response.sendRedirect("/"); | ||
return; | ||
} | ||
String currentUserId = user.getUserId(); | ||
String displayName = user.getNickname().replaceFirst("@.*", ""); | ||
|
||
String query = CharStreams.toString(request.getReader()); | ||
// socket_id, channel_name parameters are automatically set in the POST body of the request | ||
// eg.socket_id=1232.12&channel_name=presence-my-channel | ||
Map<String, String> data = splitQuery(query); | ||
String socketId = data.get("socket_id"); | ||
String channelId = data.get("channel_name"); | ||
|
||
// Presence channels (presence-*) require user identification for authentication | ||
Map<String, String> userInfo = new HashMap<>(); | ||
userInfo.put("displayName", displayName); | ||
|
||
// Inject custom authentication code for your application here to allow /deny current request | ||
|
||
String auth = | ||
pusher.authenticate(socketId, channelId, new PresenceUser(currentUserId, userInfo)); | ||
// if successful, returns authorization in the format | ||
// { | ||
// "auth":"49e26cb8e9dde3dfc009:a8cf1d3deefbb1bdc6a9d1547640d49d94b4b512320e2597c257a740edd1788f", | ||
// "channel_data":"{\"user_id\":\"23423435252\",\"user_info\":{\"displayName\":\"John Doe\"}}" | ||
// } | ||
|
||
response.getWriter().append(auth); | ||
} | ||
|
||
private static Map<String, String> splitQuery(String query) throws UnsupportedEncodingException { | ||
Map<String, String> query_pairs = new HashMap<>(); | ||
String[] pairs = query.split("&"); | ||
for (String pair : pairs) { | ||
int idx = pair.indexOf("="); | ||
query_pairs.put( | ||
URLDecoder.decode(pair.substring(0, idx), "UTF-8"), | ||
URLDecoder.decode(pair.substring(idx + 1), "UTF-8")); | ||
} | ||
return query_pairs; | ||
} | ||
} | ||
// [END pusher_authorize] |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* 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 | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.example.appengine.pusher; | ||
|
||
import com.google.appengine.api.users.User; | ||
import com.google.appengine.api.users.UserService; | ||
import com.google.appengine.api.users.UserServiceFactory; | ||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import javax.servlet.ServletException; | ||
import javax.servlet.http.HttpServlet; | ||
import javax.servlet.http.HttpServletRequest; | ||
import javax.servlet.http.HttpServletResponse; | ||
|
||
/** Homepage of chat application, redirects user to login page if not authorized. */ | ||
public class ChatServlet extends HttpServlet { | ||
|
||
public static String getUriWithChatRoom(HttpServletRequest request, String chatRoom) { | ||
try { | ||
String query = ""; | ||
if (chatRoom != null) { | ||
query = "room=" + chatRoom; | ||
} | ||
URI thisUri = new URI(request.getRequestURL().toString()); | ||
URI uriWithOptionalRoomParam = | ||
new URI( | ||
thisUri.getScheme(), | ||
thisUri.getUserInfo(), | ||
thisUri.getHost(), | ||
thisUri.getPort(), | ||
"/", | ||
query, | ||
""); | ||
return uriWithOptionalRoomParam.toString(); | ||
} catch (URISyntaxException e) { | ||
throw new RuntimeException(e); | ||
} | ||
} | ||
|
||
@Override | ||
public void doGet(HttpServletRequest req, HttpServletResponse resp) | ||
throws ServletException, IOException { | ||
final UserService userService = UserServiceFactory.getUserService(); | ||
User currentUser = userService.getCurrentUser(); | ||
String room = req.getParameter("room"); | ||
// Show login link if user is not logged in. | ||
if (currentUser == null) { | ||
String loginUrl = userService.createLoginURL(getUriWithChatRoom(req, room)); | ||
resp.getWriter().println("<p>Please <a href=\"" + loginUrl + "\">sign in</a>.</p>"); | ||
return; | ||
} | ||
|
||
// user is already logged in | ||
if (room != null) { | ||
req.setAttribute("room", room); | ||
} | ||
getServletContext().getRequestDispatcher("/WEB-INF/view/chat.jsp").forward(req, resp); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* 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 | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
|
||
package com.example.appengine.pusher; | ||
|
||
import com.pusher.rest.Pusher; | ||
|
||
// [START pusher_server_initialize] | ||
public abstract class PusherService { | ||
|
||
public static final String APP_KEY = System.getenv("PUSHER_APP_KEY"); | ||
public static final String CLUSTER = System.getenv("PUSHER_CLUSTER"); | ||
|
||
private static final String APP_ID = System.getenv("PUSHER_APP_ID"); | ||
private static final String APP_SECRET = System.getenv("PUSHER_APP_SECRET"); | ||
|
||
private static Pusher instance; | ||
|
||
static Pusher getDefaultInstance() { | ||
if (instance != null) { | ||
return instance; | ||
} // Instantiate a pusher | ||
Pusher pusher = new Pusher(APP_ID, APP_KEY, APP_SECRET); | ||
pusher.setCluster(CLUSTER); // required, if not default mt1 (us-east-1) | ||
pusher.setEncrypted(true); // optional, ensure subscriber also matches these settings | ||
instance = pusher; | ||
return pusher; | ||
} | ||
} | ||
// [END pusher_server_initialize] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this go in J8
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As discussed, I will leave it here for now, but will submit a separate PR for J8