Skip to content

Commit 929ef7f

Browse files
tlrxjkakavas
authored andcommitted
RestController should not consume request content (#44902)
The change #37504 modifies the BaseRestHandler to make it reject all requests that have an unconsumed body. The notion of consumed or unconsumed body is carried by the RestRequest object and its contentConsumed attribute, which is set to true when the content() or content(true) methods are used. In our REST layer, we usually expect the RestHandlers to consume the request content when needed, but it appears that the RestController always consumes the content upfront. This commit changes the content() method used by the RestController so that it does not mark the content as consumed.
1 parent 37b39a6 commit 929ef7f

File tree

12 files changed

+100
-134
lines changed

12 files changed

+100
-134
lines changed

client/rest-high-level/src/main/java/org/elasticsearch/client/RollupRequestConverters.java

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,9 +90,7 @@ static Request deleteJob(final DeleteRollupJobRequest deleteRollupJobRequest) th
9090
.addPathPartAsIs("_rollup", "job")
9191
.addPathPart(deleteRollupJobRequest.getId())
9292
.build();
93-
Request request = new Request(HttpDelete.METHOD_NAME, endpoint);
94-
request.setEntity(createEntity(deleteRollupJobRequest, REQUEST_BODY_CONTENT_TYPE));
95-
return request;
93+
return new Request(HttpDelete.METHOD_NAME, endpoint);
9694
}
9795

9896
static Request search(final SearchRequest request) throws IOException {
@@ -104,18 +102,14 @@ static Request getRollupCaps(final GetRollupCapsRequest getRollupCapsRequest) th
104102
.addPathPartAsIs("_rollup", "data")
105103
.addPathPart(getRollupCapsRequest.getIndexPattern())
106104
.build();
107-
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
108-
request.setEntity(createEntity(getRollupCapsRequest, REQUEST_BODY_CONTENT_TYPE));
109-
return request;
105+
return new Request(HttpGet.METHOD_NAME, endpoint);
110106
}
111107

112108
static Request getRollupIndexCaps(final GetRollupIndexCapsRequest getRollupIndexCapsRequest) throws IOException {
113109
String endpoint = new RequestConverters.EndpointBuilder()
114110
.addCommaSeparatedPathParts(getRollupIndexCapsRequest.indices())
115111
.addPathPartAsIs("_rollup", "data")
116112
.build();
117-
Request request = new Request(HttpGet.METHOD_NAME, endpoint);
118-
request.setEntity(createEntity(getRollupIndexCapsRequest, REQUEST_BODY_CONTENT_TYPE));
119-
return request;
113+
return new Request(HttpGet.METHOD_NAME, endpoint);
120114
}
121115
}

client/rest-high-level/src/main/java/org/elasticsearch/client/rollup/DeleteRollupJobRequest.java

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -19,22 +19,14 @@
1919
package org.elasticsearch.client.rollup;
2020

2121
import org.elasticsearch.client.Validatable;
22-
import org.elasticsearch.common.ParseField;
23-
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
24-
import org.elasticsearch.common.xcontent.ToXContentObject;
25-
import org.elasticsearch.common.xcontent.XContentBuilder;
26-
import org.elasticsearch.common.xcontent.XContentParser;
2722

28-
import java.io.IOException;
2923
import java.util.Objects;
3024

3125

