Move HTTP content aggregation from Netty into RestController (#129302)

This commit is contained in:
Mikhail Berezovskiy 2025-06-19 09:05:17 -07:00 committed by GitHub
parent 083326e658
commit eeca493860
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
35 changed files with 687 additions and 286 deletions

View file

@ -11,13 +11,18 @@ package org.elasticsearch.http;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.rest.RestChannel;
import org.elasticsearch.rest.RestContentAggregator;
import org.elasticsearch.rest.RestRequest;
public class NullDispatcher implements HttpServerTransport.Dispatcher {
public class AggregatingDispatcher implements HttpServerTransport.Dispatcher {
public void dispatchAggregatedRequest(RestRequest restRequest, RestChannel restChannel, ThreadContext threadContext) {
assert restRequest.isStreamedContent();
}
@Override
public void dispatchRequest(RestRequest request, RestChannel channel, ThreadContext threadContext) {
public final void dispatchRequest(RestRequest request, RestChannel channel, ThreadContext threadContext) {
RestContentAggregator.aggregate(request, (r) -> dispatchAggregatedRequest(r, channel, threadContext));
}
@Override

View file

@ -70,6 +70,7 @@ import org.elasticsearch.common.breaker.CircuitBreaker;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.bytes.CompositeBytesReference;
import org.elasticsearch.common.bytes.ReleasableBytesReference;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.NamedWriteable;
import org.elasticsearch.common.io.stream.NamedWriteableAwareStreamInput;
@ -98,6 +99,7 @@ import org.elasticsearch.common.util.concurrent.EsExecutors;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.core.AbstractRefCounted;
import org.elasticsearch.core.Booleans;
import org.elasticsearch.core.CheckedConsumer;
import org.elasticsearch.core.CheckedRunnable;
@ -998,6 +1000,13 @@ public abstract class ESTestCase extends LuceneTestCase {
return CompositeBytesReference.of(slices.toArray(BytesReference[]::new));
}
public ReleasableBytesReference randomReleasableBytesReference(int length) {
return new ReleasableBytesReference(randomBytesReference(length), LeakTracker.wrap(new AbstractRefCounted() {
@Override
protected void closeInternal() {}
}));
}
public static short randomShort() {
return (short) random().nextInt();
}

View file

@ -0,0 +1,78 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the "Elastic License
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
* Public License v 1"; you may not use this file except in compliance with, at
* your election, the "Elastic License 2.0", the "GNU Affero General Public
* License v3.0 only", or the "Server Side Public License, v 1".
*/
package org.elasticsearch.test.rest;
import org.elasticsearch.common.bytes.ReleasableBytesReference;
import org.elasticsearch.http.HttpBody;
import java.util.ArrayList;
import java.util.List;
public class FakeHttpBodyStream implements HttpBody.Stream {
private final List<ChunkHandler> tracingHandlers = new ArrayList<>();
private ChunkHandler handler;
private boolean requested;
private boolean closed;
public boolean isClosed() {
return closed;
}
public boolean isRequested() {
return requested;
}
@Override
public ChunkHandler handler() {
return handler;
}
@Override
public void addTracingHandler(ChunkHandler chunkHandler) {
tracingHandlers.add(chunkHandler);
}
@Override
public void setHandler(ChunkHandler chunkHandler) {
this.handler = chunkHandler;
}
@Override
public void next() {
if (closed) {
return;
}
requested = true;
}
public void sendNext(ReleasableBytesReference chunk, boolean isLast) {
if (requested) {
for (var h : tracingHandlers) {
h.onNext(chunk, isLast);
}
handler.onNext(chunk, isLast);
} else {
throw new IllegalStateException("chunk is not requested");
}
}
@Override
public void close() {
if (closed == false) {
closed = true;
for (var h : tracingHandlers) {
h.close();
}
if (handler != null) {
handler.close();
}
}
}
}

View file

@ -55,24 +55,22 @@ public class FakeRestRequest extends RestRequest {
private final Method method;
private final String uri;
private final HttpBody content;
private final Map<String, List<String>> headers;
private HttpBody body;
private final Exception inboundException;
public FakeHttpRequest(Method method, String uri, BytesReference content, Map<String, List<String>> headers) {
this(method, uri, content == null ? HttpBody.empty() : HttpBody.fromBytesReference(content), headers, null);
public FakeHttpRequest(Method method, String uri, BytesReference body, Map<String, List<String>> headers) {
this(method, uri, body == null ? HttpBody.empty() : HttpBody.fromBytesReference(body), headers, null);
}
private FakeHttpRequest(
Method method,
String uri,
HttpBody content,
Map<String, List<String>> headers,
Exception inboundException
) {
public FakeHttpRequest(Method method, String uri, Map<String, List<String>> headers, HttpBody body) {
this(method, uri, body, headers, null);
}
private FakeHttpRequest(Method method, String uri, HttpBody body, Map<String, List<String>> headers, Exception inboundException) {
this.method = method;
this.uri = uri;
this.content = content;
this.body = body;
this.headers = headers;
this.inboundException = inboundException;
}
@ -89,7 +87,12 @@ public class FakeRestRequest extends RestRequest {
@Override
public HttpBody body() {
return content;
return body;
}
@Override
public void setBody(HttpBody body) {
this.body = body;
}
@Override
@ -111,7 +114,12 @@ public class FakeRestRequest extends RestRequest {
public HttpRequest removeHeader(String header) {
final var filteredHeaders = new HashMap<>(headers);
filteredHeaders.remove(header);
return new FakeHttpRequest(method, uri, content, filteredHeaders, inboundException);
return new FakeHttpRequest(method, uri, body, filteredHeaders, inboundException);
}
@Override
public boolean hasContent() {
return body.isEmpty() == false;
}
@Override