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
;
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 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
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());
79 DevAppServerServersFilter(AbstractBackendServers backendServers
) {
80 this.backendServersManager
= backendServers
;
83 public DevAppServerServersFilter() {
84 this.backendServersManager
= BackendServers
.getInstance();
88 public void destroy() {
92 * Main filter method. All request to the dev-appserver pass this method.
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
) {
102 injectServerApiInfo(null, -1);
103 chain
.doFilter(hrequest
, hresponse
);
105 case REDIRECT_REQUESTED
:
106 doRedirect(hrequest
, hresponse
);
108 case DIRECT_BACKEND_REQUEST
:
109 doDirectBackendRequest(hrequest
, hresponse
, chain
);
111 case REDIRECTED_BACKEND_REQUEST
:
112 doRedirectedBackendRequest(hrequest
, hresponse
, chain
);
114 case REDIRECTED_SERVER_REQUEST
:
115 doRedirectedServerRequest(hrequest
, hresponse
, chain
);
117 case SERVER_STARTUP_REQUEST
:
118 doStartupRequest(hrequest
, hresponse
, chain
);
124 * Determine the request type for a given request.
126 * @param hrequest The Request to categorize
127 * @return The RequestType of the request
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
;
146 return RequestType
.DIRECT_BACKEND_REQUEST
;
149 String serverRedirectHeader
=
150 getHeaderOrParameter(hrequest
, BackendService
.REQUEST_HEADER_BACKEND_REDIRECT
);
151 if (serverRedirectHeader
== null && !isServerLoadBalancingServerRequest()) {
152 return RequestType
.NORMAL_REQUEST
;
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";
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
)) {
182 String
.format("Got request to non-configured instance: %d.%s", instance
, requestedServer
);
184 hresponse
.sendError(HttpServletResponse
.SC_BAD_GATEWAY
, msg
);
187 if (serversFilterHelper
.checkInstanceStopped(requestedServer
, instance
)) {
189 String
.format("Got request to stopped instance: %d.%s", instance
, requestedServer
);
191 hresponse
.sendError(SERVER_STOPPED_ERROR_CODE
, msg
);
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
);
199 hresponse
.sendError(SERVER_BUSY_ERROR_CODE
, msg
);
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) {
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
)) {
234 if (!serversFilterHelper
.checkServerExists(requestedServer
)) {
235 String msg
= String
.format("Got request to non-configured server: %s", requestedServer
);
237 hresponse
.sendError(HttpServletResponse
.SC_BAD_GATEWAY
, msg
);
240 if (serversFilterHelper
.checkServerStopped(requestedServer
)) {
241 String msg
= String
.format("Got request to stopped server: %s", requestedServer
);
243 hresponse
.sendError(SERVER_STOPPED_ERROR_CODE
, msg
);
246 instance
= serversFilterHelper
.getAndReserveFreeInstance(requestedServer
);
247 if (instance
== -1) {
248 String msg
= String
.format("all instances of server %s are busy", requestedServer
);
250 hresponse
.sendError(SERVER_BUSY_ERROR_CODE
, msg
);
256 if (isServersLoadBalancingServer
) {
257 logger
.finer(String
.format("forwarding request to server: %d.%s", instance
,
259 hrequest
.setAttribute(SERVER_INSTANCE_REDIRECT_ATTRIBUTE
, Integer
.valueOf(instance
));
261 logger
.finer(String
.format("forwarding request to backend: %d.%s", instance
,
263 hrequest
.setAttribute(BACKEND_REDIRECT_ATTRIBUTE
, requestedServer
);
264 hrequest
.setAttribute(BACKEND_INSTANCE_REDIRECT_ATTRIBUTE
, Integer
.valueOf(instance
));
266 serversFilterHelper
.forwardToServer(requestedServer
, instance
, hrequest
, hresponse
);
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
+ "."
292 logger
.finest("instacheAcceptsConnections server instance: " + instance
+ "."
294 injectServerApiInfo(requestedServer
, instance
);
295 chain
.doFilter(hrequest
, hresponse
);
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
+ "."
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")
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
383 static String
getHeaderOrParameter(HttpServletRequest request
, String key
) {
384 String value
= request
.getHeader(key
);
388 if ("GET".equals(request
.getMethod())) {
389 return request
.getParameter(key
);
395 * Checks request headers and parameters to see if an instance id was
399 static int getInstanceIdFromRequest(HttpServletRequest request
) {
401 return Integer
.parseInt(
402 getHeaderOrParameter(request
, BackendService
.REQUEST_HEADER_INSTANCE_REDIRECT
));
403 } catch (NumberFormatException e
) {
409 static enum RequestType
{
410 NORMAL_REQUEST
, REDIRECT_REQUESTED
, DIRECT_BACKEND_REQUEST
, REDIRECTED_BACKEND_REQUEST
,
411 REDIRECTED_SERVER_REQUEST
, SERVER_STARTUP_REQUEST
;