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