View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 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.server.handler;
20  
21  import java.io.IOException;
22  import java.util.Arrays;
23  import java.util.HashMap;
24  import java.util.HashSet;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentMap;
29  
30  import javax.servlet.ServletException;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  
34  import org.eclipse.jetty.server.Handler;
35  import org.eclipse.jetty.server.HandlerContainer;
36  import org.eclipse.jetty.server.HttpChannelState;
37  import org.eclipse.jetty.server.Request;
38  import org.eclipse.jetty.util.ArrayTernaryTrie;
39  import org.eclipse.jetty.util.ArrayUtil;
40  import org.eclipse.jetty.util.Trie;
41  import org.eclipse.jetty.util.annotation.ManagedObject;
42  import org.eclipse.jetty.util.annotation.ManagedOperation;
43  import org.eclipse.jetty.util.log.Log;
44  import org.eclipse.jetty.util.log.Logger;
45  
46  /* ------------------------------------------------------------ */
47  /** ContextHandlerCollection.
48   *
49   * This {@link org.eclipse.jetty.server.handler.HandlerCollection} is creates a
50   * Map of contexts to it's contained handlers based
51   * on the context path and virtual hosts of any contained {@link org.eclipse.jetty.server.handler.ContextHandler}s.
52   * The contexts do not need to be directly contained, only children of the contained handlers.
53   * Multiple contexts may have the same context path and they are called in order until one
54   * handles the request.
55   *
56   */
57  @ManagedObject("Context Handler Collection")
58  public class ContextHandlerCollection extends HandlerCollection
59  {
60      private static final Logger LOG = Log.getLogger(ContextHandlerCollection.class);
61  
62      private final ConcurrentMap<ContextHandler,Handler> _contextBranches = new ConcurrentHashMap<>();
63      private volatile Trie<Map.Entry<String,Branch[]>> _pathBranches;
64      private Class<? extends ContextHandler> _contextClass = ContextHandler.class;
65  
66      /* ------------------------------------------------------------ */
67      public ContextHandlerCollection()
68      {
69          super(true);
70      }
71  
72  
73      /* ------------------------------------------------------------ */
74      /**
75       * Remap the context paths.
76       */
77      @ManagedOperation("update the mapping of context path to context")
78      public void mapContexts()
79      {
80          _contextBranches.clear();
81          
82          if (getHandlers()==null)
83          {
84              _pathBranches=new ArrayTernaryTrie<>(false,16);
85              return;
86          }
87          
88          // Create map of contextPath to handler Branch
89          Map<String,Branch[]> map = new HashMap<>();
90          for (Handler handler:getHandlers())
91          {
92              Branch branch=new Branch(handler);
93              for (String contextPath : branch.getContextPaths())
94              {
95                  Branch[] branches=map.get(contextPath);
96                  map.put(contextPath, ArrayUtil.addToArray(branches, branch, Branch.class));
97              }
98              
99              for (ContextHandler context : branch.getContextHandlers())
100                 _contextBranches.putIfAbsent(context, branch.getHandler());
101         }
102         
103         // Sort the branches so those with virtual hosts are considered before those without
104         for (Map.Entry<String,Branch[]> entry: map.entrySet())
105         {
106             Branch[] branches=entry.getValue();
107             Branch[] sorted=new Branch[branches.length];
108             int i=0;
109             for (Branch branch:branches)
110                 if (branch.hasVirtualHost())
111                     sorted[i++]=branch;
112             for (Branch branch:branches)
113                 if (!branch.hasVirtualHost())
114                     sorted[i++]=branch;
115             entry.setValue(sorted);
116         }
117         
118         // Loop until we have a big enough trie to hold all the context paths
119         int capacity=512;
120         Trie<Map.Entry<String,Branch[]>> trie;
121         loop: while(true)
122         {
123             trie=new ArrayTernaryTrie<>(false,capacity);
124             for (Map.Entry<String,Branch[]> entry: map.entrySet())
125             {
126                 if (!trie.put(entry.getKey().substring(1),entry))
127                 {
128                     capacity+=512;
129                     continue loop;
130                 }
131             }
132             break loop;
133         }
134             
135         
136         if (LOG.isDebugEnabled())
137         {
138             for (String ctx : trie.keySet())
139                 LOG.debug("{}->{}",ctx,Arrays.asList(trie.get(ctx).getValue()));
140         }
141         _pathBranches=trie;
142     }
143 
144     /* ------------------------------------------------------------ */
145     /*
146      * @see org.eclipse.jetty.server.server.handler.HandlerCollection#setHandlers(org.eclipse.jetty.server.server.Handler[])
147      */
148     @Override
149     public void setHandlers(Handler[] handlers)
150     {
151         super.setHandlers(handlers);
152         if (isStarted())
153             mapContexts();
154     }
155 
156     /* ------------------------------------------------------------ */
157     @Override
158     protected void doStart() throws Exception
159     {
160         mapContexts();
161         super.doStart();
162     }
163 
164 
165     /* ------------------------------------------------------------ */
166     /*
167      * @see org.eclipse.jetty.server.server.Handler#handle(java.lang.String, javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, int)
168      */
169     @Override
170     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
171     {
172         Handler[] handlers = getHandlers();
173         if (handlers==null || handlers.length==0)
174             return;
175 
176         HttpChannelState async = baseRequest.getHttpChannelState();
177         if (async.isAsync())
178         {
179             ContextHandler context=async.getContextHandler();
180             if (context!=null)
181             {
182                 Handler branch = _contextBranches.get(context);
183                 
184                 if (branch==null)
185                     context.handle(target,baseRequest,request, response);
186                 else
187                     branch.handle(target, baseRequest, request, response);
188                 return;
189             }
190         }
191         
192         // data structure which maps a request to a context; first-best match wins
193         // { context path => [ context ] }
194         // }
195         if (target.startsWith("/"))
196         {
197             int limit = target.length()-1;
198 
199             while (limit>=0)
200             {
201                 // Get best match
202                 Map.Entry<String,Branch[]> branches = _pathBranches.getBest(target,1,limit);
203                 
204                 
205                 if (branches==null)
206                     break;
207                 
208                 int l=branches.getKey().length();
209                 if (l==1 || target.length()==l || target.charAt(l)=='/')
210                 {
211                     for (Branch branch : branches.getValue())
212                     {
213                         branch.getHandler().handle(target,baseRequest, request, response);
214                         if (baseRequest.isHandled())
215                             return;
216                     }
217                 }
218                 
219                 limit=l-2;
220             }
221         }
222         else
223         {
224             // This may not work in all circumstances... but then I think it should never be called
225             for (int i=0;i<handlers.length;i++)
226             {
227                 handlers[i].handle(target,baseRequest, request, response);
228                 if ( baseRequest.isHandled())
229                     return;
230             }
231         }
232     }
233 
234     /* ------------------------------------------------------------ */
235     /** Add a context handler.
236      * @param contextPath  The context path to add
237      * @param resourceBase the base (root) Resource
238      * @return the ContextHandler just added
239      */
240     public ContextHandler addContext(String contextPath,String resourceBase)
241     {
242         try
243         {
244             ContextHandler context = _contextClass.newInstance();
245             context.setContextPath(contextPath);
246             context.setResourceBase(resourceBase);
247             addHandler(context);
248             return context;
249         }
250         catch (Exception e)
251         {
252             LOG.debug(e);
253             throw new Error(e);
254         }
255     }
256 
257 
258 
259     /* ------------------------------------------------------------ */
260     /**
261      * @return The class to use to add new Contexts
262      */
263     public Class<?> getContextClass()
264     {
265         return _contextClass;
266     }
267 
268 
269     /* ------------------------------------------------------------ */
270     /**
271      * @param contextClass The class to use to add new Contexts
272      */
273     public void setContextClass(Class<? extends ContextHandler> contextClass)
274     {
275         if (contextClass ==null || !(ContextHandler.class.isAssignableFrom(contextClass)))
276             throw new IllegalArgumentException();
277         _contextClass = contextClass;
278     }
279 
280     /* ------------------------------------------------------------ */
281     /* ------------------------------------------------------------ */
282     /* ------------------------------------------------------------ */
283     private final static class Branch
284     {
285         private final Handler _handler;
286         private final ContextHandler[] _contexts;
287         
288         Branch(Handler handler)
289         {
290             _handler=handler;
291 
292             if (handler instanceof ContextHandler)
293             {
294                 _contexts = new ContextHandler[]{(ContextHandler)handler};
295             }
296             else if (handler instanceof HandlerContainer)
297             {
298                 Handler[] contexts=((HandlerContainer)handler).getChildHandlersByClass(ContextHandler.class);
299                 _contexts = new ContextHandler[contexts.length];
300                 System.arraycopy(contexts, 0, _contexts, 0, contexts.length);
301             }
302             else
303                 _contexts = new ContextHandler[0];
304         }
305         
306         Set<String> getContextPaths()
307         {
308             Set<String> set = new HashSet<String>();
309             for (ContextHandler context:_contexts)
310                 set.add(context.getContextPath());
311             return set;
312         }
313         
314         boolean hasVirtualHost()
315         {
316             for (ContextHandler context:_contexts)
317                 if (context.getVirtualHosts()!=null && context.getVirtualHosts().length>0)
318                     return true;
319             return false;
320         }
321         
322         ContextHandler[] getContextHandlers()
323         {
324             return _contexts;
325         }
326         
327         Handler getHandler()
328         {
329             return _handler;
330         }
331         
332         @Override
333         public String toString()
334         {
335             return String.format("{%s,%s}",_handler,Arrays.asList(_contexts));
336         }
337     }
338 
339 
340 }