All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.rpc.server.RpcServlet Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * 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.rpc.server;

import static com.google.gwt.user.client.rpc.RpcRequestBuilder.MODULE_BASE_HEADER;

import com.google.gwt.rpc.client.impl.RemoteException;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.AbstractRemoteServiceServlet;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RPCServletUtils;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.SoftReference;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.GZIPOutputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * EXPERIMENTAL and subject to change. Do not use this in production code.
 * 

* The servlet base class for your RPC service implementations that * automatically deserializes incoming requests from the client and serializes * outgoing responses for client/server RPCs. */ public class RpcServlet extends AbstractRemoteServiceServlet { protected static final String CLIENT_ORACLE_EXTENSION = ".gwt.rpc"; private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload"); private final Map> clientOracleCache = new ConcurrentHashMap>(); /** * The implementation of the service. */ private final Object delegate; /** * The default constructor used by service implementations that * extend this class. The servlet will delegate AJAX requests to * the appropriate method in the subclass. */ public RpcServlet() { this.delegate = this; } /** * The wrapping constructor used by service implementations that are * separate from this class. The servlet will delegate AJAX * requests to the appropriate method in the given object. */ public RpcServlet(Object delegate) { this.delegate = delegate; } /** * This method creates the ClientOracle that will provide data about the * remote client. It delegates to * {@link #findClientOracleData(String, String)} to obtain access to * ClientOracle data emitted by the GWT compiler. */ public ClientOracle getClientOracle() throws SerializationException { String permutationStrongName = getPermutationStrongName(); if (permutationStrongName == null) { throw new SecurityException( "Blocked request without GWT permutation header (XSRF attack?)"); } String basePath = getRequestModuleBasePath(); if (basePath == null) { throw new SecurityException( "Blocked request without GWT base path header (XSRF attack?)"); } ClientOracle toReturn; // Fast path if the ClientOracle is already cached. if (clientOracleCache.containsKey(permutationStrongName)) { toReturn = clientOracleCache.get(permutationStrongName).get(); if (toReturn != null) { return toReturn; } } /* Synchronize to make sure expensive calls are executed only once. Double checked locking idiom works here because of volatiles in ConcurrentHashMap.*/ synchronized (clientOracleCache) { if (clientOracleCache.containsKey(permutationStrongName)) { toReturn = clientOracleCache.get(permutationStrongName).get(); if (toReturn != null) { return toReturn; } } if ("HostedMode".equals(permutationStrongName)) { if (!allowHostedModeConnections()) { throw new SecurityException("Blocked Development Mode request"); } toReturn = new HostedModeClientOracle(); } else { InputStream in = findClientOracleData(basePath, permutationStrongName); try { toReturn = WebModeClientOracle.load(in); } catch (IOException e) { throw new SerializationException( "Could not load serialization policy for permutation " + permutationStrongName, e); } } clientOracleCache.put(permutationStrongName, new SoftReference(toReturn)); } return toReturn; } /** * Process a call originating from the given request. Uses the * {@link RPC#invokeAndStreamResponse(Object, java.lang.reflect.Method, Object[], ClientOracle, OutputStream)} * method to do the actual work. *

* Subclasses may optionally override this method to handle the payload in any * way they desire (by routing the request to a framework component, for * instance). The {@link HttpServletRequest} and {@link HttpServletResponse} * can be accessed via the {@link #getThreadLocalRequest()} and * {@link #getThreadLocalResponse()} methods. *

* This is public so that it can be unit tested easily without HTTP. * * @param clientOracle the ClientOracle that will be used to interpret the * request * @param payload the UTF-8 request payload * @param stream the OutputStream that will receive the encoded response * @throws SerializationException if we cannot serialize the response */ public void processCall(ClientOracle clientOracle, String payload, OutputStream stream) throws SerializationException { assert clientOracle != null : "clientOracle"; assert payload != null : "payload"; assert stream != null : "stream"; try { RPCRequest rpcRequest = RPC.decodeRequest(payload, delegate.getClass(), clientOracle); onAfterRequestDeserialized(rpcRequest); RPC.invokeAndStreamResponse(delegate, rpcRequest.getMethod(), rpcRequest.getParameters(), clientOracle, stream); } catch (RemoteException ex) { throw new SerializationException("An exception was sent from the client", ex.getCause()); } catch (IncompatibleRemoteServiceException ex) { log( "An IncompatibleRemoteServiceException was thrown while processing this call.", ex); RPC.streamResponseForFailure(clientOracle, stream, ex); } } /** * Standard HttpServlet method: handle the POST. * * This doPost method swallows ALL exceptions, logs them in the * ServletContext, and returns a GENERIC_FAILURE_MSG response with status code * 500. * * @throws IOException * @throws ServletException * @throws SerializationException */ @Override public final void processPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException, SerializationException { /* * Get the ClientOracle before doing anything else, so that if ClientOracle * cannot be loaded, we haven't opened the response's OutputStream. */ ClientOracle clientOracle = getClientOracle(); // Read the request fully. String requestPayload = readContent(request); if (DUMP_PAYLOAD) { System.out.println(requestPayload); } response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); // Configure the OutputStream based on configuration and capabilities boolean canCompress = RPCServletUtils.acceptsGzipEncoding(request) && shouldCompressResponse(request, response); OutputStream out; if (DUMP_PAYLOAD) { out = new ByteArrayOutputStream(); } else if (canCompress) { RPCServletUtils.setGzipEncodingHeader(response); out = new GZIPOutputStream(response.getOutputStream()); } else { out = response.getOutputStream(); } // Invoke the core dispatching logic, which returns the serialized result. processCall(clientOracle, requestPayload, out); if (DUMP_PAYLOAD) { byte[] bytes = ((ByteArrayOutputStream) out).toByteArray(); System.out.println(new String(bytes, "UTF-8")); response.getOutputStream().write(bytes); } else if (canCompress) { /* * We want to write the end of the gzip data, but not close the underlying * OutputStream in case there are servlet filters that want to write * headers after processPost(). */ ((GZIPOutputStream) out).finish(); } } /** * Indicates whether or not an RPC request from a Development Mode client * should be serviced. Requests from Development Mode clients will expose * unobfuscated identifiers in the payload. It is intended that developers * override this method to restrict access based on installation-specific * logic (such as a range of IP addresses, checking for certain cookies, etc.) *

* The default implementation allows hosted-mode connections from the local * host, loopback addresses (127.*), site local (RFC 1918), link local * (169.254/16) addresses, and their IPv6 equivalents. * * @return true if a Development Mode connection should be * allowed * @see #getThreadLocalRequest() * @see InetAddress */ protected boolean allowHostedModeConnections() { return isRequestFromLocalAddress(); } /** * Override this method to control access to permutation-specific data. For * instance, the permutation-specific data may be stored in a database in * order to support older clients. *

* The default implementation attempts to load the file from the * ServletContext as * * requestModuleBasePath + permutationStrongName + CLIENT_ORACLE_EXTENSION * * @param requestModuleBasePath the module's base path, modulo protocol and * host, as reported by {@link #getRequestModuleBasePath()} * @param permutationStrongName the module's strong name as reported by * {@link #getPermutationStrongName()} */ protected InputStream findClientOracleData(String requestModuleBasePath, String permutationStrongName) throws SerializationException { String resourcePath = requestModuleBasePath + permutationStrongName + CLIENT_ORACLE_EXTENSION; InputStream in = getServletContext().getResourceAsStream(resourcePath); if (in == null) { throw new SerializationException( "Could not find ClientOracle data for permutation " + permutationStrongName); } return in; } /** * Extract the module's base path from the current request. * * @return the module's base path, modulo protocol and host, as reported by * {@link com.google.gwt.core.client.GWT#getModuleBaseURL()} or * null if the request did not contain the * {@value com.google.gwt.user.client.rpc.RpcRequestBuilder#MODULE_BASE_HEADER} header */ protected final String getRequestModuleBasePath() { try { String header = getThreadLocalRequest().getHeader(MODULE_BASE_HEADER); if (header == null) { return null; } String path = new URL(header).getPath(); String contextPath = getThreadLocalRequest().getContextPath(); if (!path.startsWith(contextPath)) { return null; } return path.substring(contextPath.length()); } catch (MalformedURLException e) { return null; } } /** * Determines whether the response to a given servlet request should or should * not be GZIP compressed. This method is only called in cases where the * requester accepts GZIP encoding. *

* This implementation currently returns true if the request * originates from a non-local address. Subclasses can override this logic. *

* * @param request the request being served * @param response the response that will be written into * @return true if responsePayload should be GZIP compressed, * otherwise false. */ protected boolean shouldCompressResponse(HttpServletRequest request, HttpServletResponse response) { return !isRequestFromLocalAddress(); } /** * Utility function to determine if the thread-local request originates from a * local address. */ private boolean isRequestFromLocalAddress() { try { InetAddress addr = InetAddress.getByName(getThreadLocalRequest().getRemoteAddr()); return InetAddress.getLocalHost().equals(addr) || addr.isLoopbackAddress() || addr.isSiteLocalAddress() || addr.isLinkLocalAddress(); } catch (UnknownHostException e) { return false; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy