App Engine SDK 1.8.4 release.
[gae.git] / java / src / main / com / google / appengine / tools / development / DevAppServerModulesFilter.java
blob7cabd5242136e45f23eb998b3fe3fbc180943a35
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.labs.modules.ModulesException;
8 import com.google.appengine.api.labs.modules.ModulesService;
9 import com.google.appengine.api.labs.modules.ModulesServiceFactory;
10 import com.google.apphosting.api.ApiProxy;
11 import com.google.common.annotations.VisibleForTesting;
13 import java.io.IOException;
14 import java.util.Map;
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;
27 /**
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)
33 * backend instance.
35 * * REDIRECT_REQUESTED: a request requesting a redirect in one of three ways
36 * 1) The request contains a BackendService.REQUEST_HEADER_BACKEND_REDIRECT
37 * header or parameter
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";
73 @VisibleForTesting
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());
88 @VisibleForTesting
89 DevAppServerModulesFilter(AbstractBackendServers backendServers, ModulesService modulesService) {
90 this.backendServersManager = backendServers;
91 this.modulesService = modulesService;
94 public DevAppServerModulesFilter() {
95 this(BackendServers.getInstance(), ModulesServiceFactory.getModulesService());
98 @Override
99 public void destroy() {
103 * Main filter method. All request to the dev-appserver pass this method.
105 @Override
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);
114 break;
115 case REDIRECT_REQUESTED:
116 doRedirect(hrequest, hresponse);
117 break;
118 case DIRECT_BACKEND_REQUEST:
119 doDirectBackendRequest(hrequest, hresponse, chain);
120 break;
121 case REDIRECTED_BACKEND_REQUEST:
122 doRedirectedBackendRequest(hrequest, hresponse, chain);
123 break;
124 case REDIRECTED_MODULE_REQUEST:
125 doRedirectedModuleRequest(hrequest, hresponse, chain);
126 break;
127 case STARTUP_REQUEST:
128 doStartupRequest(hrequest, hresponse, chain);
129 break;
134 * Determine the request type for a given request.
136 * @param hrequest The Request to categorize
137 * @return The RequestType of the request
139 @VisibleForTesting
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) != null &&
147 hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE) instanceof String) {
148 return RequestType.REDIRECTED_BACKEND_REQUEST;
149 } else if (hrequest.getAttribute(MODULE_INSTANCE_REDIRECT_ATTRIBUTE) != null &&
150 hrequest.getAttribute(MODULE_INSTANCE_REDIRECT_ATTRIBUTE) instanceof Integer) {
151 return RequestType.REDIRECTED_MODULE_REQUEST;
152 } else if (backendServerName != null) {
153 int backendInstance = backendServersManager.getServerInstanceFromPort(instancePort);
154 if (backendInstance == -1) {
155 return RequestType.REDIRECT_REQUESTED;
156 } else {
157 return RequestType.DIRECT_BACKEND_REQUEST;
159 } else {
160 String serverRedirectHeader =
161 getHeaderOrParameter(hrequest, BackendService.REQUEST_HEADER_BACKEND_REDIRECT);
162 if (serverRedirectHeader == null && !isLoadBalancingRequest()) {
163 return RequestType.DIRECT_MODULE_REQUEST;
164 } else {
165 return RequestType.REDIRECT_REQUESTED;
170 private boolean isLoadBalancingRequest() {
171 ModulesFilterHelper modulesFilterHelper = getModulesFilterHelper();
172 String module = modulesService.getCurrentModule();
173 int instance = getCurrentModuleInstance();
174 return modulesFilterHelper.isLoadBalancingInstance(module, instance);
177 private boolean expectsGeneratedStartRequests(String backendName,
178 int requestPort) {
179 String moduleOrBackendName = backendName;
180 if (moduleOrBackendName == null) {
181 moduleOrBackendName = modulesService.getCurrentModule();
184 int instance = backendName == null ? getCurrentModuleInstance() :
185 backendServersManager.getServerInstanceFromPort(requestPort);
186 ModulesFilterHelper modulesFilterHelper = getModulesFilterHelper();
187 return modulesFilterHelper.expectsGeneratedStartRequests(moduleOrBackendName, instance);
191 * Returns the instance id for the module instance handling the current request or -1
192 * if a back end server or load balancing server is handling the request.
194 private int getCurrentModuleInstance() {
195 String instance = "-1";
196 try {
197 instance = modulesService.getCurrentInstanceId();
198 } catch (ModulesException me) {
199 logger.log(Level.FINE, "Exception getting module instance", me);
201 return Integer.parseInt(instance);
204 private ModulesFilterHelper getModulesFilterHelper() {
205 Map<String, Object> attributes = ApiProxy.getCurrentEnvironment().getAttributes();
206 return (ModulesFilterHelper) attributes.get(DevAppServerImpl.MODULES_FILTER_HELPER_PROPERTY);
209 private boolean tryToAcquireServingPermit(
210 String moduleOrBackendName, int instance, HttpServletResponse hresponse) throws IOException {
211 ModulesFilterHelper modulesFilterHelper = getModulesFilterHelper();
212 if (!modulesFilterHelper.checkInstanceExists(moduleOrBackendName, instance)) {
213 String msg =
214 String.format("Got request to non-configured instance: %d.%s", instance,
215 moduleOrBackendName);
216 logger.warning(msg);
217 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
218 return false;
220 if (modulesFilterHelper.checkInstanceStopped(moduleOrBackendName, instance)) {
221 String msg =
222 String.format("Got request to stopped instance: %d.%s", instance, moduleOrBackendName);
223 logger.warning(msg);
224 hresponse.sendError(MODULE_STOPPED_ERROR_CODE, msg);
225 return false;
228 if (!modulesFilterHelper.acquireServingPermit(moduleOrBackendName, instance, true)) {
229 String msg = String.format(
230 "Got request to module %d.%s but the instance is busy.", instance, moduleOrBackendName);
231 logger.finer(msg);
232 hresponse.sendError(INSTANCE_BUSY_ERROR_CODE, msg);
233 return false;
236 return true;
240 * Request that contains either headers or parameters specifying that it
241 * should be forwarded either to a specific module or backend instance,
242 * or to a free instance.
244 private void doRedirect(HttpServletRequest hrequest, HttpServletResponse hresponse)
245 throws IOException, ServletException {
246 String moduleOrBackendName =
247 backendServersManager.getServerNameFromPort(hrequest.getServerPort());
248 if (moduleOrBackendName == null) {
249 moduleOrBackendName =
250 getHeaderOrParameter(hrequest, BackendService.REQUEST_HEADER_BACKEND_REDIRECT);
253 boolean isLoadBalancingModuleInstance = false;
254 if (moduleOrBackendName == null) {
255 ModulesService modulesService = ModulesServiceFactory.getModulesService();
256 moduleOrBackendName = modulesService.getCurrentModule();
257 isLoadBalancingModuleInstance = true;
259 ModulesFilterHelper modulesFilterHelper = getModulesFilterHelper();
260 int instance = getInstanceIdFromRequest(hrequest);
261 logger.finest(String.format("redirect request to module: %d.%s", instance,
262 moduleOrBackendName));
263 if (instance != -1) {
264 if (!tryToAcquireServingPermit(moduleOrBackendName, instance, hresponse)) {
265 return;
267 } else {
268 if (!modulesFilterHelper.checkModuleExists(moduleOrBackendName)) {
269 String msg = String.format("Got request to non-configured module: %s", moduleOrBackendName);
270 logger.warning(msg);
271 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
272 return;
274 if (modulesFilterHelper.checkModuleStopped(moduleOrBackendName)) {
275 String msg = String.format("Got request to stopped module: %s", moduleOrBackendName);
276 logger.warning(msg);
277 hresponse.sendError(MODULE_STOPPED_ERROR_CODE, msg);
278 return;
280 instance = modulesFilterHelper.getAndReserveFreeInstance(moduleOrBackendName);
281 if (instance == -1) {
282 String msg = String.format("all instances of module %s are busy", moduleOrBackendName);
283 logger.finest(msg);
284 hresponse.sendError(INSTANCE_BUSY_ERROR_CODE, msg);
285 return;
289 try {
290 if (isLoadBalancingModuleInstance) {
291 logger.finer(String.format("forwarding request to module: %d.%s", instance,
292 moduleOrBackendName));
293 hrequest.setAttribute(MODULE_INSTANCE_REDIRECT_ATTRIBUTE, Integer.valueOf(instance));
294 } else {
295 logger.finer(String.format("forwarding request to backend: %d.%s", instance,
296 moduleOrBackendName));
297 hrequest.setAttribute(BACKEND_REDIRECT_ATTRIBUTE, moduleOrBackendName);
298 hrequest.setAttribute(BACKEND_INSTANCE_REDIRECT_ATTRIBUTE, Integer.valueOf(instance));
300 modulesFilterHelper.forwardToInstance(moduleOrBackendName, instance, hrequest, hresponse);
301 } finally {
302 modulesFilterHelper.returnServingPermit(moduleOrBackendName, instance);
306 private void doDirectBackendRequest(
307 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
308 throws IOException, ServletException {
309 int instancePort = hrequest.getServerPort();
310 String requestedBackend = backendServersManager.getServerNameFromPort(instancePort);
311 int requestedInstance = backendServersManager.getServerInstanceFromPort(instancePort);
312 injectApiInfo(requestedBackend, requestedInstance);
313 doDirectRequest(requestedBackend, requestedInstance, hrequest, hresponse, chain);
316 private void doDirectModuleRequest(
317 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
318 throws IOException, ServletException {
319 String requestedModule = modulesService.getCurrentModule();
320 int requestedInstance = getCurrentModuleInstance();
321 injectApiInfo(null, -1);
322 doDirectRequest(requestedModule, requestedInstance, hrequest, hresponse, chain);
325 private void doDirectRequest(String moduleOrBackendName, int instance,
326 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
327 throws IOException, ServletException {
328 logger.finest("request to specific module instance: " + instance
329 + "." + moduleOrBackendName);
331 if (!tryToAcquireServingPermit(moduleOrBackendName, instance, hresponse)) {
332 return;
334 try {
335 logger.finest("Acquired serving permit for: " + instance + "."
336 + moduleOrBackendName);
337 injectApiInfo(null, -1);
338 chain.doFilter(hrequest, hresponse);
339 } finally {
340 ModulesFilterHelper modulesFilterHelper = getModulesFilterHelper();
341 modulesFilterHelper.returnServingPermit(moduleOrBackendName, instance);
346 * A request forwarded from a different instance. The forwarding instance is
347 * responsible for acquiring the serving permit. All we need to do is to add
348 * the ServerApiInfo and forward the request along the chain.
350 private void doRedirectedBackendRequest(
351 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
352 throws IOException, ServletException {
353 String backendServer = (String) hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE);
354 Integer instance = (Integer) hrequest.getAttribute(BACKEND_INSTANCE_REDIRECT_ATTRIBUTE);
355 ModulesFilterHelper modulesFilterHelper = getModulesFilterHelper();
356 int port = modulesFilterHelper.getPort(backendServer, instance);
357 LocalEnvironment.setPort(ApiProxy.getCurrentEnvironment().getAttributes(), port);
358 injectApiInfo(backendServer, instance);
359 logger.finest("redirected request to backend server instance: " + instance + "."
360 + backendServer);
361 chain.doFilter(hrequest, hresponse);
365 * A request forwarded from a different instance. The forwarding instance is
366 * responsible for acquiring the serving permit. All we need to do is to add
367 * the ServerApiInfo and forward the request along the chain.
369 private void doRedirectedModuleRequest(
370 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
371 throws IOException, ServletException {
372 Integer instance = (Integer) hrequest.getAttribute(MODULE_INSTANCE_REDIRECT_ATTRIBUTE);
373 ModulesFilterHelper modulesFilterHelper = getModulesFilterHelper();
374 String moduleName = modulesService.getCurrentModule();
375 int port = modulesFilterHelper.getPort(moduleName, instance);
376 LocalEnvironment.setInstance(ApiProxy.getCurrentEnvironment().getAttributes(), instance);
377 LocalEnvironment.setPort(ApiProxy.getCurrentEnvironment().getAttributes(), port);
378 injectApiInfo(null, -1);
379 logger.finest("redirected request to module instance: " + instance + "." +
380 ApiProxy.getCurrentEnvironment().getVersionId());
381 chain.doFilter(hrequest, hresponse);
385 * Startup requests do not require any serving permits and can be forwarded
386 * along the chain straight away.
388 private void doStartupRequest(
389 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
390 throws IOException, ServletException {
391 int instancePort = hrequest.getServerPort();
392 String backendServer = backendServersManager.getServerNameFromPort(instancePort);
393 int instance = backendServersManager.getServerInstanceFromPort(instancePort);
394 logger.finest("startup request to: " + instance + "." + backendServer);
395 injectApiInfo(backendServer, instance);
396 chain.doFilter(hrequest, hresponse);
399 @SuppressWarnings("unused")
400 @Override
401 public void init(FilterConfig filterConfig) throws ServletException {
405 * Inject information about the current backend server setup so it is available
406 * to the BackendService API. This information is stored in the threadLocalAttributes
407 * in the current environment.
409 * @param backendName The server that is handling the request
410 * @param instance The server instance that is handling the request
412 private void injectApiInfo(String backendName, int instance) {
413 Map<String, String> portMapping = backendServersManager.getPortMapping();
414 if (portMapping == null) {
415 throw new IllegalStateException("backendServersManager.getPortMapping() is null");
417 injectBackendServiceCurrentApiInfo(backendName, instance, portMapping);
418 if (portMapping.size() > 0) {
419 Map<String, Object> threadLocalAttributes = ApiProxy.getCurrentEnvironment().getAttributes();
420 threadLocalAttributes.put(
421 LocalServerController.BACKEND_CONTROLLER_ATTRIBUTE_KEY, backendServersManager);
426 * Sets up {@link ApiProxy} attributes needed {@link BackendService}.
428 static void injectBackendServiceCurrentApiInfo(String backendName, int backendInstance,
429 Map<String, String> portMapping) {
430 Map<String, Object> threadLocalAttributes = ApiProxy.getCurrentEnvironment().getAttributes();
431 if (backendInstance != -1) {
432 threadLocalAttributes.put(BackendService.INSTANCE_ID_ENV_ATTRIBUTE, backendInstance + "");
434 if (backendName != null) {
435 threadLocalAttributes.put(BackendService.BACKEND_ID_ENV_ATTRIBUTE, backendName);
437 threadLocalAttributes.put(BackendService.DEVAPPSERVER_PORTMAPPING_KEY, portMapping);
441 * Checks the request headers and request parameters for the specified key
443 @VisibleForTesting
444 static String getHeaderOrParameter(HttpServletRequest request, String key) {
445 String value = request.getHeader(key);
446 if (value != null) {
447 return value;
449 if ("GET".equals(request.getMethod())) {
450 return request.getParameter(key);
452 return null;
456 * Checks request headers and parameters to see if an instance id was
457 * specified.
459 @VisibleForTesting
460 static int getInstanceIdFromRequest(HttpServletRequest request) {
461 try {
462 return Integer.parseInt(
463 getHeaderOrParameter(request, BackendService.REQUEST_HEADER_INSTANCE_REDIRECT));
464 } catch (NumberFormatException e) {
465 return -1;
469 @VisibleForTesting
470 static enum RequestType {
471 DIRECT_MODULE_REQUEST, REDIRECT_REQUESTED, DIRECT_BACKEND_REQUEST, REDIRECTED_BACKEND_REQUEST,
472 REDIRECTED_MODULE_REQUEST, STARTUP_REQUEST;