1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jetty.websocket.server;
20
21 import java.io.IOException;
22 import java.util.EnumSet;
23
24 import javax.servlet.DispatcherType;
25 import javax.servlet.Filter;
26 import javax.servlet.FilterChain;
27 import javax.servlet.FilterConfig;
28 import javax.servlet.FilterRegistration;
29 import javax.servlet.ServletContext;
30 import javax.servlet.ServletException;
31 import javax.servlet.ServletRequest;
32 import javax.servlet.ServletResponse;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35
36 import org.eclipse.jetty.http.pathmap.MappedResource;
37 import org.eclipse.jetty.http.pathmap.PathMappings;
38 import org.eclipse.jetty.http.pathmap.PathSpec;
39 import org.eclipse.jetty.http.pathmap.RegexPathSpec;
40 import org.eclipse.jetty.http.pathmap.ServletPathSpec;
41 import org.eclipse.jetty.io.ByteBufferPool;
42 import org.eclipse.jetty.io.MappedByteBufferPool;
43 import org.eclipse.jetty.servlet.FilterHolder;
44 import org.eclipse.jetty.servlet.ServletContextHandler;
45 import org.eclipse.jetty.util.annotation.ManagedAttribute;
46 import org.eclipse.jetty.util.annotation.ManagedObject;
47 import org.eclipse.jetty.util.component.ContainerLifeCycle;
48 import org.eclipse.jetty.util.component.Dumpable;
49 import org.eclipse.jetty.util.log.Log;
50 import org.eclipse.jetty.util.log.Logger;
51 import org.eclipse.jetty.websocket.api.WebSocketPolicy;
52 import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
53
54
55
56
57 @ManagedObject("WebSocket Upgrade Filter")
58 public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter, MappedWebSocketCreator, Dumpable
59 {
60 public static final String CONTEXT_ATTRIBUTE_KEY = "contextAttributeKey";
61 private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
62
63 public static WebSocketUpgradeFilter configureContext(ServletContextHandler context) throws ServletException
64 {
65
66 WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
67 if (filter != null)
68 {
69 return filter;
70 }
71
72
73 filter = new WebSocketUpgradeFilter();
74 filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
75
76 String name = "Jetty_WebSocketUpgradeFilter";
77 String pathSpec = "/*";
78 EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
79
80 FilterHolder fholder = new FilterHolder(filter);
81 fholder.setName(name);
82 fholder.setAsyncSupported(true);
83 fholder.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName());
84 context.addFilter(fholder,pathSpec,dispatcherTypes);
85
86 if (LOG.isDebugEnabled())
87 {
88 LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context);
89 }
90
91 return filter;
92 }
93
94 public static WebSocketUpgradeFilter configureContext(ServletContext context) throws ServletException
95 {
96
97 WebSocketUpgradeFilter filter = (WebSocketUpgradeFilter)context.getAttribute(WebSocketUpgradeFilter.class.getName());
98 if (filter != null)
99 {
100 return filter;
101 }
102
103
104 filter = new WebSocketUpgradeFilter();
105 filter.setToAttribute(context, WebSocketUpgradeFilter.class.getName());
106
107 String name = "Jetty_Dynamic_WebSocketUpgradeFilter";
108 String pathSpec = "/*";
109 EnumSet<DispatcherType> dispatcherTypes = EnumSet.of(DispatcherType.REQUEST);
110 boolean isMatchAfter = false;
111 String urlPatterns[] = { pathSpec };
112
113 FilterRegistration.Dynamic dyn = context.addFilter(name,filter);
114 dyn.setAsyncSupported(true);
115 dyn.setInitParameter(CONTEXT_ATTRIBUTE_KEY,WebSocketUpgradeFilter.class.getName());
116 dyn.addMappingForUrlPatterns(dispatcherTypes,isMatchAfter,urlPatterns);
117
118 if (LOG.isDebugEnabled())
119 {
120 LOG.debug("Adding [{}] {} mapped to {} to {}",name,filter,pathSpec,context);
121 }
122
123 return filter;
124 }
125
126 private final WebSocketServerFactory factory;
127 private final PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
128 private String fname;
129 private boolean alreadySetToAttribute = false;
130
131 public WebSocketUpgradeFilter()
132 {
133 this(WebSocketPolicy.newServerPolicy(),new MappedByteBufferPool());
134 }
135
136 public WebSocketUpgradeFilter(WebSocketPolicy policy, ByteBufferPool bufferPool)
137 {
138 factory = new WebSocketServerFactory(policy,bufferPool);
139 addBean(factory,true);
140 }
141
142 @Override
143 public void addMapping(PathSpec spec, WebSocketCreator creator)
144 {
145 pathmap.put(spec,creator);
146 }
147
148
149
150
151 @Deprecated
152 public void addMapping(org.eclipse.jetty.websocket.server.pathmap.PathSpec spec, WebSocketCreator creator)
153 {
154 if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.ServletPathSpec)
155 {
156 addMapping(new ServletPathSpec(spec.getSpec()), creator);
157 }
158 else if (spec instanceof org.eclipse.jetty.websocket.server.pathmap.RegexPathSpec)
159 {
160 addMapping(new RegexPathSpec(spec.getSpec()), creator);
161 }
162 else
163 {
164 throw new RuntimeException("Unsupported (Deprecated) PathSpec implementation: " + spec.getClass().getName());
165 }
166 }
167
168 @Override
169 public void destroy()
170 {
171 factory.cleanup();
172 pathmap.reset();
173 super.destroy();
174 }
175
176 @Override
177 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
178 {
179 if (factory == null)
180 {
181
182 LOG.debug("WebSocketUpgradeFilter is not operational - no WebSocketServletFactory configured");
183 chain.doFilter(request,response);
184 return;
185 }
186
187 if (LOG.isDebugEnabled())
188 {
189 LOG.debug(".doFilter({}) - {}",fname,chain);
190 }
191
192 if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse))
193 {
194 HttpServletRequest httpreq = (HttpServletRequest)request;
195 HttpServletResponse httpresp = (HttpServletResponse)response;
196
197
198 String contextPath = httpreq.getContextPath();
199 String target = httpreq.getRequestURI();
200 if (target.startsWith(contextPath))
201 {
202 target = target.substring(contextPath.length());
203 }
204
205 if (factory.isUpgradeRequest(httpreq,httpresp))
206 {
207 LOG.debug("target = [{}]",target);
208
209 MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
210 if (resource == null)
211 {
212 if (LOG.isDebugEnabled())
213 {
214 LOG.debug("WebSocket Upgrade on {} has no associated endpoint",target);
215 LOG.debug("PathMappings: {}",pathmap.dump());
216 }
217
218 chain.doFilter(request,response);
219 return;
220 }
221 LOG.debug("WebSocket Upgrade detected on {} for endpoint {}",target,resource);
222
223 WebSocketCreator creator = resource.getResource();
224
225
226 httpreq.setAttribute(PathSpec.class.getName(),resource.getPathSpec());
227
228
229 if (factory.acceptWebSocket(creator,httpreq,httpresp))
230 {
231
232 return;
233 }
234
235
236
237
238 if (response.isCommitted())
239 {
240
241 return;
242 }
243 }
244 }
245
246
247 chain.doFilter(request,response);
248 }
249
250 @Override
251 public String dump()
252 {
253 return ContainerLifeCycle.dump(this);
254 }
255
256 @Override
257 public void dump(Appendable out, String indent) throws IOException
258 {
259 out.append(indent).append(" +- pathmap=").append(pathmap.toString()).append("\n");
260 pathmap.dump(out,indent + " ");
261 }
262
263 public WebSocketServerFactory getFactory()
264 {
265 return factory;
266 }
267
268 @ManagedAttribute(value = "mappings", readonly = true)
269 @Override
270 public PathMappings<WebSocketCreator> getMappings()
271 {
272 return pathmap;
273 }
274
275 @Override
276 public void init(FilterConfig config) throws ServletException
277 {
278 fname = config.getFilterName();
279
280 try
281 {
282 ServletContext ctx = config.getServletContext();
283 factory.init(ctx);
284 WebSocketPolicy policy = factory.getPolicy();
285
286 String max = config.getInitParameter("maxIdleTime");
287 if (max != null)
288 {
289 policy.setIdleTimeout(Long.parseLong(max));
290 }
291
292 max = config.getInitParameter("maxTextMessageSize");
293 if (max != null)
294 {
295 policy.setMaxTextMessageSize(Integer.parseInt(max));
296 }
297
298 max = config.getInitParameter("maxBinaryMessageSize");
299 if (max != null)
300 {
301 policy.setMaxBinaryMessageSize(Integer.parseInt(max));
302 }
303
304 max = config.getInitParameter("inputBufferSize");
305 if (max != null)
306 {
307 policy.setInputBufferSize(Integer.parseInt(max));
308 }
309
310 String key = config.getInitParameter(CONTEXT_ATTRIBUTE_KEY);
311 if (key == null)
312 {
313
314 key = WebSocketUpgradeFilter.class.getName();
315 }
316
317 setToAttribute(ctx, key);
318
319 factory.start();
320 }
321 catch (Exception x)
322 {
323 throw new ServletException(x);
324 }
325 }
326
327 private void setToAttribute(ServletContextHandler context, String key) throws ServletException
328 {
329 if(alreadySetToAttribute)
330 {
331 return;
332 }
333
334 if (context.getAttribute(key) != null)
335 {
336 throw new ServletException(WebSocketUpgradeFilter.class.getName() +
337 " is defined twice for the same context attribute key '" + key
338 + "'. Make sure you have different init-param '" +
339 CONTEXT_ATTRIBUTE_KEY + "' values set");
340 }
341 context.setAttribute(key,this);
342
343 alreadySetToAttribute = true;
344 }
345
346 public void setToAttribute(ServletContext context, String key) throws ServletException
347 {
348 if(alreadySetToAttribute)
349 {
350 return;
351 }
352
353 if (context.getAttribute(key) != null)
354 {
355 throw new ServletException(WebSocketUpgradeFilter.class.getName() +
356 " is defined twice for the same context attribute key '" + key
357 + "'. Make sure you have different init-param '" +
358 CONTEXT_ATTRIBUTE_KEY + "' values set");
359 }
360 context.setAttribute(key,this);
361
362 alreadySetToAttribute = true;
363 }
364
365 @Override
366 public String toString()
367 {
368 return String.format("%s[factory=%s,pathmap=%s]",this.getClass().getSimpleName(),factory,pathmap);
369 }
370 }