Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / DevAppServerServersFilter.java
blob960feabc57a08cadfc49d9fa2f56e5326a2325f6
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.servers.ServersException;
8 import com.google.appengine.api.labs.servers.ServersService;
9 import com.google.appengine.api.labs.servers.ServersServiceFactory;
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 servers.
30 * There are 6 different request types that this filter will see:
32 * * NORMAL_REQUEST: a normal request sent to the main servlet handler, in this
33 * case the filter has no effect.
35 * * REDIRECT_REQUESTED: a request sent to either the main servlet handler or
36 * to a load-balancing server.
38 * If the request contains information about which instance to redirect to the
39 * filter will verify that the instance is available and either forward the
40 * request or respond with a 500 error.
42 * If no instance is specified the filter will pick an idle instance and
43 * forward the request, if no idle instances are available a 500 error response
44 * is sent.
46 * * DIRECT_SERVER_REQUEST: a request sent directly to the listening port of a
47 * specific server instance. The filter will verify that the instance is
48 * available and if not respond with a 500 error.
50 * * REDIRECTED_BACKEND_REQUEST: a request redirected to a backend instance.
51 * The filter will send the request to the handler.
53 * * REDIRECTED_SERVER_REQUEST: a request redirected to a specific instance.
54 * The filter will send the request to the handler.
56 * * SERVER_STARTUP_REQUEST: startup request sent when servers are started.
60 public class DevAppServerServersFilter implements Filter {
62 static final String BACKEND_REDIRECT_ATTRIBUTE = "com.google.appengine.backend.BackendName";
63 static final String BACKEND_INSTANCE_REDIRECT_ATTRIBUTE =
64 "com.google.appengine.backend.BackendInstance";
65 static final String SERVER_INSTANCE_REDIRECT_ATTRIBUTE =
66 "com.google.appengine.server.ServerInstance";
68 static final int SERVER_BUSY_ERROR_CODE = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
70 static final int SERVER_STOPPED_ERROR_CODE = HttpServletResponse.SC_NOT_FOUND;
72 static final int SERVER_MISSING_ERROR_CODE = HttpServletResponse.SC_BAD_GATEWAY;
74 private final AbstractBackendServers backendServersManager;
76 private final Logger logger = Logger.getLogger(DevAppServerServersFilter.class.getName());
78 @VisibleForTesting
79 DevAppServerServersFilter(AbstractBackendServers backendServers) {
80 this.backendServersManager = backendServers;
83 public DevAppServerServersFilter() {
84 this.backendServersManager = BackendServers.getInstance();
87 @Override
88 public void destroy() {
91 /**
92 * Main filter method. All request to the dev-appserver pass this method.
94 @Override
95 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
96 throws IOException, ServletException {
97 HttpServletRequest hrequest = (HttpServletRequest) request;
98 HttpServletResponse hresponse = (HttpServletResponse) response;
99 RequestType requestType = getRequestType(hrequest);
100 switch (requestType) {
101 case NORMAL_REQUEST:
102 injectServerApiInfo(null, -1);
103 chain.doFilter(hrequest, hresponse);
104 break;
105 case REDIRECT_REQUESTED:
106 doRedirect(hrequest, hresponse);
107 break;
108 case DIRECT_BACKEND_REQUEST:
109 doDirectBackendRequest(hrequest, hresponse, chain);
110 break;
111 case REDIRECTED_BACKEND_REQUEST:
112 doRedirectedBackendRequest(hrequest, hresponse, chain);
113 break;
114 case REDIRECTED_SERVER_REQUEST:
115 doRedirectedServerRequest(hrequest, hresponse, chain);
116 break;
117 case SERVER_STARTUP_REQUEST:
118 doStartupRequest(hrequest, hresponse, chain);
119 break;
124 * Determine the request type for a given request.
126 * @param hrequest The Request to categorize
127 * @return The RequestType of the request
129 @VisibleForTesting
130 RequestType getRequestType(HttpServletRequest hrequest) {
131 int serverPort = hrequest.getServerPort();
132 String backendServerName = backendServersManager.getServerNameFromPort(serverPort);
133 if (hrequest.getRequestURI().equals("/_ah/start") && backendServerName != null) {
134 return RequestType.SERVER_STARTUP_REQUEST;
135 } else if (hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE) != null &&
136 hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE) instanceof String) {
137 return RequestType.REDIRECTED_BACKEND_REQUEST;
138 } else if (hrequest.getAttribute(SERVER_INSTANCE_REDIRECT_ATTRIBUTE) != null &&
139 hrequest.getAttribute(SERVER_INSTANCE_REDIRECT_ATTRIBUTE) instanceof Integer) {
140 return RequestType.REDIRECTED_SERVER_REQUEST;
141 } else if (backendServerName != null) {
142 int backendInstance = backendServersManager.getServerInstanceFromPort(serverPort);
143 if (backendInstance == -1) {
144 return RequestType.REDIRECT_REQUESTED;
145 } else {
146 return RequestType.DIRECT_BACKEND_REQUEST;
148 } else {
149 String serverRedirectHeader =
150 getHeaderOrParameter(hrequest, BackendService.REQUEST_HEADER_BACKEND_REDIRECT);
151 if (serverRedirectHeader == null && !isServerLoadBalancingServerRequest()) {
152 return RequestType.NORMAL_REQUEST;
153 } else {
154 return RequestType.REDIRECT_REQUESTED;
159 private boolean isServerLoadBalancingServerRequest() {
160 ServersFilterHelper serversFilterHelper = getServersFilterHelper();
161 ServersService serversService = ServersServiceFactory.getServersService();
162 String server = serversService.getCurrentServer();
163 String instance = "-1";
164 try {
165 instance = serversService.getCurrentInstanceId();
166 } catch (ServersException se) {
167 logger.log(Level.FINE, "Exception getting server instance", se);
169 return serversFilterHelper.isServerLoadBalancingServer(server, Integer.parseInt(instance));
172 private ServersFilterHelper getServersFilterHelper() {
173 Map<String, Object> attributes = ApiProxy.getCurrentEnvironment().getAttributes();
174 return (ServersFilterHelper) attributes.get(DevAppServerImpl.SERVERS_FILTER_HELPER_PROPERTY);
177 private boolean instanceAcceptsConnections(
178 String requestedServer, int instance, HttpServletResponse hresponse) throws IOException {
179 ServersFilterHelper serversFilterHelper = getServersFilterHelper();
180 if (!serversFilterHelper.checkInstanceExists(requestedServer, instance)) {
181 String msg =
182 String.format("Got request to non-configured instance: %d.%s", instance, requestedServer);
183 logger.warning(msg);
184 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
185 return false;
187 if (serversFilterHelper.checkInstanceStopped(requestedServer, instance)) {
188 String msg =
189 String.format("Got request to stopped instance: %d.%s", instance, requestedServer);
190 logger.warning(msg);
191 hresponse.sendError(SERVER_STOPPED_ERROR_CODE, msg);
192 return false;
195 if (!serversFilterHelper.acquireServingPermit(requestedServer, instance, true)) {
196 String msg = String.format(
197 "Got request to server %d.%s but the instance is busy.", instance, requestedServer);
198 logger.finer(msg);
199 hresponse.sendError(SERVER_BUSY_ERROR_CODE, msg);
200 return false;
203 return true;
207 * Request that contains either headers or parameters specifying that it
208 * should be forwarded either to a specific server and instance, or to a free
209 * instance of a specific server.
211 private void doRedirect(HttpServletRequest hrequest, HttpServletResponse hresponse)
212 throws IOException, ServletException {
213 String requestedServer =
214 backendServersManager.getServerNameFromPort(hrequest.getServerPort());
215 if (requestedServer == null) {
216 requestedServer =
217 getHeaderOrParameter(hrequest, BackendService.REQUEST_HEADER_BACKEND_REDIRECT);
220 boolean isServersLoadBalancingServer = false;
221 if (requestedServer == null) {
222 ServersService serversService = ServersServiceFactory.getServersService();
223 requestedServer = serversService.getCurrentServer();
224 isServersLoadBalancingServer = true;
226 ServersFilterHelper serversFilterHelper = getServersFilterHelper();
227 int instance = getInstanceIdFromRequest(hrequest);
228 logger.finest(String.format("redirect request to server: %d.%s", instance, requestedServer));
229 if (instance != -1) {
230 if (!instanceAcceptsConnections(requestedServer, instance, hresponse)) {
231 return;
233 } else {
234 if (!serversFilterHelper.checkServerExists(requestedServer)) {
235 String msg = String.format("Got request to non-configured server: %s", requestedServer);
236 logger.warning(msg);
237 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
238 return;
240 if (serversFilterHelper.checkServerStopped(requestedServer)) {
241 String msg = String.format("Got request to stopped server: %s", requestedServer);
242 logger.warning(msg);
243 hresponse.sendError(SERVER_STOPPED_ERROR_CODE, msg);
244 return;
246 instance = serversFilterHelper.getAndReserveFreeInstance(requestedServer);
247 if (instance == -1) {
248 String msg = String.format("all instances of server %s are busy", requestedServer);
249 logger.finest(msg);
250 hresponse.sendError(SERVER_BUSY_ERROR_CODE, msg);
251 return;
255 try {
256 if (isServersLoadBalancingServer) {
257 logger.finer(String.format("forwarding request to server: %d.%s", instance,
258 requestedServer));
259 hrequest.setAttribute(SERVER_INSTANCE_REDIRECT_ATTRIBUTE, Integer.valueOf(instance));
260 } else {
261 logger.finer(String.format("forwarding request to backend: %d.%s", instance,
262 requestedServer));
263 hrequest.setAttribute(BACKEND_REDIRECT_ATTRIBUTE, requestedServer);
264 hrequest.setAttribute(BACKEND_INSTANCE_REDIRECT_ATTRIBUTE, Integer.valueOf(instance));
266 serversFilterHelper.forwardToServer(requestedServer, instance, hrequest, hresponse);
267 } finally {
268 serversFilterHelper.returnServingPermit(requestedServer, instance);
273 * A request sent straight to the local port of a specific server.
275 * If the server is busy with other requests a 500 response is sent.
278 private void doDirectBackendRequest(
279 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
280 throws IOException, ServletException {
281 int serverPort = hrequest.getServerPort();
282 String requestedServer = backendServersManager.getServerNameFromPort(serverPort);
283 int instance = backendServersManager.getServerInstanceFromPort(serverPort);
284 logger.finest("request to specific server instance: " + instance + "." + requestedServer);
286 if (!instanceAcceptsConnections(requestedServer, instance, hresponse)) {
287 logger.finest("!instacheAcceptsConnections server instance: " + instance + "."
288 + requestedServer);
289 return;
291 try {
292 logger.finest("instacheAcceptsConnections server instance: " + instance + "."
293 + requestedServer);
294 injectServerApiInfo(requestedServer, instance);
295 chain.doFilter(hrequest, hresponse);
296 } finally {
297 ServersFilterHelper serversFilterHelper = getServersFilterHelper();
298 serversFilterHelper.returnServingPermit(requestedServer, instance);
303 * A request forwarded from a different server. The forwarding server is
304 * responsible for acquiring the serving permit. All we need to do is to add
305 * the ServerApiInfo and forward the request along the chain.
307 private void doRedirectedBackendRequest(
308 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
309 throws IOException, ServletException {
310 String backendServer = (String) hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE);
311 Integer instance = (Integer) hrequest.getAttribute(BACKEND_INSTANCE_REDIRECT_ATTRIBUTE);
312 logger.finest("redirected request to backend server instance: " + instance + "."
313 + backendServer);
314 injectServerApiInfo(backendServer, instance);
315 chain.doFilter(hrequest, hresponse);
319 * A request forwarded from a different server. The forwarding server is
320 * responsible for acquiring the serving permit. All we need to do is to add
321 * the ServerApiInfo and forward the request along the chain.
323 private void doRedirectedServerRequest(
324 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
325 throws IOException, ServletException {
326 Integer instance = (Integer) hrequest.getAttribute(SERVER_INSTANCE_REDIRECT_ATTRIBUTE);
327 logger.finest("redirected request to server instance: " + instance + "." +
328 ApiProxy.getCurrentEnvironment().getVersionId());
329 injectServerApiInfo(null, -1);
330 LocalEnvironment.setInstance(ApiProxy.getCurrentEnvironment().getAttributes(), instance);
331 chain.doFilter(hrequest, hresponse);
335 * Startup requests do not require any serving permits and can be forwarded
336 * along the chain straight away.
338 private void doStartupRequest(
339 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
340 throws IOException, ServletException {
341 int serverPort = hrequest.getServerPort();
342 String backendServer = backendServersManager.getServerNameFromPort(serverPort);
343 int instance = backendServersManager.getServerInstanceFromPort(serverPort);
344 logger.finest("startup request to: " + instance + "." + backendServer);
345 injectServerApiInfo(backendServer, instance);
346 chain.doFilter(hrequest, hresponse);
349 @SuppressWarnings("unused")
350 @Override
351 public void init(FilterConfig filterConfig) throws ServletException {
355 * Inject information about the current server setup so it is available to the
356 * Servers API. This information is stored in the threadLocalAttributes in the
357 * current environment.
359 * @param currentServer The server that is handling the request
360 * @param instance The server instance that is handling the request
362 private void injectServerApiInfo(String currentServer, int instance) {
363 Map<String, Object> threadLocalAttributes = ApiProxy.getCurrentEnvironment().getAttributes();
364 if (instance != -1) {
365 threadLocalAttributes.put(BackendService.INSTANCE_ID_ENV_ATTRIBUTE, instance + "");
367 if (currentServer != null) {
368 threadLocalAttributes.put(BackendService.BACKEND_ID_ENV_ATTRIBUTE, currentServer);
370 Map<String, String> portMapping = backendServersManager.getPortMapping();
371 threadLocalAttributes.put(
372 BackendService.DEVAPPSERVER_PORTMAPPING_KEY, portMapping);
373 if (portMapping.size() > 0) {
374 threadLocalAttributes.put(
375 LocalServerController.BACKEND_CONTROLLER_ATTRIBUTE_KEY, backendServersManager);
380 * Checks the request headers and request parameters for the specified key
382 @VisibleForTesting
383 static String getHeaderOrParameter(HttpServletRequest request, String key) {
384 String value = request.getHeader(key);
385 if (value != null) {
386 return value;
388 if ("GET".equals(request.getMethod())) {
389 return request.getParameter(key);
391 return null;
395 * Checks request headers and parameters to see if an instance id was
396 * specified.
398 @VisibleForTesting
399 static int getInstanceIdFromRequest(HttpServletRequest request) {
400 try {
401 return Integer.parseInt(
402 getHeaderOrParameter(request, BackendService.REQUEST_HEADER_INSTANCE_REDIRECT));
403 } catch (NumberFormatException e) {
404 return -1;
408 @VisibleForTesting
409 static enum RequestType {
410 NORMAL_REQUEST, REDIRECT_REQUESTED, DIRECT_BACKEND_REQUEST, REDIRECTED_BACKEND_REQUEST,
411 REDIRECTED_SERVER_REQUEST, SERVER_STARTUP_REQUEST;