View Javadoc

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