1 // Copyright 2011 Google Inc. All Rights Reserved.
3 package com
.google
.appengine
.tools
.development
;
5 import com
.google
.appengine
.api
.backends
.BackendService
;
6 import com
.google
.appengine
.api
.backends
.dev
.LocalServerController
;
7 import com
.google
.appengine
.api
.modules
.ModulesException
;
8 import com
.google
.appengine
.api
.modules
.ModulesService
;
9 import com
.google
.appengine
.api
.modules
.ModulesServiceFactory
;
10 import com
.google
.apphosting
.api
.ApiProxy
;
11 import com
.google
.common
.annotations
.VisibleForTesting
;
13 import java
.io
.IOException
;
15 import java
.util
.logging
.Level
;
16 import java
.util
.logging
.Logger
;
18 import javax
.servlet
.Filter
;
19 import javax
.servlet
.FilterChain
;
20 import javax
.servlet
.FilterConfig
;
21 import javax
.servlet
.ServletException
;
22 import javax
.servlet
.ServletRequest
;
23 import javax
.servlet
.ServletResponse
;
24 import javax
.servlet
.http
.HttpServletRequest
;
25 import javax
.servlet
.http
.HttpServletResponse
;
28 * This filter intercepts all request sent to all module instances.
30 * There are 6 different request types that this filter will see:
32 * * DIRECT_BACKEND_REQUEST: a client request sent to a serving (non load balancing)
35 * * REDIRECT_REQUESTED: a request requesting a redirect in one of three ways
36 * 1) The request contains a BackendService.REQUEST_HEADER_BACKEND_REDIRECT
38 * 2) The request is sent to a load balancing module instance.
39 * 3) The request is sent to a load balancing backend instance.
41 * If the request specifies an instance with the BackendService.REQUEST_HEADER_INSTANCE_REDIRECT
42 * request header or parameter the filter verifies that the instance is available,
43 * obtains a serving permit and forwards the requests. If the instance is not available
44 * the filter responds with a 500 error.
46 * If the request does not specify an instance the filter picks one,
47 * obtains a serving permit, and and forwards the request. If no instance is
48 * available this filter responds with a 500 error.
50 * * DIRECT_MODULE_REQUEST: a request sent directly to the listening port of a
51 * specific serving module instance. The filter verifies that the instance is
52 * available, obtains a serving permit and sends the request to the handler.
53 * If no instance is available this filter responds with a 500 error.
55 * * REDIRECTED_BACKEND_REQUEST: a request redirected to a backend instance.
56 * The filter sends the request to the handler. The serving permit has
57 * already been obtained by this filter when performing the redirect.
59 * * REDIRECTED_MODULE_REQUEST: a request redirected to a specific module instance.
60 * The filter sends the request to the handler. The serving permit has
61 * already been obtained when by filter performing the redirect.
63 * * STARTUP_REQUEST: Internally generated startup request. The filter
64 * passes the request to the handler without obtaining a serving permit.
68 public class DevAppServerModulesFilter
implements Filter
{
70 static final String BACKEND_REDIRECT_ATTRIBUTE
= "com.google.appengine.backend.BackendName";
71 static final String BACKEND_INSTANCE_REDIRECT_ATTRIBUTE
=
72 "com.google.appengine.backend.BackendInstance";
74 static final String MODULE_INSTANCE_REDIRECT_ATTRIBUTE
=
75 "com.google.appengine.module.ModuleInstance";
77 static final int INSTANCE_BUSY_ERROR_CODE
= HttpServletResponse
.SC_INTERNAL_SERVER_ERROR
;
79 static final int MODULE_STOPPED_ERROR_CODE
= HttpServletResponse
.SC_NOT_FOUND
;
81 static final int MODULE_MISSING_ERROR_CODE
= HttpServletResponse
.SC_BAD_GATEWAY
;
83 private final AbstractBackendServers backendServersManager
;
84 private final ModulesService modulesService
;
86 private final Logger logger
= Logger
.getLogger(DevAppServerModulesFilter
.class.getName());
89 DevAppServerModulesFilter(AbstractBackendServers backendServers
, ModulesService modulesService
) {
90 this.backendServersManager
= backendServers
;
91 this.modulesService
= modulesService
;
94 public DevAppServerModulesFilter() {
95 this(BackendServers
.getInstance(), ModulesServiceFactory
.getModulesService());
99 public void destroy() {
103 * Main filter method. All request to the dev-appserver pass this method.
106 public void doFilter(ServletRequest request
, ServletResponse response
, FilterChain chain
)
107 throws IOException
, ServletException
{
108 HttpServletRequest hrequest
= (HttpServletRequest
) request
;
109 HttpServletResponse hresponse
= (HttpServletResponse
) response
;
110 RequestType requestType
= getRequestType(hrequest
);
111 switch (requestType
) {
112 case DIRECT_MODULE_REQUEST
:
113 doDirectModuleRequest(hrequest
, hresponse
, chain
);
115 case REDIRECT_REQUESTED
:
116 doRedirect(hrequest
, hresponse
);
118 case DIRECT_BACKEND_REQUEST
:
119 doDirectBackendRequest(hrequest
, hresponse
, chain
);
121 case REDIRECTED_BACKEND_REQUEST
:
122 doRedirectedBackendRequest(hrequest
, hresponse
, chain
);
124 case REDIRECTED_MODULE_REQUEST
:
125 doRedirectedModuleRequest(hrequest
, hresponse
, chain
);
127 case STARTUP_REQUEST
:
128 doStartupRequest(hrequest
, hresponse
, chain
);
134 * Determine the request type for a given request.
136 * @param hrequest The Request to categorize
137 * @return The RequestType of the request
140 RequestType
getRequestType(HttpServletRequest hrequest
) {
141 int instancePort
= hrequest
.getServerPort();
142 String backendServerName
= backendServersManager
.getServerNameFromPort(instancePort
);
143 if (hrequest
.getRequestURI().equals("/_ah/start") &&
144 expectsGeneratedStartRequests(backendServerName
, instancePort
)) {
145 return RequestType
.STARTUP_REQUEST
;
146 } else if (hrequest
.getAttribute(BACKEND_REDIRECT_ATTRIBUTE
) instanceof String
) {
147 return RequestType
.REDIRECTED_BACKEND_REQUEST
;
148 } else if (hrequest
.getAttribute(MODULE_INSTANCE_REDIRECT_ATTRIBUTE
) instanceof Integer
) {
149 return RequestType
.REDIRECTED_MODULE_REQUEST
;
150 } else if (backendServerName
!= null) {
151 int backendInstance
= backendServersManager
.getServerInstanceFromPort(instancePort
);
152 if (backendInstance
== -1) {
153 return RequestType
.REDIRECT_REQUESTED
;
155 return RequestType
.DIRECT_BACKEND_REQUEST
;
158 String serverRedirectHeader
=
159 getHeaderOrParameter(hrequest
, BackendService
.REQUEST_HEADER_BACKEND_REDIRECT
);
160 if (serverRedirectHeader
== null && !isLoadBalancingRequest()) {
161 return RequestType
.DIRECT_MODULE_REQUEST
;
163 return RequestType
.REDIRECT_REQUESTED
;
168 private boolean isLoadBalancingRequest() {
169 ModulesFilterHelper modulesFilterHelper
= getModulesFilterHelper();
170 String module
= modulesService
.getCurrentModule();
171 int instance
= getCurrentModuleInstance();
172 return modulesFilterHelper
.isLoadBalancingInstance(module
, instance
);
175 private boolean expectsGeneratedStartRequests(String backendName
,
177 String moduleOrBackendName
= backendName
;
178 if (moduleOrBackendName
== null) {
179 moduleOrBackendName
= modulesService
.getCurrentModule();
182 int instance
= backendName
== null ?
getCurrentModuleInstance() :
183 backendServersManager
.getServerInstanceFromPort(requestPort
);
184 ModulesFilterHelper modulesFilterHelper
= getModulesFilterHelper();
185 return modulesFilterHelper
.expectsGeneratedStartRequests(moduleOrBackendName
, instance
);
189 * Returns the instance id for the module instance handling the current request or -1
190 * if a back end server or load balancing server is handling the request.
192 private int getCurrentModuleInstance() {
193 String instance
= "-1";
195 instance
= modulesService
.getCurrentInstanceId();
196 } catch (ModulesException me
) {
197 logger
.log(Level
.FINEST
, "Ignoring Exception getting module instance and continuing", me
);
199 return Integer
.parseInt(instance
);
202 private ModulesFilterHelper
getModulesFilterHelper() {
203 Map
<String
, Object
> attributes
= ApiProxy
.getCurrentEnvironment().getAttributes();
204 return (ModulesFilterHelper
) attributes
.get(DevAppServerImpl
.MODULES_FILTER_HELPER_PROPERTY
);
207 private boolean tryToAcquireServingPermit(
208 String moduleOrBackendName
, int instance
, HttpServletResponse hresponse
) throws IOException
{
209 ModulesFilterHelper modulesFilterHelper
= getModulesFilterHelper();
210 if (!modulesFilterHelper
.checkInstanceExists(moduleOrBackendName
, instance
)) {
212 String
.format("Got request to non-configured instance: %d.%s", instance
,
213 moduleOrBackendName
);
215 hresponse
.sendError(HttpServletResponse
.SC_BAD_GATEWAY
, msg
);
218 if (modulesFilterHelper
.checkInstanceStopped(moduleOrBackendName
, instance
)) {
220 String
.format("Got request to stopped instance: %d.%s", instance
, moduleOrBackendName
);
222 hresponse
.sendError(MODULE_STOPPED_ERROR_CODE
, msg
);
226 if (!modulesFilterHelper
.acquireServingPermit(moduleOrBackendName
, instance
, true)) {
227 String msg
= String
.format(
228 "Got request to module %d.%s but the instance is busy.", instance
, moduleOrBackendName
);
230 hresponse
.sendError(INSTANCE_BUSY_ERROR_CODE
, msg
);
238 * Request that contains either headers or parameters specifying that it
239 * should be forwarded either to a specific module or backend instance,
240 * or to a free instance.
242 private void doRedirect(HttpServletRequest hrequest
, HttpServletResponse hresponse
)
243 throws IOException
, ServletException
{
244 String moduleOrBackendName
=
245 backendServersManager
.getServerNameFromPort(hrequest
.getServerPort());
246 if (moduleOrBackendName
== null) {
247 moduleOrBackendName
=
248 getHeaderOrParameter(hrequest
, BackendService
.REQUEST_HEADER_BACKEND_REDIRECT
);
251 boolean isLoadBalancingModuleInstance
= false;
252 if (moduleOrBackendName
== null) {
253 ModulesService modulesService
= ModulesServiceFactory
.getModulesService();
254 moduleOrBackendName
= modulesService
.getCurrentModule();
255 isLoadBalancingModuleInstance
= true;
257 ModulesFilterHelper modulesFilterHelper
= getModulesFilterHelper();
258 int instance
= getInstanceIdFromRequest(hrequest
);
259 logger
.finest(String
.format("redirect request to module: %d.%s", instance
,
260 moduleOrBackendName
));
261 if (instance
!= -1) {
262 if (!tryToAcquireServingPermit(moduleOrBackendName
, instance
, hresponse
)) {
266 if (!modulesFilterHelper
.checkModuleExists(moduleOrBackendName
)) {
267 String msg
= String
.format("Got request to non-configured module: %s", moduleOrBackendName
);
269 hresponse
.sendError(HttpServletResponse
.SC_BAD_GATEWAY
, msg
);
272 if (modulesFilterHelper
.checkModuleStopped(moduleOrBackendName
)) {
273 String msg
= String
.format("Got request to stopped module: %s", moduleOrBackendName
);
275 hresponse
.sendError(MODULE_STOPPED_ERROR_CODE
, msg
);
278 instance
= modulesFilterHelper
.getAndReserveFreeInstance(moduleOrBackendName
);
279 if (instance
== -1) {
280 String msg
= String
.format("all instances of module %s are busy", moduleOrBackendName
);
282 hresponse
.sendError(INSTANCE_BUSY_ERROR_CODE
, msg
);
288 if (isLoadBalancingModuleInstance
) {
289 logger
.finer(String
.format("forwarding request to module: %d.%s", instance
,
290 moduleOrBackendName
));
291 hrequest
.setAttribute(MODULE_INSTANCE_REDIRECT_ATTRIBUTE
, Integer
.valueOf(instance
));
293 logger
.finer(String
.format("forwarding request to backend: %d.%s", instance
,
294 moduleOrBackendName
));
295 hrequest
.setAttribute(BACKEND_REDIRECT_ATTRIBUTE
, moduleOrBackendName
);
296 hrequest
.setAttribute(BACKEND_INSTANCE_REDIRECT_ATTRIBUTE
, Integer
.valueOf(instance
));
298 modulesFilterHelper
.forwardToInstance(moduleOrBackendName
, instance
, hrequest
, hresponse
);
300 modulesFilterHelper
.returnServingPermit(moduleOrBackendName
, instance
);
304 private void doDirectBackendRequest(
305 HttpServletRequest hrequest
, HttpServletResponse hresponse
, FilterChain chain
)
306 throws IOException
, ServletException
{
307 int instancePort
= hrequest
.getServerPort();
308 String requestedBackend
= backendServersManager
.getServerNameFromPort(instancePort
);
309 int requestedInstance
= backendServersManager
.getServerInstanceFromPort(instancePort
);
310 injectApiInfo(requestedBackend
, requestedInstance
);
311 doDirectRequest(requestedBackend
, requestedInstance
, hrequest
, hresponse
, chain
);
314 private void doDirectModuleRequest(
315 HttpServletRequest hrequest
, HttpServletResponse hresponse
, FilterChain chain
)
316 throws IOException
, ServletException
{
317 String requestedModule
= modulesService
.getCurrentModule();
318 int requestedInstance
= getCurrentModuleInstance();
319 injectApiInfo(null, -1);
320 doDirectRequest(requestedModule
, requestedInstance
, hrequest
, hresponse
, chain
);
323 private void doDirectRequest(String moduleOrBackendName
, int instance
,
324 HttpServletRequest hrequest
, HttpServletResponse hresponse
, FilterChain chain
)
325 throws IOException
, ServletException
{
326 logger
.finest("request to specific module instance: " + instance
327 + "." + moduleOrBackendName
);
329 if (!tryToAcquireServingPermit(moduleOrBackendName
, instance
, hresponse
)) {
333 logger
.finest("Acquired serving permit for: " + instance
+ "."
334 + moduleOrBackendName
);
335 injectApiInfo(null, -1);
336 chain
.doFilter(hrequest
, hresponse
);
338 ModulesFilterHelper modulesFilterHelper
= getModulesFilterHelper();
339 modulesFilterHelper
.returnServingPermit(moduleOrBackendName
, instance
);
344 * A request forwarded from a different instance. The forwarding instance is
345 * responsible for acquiring the serving permit. All we need to do is to add
346 * the ServerApiInfo and forward the request along the chain.
348 private void doRedirectedBackendRequest(
349 HttpServletRequest hrequest
, HttpServletResponse hresponse
, FilterChain chain
)
350 throws IOException
, ServletException
{
351 String backendServer
= (String
) hrequest
.getAttribute(BACKEND_REDIRECT_ATTRIBUTE
);
352 Integer instance
= (Integer
) hrequest
.getAttribute(BACKEND_INSTANCE_REDIRECT_ATTRIBUTE
);
353 ModulesFilterHelper modulesFilterHelper
= getModulesFilterHelper();
354 int port
= modulesFilterHelper
.getPort(backendServer
, instance
);
355 LocalEnvironment
.setPort(ApiProxy
.getCurrentEnvironment().getAttributes(), port
);
356 injectApiInfo(backendServer
, instance
);
357 logger
.finest("redirected request to backend server instance: " + instance
+ "."
359 chain
.doFilter(hrequest
, hresponse
);
363 * A request forwarded from a different instance. The forwarding instance is
364 * responsible for acquiring the serving permit. All we need to do is to add
365 * the ServerApiInfo and forward the request along the chain.
367 private void doRedirectedModuleRequest(
368 HttpServletRequest hrequest
, HttpServletResponse hresponse
, FilterChain chain
)
369 throws IOException
, ServletException
{
370 Integer instance
= (Integer
) hrequest
.getAttribute(MODULE_INSTANCE_REDIRECT_ATTRIBUTE
);
371 ModulesFilterHelper modulesFilterHelper
= getModulesFilterHelper();
372 String moduleName
= modulesService
.getCurrentModule();
373 int port
= modulesFilterHelper
.getPort(moduleName
, instance
);
374 LocalEnvironment
.setInstance(ApiProxy
.getCurrentEnvironment().getAttributes(), instance
);
375 LocalEnvironment
.setPort(ApiProxy
.getCurrentEnvironment().getAttributes(), port
);
376 injectApiInfo(null, -1);
377 logger
.finest("redirected request to module instance: " + instance
+ "." +
378 ApiProxy
.getCurrentEnvironment().getModuleId() + " " +
379 ApiProxy
.getCurrentEnvironment().getVersionId());
380 chain
.doFilter(hrequest
, hresponse
);
384 * Startup requests do not require any serving permits and can be forwarded
385 * along the chain straight away.
387 private void doStartupRequest(
388 HttpServletRequest hrequest
, HttpServletResponse hresponse
, FilterChain chain
)
389 throws IOException
, ServletException
{
390 int instancePort
= hrequest
.getServerPort();
391 String backendServer
= backendServersManager
.getServerNameFromPort(instancePort
);
392 int instance
= backendServersManager
.getServerInstanceFromPort(instancePort
);
393 logger
.finest("startup request to: " + instance
+ "." + backendServer
);
394 injectApiInfo(backendServer
, instance
);
395 chain
.doFilter(hrequest
, hresponse
);
398 @SuppressWarnings("unused")
400 public void init(FilterConfig filterConfig
) throws ServletException
{
404 * Inject information about the current backend server setup so it is available
405 * to the BackendService API. This information is stored in the threadLocalAttributes
406 * in the current environment.
408 * @param backendName The server that is handling the request
409 * @param instance The server instance that is handling the request
411 private void injectApiInfo(String backendName
, int instance
) {
412 Map
<String
, String
> portMapping
= backendServersManager
.getPortMapping();
413 if (portMapping
== null) {
414 throw new IllegalStateException("backendServersManager.getPortMapping() is null");
416 injectBackendServiceCurrentApiInfo(backendName
, instance
, portMapping
);
418 Map
<String
, Object
> threadLocalAttributes
= ApiProxy
.getCurrentEnvironment().getAttributes();
420 if (!portMapping
.isEmpty()) {
421 threadLocalAttributes
.put(
422 LocalServerController
.BACKEND_CONTROLLER_ATTRIBUTE_KEY
, backendServersManager
);
425 threadLocalAttributes
.put(
426 ModulesController
.MODULES_CONTROLLER_ATTRIBUTE_KEY
,
427 Modules
.getInstance());
431 * Sets up {@link ApiProxy} attributes needed {@link BackendService}.
433 static void injectBackendServiceCurrentApiInfo(String backendName
, int backendInstance
,
434 Map
<String
, String
> portMapping
) {
435 Map
<String
, Object
> threadLocalAttributes
= ApiProxy
.getCurrentEnvironment().getAttributes();
436 if (backendInstance
!= -1) {
437 threadLocalAttributes
.put(BackendService
.INSTANCE_ID_ENV_ATTRIBUTE
, backendInstance
+ "");
439 if (backendName
!= null) {
440 threadLocalAttributes
.put(BackendService
.BACKEND_ID_ENV_ATTRIBUTE
, backendName
);
442 threadLocalAttributes
.put(BackendService
.DEVAPPSERVER_PORTMAPPING_KEY
, portMapping
);
446 * Checks the request headers and request parameters for the specified key
449 static String
getHeaderOrParameter(HttpServletRequest request
, String key
) {
450 String value
= request
.getHeader(key
);
454 if ("GET".equals(request
.getMethod())) {
455 return request
.getParameter(key
);
461 * Checks request headers and parameters to see if an instance id was
465 static int getInstanceIdFromRequest(HttpServletRequest request
) {
467 return Integer
.parseInt(
468 getHeaderOrParameter(request
, BackendService
.REQUEST_HEADER_INSTANCE_REDIRECT
));
469 } catch (NumberFormatException e
) {
475 static enum RequestType
{
476 DIRECT_MODULE_REQUEST
, REDIRECT_REQUESTED
, DIRECT_BACKEND_REQUEST
, REDIRECTED_BACKEND_REQUEST
,
477 REDIRECTED_MODULE_REQUEST
, STARTUP_REQUEST
;