10000 Web Socket Sample - JSR-356 (#1280) · Vali5681/java-docs-samples@4d06536 · GitHub
[go: up one dir, main page]

Skip to content

Commit 4d06536

Browse files
authored
Web Socket Sample - JSR-356 (GoogleCloudPlatform#1280)
* copy over sample * update maven dependency * Update parent dependency
1 parent 4d65354 commit 4d06536

File tree

8 files changed

+563
-0
lines changed

8 files changed

+563
-0
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# App Engine Flexible Environment - Web Socket Example
2+
This sample demonstrates how to use [Websockets](https://tools.ietf.org/html/rfc6455) on [Google App Engine Flexible Environment](https://cloud.google.com/appengine/docs/flexible/java/) using Java.
3+
The sample uses the [JSR-356](https://www.jcp.org/en/jsr/detail?id=356) Java API for the Websocket [client](https://mvnrepository.com/artifact/org.eclipse.jetty.websocket/javax-websocket-client-impl).
4+
5+
## Sample application workflow
6+
7+
1. The sample application creates a server socket using the endpoint `/echo`.
8+
1. The homepage (`/`) provides a form to submit a text message to the server socket. This creates a client-side socket
9+
and sends the message to the server.
10+
1. The server on receiving the message, echoes the message back to the client.
11+
1. The message received by the client is stored in an in-memory cache and is viewable on the homepage.
12+
13+
The sample also provides a Javascript [client](src/main/webapp/js_client.jsp)(`/js_client.jsp`) that you can use to test against the Websocket server.
14+
15+
## Setup
16+
17+
- [Install](https://cloud.google.com/sdk/) and initialize GCloud SDK. This will
18+
```
19+
gcloud init
20+
```
21+
- If this is your first time creating an app engine application
22+
```
23+
gcloud appengine create
24+
```
25+
26+
## Local testing
27+
28+
Run using the [Jetty Maven plugin](http://www.eclipse.org/jetty/documentation/9.4.x/jetty-maven-plugin.html).
29+
```
30+
mvn jetty:run
31+
```
32+
You can then direct your browser to `http://localhost:8080/`
33+
34+
To test the Javascript client, access `http://localhost:8080/js_client.jsp`
35+
36+
## App Engine Flex Deployment
37+
38+
#### `app.yaml` Configuration
39+
40+
App Engine Flex deployment configuration is provided in [app.yaml](src/main/appengine/app.yaml).
41+
42+
Set the environment variable `JETTY_MODULES_ENABLE:websocket` to enable the Jetty websocket module on the Jetty server.
43+
44+
Manual scaling is set to a single instance as we are using an in-memory cache of messages for this sample application.
45+
46+
For more details on configuring your `app.yaml`, please refer to [this resource](https://cloud.google.com/appengine/docs/flexible/nodejs/configuring-your-app-with-app-yaml).
47+
48+
#### Deploy
49+
50+
The sample application is packaged as a war, and hence will be automatically run using the [Java 8/Jetty 9 with Servlet 3.1 Runtime](https://cloud.google.com/appengine/docs/flexible/java/dev-jetty9).
51+
52+
```
53+
mvn appengine:deploy
54+
```
55+
You can then direct your browser to `https://YOUR_PROJECT_ID.appspot.com/`
56+
57+
To test the Javascript client, access `https://YOUR_PROJECT_ID.appspot.com/js_client.jsp`
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
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+
<project>
17+
<modelVersion>4.0.0</modelVersion>
18+
<version>1.0-SNAPSHOT</version>
19+
<groupId>com.example.flexible</groupId>
20+
<artifactId>appengine-websocket-jsr356</artifactId>
21+
<packaging>war</packaging>
22+
23+
<!--
24+
The parent pom defines common style checks and testing strategies for our samples.
25+
Removing or replacing it should not effect the execution of the samples in anyway.
26+
-->
27+
<parent>
28+
<groupId>com.google.cloud.samples</groupId>
29+
<artifactId>shared-configuration</artifactId>
30+
<version>1.0.10</version>
31+
</parent>
32+
33+
<properties>
34+
<maven.compiler.target>1.8</maven.compiler.target>
35+
<maven.compiler.source>1.8</maven.compiler.source>
36+
<failOnMissingWebXml>false</failOnMissingWebXml> <!-- REQUIRED -->
37+
<appengine.maven.plugin>1.3.2</appengine.maven.plugin>
38+
<jetty.version>9.4.4.v20170414</jetty.version>
39+
</properties>
40+
41+
<dependencies>
42+
<dependency>
43+
<groupId>javax.servlet</groupId>
44+
<artifactId>javax.servlet-api</artifactId>
45+
<version>3.1.0</version>
46+
<type>jar</type>
47+
<scope>provided</scope>
48+
</dependency>
49+
<!--Jetty JSR-356 Websocket client side dependency-->
50+
<dep 10000 endency>
51+
<groupId>org.eclipse.jetty.websocket</groupId>
52+
<artifactId>javax-websocket-client-impl</artifactId>
53+
<version>${jetty.version}</version>
54+
</dependency>
55+
<!--Optional dependency for SettableFuture, see com.example.flexible.websocket.jsr356.ServerSocket -->
56+
<dependency>
57+
<groupId>com.google.guava</groupId>
58+
<artifactId>guava</artifactId>
59+
<version>20.0</version>
60+
</dependency>
61+
</dependencies>
62+
63+
<build>
64+
<!-- for hot reload of the web application -->
65+
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes
66+
</outputDirectory>
67+
<plugins>
68+
<!-- for deployment of web application -->
69+
<plugin>
70+
<groupId>com.google.cloud.tools</groupId>
71+
<artifactId>appengine-maven-plugin</artifactId>
72+
<version>${appengine.maven.plugin}</version>
73+
<configuration>
74+
</configuration>
75+
</plugin>
76+
<!-- for local testing of web application -->
77+
<plugin>
78+
<groupId>org.eclipse.jetty</groupId>
79+
<artifactId>jetty-maven-plugin</artifactId>
80+
<version>${jetty.version}</version>
81+
</plugin>
82+
</plugins>
83+
</build>
84+
</project>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
runtime: java
16+
env: flex
17+
manual_scaling:
18+
instances: 1
19+
20+
handlers:
21+
- url: /.*
22+
script: this field is required, but ignored
23+
24+
env_variables:
25+
JETTY_MODULES_ENABLE: websocket
26+
27+
28+
# For applications which can take advantage of session affinity
29+
# (where the load balancer will attempt to route multiple connections from
30+
# the same user to the same App Engine instance), uncomment the folowing:
31+
32+
# network:
33+
# session_affinity: true
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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+
17+
package com.example.flexible.websocket.jsr356;
18+
19+
import com.google.common.util.concurrent.SettableFuture;
20+
21+
import java.net.URI;
22+
import java.util.Collection;
23+
import java.util.Collections;
24+
import java.util.concurrent.ConcurrentLinkedDeque;
25+
import java.util.concurrent.ExecutionException;
26+
import java.util.logging.Logger;
27+
import javax.websocket.ClientEndpoint;
28+
import javax.websocket.CloseReason;
29+
import javax.websocket.ContainerProvider;
30+
import javax.websocket.OnClose;
31+
import javax.websocket.OnError;
32+
import javax.websocket.OnMessage;
33+
import javax.websocket.OnOpen;
34+
import javax.websocket.Session;
35+
import javax.websocket.WebSocketContainer;
36+
37+
/**
38+
* Web socket client example using JSR-356 Java WebSocket API. Sends a message to the server, and
39+
* stores the echoed messages received from the server.
40+
*/
41+
@ClientEndpoint
42+
public class ClientSocket {
43+
44+
private static final Logger logger = Logger.getLogger(ClientSocket.class.getName());
45+
46+
// stores the messages in-memory.
47+
// Note : this is currently an in-memory store for demonstration,
48+
// not recommended for production use-cases.
49+
private static Collection<String> messages = new ConcurrentLinkedDeque<>();
50+
51+
private SettableFuture<Boolean> future = SettableFuture.create();
52+
private Session session;
53+
54+
ClientSocket(URI endpointUri) {
55+
try {
56+
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
57+
session = container.connectToServer(this, endpointUri);
58+
} catch (Exception e) {
59+
throw new RuntimeException(e);
60+
}
61+
}
62+
63+
@OnOpen
64+
public void onOpen(Session session) {
65+
future.set(true);
66+
}
67+
68+
/**
69+
* Handles message received from the server.
70+
* @param message server message in String format
71+
* @param session current session
72+
*/
73+
@OnMessage
74+
public void onMessage(String message, Session session) {
75+
logger.fine("Received message from server : " + message);
76+
messages.add(message);
77+
}
78+
79+
boolean waitOnOpen() throws InterruptedException, ExecutionException {
80+
// wait on handling onOpen
81+
boolean opened = future.get();
82+
logger.fine("Connected to server");
83+
return opened;
84+
}
85+
86+
@OnClose
87+
public void onClose(CloseReason reason, Session session) {
88+
logger.fine("Closing Web Socket: " + reason.getReasonPhrase());
89+
}
90+
91+
void sendMessage(String str) {
92+
try {
93+
// Send a message to the server
94+
logger.fine("Sending message : " + str);
95+
session.getAsyncRemote().sendText(str);
96+
} catch (Exception e) {
97+
logger.severe("Error sending message : " + e.getMessage());
98+
}
99+
}
100+
101+
// Retrieve all received messages.
102+
public static Collection<String> getReceivedMessages() {
103+
return Collections.unmodifiableCollection(messages);
104+
}
105+
106+
@OnError
107+
public void logErrors(Throwable t) {
108+
logger.severe(t.getMessage());
109+
}
110+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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+
17+
package com.example.flexible.websocket.jsr356;
18+
19+
import com.google.common.base.Preconditions;
20+
import java.io.IOException;
21+
import java.net.URI;
22+
import java.net.URISyntaxException;
23+
import java.util.logging.Logger;
24+
import javax.servlet.annotation.WebServlet;
25+
import javax.servlet.http.HttpServlet;
26+
import javax.servlet.http.HttpServletRequest;
27+
import javax.servlet.http.HttpServletResponse;
28+
import org.eclipse.jetty.http.HttpStatus;
29+
30+
@WebServlet("/send")
31+
/** Servlet that converts the message sent over POST to be over websocket. */
32+
public class SendServlet extends HttpServlet {
33+
34+
private Logger logger = Logger.getLogger(SendServlet.class.getName());
35+
private final String webSocketAddress = ServerSocket.getWebSocketAddress();
36+
private ClientSocket clientSocket;
37+
38+
private void initializeWebSocket() throws Exception {
39+
clientSocket = new ClientSocket(new URI(webSocketAddress));
40+
clientSocket.waitOnOpen();
41+
logger.info("REST service: open websocket client at " + webSocketAddress);
42+
}
43+
44+
private void sendMessageOverWebSocket(String message) throws Exception {
45+
if (clientSocket == null) {
46+
try {
47+
initializeWebSocket();
48+
} catch (URISyntaxException e) {
49+
e.printStackTrace();
50+
}
51+
}
52+
clientSocket.sendMessage(message);
53+
}
54+
55+
@Override
56+
public void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException {
57+
String message = request.getParameter("message");
58+
Preconditions.checkNotNull(message);
59+
try {
60+
sendMessageOverWebSocket(message);
61+
response.sendRedirect("/");
62+
} catch (Exception e) {
63+
e.printStackTrace(response.getWriter());
64+
response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR_500);
65+
}
66+
}
67+
}

0 commit comments

Comments
 (0)
0