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

com.google.apphosting.vmruntime.VmApiProxyDelegate Maven / Gradle / Ivy

The newest version!
/**
 * Copyright 2012 Google Inc. All Rights Reserved.
 *
 * 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.apphosting.vmruntime;

import com.google.appengine.repackaged.com.google.common.collect.Lists;
import com.google.apphosting.api.ApiProxy;
import com.google.apphosting.api.ApiProxy.ApiConfig;
import com.google.apphosting.api.ApiProxy.ApiProxyException;
import com.google.apphosting.api.ApiProxy.LogRecord;
import com.google.apphosting.api.ApiProxy.RPCFailedException;
import com.google.apphosting.utils.remoteapi.RemoteApiPb;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.params.ConnManagerPNames;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.PoolingClientConnectionManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpContext;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.util.List;
import java.util.Scanner;
import java.util.concurrent.*;
import java.util.logging.Logger;

/**
 * Delegates AppEngine API calls to a local http API proxy when running inside a VM.
 * 

*

Instances should be registered using ApiProxy.setDelegate(ApiProxy.Delegate). */ public class VmApiProxyDelegate implements ApiProxy.Delegate { private static final Logger logger = Logger.getLogger(VmApiProxyDelegate.class.getName()); public static final String RPC_DEADLINE_HEADER = "X-Google-RPC-Service-Deadline"; public static final String RPC_STUB_ID_HEADER = "X-Google-RPC-Service-Endpoint"; public static final String RPC_METHOD_HEADER = "X-Google-RPC-Service-Method"; public static final String REQUEST_ENDPOINT = "/rpc_http"; public static final String REQUEST_STUB_ID = "app-engine-apis"; public static final String REQUEST_STUB_METHOD = "/VMRemoteAPI.CallRemoteAPI"; protected static final String API_DEADLINE_KEY = "com.google.apphosting.api.ApiProxy.api_deadline_key"; static final int ADDITIONAL_HTTP_TIMEOUT_BUFFER_MS = 1000; protected int defaultTimeoutMs; protected final ExecutorService executor; protected final HttpClient httpclient; final IdleConnectionMonitorThread monitorThread; private static ClientConnectionManager createConnectionManager() { PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(); connectionManager.setMaxTotal(VmApiProxyEnvironment.MAX_CONCURRENT_API_CALLS); connectionManager.setDefaultMaxPerRoute(VmApiProxyEnvironment.MAX_CONCURRENT_API_CALLS); return connectionManager; } public VmApiProxyDelegate() { this(new DefaultHttpClient(createConnectionManager())); } VmApiProxyDelegate(HttpClient httpclient) { this.defaultTimeoutMs = 5 * 60 * 1000; this.executor = Executors.newCachedThreadPool(); this.httpclient = httpclient; this.monitorThread = new IdleConnectionMonitorThread(httpclient.getConnectionManager()); this.monitorThread.start(); } @Override public byte[] makeSyncCall( LazyApiProxyEnvironment environment, String packageName, String methodName, byte[] requestData) throws ApiProxyException { return makeSyncCallWithTimeout(environment, packageName, methodName, requestData, defaultTimeoutMs); } private byte[] makeSyncCallWithTimeout( LazyApiProxyEnvironment environment, String packageName, String methodName, byte[] requestData, int timeoutMs) throws ApiProxyException { return makeApiCall(environment, packageName, methodName, requestData, timeoutMs, false); } private byte[] makeApiCall(LazyApiProxyEnvironment environment, String packageName, String methodName, byte[] requestData, int timeoutMs, boolean wasAsync) { environment.apiCallStarted(VmRuntimeUtils.MAX_USER_API_CALL_WAIT_MS, wasAsync); try { return runSyncCall(environment, packageName, methodName, requestData, timeoutMs); } finally { environment.apiCallCompleted(); } } protected byte[] runSyncCall(LazyApiProxyEnvironment environment, String packageName, String methodName, byte[] requestData, int timeoutMs) { HttpPost request = createRequest(environment, packageName, methodName, requestData, timeoutMs); try { BasicHttpContext context = new BasicHttpContext(); HttpResponse response = httpclient.execute(request, context); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { try (Scanner errorStreamScanner = new Scanner(new BufferedInputStream(response.getEntity().getContent()));) { logger.info("Error body: " + errorStreamScanner.useDelimiter("\\Z").next()); throw new RPCFailedException(packageName, methodName); } } try (BufferedInputStream bis = new BufferedInputStream(response.getEntity().getContent())) { RemoteApiPb.Response remoteResponse = new RemoteApiPb.Response(); if (!remoteResponse.parseFrom(bis)) { logger.info( "HTTP ApiProxy unable to parse response for " + packageName + "." + methodName); throw new RPCFailedException(packageName, methodName); } if (remoteResponse.hasRpcError() || remoteResponse.hasApplicationError()) { throw convertRemoteError(remoteResponse, packageName, methodName, logger); } return remoteResponse.getResponseAsBytes(); } } catch (IOException e) { logger.info( "HTTP ApiProxy I/O error for " + packageName + "." + methodName + ": " + e.getMessage()); throw new RPCFailedException(packageName, methodName); } finally { request.releaseConnection(); } } /** * Create an HTTP post request suitable for sending to the API server. * * @param environment The current VMApiProxyEnvironment * @param packageName The API call package * @param methodName The API call method * @param requestData The POST payload. * @param timeoutMs The timeout for this request * @return an HttpPost object to send to the API. */ static HttpPost createRequest(LazyApiProxyEnvironment environment, String packageName, String methodName, byte[] requestData, int timeoutMs) { RemoteApiPb.Request remoteRequest = new RemoteApiPb.Request(); remoteRequest.setServiceName(packageName); remoteRequest.setMethod(methodName); remoteRequest.setRequestId(environment.getTicket()); remoteRequest.setRequestAsBytes(requestData); HttpPost request = new HttpPost("http://" + environment.getServer() + REQUEST_ENDPOINT); request.setHeader(RPC_STUB_ID_HEADER, REQUEST_STUB_ID); request.setHeader(RPC_METHOD_HEADER, REQUEST_STUB_METHOD); HttpParams params = new BasicHttpParams(); params.setLongParameter(ConnManagerPNames.TIMEOUT, timeoutMs + ADDITIONAL_HTTP_TIMEOUT_BUFFER_MS); params.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, timeoutMs + ADDITIONAL_HTTP_TIMEOUT_BUFFER_MS); params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, timeoutMs + ADDITIONAL_HTTP_TIMEOUT_BUFFER_MS); params.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, Boolean.TRUE); params.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, Boolean.FALSE); request.setParams(params); Double deadline = (Double) (environment.getAttributes().get(API_DEADLINE_KEY)); if (deadline == null) { request.setHeader(RPC_DEADLINE_HEADER, Double.toString(TimeUnit.SECONDS.convert(timeoutMs, TimeUnit.MILLISECONDS))); } else { request.setHeader(RPC_DEADLINE_HEADER, Double.toString(deadline)); } Object dapperHeader = environment.getAttributes() .get(VmApiProxyEnvironment.AttributeMapping.DAPPER_ID.attributeKey); if (dapperHeader instanceof String) { request.setHeader( VmApiProxyEnvironment.AttributeMapping.DAPPER_ID.headerKey, (String) dapperHeader); } ByteArrayEntity postPayload = new ByteArrayEntity(remoteRequest.toByteArray(), ContentType.APPLICATION_OCTET_STREAM); postPayload.setChunked(false); request.setEntity(postPayload); return request; } /** * Convert RemoteApiPb.Response errors to the appropriate exception. *

*

The response must have exactly one of the RpcError and ApplicationError fields set. * * @param remoteResponse the Response * @param packageName the name of the API package. * @param methodName the name of the method within the API package. * @param logger the Logger used to create log messages. * @return ApiProxyException */ private static ApiProxyException convertRemoteError(RemoteApiPb.Response remoteResponse, String packageName, String methodName, Logger logger) { if (remoteResponse.hasRpcError()) { return convertApiResponseRpcErrorToException( remoteResponse.getRpcError(), packageName, methodName, logger); } RemoteApiPb.ApplicationError error = remoteResponse.getApplicationError(); return new ApiProxy.ApplicationException(error.getCode(), error.getDetail()); } /** * Convert the RemoteApiPb.RpcError to the appropriate exception. * * @param rpcError the RemoteApiPb.RpcError. * @param packageName the name of the API package. * @param methodName the name of the method within the API package. * @param logger the Logger used to create log messages. * @return ApiProxyException */ private static ApiProxyException convertApiResponseRpcErrorToException( RemoteApiPb.RpcError rpcError, String packageName, String methodName, Logger logger) { int rpcCode = rpcError.getCode(); String errorDetail = rpcError.getDetail(); if (rpcCode > RemoteApiPb.RpcError.ErrorCode.values().length) { logger.severe("Received unrecognized error code from server: " + rpcError.getCode() + " details: " + errorDetail); return new ApiProxy.UnknownException(packageName, methodName); } RemoteApiPb.RpcError.ErrorCode errorCode = RemoteApiPb.RpcError.ErrorCode.values()[ rpcError.getCode()]; logger.warning("RPC failed : " + errorCode + " : " + errorDetail); switch (errorCode) { case CALL_NOT_FOUND: return new ApiProxy.CallNotFoundException(packageName, methodName); case PARSE_ERROR: return new ApiProxy.ArgumentException(packageName, methodName); case SECURITY_VIOLATION: logger.severe("Security violation: invalid request id used!"); return new ApiProxy.UnknownException(packageName, methodName); case CAPABILITY_DISABLED: return new ApiProxy.CapabilityDisabledException( errorDetail, packageName, methodName); case OVER_QUOTA: return new ApiProxy.OverQuotaException(packageName, methodName); case REQUEST_TOO_LARGE: return new ApiProxy.RequestTooLargeException(packageName, methodName); case RESPONSE_TOO_LARGE: return new ApiProxy.ResponseTooLargeException(packageName, methodName); case BAD_REQUEST: return new ApiProxy.ArgumentException(packageName, methodName); case CANCELLED: return new ApiProxy.CancelledException(packageName, methodName); case FEATURE_DISABLED: return new ApiProxy.FeatureNotEnabledException( errorDetail, packageName, methodName); case DEADLINE_EXCEEDED: return new ApiProxy.ApiDeadlineExceededException(packageName, methodName); default: return new ApiProxy.UnknownException(packageName, methodName); } } private class MakeSyncCall implements Callable { private final VmApiProxyDelegate delegate; private final LazyApiProxyEnvironment environment; private final String packageName; private final String methodName; private final byte[] requestData; private final int timeoutMs; public MakeSyncCall(VmApiProxyDelegate delegate, LazyApiProxyEnvironment environment, String packageName, String methodName, byte[] requestData, int timeoutMs) { this.delegate = delegate; this.environment = environment; this.packageName = packageName; this.methodName = methodName; this.requestData = requestData; this.timeoutMs = timeoutMs; } @Override public byte[] call() throws Exception { return delegate.makeApiCall(environment, packageName, methodName, requestData, timeoutMs, true); } } @Override public Future makeAsyncCall( LazyApiProxyEnvironment environment, String packageName, String methodName, byte[] request, ApiConfig apiConfig) { int timeoutMs = defaultTimeoutMs; if (apiConfig != null && apiConfig.getDeadlineInSeconds() != null) { timeoutMs = (int) (apiConfig.getDeadlineInSeconds() * 1000); } environment.aSyncApiCallAdded(VmRuntimeUtils.MAX_USER_API_CALL_WAIT_MS); return executor.submit(new MakeSyncCall(this, environment, packageName, methodName, request, timeoutMs)); } @Override public void log(LazyApiProxyEnvironment environment, LogRecord record) { if (environment != null) { environment.addLogRecord(record); } } @Override public void flushLogs(LazyApiProxyEnvironment environment) { if (environment != null) { environment.flushLogs(); } } @Override public List getRequestThreads(LazyApiProxyEnvironment environment) { Object threadFactory = environment.getAttributes().get(VmApiProxyEnvironment.REQUEST_THREAD_FACTORY_ATTR); if (threadFactory != null && threadFactory instanceof VmRequestThreadFactory) { return ((VmRequestThreadFactory) threadFactory).getRequestThreads(); } logger.warning("Got a call to getRequestThreads() but no VmRequestThreadFactory is available"); return Lists.newLinkedList(); } /** * Simple connection watchdog verifying that our connections are alive. Any stale connections are * cleared as well. */ class IdleConnectionMonitorThread extends Thread { private final ClientConnectionManager connectionManager; public IdleConnectionMonitorThread(ClientConnectionManager connectionManager) { super("IdleApiConnectionMontorThread"); this.connectionManager = connectionManager; this.setDaemon(false); } @Override public void run() { try { while (true) { connectionManager.closeExpiredConnections(); connectionManager.closeIdleConnections(60, TimeUnit.SECONDS); Thread.sleep(5000); } } catch (InterruptedException ex) { } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy