Skip to content

Commit 23eb21b

Browse files
committed
t push origin atmosphere-2.4.xMerge branch 'shanielh-atmosphere-2.4.x' into atmosphere-2.4.x
2 parents f05da20 + f7242e3 commit 23eb21b

File tree

8 files changed

+337
-189
lines changed

8 files changed

+337
-189
lines changed

modules/cpr/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
<dependency>
9292
<groupId>org.eclipse.jetty.websocket</groupId>
9393
<artifactId>websocket-server</artifactId>
94-
<version>${jetty9-version}</version>
94+
<version>${jetty9_3-version}</version>
9595
<scope>provided</scope>
9696
<optional>true</optional>
9797
<exclusions>

modules/cpr/src/main/java/org/atmosphere/cache/UUIDBroadcasterCache.java

Lines changed: 63 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import org.slf4j.Logger;
2525
import org.slf4j.LoggerFactory;
2626

27+
import java.io.Serializable;
2728
import java.util.ArrayList;
2829
import java.util.Collections;
2930
import java.util.HashSet;
@@ -52,7 +53,7 @@ public class UUIDBroadcasterCache implements BroadcasterCache {
5253

5354
private final static Logger logger = LoggerFactory.getLogger(UUIDBroadcasterCache.class);
5455

55-
private final Map<String, ConcurrentLinkedQueue> messages = new ConcurrentHashMap<String, ConcurrentLinkedQueue>();
56+
private final Map<String, ClientQueue> messages = new ConcurrentHashMap<String, ClientQueue>();
5657
private final Map<String, Long> activeClients = new ConcurrentHashMap<String, Long>();
5758
protected final List<BroadcasterCacheInspector> inspectors = new LinkedList<BroadcasterCacheInspector>();
5859
private ScheduledFuture scheduledFuture;
@@ -64,6 +65,30 @@ public class UUIDBroadcasterCache implements BroadcasterCache {
6465
protected final List<BroadcasterCacheListener> listeners = new LinkedList<BroadcasterCacheListener>();
6566
private UUIDProvider uuidProvider;
6667

68+
/**
69+
* This class wraps all messages to be delivered to a client. The class is thread safe to be accessed in a
70+
* concurrent context.
71+
*/
72+
public final static class ClientQueue implements Serializable {
73+
private static final long serialVersionUID = -126253550299206646L;
74+
75+
private final ConcurrentLinkedQueue<CacheMessage> queue = new ConcurrentLinkedQueue<CacheMessage>();
76+
private final Set<String> ids = Collections.synchronizedSet(new HashSet<String>());
77+
78+
public ConcurrentLinkedQueue<CacheMessage> getQueue() {
79+
return queue;
80+
}
81+
82+
public Set<String> getIds() {
83+
return ids;
84+
}
85+
86+
@Override
87+
public String toString() {
88+
return queue.toString();
89+
}
90+
}
91+
6792
@Override
6893
public void configure(AtmosphereConfig config) {
6994
Object o = config.properties().get("shared");
@@ -131,7 +156,7 @@ public CacheMessage addToCache(String broadcasterId, String uuid, BroadcastMessa
131156
cache = false;
132157
}
133158

134-
CacheMessage cacheMessage = new CacheMessage(messageId, message.message(), uuid);
159+
CacheMessage cacheMessage = new CacheMessage(messageId, message.message(), uuid);;
135160
if (cache) {
136161
if (uuid.equals(NULL)) {
137162
//no clients are connected right now, caching message for all active clients
@@ -149,33 +174,38 @@ public CacheMessage addToCache(String broadcasterId, String uuid, BroadcastMessa
149174
@Override
150175
public List<Object> retrieveFromCache(String broadcasterId, String uuid) {
151176

152-
ConcurrentLinkedQueue<CacheMessage> clientQueue = messages.get(uuid);
153-
CacheMessage message;
154177
List<Object> result = new ArrayList<Object>();
155-
if (clientQueue == null) {
156-
logger.debug("client queue is null (not yet created), hence returning back for broadcaster %s and uuid %s",
157-
broadcasterId, uuid);
158-
return result;
178+
179+
ClientQueue clientQueue;
180+
cacheCandidate(broadcasterId, uuid);
181+
clientQueue = messages.remove(uuid);
182+
ConcurrentLinkedQueue<CacheMessage> clientMessages;
183+
if (clientQueue != null) {
184+
clientMessages = clientQueue.getQueue();
185+
186+
for (CacheMessage cacheMessage : clientMessages) {
187+
result.add(cacheMessage.getMessage());
188+
}
159189
}
160190

161-
while ((message = clientQueue.poll()) != null) {
162-
result.add(message);
191+
if (logger.isTraceEnabled()) {
192+
logger.trace("Retrieved for AtmosphereResource {} cached messages {}", uuid, result);
193+
logger.trace("Available cached message {}", messages);
163194
}
164195

165196
return result;
166197
}
167198

168199
@Override
169200
public BroadcasterCache clearCache(String broadcasterId, String uuid, CacheMessage message) {
170-
ConcurrentLinkedQueue<CacheMessage> clientQueue = messages.remove(uuid);
171-
172-
if (clientQueue == null) {
173-
logger.error("Invalid State, no Queue available");
174-
return this;
201+
ClientQueue clientQueue;
202+
clientQueue = messages.get(uuid);
203+
if (clientQueue != null) {
204+
logger.trace("Removing for AtmosphereResource {} cached message {}", uuid, message.getMessage());
205+
notifyRemoveCache(broadcasterId, message);
206+
clientQueue.getQueue().remove(message);
207+
clientQueue.getIds().remove(message.getId());
175208
}
176-
177-
logger.trace("Removing for AtmosphereResource {} cached message {}", uuid, message.getMessage());
178-
notifyRemoveCache(broadcasterId, message);
179209
return this;
180210
}
181211

@@ -210,22 +240,21 @@ private void addMessageIfNotExists(String broadcasterId, String clientId, CacheM
210240
}
211241

212242
private void addMessage(String broadcasterId, String clientId, CacheMessage message) {
213-
ConcurrentLinkedQueue clientQueue = messages.get(clientId);
243+
ClientQueue clientQueue = messages.get(clientId);
214244
if (clientQueue == null) {
215-
synchronized (message) {
216-
clientQueue = new ConcurrentLinkedQueue();
217-
// Make sure the client is not in the process of being invalidated
218-
if (activeClients.get(clientId) != null) {
219-
messages.put(clientId, clientQueue);
220-
} else {
221-
// The entry has been invalidated
222-
logger.debug("Client {} is no longer active. Not caching message {}}", clientId, message);
223-
return;
224-
}
245+
clientQueue = new ClientQueue();
246+
// Make sure the client is not in the process of being invalidated
247+
if (activeClients.get(clientId) != null) {
248+
messages.put(clientId, clientQueue);
249+
} else {
250+
// The entry has been invalidated
251+
logger.debug("Client {} is no longer active. Not caching message {}}", clientId, message);
252+
return;
225253
}
226254
}
227255
notifyAddCache(broadcasterId, message);
228-
messages.put(clientId, clientQueue);
256+
clientQueue.getQueue().offer(message);
257+
clientQueue.getIds().add(message.getId());
229258
}
230259

231260
private void notifyAddCache(String broadcasterId, CacheMessage message) {
@@ -249,11 +278,11 @@ private void notifyRemoveCache(String broadcasterId, CacheMessage message) {
249278
}
250279

251280
private boolean hasMessage(String clientId, String messageId) {
252-
ConcurrentLinkedQueue clientQueue = messages.get(clientId);
253-
return clientQueue != null && clientQueue.contains(messageId);
281+
ClientQueue clientQueue = messages.get(clientId);
282+
return clientQueue != null && clientQueue.getIds().contains(messageId);
254283
}
255284

256-
public Map<String, ConcurrentLinkedQueue> messages() {
285+
public Map<String, ClientQueue> messages() {
257286
return messages;
258287
}
259288

@@ -296,7 +325,7 @@ protected void invalidateExpiredEntries() {
296325

297326
for (String msg : messages().keySet()) {
298327
if (!activeClients().containsKey(msg)) {
299-
messages.remove(msg);
328+
messages().remove(msg);
300329
}
301330
}
302331
}
Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Copyright 2017 Async-IO.org
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy of
6+
* 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, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations under
14+
* the License.
15+
*/
16+
package org.atmosphere.container;
17+
18+
import org.atmosphere.cpr.Action;
19+
import org.atmosphere.cpr.ApplicationConfig;
20+
import org.atmosphere.cpr.AtmosphereConfig;
21+
import org.atmosphere.cpr.AtmosphereRequest;
22+
import org.atmosphere.cpr.AtmosphereResource;
23+
import org.atmosphere.cpr.AtmosphereResponse;
24+
import org.atmosphere.util.Utils;
25+
import org.atmosphere.websocket.WebSocket;
26+
import org.atmosphere.websocket.WebSocketProcessor;
27+
import org.eclipse.jetty.websocket.api.UpgradeRequest;
28+
import org.eclipse.jetty.websocket.api.UpgradeResponse;
29+
import org.eclipse.jetty.websocket.api.WebSocketBehavior;
30+
import org.eclipse.jetty.websocket.api.WebSocketPolicy;
31+
import org.eclipse.jetty.websocket.server.ServletWebSocketRequest;
32+
import org.eclipse.jetty.websocket.server.WebSocketServerFactory;
33+
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
34+
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
35+
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
36+
import org.slf4j.Logger;
37+
import org.slf4j.LoggerFactory;
38+
39+
import javax.servlet.ServletException;
40+
import javax.servlet.http.HttpServletRequest;
41+
import javax.servlet.http.HttpServletResponse;
42+
import java.io.IOException;
43+
import java.lang.reflect.Method;
44+
45+
abstract class AbstractJetty9AsyncSupportWithWebSocket extends Servlet30CometSupport {
46+
47+
private static final Logger staticLogger = LoggerFactory.getLogger(AbstractJetty9AsyncSupportWithWebSocket.class);
48+
49+
private final Logger logger;
50+
51+
52+
static WebSocketPolicy GetPolicy(final AtmosphereConfig config) {
53+
String bs = config.getInitParameter(ApplicationConfig.WEBSOCKET_BUFFER_SIZE);
54+
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
55+
if (bs != null) {
56+
policy.setInputBufferSize(Integer.parseInt(bs));
57+
}
58+
59+
String max = config.getInitParameter(ApplicationConfig.WEBSOCKET_IDLETIME);
60+
if (max != null) {
61+
policy.setIdleTimeout(Integer.parseInt(max));
62+
}
63+
64+
try {
65+
// Crazy Jetty API Incompatibility
66+
String serverInfo = config.getServletConfig().getServletContext().getServerInfo();
67+
boolean isJetty91Plus = false;
68+
if (serverInfo != null) {
69+
int version = Integer.valueOf(serverInfo.split("/")[1].substring(0, 3).replace(".", ""));
70+
isJetty91Plus = version > 90;
71+
}
72+
73+
max = config.getInitParameter(ApplicationConfig.WEBSOCKET_MAXTEXTSIZE);
74+
if (max != null) {
75+
//policy.setMaxMessageSize(Integer.parseInt(max));
76+
Method m;
77+
if (isJetty91Plus) {
78+
m = policy.getClass().getMethod("setMaxTextMessageSize", int.class);
79+
} else {
80+
m = policy.getClass().getMethod("setMaxMessageSize", long.class);
81+
}
82+
m.invoke(policy, Integer.parseInt(max));
83+
}
84+
85+
max = config.getInitParameter(ApplicationConfig.WEBSOCKET_MAXBINARYSIZE);
86+
if (max != null) {
87+
//policy.setMaxMessageSize(Integer.parseInt(max));
88+
Method m;
89+
if (isJetty91Plus) {
90+
m = policy.getClass().getMethod("setMaxBinaryMessageSize", int.class);
91+
} else {
92+
m = policy.getClass().getMethod("setMaxMessageSize", long.class);
93+
}
94+
m.invoke(policy, Integer.parseInt(max));
95+
}
96+
} catch (Exception ex) {
97+
staticLogger.warn("", ex);
98+
}
99+
100+
return policy;
101+
}
102+
103+
AbstractJetty9AsyncSupportWithWebSocket(AtmosphereConfig config, Logger logger) {
104+
super(config);
105+
this.logger = logger;
106+
}
107+
108+
WebSocketCreator buildCreator(final HttpServletRequest request, final HttpServletResponse response, final WebSocketProcessor webSocketProcessor) {
109+
return new WebSocketCreator() {
110+
111+
// @Override 9.0.x
112+
public Object createWebSocket(UpgradeRequest upgradeRequest, UpgradeResponse upgradeResponse) {
113+
114+
ServletWebSocketRequest r = ServletWebSocketRequest.class.cast(upgradeRequest);
115+
r.getExtensions().clear();
116+
117+
if (!webSocketProcessor.handshake(request)) {
118+
try {
119+
response.sendError(HttpServletResponse.SC_FORBIDDEN, "WebSocket requests rejected.");
120+
} catch (IOException e) {
121+
logger.trace("", e);
122+
}
123+
return null;
124+
}
125+
126+
return new Jetty9WebSocketHandler(request, config.framework(), webSocketProcessor);
127+
}
128+
129+
// @Override 9.1.x
130+
public Object createWebSocket(ServletUpgradeRequest req, ServletUpgradeResponse resp) {
131+
req.getExtensions().clear();
132+
133+
if (!webSocketProcessor.handshake(request)) {
134+
try {
135+
response.sendError(HttpServletResponse.SC_FORBIDDEN, "WebSocket requests rejected.");
136+
} catch (IOException e) {
137+
logger.trace("", e);
138+
}
139+
return null;
140+
}
141+
return new Jetty9WebSocketHandler(request, config.framework(), webSocketProcessor);
142+
}
143+
};
144+
}
145+
146+
abstract WebSocketServerFactory getWebSocketFactory();
147+
148+
@Override
149+
public Action service(AtmosphereRequest req, AtmosphereResponse res)
150+
throws IOException, ServletException {
151+
152+
Action action;
153+
Boolean b = (Boolean) req.getAttribute(WebSocket.WEBSOCKET_INITIATED);
154+
if (b == null) b = Boolean.FALSE;
155+
156+
if (!Utils.webSocketEnabled(req) && req.getAttribute(WebSocket.WEBSOCKET_ACCEPT_DONE) == null) {
157+
if (req.resource() != null && req.resource().transport() == AtmosphereResource.TRANSPORT.WEBSOCKET) {
158+
WebSocket.notSupported(req, res);
159+
return Action.CANCELLED;
160+
} else {
161+
return super.service(req, res);
162+
}
163+
} else {
164+
if (getWebSocketFactory() != null && !b) {
165+
req.setAttribute(WebSocket.WEBSOCKET_INITIATED, true);
166+
getWebSocketFactory().acceptWebSocket(req, res);
167+
req.setAttribute(WebSocket.WEBSOCKET_ACCEPT_DONE, true);
168+
return new Action();
169+
}
170+
171+
action = suspended(req, res);
172+
if (action.type() == Action.TYPE.SUSPEND) {
173+
} else if (action.type() == Action.TYPE.RESUME) {
174+
req.setAttribute(WebSocket.WEBSOCKET_RESUME, true);
175+
}
176+
}
177+
178+
return action == null ? super.service(req, res) : action;
179+
}
180+
181+
/**
182+
* Return the container's name.
183+
*/
184+
public String getContainerName() {
185+
return config.getServletConfig().getServletContext().getServerInfo() + " with WebSocket enabled.";
186+
}
187+
188+
@Override
189+
public boolean supportWebSocket() {
190+
return true;
191+
}
192+
193+
}

0 commit comments

Comments
 (0)