1 /* 2 * Copyright (C) 2008 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.inject.servlet; 17 18 import static com.google.inject.servlet.ManagedServletPipeline.REQUEST_DISPATCHER_REQUEST; 19 20 import com.google.common.collect.Iterators; 21 import com.google.inject.Injector; 22 import com.google.inject.Key; 23 import com.google.inject.Scopes; 24 import com.google.inject.spi.BindingTargetVisitor; 25 import com.google.inject.spi.ProviderInstanceBinding; 26 import com.google.inject.spi.ProviderWithExtensionVisitor; 27 import java.io.IOException; 28 import java.net.URI; 29 import java.net.URISyntaxException; 30 import java.util.Collections; 31 import java.util.Enumeration; 32 import java.util.HashMap; 33 import java.util.Map; 34 import java.util.Set; 35 import java.util.concurrent.atomic.AtomicReference; 36 import javax.servlet.ServletConfig; 37 import javax.servlet.ServletContext; 38 import javax.servlet.ServletException; 39 import javax.servlet.ServletRequest; 40 import javax.servlet.ServletResponse; 41 import javax.servlet.http.HttpServlet; 42 import javax.servlet.http.HttpServletRequest; 43 import javax.servlet.http.HttpServletRequestWrapper; 44 import javax.servlet.http.HttpServletResponse; 45 46 /** 47 * An internal representation of a servlet definition mapped to a particular URI pattern. Also 48 * performs the request dispatch to that servlet. How nice and OO =) 49 * 50 * @author dhanji@gmail.com (Dhanji R. Prasanna) 51 */ 52 class ServletDefinition implements ProviderWithExtensionVisitor<ServletDefinition> { 53 private final Key<? extends HttpServlet> servletKey; 54 private final UriPatternMatcher patternMatcher; 55 private final Map<String, String> initParams; 56 // set only if this was bound using a servlet instance. 57 private final HttpServlet servletInstance; 58 59 //always set in init, our servlet is always presumed to be a singleton 60 private final AtomicReference<HttpServlet> httpServlet = new AtomicReference<>(); 61 ServletDefinition( Key<? extends HttpServlet> servletKey, UriPatternMatcher patternMatcher, Map<String, String> initParams, HttpServlet servletInstance)62 public ServletDefinition( 63 Key<? extends HttpServlet> servletKey, 64 UriPatternMatcher patternMatcher, 65 Map<String, String> initParams, 66 HttpServlet servletInstance) { 67 this.servletKey = servletKey; 68 this.patternMatcher = patternMatcher; 69 this.initParams = Collections.unmodifiableMap(new HashMap<String, String>(initParams)); 70 this.servletInstance = servletInstance; 71 } 72 73 @Override get()74 public ServletDefinition get() { 75 return this; 76 } 77 78 @Override acceptExtensionVisitor( BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding)79 public <B, V> V acceptExtensionVisitor( 80 BindingTargetVisitor<B, V> visitor, ProviderInstanceBinding<? extends B> binding) { 81 if (visitor instanceof ServletModuleTargetVisitor) { 82 if (servletInstance != null) { 83 return ((ServletModuleTargetVisitor<B, V>) visitor) 84 .visit(new InstanceServletBindingImpl(initParams, servletInstance, patternMatcher)); 85 } else { 86 return ((ServletModuleTargetVisitor<B, V>) visitor) 87 .visit(new LinkedServletBindingImpl(initParams, servletKey, patternMatcher)); 88 } 89 } else { 90 return visitor.visit(binding); 91 } 92 } 93 shouldServe(String uri)94 boolean shouldServe(String uri) { 95 return uri != null && patternMatcher.matches(uri); 96 } 97 init( final ServletContext servletContext, Injector injector, Set<HttpServlet> initializedSoFar)98 public void init( 99 final ServletContext servletContext, Injector injector, Set<HttpServlet> initializedSoFar) 100 throws ServletException { 101 102 // This absolutely must be a singleton, and so is only initialized once. 103 if (!Scopes.isSingleton(injector.getBinding(servletKey))) { 104 throw new ServletException( 105 "Servlets must be bound as singletons. " 106 + servletKey 107 + " was not bound in singleton scope."); 108 } 109 110 HttpServlet httpServlet = injector.getInstance(servletKey); 111 this.httpServlet.set(httpServlet); 112 113 // Only fire init() if we have not appeared before in the filter chain. 114 if (initializedSoFar.contains(httpServlet)) { 115 return; 116 } 117 118 //initialize our servlet with the configured context params and servlet context 119 httpServlet.init( 120 new ServletConfig() { 121 @Override 122 public String getServletName() { 123 return servletKey.toString(); 124 } 125 126 @Override 127 public ServletContext getServletContext() { 128 return servletContext; 129 } 130 131 @Override 132 public String getInitParameter(String s) { 133 return initParams.get(s); 134 } 135 136 @Override 137 public Enumeration getInitParameterNames() { 138 return Iterators.asEnumeration(initParams.keySet().iterator()); 139 } 140 }); 141 142 // Mark as initialized. 143 initializedSoFar.add(httpServlet); 144 } 145 destroy(Set<HttpServlet> destroyedSoFar)146 public void destroy(Set<HttpServlet> destroyedSoFar) { 147 HttpServlet reference = httpServlet.get(); 148 149 // Do nothing if this Servlet was invalid (usually due to not being scoped 150 // properly). According to Servlet Spec: it is "out of service", and does not 151 // need to be destroyed. 152 // Also prevent duplicate destroys to the same singleton that may appear 153 // more than once on the filter chain. 154 if (null == reference || destroyedSoFar.contains(reference)) { 155 return; 156 } 157 158 try { 159 reference.destroy(); 160 } finally { 161 destroyedSoFar.add(reference); 162 } 163 } 164 165 /** 166 * Wrapper around the service chain to ensure a servlet is servicing what it must and provides it 167 * with a wrapped request. 168 * 169 * @return Returns true if this servlet triggered for the given request. Or false if guice-servlet 170 * should continue dispatching down the servlet pipeline. 171 * @throws IOException If thrown by underlying servlet 172 * @throws ServletException If thrown by underlying servlet 173 */ service(ServletRequest servletRequest, ServletResponse servletResponse)174 public boolean service(ServletRequest servletRequest, ServletResponse servletResponse) 175 throws IOException, ServletException { 176 177 final HttpServletRequest request = (HttpServletRequest) servletRequest; 178 final String path = ServletUtils.getContextRelativePath(request); 179 180 final boolean serve = shouldServe(path); 181 182 //invocations of the chain end at the first matched servlet 183 if (serve) { 184 doService(servletRequest, servletResponse); 185 } 186 187 //return false if no servlet matched (so we can proceed down to the web.xml servlets) 188 return serve; 189 } 190 191 /** 192 * Utility that delegates to the actual service method of the servlet wrapped with a contextual 193 * request (i.e. with correctly computed path info). 194 * 195 * <p>We need to suppress deprecation coz we use HttpServletRequestWrapper, which implements 196 * deprecated API for backwards compatibility. 197 */ doService(final ServletRequest servletRequest, ServletResponse servletResponse)198 void doService(final ServletRequest servletRequest, ServletResponse servletResponse) 199 throws ServletException, IOException { 200 201 HttpServletRequest request = 202 new HttpServletRequestWrapper((HttpServletRequest) servletRequest) { 203 private boolean pathComputed; 204 private String path; 205 206 private boolean pathInfoComputed; 207 private String pathInfo; 208 209 @Override 210 public String getPathInfo() { 211 if (!isPathInfoComputed()) { 212 String servletPath = getServletPath(); 213 int servletPathLength = servletPath.length(); 214 String requestUri = getRequestURI(); 215 pathInfo = requestUri.substring(getContextPath().length()).replaceAll("[/]{2,}", "/"); 216 // See: https://github.com/google/guice/issues/372 217 if (pathInfo.startsWith(servletPath)) { 218 pathInfo = pathInfo.substring(servletPathLength); 219 // Corner case: when servlet path & request path match exactly (without trailing '/'), 220 // then pathinfo is null. 221 if (pathInfo.isEmpty() && servletPathLength > 0) { 222 pathInfo = null; 223 } else { 224 try { 225 pathInfo = new URI(pathInfo).getPath(); 226 } catch (URISyntaxException e) { 227 // ugh, just leave it alone then 228 } 229 } 230 } else { 231 pathInfo = null; // we know nothing additional about the URI. 232 } 233 pathInfoComputed = true; 234 } 235 236 return pathInfo; 237 } 238 239 // NOTE(dhanji): These two are a bit of a hack to help ensure that request dispatcher-sent 240 // requests don't use the same path info that was memoized for the original request. 241 // NOTE(iqshum): I don't think this is possible, since the dispatcher-sent request would 242 // perform its own wrapping. 243 private boolean isPathInfoComputed() { 244 return pathInfoComputed 245 && servletRequest.getAttribute(REQUEST_DISPATCHER_REQUEST) == null; 246 } 247 248 private boolean isPathComputed() { 249 return pathComputed && servletRequest.getAttribute(REQUEST_DISPATCHER_REQUEST) == null; 250 } 251 252 @Override 253 public String getServletPath() { 254 return computePath(); 255 } 256 257 @Override 258 public String getPathTranslated() { 259 final String info = getPathInfo(); 260 261 return (null == info) ? null : getRealPath(info); 262 } 263 264 // Memoizer pattern. 265 private String computePath() { 266 if (!isPathComputed()) { 267 String servletPath = super.getServletPath(); 268 path = patternMatcher.extractPath(servletPath); 269 pathComputed = true; 270 271 if (null == path) { 272 path = servletPath; 273 } 274 } 275 276 return path; 277 } 278 }; 279 280 doServiceImpl(request, (HttpServletResponse) servletResponse); 281 } 282 doServiceImpl(HttpServletRequest request, HttpServletResponse response)283 private void doServiceImpl(HttpServletRequest request, HttpServletResponse response) 284 throws ServletException, IOException { 285 GuiceFilter.Context previous = GuiceFilter.localContext.get(); 286 HttpServletRequest originalRequest = 287 (previous != null) ? previous.getOriginalRequest() : request; 288 GuiceFilter.localContext.set(new GuiceFilter.Context(originalRequest, request, response)); 289 try { 290 httpServlet.get().service(request, response); 291 } finally { 292 GuiceFilter.localContext.set(previous); 293 } 294 } 295 getKey()296 String getKey() { 297 return servletKey.toString(); 298 } 299 } 300