View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2013 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
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.ServletException;
29  import javax.servlet.ServletRequest;
30  import javax.servlet.ServletResponse;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.eclipse.jetty.servlet.FilterHolder;
35  import org.eclipse.jetty.servlet.ServletContextHandler;
36  import org.eclipse.jetty.util.annotation.ManagedAttribute;
37  import org.eclipse.jetty.util.annotation.ManagedObject;
38  import org.eclipse.jetty.util.component.ContainerLifeCycle;
39  import org.eclipse.jetty.util.component.Dumpable;
40  import org.eclipse.jetty.util.log.Log;
41  import org.eclipse.jetty.util.log.Logger;
42  import org.eclipse.jetty.websocket.api.WebSocketBehavior;
43  import org.eclipse.jetty.websocket.api.WebSocketPolicy;
44  import org.eclipse.jetty.websocket.server.pathmap.PathMappings;
45  import org.eclipse.jetty.websocket.server.pathmap.PathMappings.MappedResource;
46  import org.eclipse.jetty.websocket.server.pathmap.PathSpec;
47  import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
48  
49  /**
50   * Inline Servlet Filter to capture WebSocket upgrade requests and perform path mappings to {@link WebSocketCreator} objects.
51   */
52  @ManagedObject("WebSocket Upgrade Filter")
53  public class WebSocketUpgradeFilter extends ContainerLifeCycle implements Filter, MappedWebSocketCreator, Dumpable
54  {
55      private static final Logger LOG = Log.getLogger(WebSocketUpgradeFilter.class);
56  
57      public static WebSocketUpgradeFilter configureContext(ServletContextHandler context)
58      {
59          WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
60  
61          WebSocketUpgradeFilter filter = new WebSocketUpgradeFilter(policy);
62          FilterHolder fholder = new FilterHolder(filter);
63          fholder.setName("Jetty_WebSocketUpgradeFilter");
64          fholder.setDisplayName("WebSocket Upgrade Filter");
65          String pathSpec = "/*";
66          context.addFilter(fholder,pathSpec,EnumSet.of(DispatcherType.REQUEST));
67          LOG.debug("Adding {} mapped to {} to {}",filter,pathSpec,context);
68  
69          // Store reference to the WebSocketUpgradeFilter
70          context.setAttribute(WebSocketUpgradeFilter.class.getName(),filter);
71  
72          return filter;
73      }
74  
75      private final WebSocketServerFactory factory;
76      private final PathMappings<WebSocketCreator> pathmap = new PathMappings<>();
77  
78      public WebSocketUpgradeFilter(WebSocketPolicy policy)
79      {
80          factory = new WebSocketServerFactory(policy);
81          addBean(factory,true);
82      }
83  
84      @Override
85      public void addMapping(PathSpec spec, WebSocketCreator creator)
86      {
87          pathmap.put(spec,creator);
88      }
89  
90      @Override
91      public void destroy()
92      {
93          factory.cleanup();
94          pathmap.reset();
95          super.destroy();
96      }
97  
98      @Override
99      public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
100     {
101         if (factory == null)
102         {
103             // no factory, cannot operate
104             LOG.debug("WebSocketUpgradeFilter is not operational - no WebSocketServletFactory configured");
105             chain.doFilter(request,response);
106             return;
107         }
108 
109         if ((request instanceof HttpServletRequest) && (response instanceof HttpServletResponse))
110         {
111             HttpServletRequest httpreq = (HttpServletRequest)request;
112             HttpServletResponse httpresp = (HttpServletResponse)response;
113 
114             // Since this is a filter, we need to be smart about determining the target path
115             String contextPath = httpreq.getContextPath();
116             String target = httpreq.getRequestURI();
117             if (target.startsWith(contextPath))
118             {
119                 target = target.substring(contextPath.length());
120             }
121 
122             if (factory.isUpgradeRequest(httpreq,httpresp))
123             {
124                 LOG.debug("target = [{}]",target);
125 
126                 MappedResource<WebSocketCreator> resource = pathmap.getMatch(target);
127                 if (resource == null)
128                 {
129                     if (LOG.isDebugEnabled())
130                     {
131                         LOG.debug("WebSocket Upgrade on {} has no associated endpoint",target);
132                         LOG.debug("PathMappings: {}",pathmap.dump());
133                     }
134                     // no match.
135                     chain.doFilter(request,response);
136                     return;
137                 }
138                 LOG.debug("WebSocket Upgrade detected on {} for endpoint {}",target,resource);
139 
140                 WebSocketCreator creator = resource.getResource();
141 
142                 // Store PathSpec resource mapping as request attribute
143                 httpreq.setAttribute(PathSpec.class.getName(),resource.getPathSpec());
144 
145                 // We have an upgrade request
146                 if (factory.acceptWebSocket(creator,httpreq,httpresp))
147                 {
148                     // We have a socket instance created
149                     return;
150                 }
151 
152                 // If we reach this point, it means we had an incoming request to upgrade
153                 // but it was either not a proper websocket upgrade, or it was possibly rejected
154                 // due to incoming request constraints (controlled by WebSocketCreator)
155                 if (response.isCommitted())
156                 {
157                     // not much we can do at this point.
158                     return;
159                 }
160             }
161         }
162 
163         // not an Upgrade request
164         chain.doFilter(request,response);
165     }
166 
167     @Override
168     public String dump()
169     {
170         return ContainerLifeCycle.dump(this);
171     }
172 
173     @Override
174     public void dump(Appendable out, String indent) throws IOException
175     {
176         out.append(indent).append(" +- pathmap=").append(pathmap.toString()).append("\n");
177         pathmap.dump(out,indent + "   ");
178     }
179 
180     public WebSocketServerFactory getFactory()
181     {
182         return factory;
183     }
184 
185     @ManagedAttribute(value = "mappings", readonly = true)
186     @Override
187     public PathMappings<WebSocketCreator> getMappings()
188     {
189         return pathmap;
190     }
191 
192     @Override
193     public void init(FilterConfig config) throws ServletException
194     {
195         try
196         {
197             WebSocketPolicy policy = factory.getPolicy();
198 
199             String max = config.getInitParameter("maxIdleTime");
200             if (max != null)
201             {
202                 policy.setIdleTimeout(Long.parseLong(max));
203             }
204 
205             max = config.getInitParameter("maxTextMessageSize");
206             if (max != null)
207             {
208                 policy.setMaxTextMessageSize(Integer.parseInt(max));
209             }
210 
211             max = config.getInitParameter("maxBinaryMessageSize");
212             if (max != null)
213             {
214                 policy.setMaxBinaryMessageSize(Integer.parseInt(max));
215             }
216 
217             max = config.getInitParameter("inputBufferSize");
218             if (max != null)
219             {
220                 policy.setInputBufferSize(Integer.parseInt(max));
221             }
222 
223             factory.start();
224         }
225         catch (Exception x)
226         {
227             throw new ServletException(x);
228         }
229     }
230 
231     @Override
232     public String toString()
233     {
234         return String.format("%s[factory=%s,pathmap=%s]",this.getClass().getSimpleName(),factory,pathmap);
235     }
236 }