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
;
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
;
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
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());
69 BackendServersFilter(AbstractBackendServers backendServers
) {
70 this.backendServersManager
= backendServers
;
73 public BackendServersFilter() {
74 this.backendServersManager
= BackendServers
.getInstance();
78 public void destroy() {
82 * Main filter method. All request to the dev-appserver pass this method.
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
) {
93 injectServerApiInfo(null, -1);
94 chain
.doFilter(hrequest
, hresponse
);
96 case REDIRECT_REQUESTED
:
97 doServerRedirect(hrequest
, hresponse
);
99 case DIRECT_SERVER_REQUEST
:
100 doDirectServerRequest(hrequest
, hresponse
, chain
);
102 case REDIRECTED_SERVER_REQUEST
:
103 doRedirectedServerRequest(hrequest
, hresponse
, chain
);
105 case SERVER_STARTUP_REQUEST
:
106 doStartupRequest(hrequest
, hresponse
, chain
);
112 * Determine the request type for a given request.
114 * @param hrequest The Request to categorize
115 * @return The RequestType of the request
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
;
132 return RequestType
.DIRECT_SERVER_REQUEST
;
135 String serverRedirectHeader
=
136 getHeaderOrParameter(hrequest
, BackendService
.REQUEST_HEADER_BACKEND_REDIRECT
);
137 if (serverRedirectHeader
== null) {
138 return RequestType
.NORMAL_REQUEST
;
140 return RequestType
.REDIRECT_REQUESTED
;
145 private boolean instanceAcceptsConnections(
146 String requestedServer
, int instance
, HttpServletResponse hresponse
) throws IOException
{
147 if (!backendServersManager
.checkInstanceExists(requestedServer
, instance
)) {
149 String
.format("Got request to non-configured instance: %d.%s", instance
, requestedServer
);
151 hresponse
.sendError(HttpServletResponse
.SC_BAD_GATEWAY
, msg
);
154 if (backendServersManager
.checkInstanceStopped(requestedServer
, instance
)) {
156 String
.format("Got request to stopped instance: %d.%s", instance
, requestedServer
);
158 hresponse
.sendError(SERVER_STOPPED_ERROR_CODE
, msg
);
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
);
166 hresponse
.sendError(SERVER_BUSY_ERROR_CODE
, msg
);
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) {
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
)) {
194 if (!backendServersManager
.checkServerExists(requestedServer
)) {
195 String msg
= String
.format("Got request to non-configured server: %s", requestedServer
);
197 hresponse
.sendError(HttpServletResponse
.SC_BAD_GATEWAY
, msg
);
200 if (backendServersManager
.checkServerStopped(requestedServer
)) {
201 String msg
= String
.format("Got request to stopped server: %s", requestedServer
);
203 hresponse
.sendError(SERVER_STOPPED_ERROR_CODE
, msg
);
206 instance
= backendServersManager
.getAndReserveFreeInstance(requestedServer
);
207 if (instance
== -1) {
208 String msg
= String
.format("all instances of server %s are busy", requestedServer
);
210 hresponse
.sendError(SERVER_BUSY_ERROR_CODE
, msg
);
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
);
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
+ "."
245 logger
.finest("instacheAcceptsConnections server instance: " + instance
+ "."
247 injectServerApiInfo(requestedServer
, instance
);
248 chain
.doFilter(hrequest
, hresponse
);
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")
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
321 static String
getHeaderOrParameter(HttpServletRequest request
, String key
) {
322 String value
= request
.getHeader(key
);
326 if ("GET".equals(request
.getMethod())) {
327 return request
.getParameter(key
);
333 * Checks request headers and parameters to see if an instance id was
337 static int getInstanceIdFromRequest(HttpServletRequest request
) {
339 return Integer
.parseInt(
340 getHeaderOrParameter(request
, BackendService
.REQUEST_HEADER_INSTANCE_REDIRECT
));
341 } catch (NumberFormatException e
) {
347 static enum RequestType
{
348 NORMAL_REQUEST
, REDIRECT_REQUESTED
, DIRECT_SERVER_REQUEST
, REDIRECTED_SERVER_REQUEST
,
349 SERVER_STARTUP_REQUEST
;