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