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 19package org.eclipse.jetty.servlet; 20 21import java.io.IOException; 22import java.util.Enumeration; 23import java.util.HashMap; 24import java.util.Locale; 25import java.util.Map; 26 27import javax.servlet.ServletContext; 28import javax.servlet.ServletException; 29import javax.servlet.UnavailableException; 30import javax.servlet.http.HttpServlet; 31import javax.servlet.http.HttpServletRequest; 32import javax.servlet.http.HttpServletRequestWrapper; 33import javax.servlet.http.HttpServletResponse; 34 35import org.eclipse.jetty.server.AbstractHttpConnection; 36import org.eclipse.jetty.server.Dispatcher; 37import org.eclipse.jetty.server.Handler; 38import org.eclipse.jetty.server.Request; 39import org.eclipse.jetty.server.handler.ContextHandler; 40import org.eclipse.jetty.server.handler.HandlerWrapper; 41import org.eclipse.jetty.util.LazyList; 42import org.eclipse.jetty.util.URIUtil; 43import org.eclipse.jetty.util.log.Log; 44import org.eclipse.jetty.util.log.Logger; 45 46/* ------------------------------------------------------------ */ 47/** Dynamic Servlet Invoker. 48 * This servlet invokes anonymous servlets that have not been defined 49 * in the web.xml or by other means. The first element of the pathInfo 50 * of a request passed to the envoker is treated as a servlet name for 51 * an existing servlet, or as a class name of a new servlet. 52 * This servlet is normally mapped to /servlet/* 53 * This servlet support the following initParams: 54 * <PRE> 55 * nonContextServlets If false, the invoker can only load 56 * servlets from the contexts classloader. 57 * This is false by default and setting this 58 * to true may have security implications. 59 * 60 * verbose If true, log dynamic loads 61 * 62 * * All other parameters are copied to the 63 * each dynamic servlet as init parameters 64 * </PRE> 65 * @version $Id: Invoker.java 4780 2009-03-17 15:36:08Z jesse $ 66 * 67 */ 68public class Invoker extends HttpServlet 69{ 70 private static final Logger LOG = Log.getLogger(Invoker.class); 71 72 73 private ContextHandler _contextHandler; 74 private ServletHandler _servletHandler; 75 private Map.Entry _invokerEntry; 76 private Map _parameters; 77 private boolean _nonContextServlets; 78 private boolean _verbose; 79 80 /* ------------------------------------------------------------ */ 81 public void init() 82 { 83 ServletContext config=getServletContext(); 84 _contextHandler=((ContextHandler.Context)config).getContextHandler(); 85 86 Handler handler=_contextHandler.getHandler(); 87 while (handler!=null && !(handler instanceof ServletHandler) && (handler instanceof HandlerWrapper)) 88 handler=((HandlerWrapper)handler).getHandler(); 89 _servletHandler = (ServletHandler)handler; 90 Enumeration e = getInitParameterNames(); 91 while(e.hasMoreElements()) 92 { 93 String param=(String)e.nextElement(); 94 String value=getInitParameter(param); 95 String lvalue=value.toLowerCase(Locale.ENGLISH); 96 if ("nonContextServlets".equals(param)) 97 { 98 _nonContextServlets=value.length()>0 && lvalue.startsWith("t"); 99 } 100 if ("verbose".equals(param)) 101 { 102 _verbose=value.length()>0 && lvalue.startsWith("t"); 103 } 104 else 105 { 106 if (_parameters==null) 107 _parameters=new HashMap(); 108 _parameters.put(param,value); 109 } 110 } 111 } 112 113 /* ------------------------------------------------------------ */ 114 protected void service(HttpServletRequest request, HttpServletResponse response) 115 throws ServletException, IOException 116 { 117 // Get the requested path and info 118 boolean included=false; 119 String servlet_path=(String)request.getAttribute(Dispatcher.INCLUDE_SERVLET_PATH); 120 if (servlet_path==null) 121 servlet_path=request.getServletPath(); 122 else 123 included=true; 124 String path_info = (String)request.getAttribute(Dispatcher.INCLUDE_PATH_INFO); 125 if (path_info==null) 126 path_info=request.getPathInfo(); 127 128 // Get the servlet class 129 String servlet = path_info; 130 if (servlet==null || servlet.length()<=1 ) 131 { 132 response.sendError(404); 133 return; 134 } 135 136 137 int i0=servlet.charAt(0)=='/'?1:0; 138 int i1=servlet.indexOf('/',i0); 139 servlet=i1<0?servlet.substring(i0):servlet.substring(i0,i1); 140 141 // look for a named holder 142 ServletHolder[] holders = _servletHandler.getServlets(); 143 ServletHolder holder = getHolder (holders, servlet); 144 145 if (holder!=null) 146 { 147 // Found a named servlet (from a user's web.xml file) so 148 // now we add a mapping for it 149 if (LOG.isDebugEnabled()) 150 LOG.debug("Adding servlet mapping for named servlet:"+servlet+":"+URIUtil.addPaths(servlet_path,servlet)+"/*"); 151 ServletMapping mapping = new ServletMapping(); 152 mapping.setServletName(servlet); 153 mapping.setPathSpec(URIUtil.addPaths(servlet_path,servlet)+"/*"); 154 _servletHandler.setServletMappings((ServletMapping[])LazyList.addToArray(_servletHandler.getServletMappings(), mapping, ServletMapping.class)); 155 } 156 else 157 { 158 // look for a class mapping 159 if (servlet.endsWith(".class")) 160 servlet=servlet.substring(0,servlet.length()-6); 161 if (servlet==null || servlet.length()==0) 162 { 163 response.sendError(404); 164 return; 165 } 166 167 synchronized(_servletHandler) 168 { 169 // find the entry for the invoker (me) 170 _invokerEntry=_servletHandler.getHolderEntry(servlet_path); 171 172 // Check for existing mapping (avoid threaded race). 173 String path=URIUtil.addPaths(servlet_path,servlet); 174 Map.Entry entry = _servletHandler.getHolderEntry(path); 175 176 if (entry!=null && !entry.equals(_invokerEntry)) 177 { 178 // Use the holder 179 holder=(ServletHolder)entry.getValue(); 180 } 181 else 182 { 183 // Make a holder 184 if (LOG.isDebugEnabled()) 185 LOG.debug("Making new servlet="+servlet+" with path="+path+"/*"); 186 holder=_servletHandler.addServletWithMapping(servlet, path+"/*"); 187 188 if (_parameters!=null) 189 holder.setInitParameters(_parameters); 190 191 try {holder.start();} 192 catch (Exception e) 193 { 194 LOG.debug(e); 195 throw new UnavailableException(e.toString()); 196 } 197 198 // Check it is from an allowable classloader 199 if (!_nonContextServlets) 200 { 201 Object s=holder.getServlet(); 202 203 if (_contextHandler.getClassLoader()!= 204 s.getClass().getClassLoader()) 205 { 206 try 207 { 208 holder.stop(); 209 } 210 catch (Exception e) 211 { 212 LOG.ignore(e); 213 } 214 215 LOG.warn("Dynamic servlet "+s+ 216 " not loaded from context "+ 217 request.getContextPath()); 218 throw new UnavailableException("Not in context"); 219 } 220 } 221 222 if (_verbose && LOG.isDebugEnabled()) 223 LOG.debug("Dynamic load '"+servlet+"' at "+path); 224 } 225 } 226 } 227 228 if (holder!=null) 229 { 230 final Request baseRequest=(request instanceof Request)?((Request)request):AbstractHttpConnection.getCurrentConnection().getRequest(); 231 holder.handle(baseRequest, 232 new InvokedRequest(request,included,servlet,servlet_path,path_info), 233 response); 234 } 235 else 236 { 237 LOG.info("Can't find holder for servlet: "+servlet); 238 response.sendError(404); 239 } 240 241 242 } 243 244 /* ------------------------------------------------------------ */ 245 class InvokedRequest extends HttpServletRequestWrapper 246 { 247 String _servletPath; 248 String _pathInfo; 249 boolean _included; 250 251 /* ------------------------------------------------------------ */ 252 InvokedRequest(HttpServletRequest request, 253 boolean included, 254 String name, 255 String servletPath, 256 String pathInfo) 257 { 258 super(request); 259 _included=included; 260 _servletPath=URIUtil.addPaths(servletPath,name); 261 _pathInfo=pathInfo.substring(name.length()+1); 262 if (_pathInfo.length()==0) 263 _pathInfo=null; 264 } 265 266 /* ------------------------------------------------------------ */ 267 public String getServletPath() 268 { 269 if (_included) 270 return super.getServletPath(); 271 return _servletPath; 272 } 273 274 /* ------------------------------------------------------------ */ 275 public String getPathInfo() 276 { 277 if (_included) 278 return super.getPathInfo(); 279 return _pathInfo; 280 } 281 282 /* ------------------------------------------------------------ */ 283 public Object getAttribute(String name) 284 { 285 if (_included) 286 { 287 if (name.equals(Dispatcher.INCLUDE_REQUEST_URI)) 288 return URIUtil.addPaths(URIUtil.addPaths(getContextPath(),_servletPath),_pathInfo); 289 if (name.equals(Dispatcher.INCLUDE_PATH_INFO)) 290 return _pathInfo; 291 if (name.equals(Dispatcher.INCLUDE_SERVLET_PATH)) 292 return _servletPath; 293 } 294 return super.getAttribute(name); 295 } 296 } 297 298 299 private ServletHolder getHolder(ServletHolder[] holders, String servlet) 300 { 301 if (holders == null) 302 return null; 303 304 ServletHolder holder = null; 305 for (int i=0; holder==null && i<holders.length; i++) 306 { 307 if (holders[i].getName().equals(servlet)) 308 { 309 holder = holders[i]; 310 } 311 } 312 return holder; 313 } 314} 315