Skip to content

Commit c9de8e5

Browse files
authored
Pusher on App Engine sample (GoogleCloudPlatform#798)
1 parent 7949498 commit c9de8e5

File tree

11 files changed

+666
-0
lines changed

11 files changed

+666
-0
lines changed

appengine/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
<module>memcache</module>
7373
<module>multitenancy</module>
7474
<module>oauth2</module>
75+
<module>pusher-chat</module>
7576
<module>requests</module>
7677
<module>search</module>
7778
<module>sendgrid</module>

appengine/pusher-chat/README.md

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Pusher sample for Google App Engine
2+
3+
This sample demonstrates how to use the [Pusher][pusher] on [Google App Engine][ae-docs].
4+
Pusher enables you to create public / private channels with presence information for real time messaging.
5+
This application demonstrates presence channels in Pusher using chat rooms.
6+
All users joining the chat room are authenticated using the `/authorize` endpoint.
7+
All users currently in the chat room receive updates of users joining / leaving the room.
8+
[Java HTTP library](https://github.com/pusher/pusher-http-java) is used for publishing messages to the channel
9+
and the [JS Websocket library](https://github.com/pusher/pusher-js) is used for subscribing.
10+
11+
[pusher]: https://pusher.com
12+
[ae-docs]: https://cloud.google.com/appengine/docs/java/
13+
14+
## Setup
15+
16+
Install the [Google Cloud SDK](https://cloud.google.com/sdk/) and run:
17+
```
18+
gcloud init
19+
```
20+
If this is your first time creating an App engine application:
21+
```
22+
gcloud app create
23+
```
24+
25+
#### Setup Pusher
26+
27+
- Create a [Pusher] application and note down the `app_id`, `app_key`, `app_secret` and the cluster.
28+
- Update [appengine-web.xml](src/main/webapp/WEB-INF/appengine-web.xml) with these credentials.
29+
30+
## Running locally
31+
32+
```
33+
mvn clean appengine:run
34+
```
35+
36+
Access [http://localhost:8080](http://localhost:8080) via the browser, login and join the chat room.
37+
The chat window will contain a link you can use to join the room as a different user in another browser.
38+
You should now be able to view both the users within the chat application window and send messages to one another.
39+
40+
## Deploying
41+
42+
- Deploy the application to the project
43+
```
44+
mvn clean appengine:deploy
45+
46+
```
47+
Access `https://YOUR_PROJECT_ID.appspot.com`

appengine/pusher-chat/pom.xml

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!--
2+
Copyright 2017 Google Inc.
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+
<project>
17+
<modelVersion>4.0.0</modelVersion>
18+
<packaging>war</packaging>
19+
<version>1.0-SNAPSHOT</version>
20+
<groupId>com.example.appengine</groupId>
21+
<artifactId>appengine-pusher-chat</artifactId>
22+
23+
<parent>
24+
<groupId>com.google.cloud</groupId>
25+
<artifactId>appengine-doc-samples</artifactId>
26+
<version>1.0.0</version>
27+
<relativePath>..</relativePath>
28+
</parent>
29+
30+
<properties>
31+
<maven.compiler.source>1.7</maven.compiler.source>
32+
<maven.compiler.target>1.7</maven.compiler.target>
33+
</properties>
34+
35+
<dependencies>
36+
<dependency>
37+
<groupId>com.pusher</groupId>
38+
<artifactId>pusher-http-java</artifactId>
39+
<version>1.0.0</version>
40+
</dependency>
41+
<dependency>
42+
<groupId>com.google.guava</groupId>
43+
<artifactId>guava</artifactId>
44+
<version>20.0</version>
45+
</dependency>
46+
<dependency>
47+
<groupId>com.fasterxml.jackson.core</groupId>
48+
<artifactId>jackson-databind</artifactId>
49+
<version>2.8.8</version>
50+
</dependency>
51+
<dependency>
52+
<groupId>javax.servlet</groupId>
53+
<artifactId>servlet-api</artifactId>
54+
<version>2.5</version>
55+
<scope>provided</scope>
56+
</dependency>
57+
<dependency>
58+
<groupId>com.google.appengine</groupId>
59+
<artifactId>appengine-api-1.0-sdk</artifactId>
60+
<version>1.9.54</version>
61+
</dependency>
62+
</dependencies>
63+
64+
<build>
65+
<!-- for hot reload of the web application -->
66+
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
67+
<plugins>
68+
<plugin>
69+
<groupId>com.google.cloud.tools</groupId>
70+
<artifactId>appengine-maven-plugin</artifactId>
71+
<version>1.3.1</version>
72+
</plugin>
73+
</plugins>
74+
</build>
75+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright 2017 Google Inc.
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+
17+
package com.example.appengine.pusher;
18+
19+
import com.google.appengine.api.users.User;
20+
import com.google.appengine.api.users.UserServiceFactory;
21+
import com.google.common.io.CharStreams;
22+
import com.pusher.rest.Pusher;
23+
import com.pusher.rest.data.PresenceUser;
24+
import java.io.IOException;
25+
import java.io.UnsupportedEncodingException;
26+
import java.net.URLDecoder;
27+
import java.util.HashMap;
28+
import java.util.Map;
29+
import javax.servlet.http.HttpServlet;
30+
import javax.servlet.http.HttpServletRequest;
31+
import javax.servlet.http.HttpServletResponse;
32+
33+
/**
34+
* Authorization endpoint that is automatically triggered on `Pusher.subscribe` for private,
35+
* presence channels. Successful authentication returns valid authorization token with user
36+
* information.
37+
*
38+
* @see <a href="https://pusher.com/docs/authenticating_users">Pusher Authentication Docs</a>
39+
*/
40+
// [START pusher_authorize]
41+
public class AuthorizeServlet extends HttpServlet {
42+
43+
@Override
44+
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
45+
46+
// Instantiate a pusher connection
47+
Pusher pusher = PusherService.getDefaultInstance();
48+
// Get current logged in user credentials
49+
User user = UserServiceFactory.getUserService().getCurrentUser();
50+
51+
// redirect to homepage if user is not authorized
52+
if (user == null) {
53+
response.sendRedirect("/");
54+
return;
55+
}
56+
String currentUserId = user.getUserId();
57+
String displayName = user.getNickname().replaceFirst("@.*", "");
58+
59+
String query = CharStreams.toString(request.getReader());
60+
// socket_id, channel_name parameters are automatically set in the POST body of the request
61+
// eg.socket_id=1232.12&channel_name=presence-my-channel
62+
Map<String, String> data = splitQuery(query);
63+
String socketId = data.get("socket_id");
64+
String channelId = data.get("channel_name");
65+
66+
// Presence channels (presence-*) require user identification for authentication
67+
Map<String, String> userInfo = new HashMap<>();
68+
userInfo.put("displayName", displayName);
69+
70+
// Inject custom authentication code for your application here to allow /deny current request
71+
72+
String auth =
73+
pusher.authenticate(socketId, channelId, new PresenceUser(currentUserId, userInfo));
74+
// if successful, returns authorization in the format
75+
// {
76+
// "auth":"49e26cb8e9dde3dfc009:a8cf1d3deefbb1bdc6a9d1547640d49d94b4b512320e2597c257a740edd1788f",
77+
// "channel_data":"{\"user_id\":\"23423435252\",\"user_info\":{\"displayName\":\"John Doe\"}}"
78+
// }
79+
80+
response.getWriter().append(auth);
81+
}
82+
83+
private static Map<String, String> splitQuery(String query) throws UnsupportedEncodingException {
84+
Map<String, String> query_pairs = new HashMap<>();
85+
String[] pairs = query.split("&");
86+
for (String pair : pairs) {
87+
int idx = pair.indexOf("=");
88+
query_pairs.put(
89+
URLDecoder.decode(pair.substring(0, idx), "UTF-8"),
90+
URLDecoder.decode(pair.substring(idx + 1), "UTF-8"));
91+
}
92+
return query_pairs;
93+
}
94+
}
95+
// [END pusher_authorize]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Copyright 2017 Google Inc.
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+
17+
package com.example.appengine.pusher;
18+
19+
import com.google.appengine.api.users.User;
20+
import com.google.appengine.api.users.UserService;
21+
import com.google.appengine.api.users.UserServiceFactory;
22+
import java.io.IOException;
23+
import java.net.URI;
24+
import java.net.URISyntaxException;
25+
import javax.servlet.ServletException;
26+
import javax.servlet.http.HttpServlet;
27+
import javax.servlet.http.HttpServletRequest;
28+
import javax.servlet.http.HttpServletResponse;
29+
30+
/** Homepage of chat application, redirects user to login page if not authorized. */
31+
public class ChatServlet extends HttpServlet {
32+
33+
public static String getUriWithChatRoom(HttpServletRequest request, String chatRoom) {
34+
try {
35+
String query = "";
36+
if (chatRoom != null) {
37+
query = "room=" + chatRoom;
38+
}
39+
URI thisUri = new URI(request.getRequestURL().toString());
40+
URI uriWithOptionalRoomParam =
41+
new URI(
42+
thisUri.getScheme(),
43+
thisUri.getUserInfo(),
44+
thisUri.getHost(),
45+
thisUri.getPort(),
46+
"/",
47+
query,
48+
"");
49+
return uriWithOptionalRoomParam.toString();
50+
} catch (URISyntaxException e) {
51+
throw new RuntimeException(e);
52+
}
53+
}
54+
55+
@Override
56+
public void doGet(HttpServletRequest req, HttpServletResponse resp)
57+
throws ServletException, IOException {
58+
final UserService userService = UserServiceFactory.getUserService();
59+
User currentUser = userService.getCurrentUser();
60+
String room = req.getParameter("room");
61+
// Show login link if user is not logged in.
62+
if (currentUser == null) {
63+
String loginUrl = userService.createLoginURL(getUriWithChatRoom(req, room));
64+
resp.getWriter().println("<p>Please <a href=\"" + loginUrl + "\">sign in</a>.</p>");
65+
return;
66+
}
67+
68+
// user is already logged in
69+
if (room != null) {
70+
req.setAttribute("room", room);
71+
}
72+
getServletContext().getRequestDispatcher("/WEB-INF/view/chat.jsp").forward(req, resp);
73+
}
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/*
2+
* Copyright 2017 Google Inc.
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+
17+
package com.example.appengine.pusher;
18+
19+
import com.pusher.rest.Pusher;
20+
21+
// [START pusher_server_initialize]
22+
public abstract class PusherService {
23+
24+
public static final String APP_KEY = System.getenv("PUSHER_APP_KEY");
25+
public static final String CLUSTER = System.getenv("PUSHER_CLUSTER");
26+
27+
private static final String APP_ID = System.getenv("PUSHER_APP_ID");
28+
private static final String APP_SECRET = System.getenv("PUSHER_APP_SECRET");
29+
30+
private static Pusher instance;
31+
32+
static Pusher getDefaultInstance() {
33+
if (instance != null) {
34+
return instance;
35+
} // Instantiate a pusher
36+
Pusher pusher = new Pusher(APP_ID, APP_KEY, APP_SECRET);
37+
pusher.setCluster(CLUSTER); // required, if not default mt1 (us-east-1)
38+
pusher.setEncrypted(true); // optional, ensure subscriber also matches these settings
39+
instance = pusher;
40+
return pusher;
41+
}
42+
}
43+
// [END pusher_server_initialize]

0 commit comments

Comments
 (0)