blob: a21e4fb7a3f22dde3d841a9bc73d17a25cf39b0a [file] [log] [blame]
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.gwt.user.server.rpc;
import static com.google.gwt.user.client.rpc.RpcRequestBuilder.STRONG_NAME_HEADER;
import java.io.IOException;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* An abstract base class containing utility methods.
*/
public abstract class AbstractRemoteServiceServlet extends HttpServlet {
protected transient ThreadLocal<HttpServletRequest> perThreadRequest;
protected transient ThreadLocal<HttpServletResponse> perThreadResponse;
public AbstractRemoteServiceServlet() {
super();
}
/**
* Standard HttpServlet method: handle the POST. Delegates to
* {@link #processPost(HttpServletRequest, HttpServletResponse)}.
*
* This doPost method swallows ALL exceptions, logs them in the
* ServletContext, and returns a GENERIC_FAILURE_MSG response with status code
* 500.
*/
@Override
public final void doPost(HttpServletRequest request,
HttpServletResponse response) {
// Ensure the thread-local data fields have been initialized
try {
// Store the request & response objects in thread-local storage.
//
synchronized (this) {
validateThreadLocalData();
perThreadRequest.set(request);
perThreadResponse.set(response);
}
processPost(request, response);
} catch (Throwable e) {
// Give a subclass a chance to either handle the exception or rethrow it
//
doUnexpectedFailure(e);
} finally {
// null the thread-locals to avoid holding request/response
//
perThreadRequest.set(null);
perThreadResponse.set(null);
}
}
/**
* Override this method to control what should happen when an exception
* escapes the {@link #doPost} method. The default implementation will log the
* failure and send a generic failure response to the client.
* <p>
* An "expected failure" is an exception thrown by a service method that is
* declared in the signature of the service method. These exceptions are
* serialized back to the client, and are not passed to this method. This
* method is called only for exceptions or errors that are not part of the
* service method's signature, or that result from SecurityExceptions,
* SerializationExceptions, or other failures within the RPC framework.
* <p>
* Note that if the desired behavior is to both send the GENERIC_FAILURE_MSG
* response AND to rethrow the exception, then this method should first send
* the GENERIC_FAILURE_MSG response itself (using getThreadLocalResponse), and
* then rethrow the exception. Rethrowing the exception will cause it to
* escape into the servlet container.
*
* @param e the exception which was thrown
*/
protected void doUnexpectedFailure(Throwable e) {
try {
getThreadLocalResponse().reset();
} catch (IllegalStateException ex) {
/*
* If we can't reset the request, the only way to signal that something
* has gone wrong is to throw an exception from here. It should be the
* case that we call the user's implementation code before emitting data
* into the response, so the only time that gets tripped is if the object
* serialization code blows up.
*/
throw new RuntimeException("Unable to report failure", e);
}
ServletContext servletContext = getServletContext();
RPCServletUtils.writeResponseForUnexpectedFailure(servletContext,
getThreadLocalResponse(), e);
}
/**
* Returns the strong name of the permutation, as reported by the client that
* issued the request, or <code>null</code> if it could not be determined.
* This information is encoded in the
* {@value com.google.gwt.user.client.rpc.RpcRequestBuilder#STRONG_NAME_HEADER}
* HTTP header.
*/
protected final String getPermutationStrongName() {
return getThreadLocalRequest().getHeader(STRONG_NAME_HEADER);
}
/**
* Gets the <code>HttpServletRequest</code> object for the current call. It is
* stored thread-locally so that simultaneous invocations can have different
* request objects.
*/
protected final HttpServletRequest getThreadLocalRequest() {
synchronized (this) {
validateThreadLocalData();
return perThreadRequest.get();
}
}
/**
* Gets the <code>HttpServletResponse</code> object for the current call. It
* is stored thread-locally so that simultaneous invocations can have
* different response objects.
*/
protected final HttpServletResponse getThreadLocalResponse() {
synchronized (this) {
validateThreadLocalData();
return perThreadResponse.get();
}
}
/**
* Override this method to examine the deserialized version of the request
* before the call to the servlet method is made. The default implementation
* does nothing and need not be called by subclasses.
*
* @param rpcRequest
*/
protected void onAfterRequestDeserialized(RPCRequest rpcRequest) {
}
/**
* Called by {@link #doPost} for type-specific processing of the request.
* Because <code>doPost</code> swallows all <code>Throwables</code>, this
* method may throw any exception the implementor wishes.
*/
protected abstract void processPost(HttpServletRequest request,
HttpServletResponse response) throws Throwable;
/**
* Override this method in order to control the parsing of the incoming
* request. For example, you may want to bypass the check of the Content-Type
* and character encoding headers in the request, as some proxies re-write the
* request headers. Note that bypassing these checks may expose the servlet to
* some cross-site vulnerabilities. Your implementation should comply with the
* HTTP/1.1 specification, which includes handling both requests which include
* a Content-Length header and requests utilizing <code>Transfer-Encoding:
* chuncked</code>.
*
* @param request the incoming request
* @return the content of the incoming request encoded as a string.
*/
protected String readContent(HttpServletRequest request)
throws ServletException, IOException {
return RPCServletUtils.readContentAsGwtRpc(request);
}
/**
* Initializes the perThreadRequest and perThreadResponse fields if they are
* null. This will occur the first time they are accessed after an instance of
* this class is constructed or deserialized. This method should be called
* from within a 'synchronized(this) {}' block in order to ensure that only
* one thread creates the objects.
*/
private void validateThreadLocalData() {
if (perThreadRequest == null) {
perThreadRequest = new ThreadLocal<HttpServletRequest>();
}
if (perThreadResponse == null) {
perThreadResponse = new ThreadLocal<HttpServletResponse>();
}
}
}