diff --git a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/CoreSizeLimitHandler.java b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/CoreSizeLimitHandler.java deleted file mode 100644 index 0d46af3ee..000000000 --- a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/CoreSizeLimitHandler.java +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2021 Google LLC - * - * 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 - * - * https://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.runtime.jetty; - -import java.nio.ByteBuffer; -import org.eclipse.jetty.http.BadMessageException; -import org.eclipse.jetty.http.HttpException; -import org.eclipse.jetty.http.HttpField; -import org.eclipse.jetty.http.HttpFields; -import org.eclipse.jetty.http.HttpHeader; -import org.eclipse.jetty.http.HttpStatus; -import org.eclipse.jetty.io.Content; -import org.eclipse.jetty.server.Handler; -import org.eclipse.jetty.server.Request; -import org.eclipse.jetty.server.Response; -import org.eclipse.jetty.util.Callback; - -/** - * A handler that can limit the size of message bodies in requests and responses. - * - *

The optional request and response limits are imposed by checking the {@code Content-Length} - * header or observing the actual bytes seen by the handler. Handler order is important, in as much - * as if this handler is before a the {@link org.eclipse.jetty.server.handler.gzip.GzipHandler}, - * then it will limit compressed sized, if it as after the {@link - * org.eclipse.jetty.server.handler.gzip.GzipHandler} then the limit is applied to uncompressed - * bytes. If a size limit is exceeded then {@link BadMessageException} is thrown with a {@link - * org.eclipse.jetty.http.HttpStatus#PAYLOAD_TOO_LARGE_413} status. - */ -public class CoreSizeLimitHandler extends Handler.Wrapper -{ - private final long _requestLimit; - private final long _responseLimit; - - /** - * @param requestLimit The request body size limit in bytes or -1 for no limit - * @param responseLimit The response body size limit in bytes or -1 for no limit - */ - public CoreSizeLimitHandler(long requestLimit, long responseLimit) - { - _requestLimit = requestLimit; - _responseLimit = responseLimit; - } - - @Override - public boolean handle(Request request, Response response, Callback callback) throws Exception - { - HttpField contentLengthField = request.getHeaders().getField(HttpHeader.CONTENT_LENGTH); - if (contentLengthField != null) - { - long contentLength = contentLengthField.getLongValue(); - if (_requestLimit >= 0 && contentLength > _requestLimit) - { - String s = "Request body is too large: " + contentLength + ">" + _requestLimit; - Response.writeError(request, response, callback, HttpStatus.PAYLOAD_TOO_LARGE_413, s); - return true; - } - } - - SizeLimitRequestWrapper wrappedRequest = new SizeLimitRequestWrapper(request); - SizeLimitResponseWrapper wrappedResponse = new SizeLimitResponseWrapper(wrappedRequest, response); - return super.handle(wrappedRequest, wrappedResponse, callback); - } - - private class SizeLimitRequestWrapper extends Request.Wrapper - { - private long _read = 0; - - public SizeLimitRequestWrapper(Request wrapped) - { - super(wrapped); - } - - @Override - public Content.Chunk read() - { - Content.Chunk chunk = super.read(); - if (chunk == null) - return null; - if (chunk.getFailure() != null) - return chunk; - - // Check request content limit. - ByteBuffer content = chunk.getByteBuffer(); - if (content != null && content.remaining() > 0) - { - _read += content.remaining(); - if (_requestLimit >= 0 && _read > _requestLimit) - { - BadMessageException e = - new BadMessageException( - HttpStatus.PAYLOAD_TOO_LARGE_413, - "Request body is too large: " + _read + ">" + _requestLimit); - getWrapped().fail(e); - return null; - } - } - - return chunk; - } - } - - private class SizeLimitResponseWrapper extends Response.Wrapper - { - private final HttpFields.Mutable _httpFields; - private long _written = 0; - private String failed; - - public SizeLimitResponseWrapper(Request request, Response wrapped) { - super(request, wrapped); - - _httpFields = - new HttpFields.Mutable.Wrapper(wrapped.getHeaders()) { - @Override - public HttpField onAddField(HttpField field) { - if (HttpHeader.CONTENT_LENGTH.is(field.getName())) { - long contentLength = field.getLongValue(); - if (_responseLimit >= 0 && contentLength > _responseLimit) - throw new HttpException.RuntimeException( - HttpStatus.INTERNAL_SERVER_ERROR_500, - "Response body is too large: " + contentLength + ">" + _responseLimit); - } - return super.onAddField(field); - } - }; - } - - @Override - public HttpFields.Mutable getHeaders() { - return _httpFields; - } - - @Override - public void write(boolean last, ByteBuffer content, Callback callback) - { - if (failed != null) { - callback.failed( - new HttpException.RuntimeException(HttpStatus.INTERNAL_SERVER_ERROR_500, failed)); - return; - } - - if (content != null && content.remaining() > 0) - { - if (_responseLimit >= 0 && (_written + content.remaining()) > _responseLimit) - { - failed = - "Response body is too large: " - + _written - + content.remaining() - + ">" - + _responseLimit; - callback.failed( - new HttpException.RuntimeException(HttpStatus.INTERNAL_SERVER_ERROR_500, failed)); - return; - } - _written += content.remaining(); - } - - super.write(last, content, callback); - } - } -} diff --git a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/JettyServletEngineAdapter.java b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/JettyServletEngineAdapter.java index 160e71979..e57dc3712 100644 --- a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/JettyServletEngineAdapter.java +++ b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/JettyServletEngineAdapter.java @@ -49,6 +49,7 @@ import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SizeLimitHandler; import org.eclipse.jetty.util.VirtualThreads; import org.eclipse.jetty.util.resource.Resource; import org.eclipse.jetty.util.resource.ResourceFactory; @@ -102,6 +103,7 @@ private static AppYaml getAppYaml(ServletEngineAdapter.Config runtimeOptions) { @Override public void start(String serverInfo, ServletEngineAdapter.Config runtimeOptions) { + boolean isHttpConnectorMode = Boolean.getBoolean(HTTP_CONNECTOR_MODE); QueuedThreadPool threadPool = new QueuedThreadPool(MAX_THREAD_POOL_THREADS, MIN_THREAD_POOL_THREADS); // Try to enable virtual threads if requested and on java21: @@ -118,27 +120,45 @@ public InvocationType getInvocationType() { return InvocationType.BLOCKING; } }; - rpcConnector = - new DelegateConnector(server, "RPC") { - @Override - public void run(Runnable runnable) { - // Override this so that it does the initial run in the same thread. - // Currently, we block until completion in serviceRequest() so no point starting new - // thread. - runnable.run(); - } - }; - server.addConnector(rpcConnector); + + // Don't add the RPC Connector if in HttpConnector mode. + if (!isHttpConnectorMode) + { + rpcConnector = + new DelegateConnector(server, "RPC") { + @Override + public void run(Runnable runnable) { + // Override this so that it does the initial run in the same thread. + // Currently, we block until completion in serviceRequest() so no point starting new + // thread. + runnable.run(); + } + }; + + HttpConfiguration httpConfiguration = rpcConnector.getHttpConfiguration(); + httpConfiguration.setSendDateHeader(false); + httpConfiguration.setSendServerVersion(false); + httpConfiguration.setSendXPoweredBy(false); + if (LEGACY_MODE) { + httpConfiguration.setRequestCookieCompliance(CookieCompliance.RFC2965); + httpConfiguration.setResponseCookieCompliance(CookieCompliance.RFC2965); + httpConfiguration.setUriCompliance(UriCompliance.LEGACY); + } + + server.addConnector(rpcConnector); + } + AppVersionHandlerFactory appVersionHandlerFactory = AppVersionHandlerFactory.newInstance(server, serverInfo); appVersionHandler = new AppVersionHandler(appVersionHandlerFactory); - if (!Boolean.getBoolean(IGNORE_RESPONSE_SIZE_LIMIT)) { - CoreSizeLimitHandler sizeLimitHandler = new CoreSizeLimitHandler(-1, MAX_RESPONSE_SIZE); - sizeLimitHandler.setHandler(appVersionHandler); - server.setHandler(sizeLimitHandler); - } else { - server.setHandler(appVersionHandler); + server.setHandler(appVersionHandler); + + // In HttpConnector mode we will combine both SizeLimitHandlers. + boolean ignoreResponseSizeLimit = Boolean.getBoolean(IGNORE_RESPONSE_SIZE_LIMIT); + if (!ignoreResponseSizeLimit && !isHttpConnectorMode) { + server.insertHandler(new SizeLimitHandler(-1, MAX_RESPONSE_SIZE)); } + boolean startJettyHttpProxy = false; if (runtimeOptions.useJettyHttpProxy()) { AppInfoFactory appInfoFactory; @@ -159,14 +179,13 @@ public void run(Runnable runnable) { } catch (Exception e) { throw new IllegalStateException(e); } - if (Boolean.getBoolean(HTTP_CONNECTOR_MODE)) { + if (isHttpConnectorMode) { logger.atInfo().log("Using HTTP_CONNECTOR_MODE to bypass RPC"); - JettyHttpProxy.insertHandlers(server); server.insertHandler( new JettyHttpHandler( runtimeOptions, appVersionHandler.getAppVersion(), appVersionKey, appInfoFactory)); - ServerConnector connector = JettyHttpProxy.newConnector(server, runtimeOptions); - server.addConnector(connector); + JettyHttpProxy.insertHandlers(server, ignoreResponseSizeLimit); + server.addConnector(JettyHttpProxy.newConnector(server, runtimeOptions)); } else { server.setAttribute( "com.google.apphosting.runtime.jetty.appYaml", @@ -197,7 +216,7 @@ public void stop() { } @Override - public void addAppVersion(AppVersion appVersion) throws FileNotFoundException { + public void addAppVersion(AppVersion appVersion) { appVersionHandler.addAppVersion(appVersion); } @@ -239,16 +258,7 @@ public void serviceRequest(UPRequest upRequest, MutableUpResponse upResponse) th } lastAppVersionKey = appVersionKey; } - // TODO: lots of compliance modes to handle. - HttpConfiguration httpConfiguration = rpcConnector.getHttpConfiguration(); - httpConfiguration.setSendDateHeader(false); - httpConfiguration.setSendServerVersion(false); - httpConfiguration.setSendXPoweredBy(false); - if (LEGACY_MODE) { - httpConfiguration.setRequestCookieCompliance(CookieCompliance.RFC2965); - httpConfiguration.setResponseCookieCompliance(CookieCompliance.RFC2965); - httpConfiguration.setUriCompliance(UriCompliance.LEGACY); - } + DelegateRpcExchange rpcExchange = new DelegateRpcExchange(upRequest, upResponse); rpcExchange.setAttribute(AppEngineConstants.APP_VERSION_KEY_REQUEST_ATTR, appVersionKey); rpcExchange.setAttribute(AppEngineConstants.ENVIRONMENT_ATTR, ApiProxy.getCurrentEnvironment()); diff --git a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/proxy/JettyHttpProxy.java b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/proxy/JettyHttpProxy.java index f8f030817..24f8bce26 100644 --- a/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/proxy/JettyHttpProxy.java +++ b/runtime/runtime_impl_jetty12/src/main/java/com/google/apphosting/runtime/jetty/proxy/JettyHttpProxy.java @@ -24,7 +24,6 @@ import com.google.apphosting.runtime.ServletEngineAdapter; import com.google.apphosting.runtime.anyrpc.EvaluationRuntimeServerInterface; import com.google.apphosting.runtime.jetty.AppInfoFactory; -import com.google.apphosting.runtime.jetty.CoreSizeLimitHandler; import com.google.apphosting.runtime.jetty.JettyServletEngineAdapter; import com.google.common.base.Ascii; import com.google.common.base.Throwables; @@ -44,9 +43,12 @@ import org.eclipse.jetty.server.Response; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SizeLimitHandler; import org.eclipse.jetty.server.handler.gzip.GzipHandler; import org.eclipse.jetty.util.Callback; +import static com.google.apphosting.runtime.AppEngineConstants.IGNORE_RESPONSE_SIZE_LIMIT; + /** * A Jetty web server handling HTTP requests on a given port and forwarding them via gRPC to the * Java8 App Engine runtime implementation. The deployed application is assumed to be located in a @@ -68,6 +70,7 @@ public class JettyHttpProxy { private static final GoogleLogger logger = GoogleLogger.forEnclosingClass(); private static final long MAX_REQUEST_SIZE = 32 * 1024 * 1024; + private static final long MAX_RESPONSE_SIZE = 32 * 1024 * 1024; /** * Based on the adapter configuration, this will start a new Jetty server in charge of proxying @@ -108,22 +111,26 @@ public static ServerConnector newConnector( return connector; } - public static void insertHandlers(Server server) { - CoreSizeLimitHandler sizeLimitHandler = new CoreSizeLimitHandler(MAX_REQUEST_SIZE, -1); - sizeLimitHandler.setHandler(server.getHandler()); + public static void insertHandlers(Server server, boolean ignoreResponseSizeLimit) { + + long responseLimit = -1; + if (!ignoreResponseSizeLimit) { + responseLimit = MAX_RESPONSE_SIZE; + } + SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(MAX_REQUEST_SIZE, responseLimit); + server.insertHandler(sizeLimitHandler); GzipHandler gzip = new GzipHandler(); gzip.setInflateBufferSize(8 * 1024); - gzip.setHandler(sizeLimitHandler); gzip.setIncludedMethods(); // Include all methods for the GzipHandler. - server.setHandler(gzip); + server.insertHandler(gzip); } public static Server newServer( ServletEngineAdapter.Config runtimeOptions, ForwardingHandler forwardingHandler) { Server server = new Server(); server.setHandler(forwardingHandler); - insertHandlers(server); + insertHandlers(server, true); ServerConnector connector = newConnector(server, runtimeOptions); server.addConnector(connector); diff --git a/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyHttpProxy.java b/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyHttpProxy.java index ec75a1683..cb1768b06 100644 --- a/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyHttpProxy.java +++ b/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyHttpProxy.java @@ -66,6 +66,7 @@ public class JettyHttpProxy { private static final String JETTY_LOG_CLASS = "org.eclipse.jetty.util.log.class"; private static final String JETTY_STDERRLOG = "org.eclipse.jetty.util.log.StdErrLog"; private static final long MAX_REQUEST_SIZE = 32 * 1024 * 1024; + private static final long MAX_RESPONSE_SIZE = 32 * 1024 * 1024; /** * Based on the adapter configuration, this will start a new Jetty server in charge of proxying @@ -104,23 +105,26 @@ public static ServerConnector newConnector( return connector; } - public static void insertHandlers(Server server) { - SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(MAX_REQUEST_SIZE, -1); - sizeLimitHandler.setHandler(server.getHandler()); + public static void insertHandlers(Server server, boolean ignoreResponseSizeLimit) { + + long responseLimit = -1; + if (!ignoreResponseSizeLimit) { + responseLimit = MAX_RESPONSE_SIZE; + } + SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(MAX_REQUEST_SIZE, responseLimit); + server.insertHandler(sizeLimitHandler); GzipHandler gzip = new GzipHandler(); gzip.setInflateBufferSize(8 * 1024); - gzip.setHandler(sizeLimitHandler); - gzip.setExcludedAgentPatterns(); gzip.setIncludedMethods(); // Include all methods for the GzipHandler. - server.setHandler(gzip); + server.insertHandler(gzip); } public static Server newServer( ServletEngineAdapter.Config runtimeOptions, ForwardingHandler handler) { Server server = new Server(); server.setHandler(handler); - insertHandlers(server); + insertHandlers(server, true); ServerConnector connector = newConnector(server, runtimeOptions); server.addConnector(connector); diff --git a/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyServletEngineAdapter.java b/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyServletEngineAdapter.java index 86644f4e7..6fc7e1e84 100644 --- a/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyServletEngineAdapter.java +++ b/runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/JettyServletEngineAdapter.java @@ -42,9 +42,7 @@ import java.util.Objects; import java.util.Optional; import javax.servlet.ServletException; -import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.handler.SizeLimitHandler; import org.eclipse.jetty.util.thread.QueuedThreadPool; @@ -104,21 +102,24 @@ private static AppYaml getAppYaml(ServletEngineAdapter.Config runtimeOptions) { @Override public void start(String serverInfo, ServletEngineAdapter.Config runtimeOptions) { + boolean isHttpConnectorMode = Boolean.getBoolean(HTTP_CONNECTOR_MODE); server = new Server(new QueuedThreadPool(MAX_THREAD_POOL_THREADS, MIN_THREAD_POOL_THREADS)); - rpcConnector = new RpcConnector(server); - server.setConnectors(new Connector[] {rpcConnector}); + + if (!isHttpConnectorMode) { + rpcConnector = new RpcConnector(server); + server.addConnector(rpcConnector); + } + AppVersionHandlerFactory appVersionHandlerFactory = new AppVersionHandlerFactory( server, serverInfo, contextFactory, /* useJettyErrorPageHandler= */ false); appVersionHandlerMap = new AppVersionHandlerMap(appVersionHandlerFactory); + server.setHandler(appVersionHandlerMap); - if (!Objects.equals(System.getenv("GAE_RUNTIME"), "java8") - && !Boolean.getBoolean(IGNORE_RESPONSE_SIZE_LIMIT)) { - SizeLimitHandler sizeLimitHandler = new SizeLimitHandler(-1, MAX_RESPONSE_SIZE); - sizeLimitHandler.setHandler(appVersionHandlerMap); - server.setHandler(sizeLimitHandler); - } else { - server.setHandler(appVersionHandlerMap); + boolean ignoreResponseSizeLimit = Objects.equals(System.getenv("GAE_RUNTIME"), "java8") + || Boolean.getBoolean(IGNORE_RESPONSE_SIZE_LIMIT); + if (!ignoreResponseSizeLimit && !isHttpConnectorMode) { + server.insertHandler(new SizeLimitHandler(-1, MAX_RESPONSE_SIZE)); } try { @@ -137,15 +138,14 @@ public void start(String serverInfo, ServletEngineAdapter.Config runtimeOptions) evaluationRuntimeServerInterface.addAppVersion(context, appinfo); EmptyMessage unused = context.getResponse(); - if (Boolean.getBoolean(HTTP_CONNECTOR_MODE)) { + if (isHttpConnectorMode) { logger.atInfo().log("Using HTTP_CONNECTOR_MODE to bypass RPC"); appVersionKey = AppVersionKey.fromAppInfo(appinfo); AppVersion appVersion = appVersionHandlerMap.getAppVersion(appVersionKey); server.insertHandler( new JettyHttpHandler(runtimeOptions, appVersion, appVersionKey, appInfoFactory)); - JettyHttpProxy.insertHandlers(server); - ServerConnector connector = JettyHttpProxy.newConnector(server, runtimeOptions); - server.addConnector(connector); + JettyHttpProxy.insertHandlers(server, ignoreResponseSizeLimit); + server.addConnector(JettyHttpProxy.newConnector(server, runtimeOptions)); } else { server.setAttribute( "com.google.apphosting.runtime.jetty9.appYaml", @@ -184,7 +184,7 @@ public void stop() { } @Override - public void addAppVersion(AppVersion appVersion) throws FileNotFoundException { + public void addAppVersion(AppVersion appVersion) { appVersionHandlerMap.addAppVersion(appVersion); }