32-
public class DeleteRollupJobRequest implements Validatable, ToXContentObject {
26+
public class DeleteRollupJobRequest implements Validatable {
3327

34-
private static final ParseField ID_FIELD = new ParseField("id");
3528
private final String id;
3629

37-
3830
public DeleteRollupJobRequest(String id) {
3931
this.id = Objects.requireNonNull(id, "id parameter must not be null");
4032
}
@@ -43,27 +35,6 @@ public String getId() {
4335
return id;
4436
}
4537

46-
private static final ConstructingObjectParser<DeleteRollupJobRequest, Void> PARSER =
47-
new ConstructingObjectParser<>("request", a -> {
48-
return new DeleteRollupJobRequest((String) a[0]);
49-
});
50-
51-
static {
52-
PARSER.declareString(ConstructingObjectParser.constructorArg(), ID_FIELD);
53-
}
54-
55-
public static DeleteRollupJobRequest fromXContent(XContentParser parser) {
56-
return PARSER.apply(parser, null);
57-
}
58-
59-
@Override
60-
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
61-
builder.startObject();
62-
builder.field(ID_FIELD.getPreferredName(), this.id);
63-
builder.endObject();
64-
return builder;
65-
}
66-
6738
@Override
6839
public boolean equals(Object o) {
6940
if (this == o) return true;

client/rest-high-level/src/main/java/org/elasticsearch/client/rollup/GetRollupCapsRequest.java

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,11 @@
2121
import org.elasticsearch.client.Validatable;
2222
import org.elasticsearch.cluster.metadata.MetaData;
2323
import org.elasticsearch.common.Strings;
24-
import org.elasticsearch.common.xcontent.ToXContentObject;
25-
import org.elasticsearch.common.xcontent.XContentBuilder;
2624

27-
import java.io.IOException;
2825
import java.util.Objects;
2926

30-
public class GetRollupCapsRequest implements Validatable, ToXContentObject {
31-
private static final String ID = "id";
27+
public class GetRollupCapsRequest implements Validatable {
28+
3229
private final String indexPattern;
3330

3431
public GetRollupCapsRequest(final String indexPattern) {
@@ -43,14 +40,6 @@ public String getIndexPattern() {
4340
return indexPattern;
4441
}
4542

46-
@Override
47-
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
48-
builder.startObject();
49-
builder.field(ID, indexPattern);
50-
builder.endObject();
51-
return builder;
52-
}
53-
5443
@Override
5544
public int hashCode() {
5645
return Objects.hash(indexPattern);

client/rest-high-level/src/main/java/org/elasticsearch/client/rollup/GetRollupIndexCapsRequest.java

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,11 @@
2121
import org.elasticsearch.action.support.IndicesOptions;
2222
import org.elasticsearch.client.Validatable;
2323
import org.elasticsearch.common.Strings;
24-
import org.elasticsearch.common.xcontent.ToXContentObject;
25-
import org.elasticsearch.common.xcontent.XContentBuilder;
2624

27-
import java.io.IOException;
2825
import java.util.Arrays;
2926
import java.util.Objects;
3027

31-
public class GetRollupIndexCapsRequest implements Validatable, ToXContentObject {
32-
private static final String INDICES = "indices";
33-
private static final String INDICES_OPTIONS = "indices_options";
28+
public class GetRollupIndexCapsRequest implements Validatable {
3429

3530
private String[] indices;
3631
private IndicesOptions options;
@@ -60,21 +55,6 @@ public String[] indices() {
6055
return indices;
6156
}
6257

63-
@Override
64-
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
65-
builder.startObject();
66-
{
67-
builder.array(INDICES, indices);
68-
builder.startObject(INDICES_OPTIONS);
69-
{
70-
options.toXContent(builder, params);
71-
}
72-
builder.endObject();
73-
}
74-
builder.endObject();
75-
return builder;
76-
}
77-
7858
@Override
7959
public int hashCode() {
8060
return Objects.hash(Arrays.hashCode(indices), options);

client/rest-high-level/src/test/java/org/elasticsearch/client/rollup/DeleteRollupJobRequestTests.java

Lines changed: 2 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,39 +18,12 @@
1818
*/
1919
package org.elasticsearch.client.rollup;
2020

21-
import org.elasticsearch.common.xcontent.XContentParser;
22-
import org.elasticsearch.test.AbstractXContentTestCase;
23-
import org.junit.Before;
21+
import org.elasticsearch.test.ESTestCase;
2422

25-
import java.io.IOException;
26-
27-
public class DeleteRollupJobRequestTests extends AbstractXContentTestCase<DeleteRollupJobRequest> {
28-
29-
private String jobId;
30-
31-
@Before
32-
public void setUpOptionalId() {
33-
jobId = randomAlphaOfLengthBetween(1, 10);
34-
}
35-
36-
@Override
37-
protected DeleteRollupJobRequest createTestInstance() {
38-
return new DeleteRollupJobRequest(jobId);
39-
}
40-
41-
@Override
42-
protected DeleteRollupJobRequest doParseInstance(final XContentParser parser) throws IOException {
43-
return DeleteRollupJobRequest.fromXContent(parser);
44-
}
45-
46-
@Override
47-
protected boolean supportsUnknownFields() {
48-
return false;
49-
}
23+
public class DeleteRollupJobRequestTests extends ESTestCase {
5024

5125
public void testRequireConfiguration() {
5226
final NullPointerException e = expectThrows(NullPointerException.class, ()-> new DeleteRollupJobRequest(null));
5327
assertEquals("id parameter must not be null", e.getMessage());
5428
}
55-
5629
}

server/src/main/java/org/elasticsearch/rest/RestController.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ public void dispatchBadRequest(final RestRequest request, final RestChannel chan
209209
*/
210210
boolean dispatchRequest(final RestRequest request, final RestChannel channel, final NodeClient client,
211211
final Optional<RestHandler> mHandler) throws Exception {
212-
final int contentLength = request.hasContent() ? request.content().length() : 0;
212+
final int contentLength = request.contentLength();
213213

214214
RestChannel responseChannel = channel;
215215
// Indicator of whether a response was sent or not

server/src/main/java/org/elasticsearch/rest/RestRequest.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,15 @@ public final String path() {
185185
}
186186

187187
public boolean hasContent() {
188-
return content(false).length() > 0;
188+
return contentLength() > 0;
189189
}
190190

191-
public BytesReference content() {
192-
return content(true);
191+
public int contentLength() {
192+
return httpRequest.content().length();
193193
}
194194

195-
protected BytesReference content(final boolean contentConsumed) {
196-
this.contentConsumed = this.contentConsumed | contentConsumed;
195+
public BytesReference content() {
196+
this.contentConsumed = true;
197197
return httpRequest.content();
198198
}
199199

server/src/test/java/org/elasticsearch/action/admin/indices/forcemerge/RestForceMergeActionTests.java

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,58 @@
1919

2020
package org.elasticsearch.action.admin.indices.forcemerge;
2121

22-
import org.elasticsearch.client.node.NodeClient;
22+
import org.apache.lucene.util.SetOnce;
2323
import org.elasticsearch.common.bytes.BytesArray;
2424
import org.elasticsearch.common.settings.Settings;
25+
import org.elasticsearch.common.util.concurrent.ThreadContext;
2526
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
2627
import org.elasticsearch.common.xcontent.XContentType;
2728
import org.elasticsearch.common.xcontent.json.JsonXContent;
28-
import org.elasticsearch.rest.RestController;
29+
import org.elasticsearch.rest.AbstractRestChannel;
30+
import org.elasticsearch.rest.RestChannel;
31+
import org.elasticsearch.rest.RestRequest;
32+
import org.elasticsearch.rest.RestResponse;
33+
import org.elasticsearch.rest.RestStatus;
2934
import org.elasticsearch.rest.action.admin.indices.RestForceMergeAction;
30-
import org.elasticsearch.test.ESTestCase;
31-
import org.elasticsearch.test.rest.FakeRestChannel;
3235
import org.elasticsearch.test.rest.FakeRestRequest;
36+
import org.elasticsearch.test.rest.RestActionTestCase;
37+
import org.junit.Before;
3338

34-
import static org.hamcrest.Matchers.equalTo;
35-
import static org.mockito.Mockito.mock;
39+
import static org.hamcrest.Matchers.containsString;
40+
import static org.hamcrest.Matchers.is;
41+
import static org.hamcrest.Matchers.notNullValue;
3642

37-
public class RestForceMergeActionTests extends ESTestCase {
43+
public class RestForceMergeActionTests extends RestActionTestCase {
44+
45+
@Before
46+
public void setUpAction() {
47+
new RestForceMergeAction(Settings.EMPTY, controller());
48+
}
3849

3950
public void testBodyRejection() throws Exception {
40-
final RestForceMergeAction handler = new RestForceMergeAction(Settings.EMPTY, mock(RestController.class));
4151
String json = JsonXContent.contentBuilder().startObject().field("max_num_segments", 1).endObject().toString();
4252
final FakeRestRequest request = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY)
4353
.withContent(new BytesArray(json), XContentType.JSON)
54+
.withMethod(RestRequest.Method.POST)
4455
.withPath("/_forcemerge")
4556
.build();
46-
IllegalArgumentException e = expectThrows(IllegalArgumentException.class,
47-
() -> handler.handleRequest(request, new FakeRestChannel(request, randomBoolean(), 1), mock(NodeClient.class)));
48-
assertThat(e.getMessage(), equalTo("request [GET /_forcemerge] does not support having a body"));
57+
58+
final SetOnce<RestResponse> responseSetOnce = new SetOnce<>();
59+
dispatchRequest(request, new AbstractRestChannel(request, true) {
60+
@Override
61+
public void sendResponse(RestResponse response) {
62+
responseSetOnce.set(response);
63+
}
64+
});
65+
66+
final RestResponse response = responseSetOnce.get();
67+
assertThat(response, notNullValue());
68+
assertThat(response.status(), is(RestStatus.BAD_REQUEST));
69+
assertThat(response.content().utf8ToString(), containsString("request [POST /_forcemerge] does not support having a body"));
4970
}
5071

72+
protected void dispatchRequest(final RestRequest request, final RestChannel channel) {
73+
ThreadContext threadContext = new ThreadContext(Settings.EMPTY);
74+
controller().dispatchRequest(request, channel, threadContext);
75+
}
5176
}

server/src/test/java/org/elasticsearch/rest/RestControllerTests.java

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import org.elasticsearch.common.unit.ByteSizeValue;
3333
import org.elasticsearch.common.util.concurrent.ThreadContext;
3434
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
35+
import org.elasticsearch.common.xcontent.XContentBuilder;
3536
import org.elasticsearch.common.xcontent.XContentType;
3637
import org.elasticsearch.common.xcontent.yaml.YamlXContent;
3738
import org.elasticsearch.http.HttpInfo;
@@ -466,6 +467,38 @@ public void testDispatchBadRequest() {
466467
assertThat(channel.getRestResponse().content().utf8ToString(), containsString("bad request"));
467468
}
468469

470+
public void testDoesNotConsumeContent() throws Exception {
471+
final RestRequest.Method method = randomFrom(RestRequest.Method.values());
472+
restController.registerHandler(method, "/notconsumed", new RestHandler() {
473+
@Override
474+
public void handleRequest(RestRequest request, RestChannel channel, NodeClient client) throws Exception {
475+
channel.sendResponse(new BytesRestResponse(RestStatus.OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
476+
}
477+
478+
@Override
479+
public boolean canTripCircuitBreaker() {
480+
return false;
481+
}
482+
});
483+
484+
final XContentBuilder content = XContentBuilder.builder(randomFrom(XContentType.values()).xContent())
485+
.startObject().field("field", "value").endObject();
486+
final FakeRestRequest restRequest = new FakeRestRequest.Builder(xContentRegistry())
487+
.withPath("/notconsumed")
488+
.withMethod(method)
489+
.withContent(BytesReference.bytes(content), content.contentType())
490+
.build();
491+
492+
final AssertingChannel channel = new AssertingChannel(restRequest, true, RestStatus.OK);
493+
assertFalse(channel.getSendResponseCalled());
494+
assertFalse(restRequest.isContentConsumed());
495+
496+
restController.dispatchRequest(restRequest, channel, new ThreadContext(Settings.EMPTY));
497+
498+
assertTrue(channel.getSendResponseCalled());
499+
assertFalse("RestController must not consume request content", restRequest.isContentConsumed());
500+
}
501+
469502
public void testDispatchBadRequestUnknownCause() {
470503
final FakeRestRequest fakeRestRequest = new FakeRestRequest.Builder(NamedXContentRegistry.EMPTY).build();
471504
final AssertingChannel channel = new AssertingChannel(fakeRestRequest, true, RestStatus.BAD_REQUEST);

server/src/test/java/org/elasticsearch/rest/RestRequestTests.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ public void testHasContentDoesNotConsumesContent() {
8282
runConsumesContentTest(RestRequest::hasContent, false);
8383
}
8484

85+
public void testContentLengthDoesNotConsumesContent() {
86+
runConsumesContentTest(RestRequest::contentLength, false);
87+
}
88+
8589
private <T extends Exception> void runConsumesContentTest(
8690
final CheckedConsumer<RestRequest, T> consumer, final boolean expected) {
8791
final HttpRequest httpRequest = mock(HttpRequest.class);

0 commit comments

Comments
 (0)