in runtime/runtime_impl_jetty9/src/main/java/com/google/apphosting/runtime/jetty9/RpcConnection.java [145:316]
public void handle(final AppVersionKey appVersionKey) throws ServletException, IOException {
HttpRequest rpc = endPoint.getUpRequest().getRequest();
final byte[] postdata = rpc.getPostdata().toByteArray();
final CountDownLatch blockEndRequest = new CountDownLatch(1);
HttpChannel channel =
new HttpChannel(connector, connector.getHttpConfiguration(), endPoint, this) {
@Override
protected void handleException(Throwable th) {
boolean requestWasCommitted = isCommitted();
super.handleException(th);
if (requestWasCommitted) {
// The response was already committed before Jetty handled the exception.
// In order to preserve Jetty 6 behavior we clear out the
// attribute that holds the exception to avoid rethrowing it further.
getRequest().removeAttribute(RequestDispatcher.ERROR_EXCEPTION);
}
blockEndRequest.countDown();
}
@Override
protected String formatAddrOrHost(String addr) {
return NORMALIZE_INET_ADDR ? super.formatAddrOrHost(addr) : addr;
}
@Override
public void onCompleted() {
super.onCompleted();
blockEndRequest.countDown();
}
};
Request request = channel.getRequest();
// Enable async via a property
request.setAsyncSupported(Boolean.getBoolean(ASYNC_ENABLE_PPROPERTY), null);
// pretend to parse the request line
// LEGACY_MODE is case insensitive for known methods
HttpMethod method = LEGACY_MODE
? HttpMethod.INSENSITIVE_CACHE.get(rpc.getProtocol())
: HttpMethod.CACHE.get(rpc.getProtocol());
String methodS = method != null ? method.asString() : rpc.getProtocol();
try {
String url = rpc.getUrl();
HttpURI uri = new HttpURI(url);
HttpVersion version = HttpVersion.CACHE.getBest(rpc.getHttpVersion());
MetaData.Request requestData =
new MetaData.Request(
methodS, uri, version, new HttpFields(), postdata == null ? -1 : postdata.length);
// pretend to parse the header fields
boolean contentLength = false;
for (ParsedHttpHeader header : rpc.getHeadersList()) {
HttpField field = getField(header);
// Handle LegacyMode Headers
if (LEGACY_MODE && field.getHeader() != null) {
switch (field.getHeader()) {
case CONTENT_ENCODING:
continue;
case CONTENT_LENGTH:
if (contentLength) {
throw new BadMessageException("Duplicate Content-Length");
}
contentLength = true;
break;
default:
break;
}
}
requestData.getFields().add(field);
}
// end of headers. This should return true to indicate that we are good to continue handling
channel.onRequest(requestData);
// is this SSL
if (rpc.getIsHttps()) {
// the following code has to be done after the channel.onRequest(requestData) call
// to avoid NPE.
request.setScheme(HttpScheme.HTTPS.asString());
request.setSecure(true);
}
// signal the end of the request
channel.onRequestComplete();
// Give the input any post content.
if (postdata != null) {
channel.getRequest().getHttpInput().addContent(new Content(BufferUtil.toBuffer(postdata)));
}
} catch (Exception t) {
// Any exception at this stage is most likely due to a bad message
// We cannot use response.sendError as it needs a validly initiated channel to work.
upResponse.setHttpResponseCodeAndResponse(400, "");
channel.getResponse().setStatus(400);
return;
}
// Tell AppVersionHandlerMap which app version should handle this
// request.
request.setAttribute(AppEngineConstants.APP_VERSION_KEY_REQUEST_ATTR, appVersionKey);
final boolean skipAdmin = hasSkipAdminCheck(endPoint.getUpRequest());
// Translate the X-Google-Internal-SkipAdminCheck to a servlet attribute.
if (skipAdmin) {
request.setAttribute(SKIP_ADMIN_CHECK_ATTR, true);
// N.B.: If SkipAdminCheck is set, we're actually lying
// to Jetty here to tell it that HTTPS is in use when it may not
// be. This is useful because we want to bypass Jetty's
// transport-guarantee checks (to match Python, which bypasses
// handler_security: for these requests), but unlike
// authentication SecurityHandler does not provide an easy way to
// plug in custom logic here. I do not believe that our lie is
// user-visible (ServletRequest.getProtocol() is unchanged).
request.setSecure(true);
}
Throwable exception = null;
try {
// This will invoke a servlet and mutate upResponse before returning.
channel.handle();
waitforAsyncDone(blockEndRequest);
// If an exception occurred while running GenericServlet.service,
// this attribute will either be thrown or set as an attribute for the WebAppContext's
// ErrorHandler to be invoked.
exception = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
} catch (Exception ex) {
exception = ex;
}
// TODO(b/263341977) this is a correct behavior, but customers depend on this bug, so we
// enable it only for non java8 runtimes.
if ((exception == null)
&& (abortedError != null)
&& !"java8".equals(GAE_RUNTIME)) {
exception = abortedError;
}
if (exception != null) {
Throwable cause = unwrap(exception);
if (cause instanceof BadMessageException) {
// Jetty bad messages exceptions are handled here to prevent
// 4xx client issues being signalled as 5xx server issues
BadMessageException bme = (BadMessageException) cause;
upResponse.clearHttpResponse();
upResponse.setError(UPResponse.ERROR.OK_VALUE);
upResponse.setHttpResponseCode(bme.getCode());
upResponse.setErrorMessage(bme.getReason());
} else if (!hasExceptionHandledByErrorPage(request)) {
// We will most likely have set something here, but the
// AppServer will only do the right thing (print stack traces
// for admins, generic Prometheus error message others) if this
// is completely unset.
upResponse.clearHttpResponse();
if (exception instanceof ServletException) {
throw (ServletException) exception;
} else {
throw new ServletException(exception);
}
}
}
}