Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / DevAppServerModulesFilter.java
blob394dd6251784cc12083ba2731e3b8322288cdcda
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;
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) 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;
154 } else {
155 return RequestType.DIRECT_BACKEND_REQUEST;
157 } else {
158 String serverRedirectHeader =
159 getHeaderOrParameter(hrequest, BackendService.REQUEST_HEADER_BACKEND_REDIRECT);
160 if (serverRedirectHeader == null && !isLoadBalancingRequest()) {
161 return RequestType.DIRECT_MODULE_REQUEST;
162 } else {
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,
176 int requestPort) {
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";
194 try {
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)) {
211 String msg =
212 String.format("Got request to non-configured instance: %d.%s", instance,
213 moduleOrBackendName);
214 logger.warning(msg);
215 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
216 return false;
218 if (modulesFilterHelper.checkInstanceStopped(moduleOrBackendName, instance)) {
219 String msg =
220 String.format("Got request to stopped instance: %d.%s", instance, moduleOrBackendName);
221 logger.warning(msg);
222 hresponse.sendError(MODULE_STOPPED_ERROR_CODE, msg);
223 return false;
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);
229 logger.finer(msg);
230 hresponse.sendError(INSTANCE_BUSY_ERROR_CODE, msg);
231 return false;
234 return true;
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)) {
263 return;
265 } else {
266 if (!modulesFilterHelper.checkModuleExists(moduleOrBackendName)) {
267 String msg = String.format("Got request to non-configured module: %s", moduleOrBackendName);
268 logger.warning(msg);
269 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
270 return;
272 if (modulesFilterHelper.checkModuleStopped(moduleOrBackendName)) {
273 String msg = String.format("Got request to stopped module: %s", moduleOrBackendName);
274 logger.warning(msg);
275 hresponse.sendError(MODULE_STOPPED_ERROR_CODE, msg);
276 return;
278 instance = modulesFilterHelper.getAndReserveFreeInstance(moduleOrBackendName);
279 if (instance == -1) {
280 String msg = String.format("all instances of module %s are busy", moduleOrBackendName);
281 logger.finest(msg);
282 hresponse.sendError(INSTANCE_BUSY_ERROR_CODE, msg);
283 return;
287 try {
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));
292 } else {
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);
299 } finally {
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)) {
330 return;
332 try {
333 logger.finest("Acquired serving permit for: " + instance + "."
334 + moduleOrBackendName);
335 injectApiInfo(null, -1);
336 chain.doFilter(hrequest, hresponse);
337 } finally {
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 + "."
358 + backendServer);
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")
399 @Override
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
448 @VisibleForTesting
449 static String getHeaderOrParameter(HttpServletRequest request, String key) {
450 String value = request.getHeader(key);
451 if (value != null) {
452 return value;
454 if ("GET".equals(request.getMethod())) {
455 return request.getParameter(key);
457 return null;
461 * Checks request headers and parameters to see if an instance id was
462 * specified.
464 @VisibleForTesting
465 static int getInstanceIdFromRequest(HttpServletRequest request) {
466 try {
467 return Integer.parseInt(
468 getHeaderOrParameter(request, BackendService.REQUEST_HEADER_INSTANCE_REDIRECT));
469 } catch (NumberFormatException e) {
470 return -1;
474 @VisibleForTesting
475 static enum RequestType {
476 DIRECT_MODULE_REQUEST, REDIRECT_REQUESTED, DIRECT_BACKEND_REQUEST, REDIRECTED_BACKEND_REQUEST,
477 REDIRECTED_MODULE_REQUEST, STARTUP_REQUEST;