Revision created by MOE tool push_codebase.
[gae.git] / java / src / main / com / google / appengine / tools / development / BackendServersFilter.java
blobe86873151129184b51c003dcf4664ba77b44964d
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.apphosting.api.ApiProxy;
8 import com.google.common.annotations.VisibleForTesting;
10 import java.io.IOException;
11 import java.util.Map;
12 import java.util.logging.Logger;
14 import javax.servlet.Filter;
15 import javax.servlet.FilterChain;
16 import javax.servlet.FilterConfig;
17 import javax.servlet.ServletException;
18 import javax.servlet.ServletRequest;
19 import javax.servlet.ServletResponse;
20 import javax.servlet.http.HttpServletRequest;
21 import javax.servlet.http.HttpServletResponse;
23 /**
24 * This filter intercepts all request sent to all servers.
26 * There are 5 different request types that this filter will see:
28 * * NORMAL_REQUEST: a normal request sent to the main servlet handler, in this
29 * case the filter has no effect.
31 * * REDIRECT_REQUESTED: a request sent to either the main servlet handler or
32 * to a load-balancing server.
34 * If the request contains information about which instance to redirect to the
35 * filter will verify that the instance is available and either forward the
36 * request or respond with a 500 error.
38 * If no instance is specified the filter will pick an idle instance and
39 * forward the request, if no idle instances are available a 500 error response
40 * is sent.
42 * * DIRECT_SERVER_REQUEST: a request sent directly to the listening port of a
43 * specific server instance. The filter will verify that the instance is
44 * available and if not respond with a 500 error.
46 * * REDIRECTED_SERVER_REQUEST: a request redirected to a specific instance.
47 * The filter will send the request to the handler.
49 * * SERVER_STARTUP_REQUEST: startup request sent when servers are started.
53 public class BackendServersFilter implements Filter {
55 static final String BACKEND_REDIRECT_ATTRIBUTE = "com.google.appengine.backend.BackendName";
56 static final String INSTANCE_REDIRECT_ATTRIBUTE = "com.google.appengine.backend.BackendInstance";
58 static final int SERVER_BUSY_ERROR_CODE = HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
60 static final int SERVER_STOPPED_ERROR_CODE = HttpServletResponse.SC_NOT_FOUND;
62 static final int SERVER_MISSING_ERROR_CODE = HttpServletResponse.SC_BAD_GATEWAY;
64 protected AbstractBackendServers backendServersManager;
66 private final Logger logger = Logger.getLogger(BackendServersFilter.class.getName());
68 @VisibleForTesting
69 BackendServersFilter(AbstractBackendServers backendServers) {
70 this.backendServersManager = backendServers;
73 public BackendServersFilter() {
74 this.backendServersManager = BackendServers.getInstance();
77 @Override
78 public void destroy() {
81 /**
82 * Main filter method. All request to the dev-appserver pass this method.
84 @Override
85 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
86 throws IOException, ServletException {
87 HttpServletRequest hrequest = (HttpServletRequest) request;
88 HttpServletResponse hresponse = (HttpServletResponse) response;
89 RequestType requestType = getRequestType(hrequest);
90 logger.finer("got request, type=" + requestType);
91 switch (requestType) {
92 case NORMAL_REQUEST:
93 injectServerApiInfo(null, -1);
94 chain.doFilter(hrequest, hresponse);
95 break;
96 case REDIRECT_REQUESTED:
97 doServerRedirect(hrequest, hresponse);
98 break;
99 case DIRECT_SERVER_REQUEST:
100 doDirectServerRequest(hrequest, hresponse, chain);
101 break;
102 case REDIRECTED_SERVER_REQUEST:
103 doRedirectedServerRequest(hrequest, hresponse, chain);
104 break;
105 case SERVER_STARTUP_REQUEST:
106 doStartupRequest(hrequest, hresponse, chain);
107 break;
112 * Determine the request type for a given request.
114 * @param hrequest The Request to categorize
115 * @return The RequestType of the request
117 @VisibleForTesting
118 RequestType getRequestType(HttpServletRequest hrequest) {
119 int serverPort = hrequest.getServerPort();
120 String directServerName = backendServersManager.getServerNameFromPort(serverPort);
122 if (hrequest.getRequestURI().equals("/_ah/start") && directServerName != null) {
123 return RequestType.SERVER_STARTUP_REQUEST;
124 } else if (hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE) != null &&
125 hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE) instanceof String) {
126 return RequestType.REDIRECTED_SERVER_REQUEST;
127 } else if (directServerName != null) {
128 int directServerReplica = backendServersManager.getServerInstanceFromPort(serverPort);
129 if (directServerReplica == -1) {
130 return RequestType.REDIRECT_REQUESTED;
131 } else {
132 return RequestType.DIRECT_SERVER_REQUEST;
134 } else {
135 String serverRedirectHeader =
136 getHeaderOrParameter(hrequest, BackendService.REQUEST_HEADER_BACKEND_REDIRECT);
137 if (serverRedirectHeader == null) {
138 return RequestType.NORMAL_REQUEST;
139 } else {
140 return RequestType.REDIRECT_REQUESTED;
145 private boolean instanceAcceptsConnections(
146 String requestedServer, int instance, HttpServletResponse hresponse) throws IOException {
147 if (!backendServersManager.checkInstanceExists(requestedServer, instance)) {
148 String msg =
149 String.format("Got request to non-configured instance: %d.%s", instance, requestedServer);
150 logger.warning(msg);
151 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
152 return false;
154 if (backendServersManager.checkInstanceStopped(requestedServer, instance)) {
155 String msg =
156 String.format("Got request to stopped instance: %d.%s", instance, requestedServer);
157 logger.warning(msg);
158 hresponse.sendError(SERVER_STOPPED_ERROR_CODE, msg);
159 return false;
162 if (!backendServersManager.acquireServingPermit(requestedServer, instance, true)) {
163 String msg = String.format(
164 "Got request to server %d.%s but the instance is busy.", instance, requestedServer);
165 logger.finer(msg);
166 hresponse.sendError(SERVER_BUSY_ERROR_CODE, msg);
167 return false;
170 return true;
174 * Request that contains either headers or parameters specifying that it
175 * should be forwarded either to a specific server and instance, or to a free
176 * instance of a specific server.
178 private void doServerRedirect(HttpServletRequest hrequest, HttpServletResponse hresponse)
179 throws IOException, ServletException {
180 String requestedServer =
181 backendServersManager.getServerNameFromPort(hrequest.getServerPort());
182 if (requestedServer == null) {
183 requestedServer =
184 getHeaderOrParameter(hrequest, BackendService.REQUEST_HEADER_BACKEND_REDIRECT);
187 int instance = getInstanceIdFromRequest(hrequest);
188 logger.finest(String.format("redirect request to server: %d.%s", instance, requestedServer));
189 if (instance != -1) {
190 if (!instanceAcceptsConnections(requestedServer, instance, hresponse)) {
191 return;
193 } else {
194 if (!backendServersManager.checkServerExists(requestedServer)) {
195 String msg = String.format("Got request to non-configured server: %s", requestedServer);
196 logger.warning(msg);
197 hresponse.sendError(HttpServletResponse.SC_BAD_GATEWAY, msg);
198 return;
200 if (backendServersManager.checkServerStopped(requestedServer)) {
201 String msg = String.format("Got request to stopped server: %s", requestedServer);
202 logger.warning(msg);
203 hresponse.sendError(SERVER_STOPPED_ERROR_CODE, msg);
204 return;
206 instance = backendServersManager.getAndReserveFreeInstance(requestedServer);
207 if (instance == -1) {
208 String msg = String.format("all instances of server %s are busy", requestedServer);
209 logger.finest(msg);
210 hresponse.sendError(SERVER_BUSY_ERROR_CODE, msg);
211 return;
215 try {
216 logger.finer(String.format("forwarding request to server: %d.%s", instance, requestedServer));
217 hrequest.setAttribute(BACKEND_REDIRECT_ATTRIBUTE, requestedServer);
218 hrequest.setAttribute(INSTANCE_REDIRECT_ATTRIBUTE, Integer.valueOf(instance));
219 backendServersManager.forwardToServer(requestedServer, instance, hrequest, hresponse);
220 } finally {
221 backendServersManager.returnServingPermit(requestedServer, instance);
226 * A request sent straight to the local port of a specific server.
228 * If the server is busy with other requests a 500 response is sent.
231 private void doDirectServerRequest(
232 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
233 throws IOException, ServletException {
234 int serverPort = hrequest.getServerPort();
235 String requestedServer = backendServersManager.getServerNameFromPort(serverPort);
236 int instance = backendServersManager.getServerInstanceFromPort(serverPort);
237 logger.finest("request to specific server instance: " + instance + "." + requestedServer);
239 if (!instanceAcceptsConnections(requestedServer, instance, hresponse)) {
240 logger.finest("!instacheAcceptsConnections server instance: " + instance + "."
241 + requestedServer);
242 return;
244 try {
245 logger.finest("instacheAcceptsConnections server instance: " + instance + "."
246 + requestedServer);
247 injectServerApiInfo(requestedServer, instance);
248 chain.doFilter(hrequest, hresponse);
249 } finally {
250 backendServersManager.returnServingPermit(requestedServer, instance);
255 * A request forwarded from a different server. The forwarding server is
256 * responsible for acquiring the serving permit. All we need to do is to add
257 * the ServerApiInfo and forward the request along the chain.
259 private void doRedirectedServerRequest(
260 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
261 throws IOException, ServletException {
262 Object backendServerValue = hrequest.getAttribute(BACKEND_REDIRECT_ATTRIBUTE);
263 String backendServer = (backendServerValue instanceof String) ?
264 ((String) backendServerValue) : null;
265 Object instanceValue = hrequest.getAttribute(INSTANCE_REDIRECT_ATTRIBUTE);
266 Integer instance = (instanceValue instanceof Integer) ? ((Integer) instanceValue) : null;
267 logger.finest("redirected request to server instance: " + instance + "." + backendServer);
269 injectServerApiInfo(backendServer, instance);
270 chain.doFilter(hrequest, hresponse);
274 * Startup requests do not require any serving permits and can be forwarded
275 * along the chain straight away.
277 private void doStartupRequest(
278 HttpServletRequest hrequest, HttpServletResponse hresponse, FilterChain chain)
279 throws IOException, ServletException {
280 int serverPort = hrequest.getServerPort();
281 String backendServer = backendServersManager.getServerNameFromPort(serverPort);
282 int instance = backendServersManager.getServerInstanceFromPort(serverPort);
283 logger.finest("startup request to: " + instance + "." + backendServer);
284 injectServerApiInfo(backendServer, instance);
285 chain.doFilter(hrequest, hresponse);
288 @SuppressWarnings("unused")
289 @Override
290 public void init(FilterConfig filterConfig) throws ServletException {
294 * Inject information about the current server setup so it is available to the
295 * Servers API. This information is stored in the threadLocalAttributes in the
296 * current environment.
298 * @param currentServer The server that is handling the request
299 * @param instance The server instance that is handling the request
301 private void injectServerApiInfo(String currentServer, int instance) {
302 Map<String, Object> threadLocalAttributes = ApiProxy.getCurrentEnvironment().getAttributes();
304 threadLocalAttributes.put(BackendService.INSTANCE_ID_ENV_ATTRIBUTE, instance + "");
305 if (currentServer != null) {
306 threadLocalAttributes.put(BackendService.BACKEND_ID_ENV_ATTRIBUTE, currentServer);
308 Map<String, String> portMapping = backendServersManager.getPortMapping();
309 threadLocalAttributes.put(
310 BackendService.DEVAPPSERVER_PORTMAPPING_KEY, portMapping);
311 if (portMapping.size() > 0) {
312 threadLocalAttributes.put(
313 LocalServerController.BACKEND_CONTROLLER_ATTRIBUTE_KEY, backendServersManager);
318 * Checks the request headers and request parameters for the specified key
320 @VisibleForTesting
321 static String getHeaderOrParameter(HttpServletRequest request, String key) {
322 String value = request.getHeader(key);
323 if (value != null) {
324 return value;
326 if ("GET".equals(request.getMethod())) {
327 return request.getParameter(key);
329 return null;
333 * Checks request headers and parameters to see if an instance id was
334 * specified.
336 @VisibleForTesting
337 static int getInstanceIdFromRequest(HttpServletRequest request) {
338 try {
339 return Integer.parseInt(
340 getHeaderOrParameter(request, BackendService.REQUEST_HEADER_INSTANCE_REDIRECT));
341 } catch (NumberFormatException e) {
342 return -1;
346 @VisibleForTesting
347 static enum RequestType {
348 NORMAL_REQUEST, REDIRECT_REQUESTED, DIRECT_SERVER_REQUEST, REDIRECTED_SERVER_REQUEST,
349 SERVER_STARTUP_REQUEST;