summaryrefslogtreecommitdiff
path: root/core/src/main/java/org/elasticsearch/rest/RestController.java
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/main/java/org/elasticsearch/rest/RestController.java')
-rw-r--r--core/src/main/java/org/elasticsearch/rest/RestController.java132
1 files changed, 90 insertions, 42 deletions
diff --git a/core/src/main/java/org/elasticsearch/rest/RestController.java b/core/src/main/java/org/elasticsearch/rest/RestController.java
index 5ac82b7e45..fd3587df2a 100644
--- a/core/src/main/java/org/elasticsearch/rest/RestController.java
+++ b/core/src/main/java/org/elasticsearch/rest/RestController.java
@@ -22,18 +22,19 @@ package org.elasticsearch.rest;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Supplier;
import java.util.function.UnaryOperator;
import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.Strings;
import org.elasticsearch.common.breaker.CircuitBreaker;
import org.elasticsearch.common.bytes.BytesArray;
-import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.io.Streams;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
@@ -42,12 +43,15 @@ import org.elasticsearch.common.path.PathTrie;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.http.HttpServerTransport;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.http.HttpTransportSettings;
import org.elasticsearch.indices.breaker.CircuitBreakerService;
+import org.elasticsearch.common.xcontent.XContentType;
import static org.elasticsearch.rest.RestStatus.BAD_REQUEST;
import static org.elasticsearch.rest.RestStatus.FORBIDDEN;
import static org.elasticsearch.rest.RestStatus.INTERNAL_SERVER_ERROR;
+import static org.elasticsearch.rest.RestStatus.NOT_ACCEPTABLE;
import static org.elasticsearch.rest.RestStatus.OK;
public class RestController extends AbstractComponent {
@@ -67,6 +71,10 @@ public class RestController extends AbstractComponent {
/** Rest headers that are copied to internal requests made during a rest request. */
private final Set<String> headersToCopy;
+ private final boolean isContentTypeRequired;
+
+ private final DeprecationLogger deprecationLogger;
+
public RestController(Settings settings, Set<String> headersToCopy, UnaryOperator<RestHandler> handlerWrapper,
NodeClient client, CircuitBreakerService circuitBreakerService) {
super(settings);
@@ -77,10 +85,10 @@ public class RestController extends AbstractComponent {
this.handlerWrapper = handlerWrapper;
this.client = client;
this.circuitBreakerService = circuitBreakerService;
+ this.isContentTypeRequired = HttpTransportSettings.SETTING_HTTP_CONTENT_TYPE_REQUIRED.get(settings);
+ this.deprecationLogger = new DeprecationLogger(logger);
}
-
-
/**
* Registers a REST handler to be executed when the provided {@code method} and {@code path} match the request.
*
@@ -165,15 +173,22 @@ public class RestController extends AbstractComponent {
}
RestChannel responseChannel = channel;
try {
- int contentLength = request.content().length();
- if (canTripCircuitBreaker(request)) {
- inFlightRequestsBreaker(circuitBreakerService).addEstimateBytesAndMaybeBreak(contentLength, "<http_request>");
+ final int contentLength = request.hasContent() ? request.content().length() : 0;
+ assert contentLength >= 0 : "content length was negative, how is that possible?";
+ final RestHandler handler = getHandler(request);
+
+ if (contentLength > 0 && hasContentTypeOrCanAutoDetect(request, handler) == false) {
+ sendContentTypeErrorMessage(request, responseChannel);
} else {
- inFlightRequestsBreaker(circuitBreakerService).addWithoutBreaking(contentLength);
+ if (canTripCircuitBreaker(request)) {
+ inFlightRequestsBreaker(circuitBreakerService).addEstimateBytesAndMaybeBreak(contentLength, "<http_request>");
+ } else {
+ inFlightRequestsBreaker(circuitBreakerService).addWithoutBreaking(contentLength);
+ }
+ // iff we could reserve bytes for the request we need to send the response also over this channel
+ responseChannel = new ResourceHandlingHttpChannel(channel, circuitBreakerService, contentLength);
+ dispatchRequest(request, responseChannel, client, threadContext, handler);
}
- // iff we could reserve bytes for the request we need to send the response also over this channel
- responseChannel = new ResourceHandlingHttpChannel(channel, circuitBreakerService, contentLength);
- dispatchRequest(request, responseChannel, client, threadContext);
} catch (Exception e) {
try {
responseChannel.sendResponse(new BytesRestResponse(channel, e));
@@ -185,33 +200,75 @@ public class RestController extends AbstractComponent {
}
}
- void dispatchRequest(final RestRequest request, final RestChannel channel, final NodeClient client, ThreadContext threadContext) throws Exception {
- if (!checkRequestParameters(request, channel)) {
- return;
- }
- try (ThreadContext.StoredContext ignored = threadContext.stashContext()) {
- for (String key : headersToCopy) {
- String httpHeader = request.header(key);
- if (httpHeader != null) {
- threadContext.putHeader(key, httpHeader);
+ void dispatchRequest(final RestRequest request, final RestChannel channel, final NodeClient client, ThreadContext threadContext,
+ final RestHandler handler) throws Exception {
+ if (checkRequestParameters(request, channel) == false) {
+ channel.sendResponse(BytesRestResponse.createSimpleErrorResponse(BAD_REQUEST, "error traces in responses are disabled."));
+ } else {
+ try (ThreadContext.StoredContext ignored = threadContext.stashContext()) {
+ for (String key : headersToCopy) {
+ String httpHeader = request.header(key);
+ if (httpHeader != null) {
+ threadContext.putHeader(key, httpHeader);
+ }
}
- }
- final RestHandler handler = getHandler(request);
+ if (handler == null) {
+ if (request.method() == RestRequest.Method.OPTIONS) {
+ // when we have OPTIONS request, simply send OK by default (with the Access Control Origin header which gets automatically added)
- if (handler == null) {
- if (request.method() == RestRequest.Method.OPTIONS) {
- // when we have OPTIONS request, simply send OK by default (with the Access Control Origin header which gets automatically added)
- channel.sendResponse(new BytesRestResponse(OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
+ channel.sendResponse(new BytesRestResponse(OK, BytesRestResponse.TEXT_CONTENT_TYPE, BytesArray.EMPTY));
+ } else {
+ final String msg = "No handler found for uri [" + request.uri() + "] and method [" + request.method() + "]";
+ channel.sendResponse(new BytesRestResponse(BAD_REQUEST, msg));
+ }
} else {
- final String msg = "No handler found for uri [" + request.uri() + "] and method [" + request.method() + "]";
- channel.sendResponse(new BytesRestResponse(BAD_REQUEST, msg));
+ final RestHandler wrappedHandler = Objects.requireNonNull(handlerWrapper.apply(handler));
+ wrappedHandler.handleRequest(request, channel, client);
}
+ }
+ }
+ }
+
+ /**
+ * If a request contains content, this method will return {@code true} if the {@code Content-Type} header is present, matches an
+ * {@link XContentType} or the request is plain text, and content type is required. If content type is not required then this method
+ * returns true unless a content type could not be inferred from the body and the rest handler does not support plain text
+ */
+ private boolean hasContentTypeOrCanAutoDetect(final RestRequest restRequest, final RestHandler restHandler) {
+ if (restRequest.getXContentType() == null) {
+ if (restHandler != null && restHandler.supportsPlainText()) {
+ // content type of null with a handler that supports plain text gets through for now. Once we remove plain text this can
+ // be removed!
+ deprecationLogger.deprecated("Plain text request bodies are deprecated. Use request parameters or body " +
+ "in a supported format.");
+ } else if (isContentTypeRequired) {
+ return false;
} else {
- final RestHandler wrappedHandler = Objects.requireNonNull(handlerWrapper.apply(handler));
- wrappedHandler.handleRequest(request, channel, client);
+ deprecationLogger.deprecated("Content type detection for rest requests is deprecated. Specify the content type using " +
+ "the [Content-Type] header.");
+ XContentType xContentType = XContentFactory.xContentType(restRequest.content());
+ if (xContentType == null) {
+ return false;
+ } else {
+ restRequest.setXContentType(xContentType);
+ }
}
}
+ return true;
+ }
+
+ private void sendContentTypeErrorMessage(RestRequest restRequest, RestChannel channel) throws IOException {
+ final List<String> contentTypeHeader = restRequest.getAllHeaderValues("Content-Type");
+ final String errorMessage;
+ if (contentTypeHeader == null) {
+ errorMessage = "Content-Type header is missing";
+ } else {
+ errorMessage = "Content-Type header [" +
+ Strings.collectionToCommaDelimitedString(restRequest.getAllHeaderValues("Content-Type")) + "] is not supported";
+ }
+
+ channel.sendResponse(BytesRestResponse.createSimpleErrorResponse(NOT_ACCEPTABLE, errorMessage));
}
/**
@@ -222,15 +279,6 @@ public class RestController extends AbstractComponent {
// error_trace cannot be used when we disable detailed errors
// we consume the error_trace parameter first to ensure that it is always consumed
if (request.paramAsBoolean("error_trace", false) && channel.detailedErrorsEnabled() == false) {
- try {
- XContentBuilder builder = channel.newErrorBuilder();
- builder.startObject().field("error", "error traces in responses are disabled.").endObject().string();
- RestResponse response = new BytesRestResponse(BAD_REQUEST, builder);
- response.addHeader("Content-Type", "application/json");
- channel.sendResponse(response);
- } catch (IOException e) {
- logger.warn("Failed to send response", e);
- }
return false;
}
@@ -312,8 +360,8 @@ public class RestController extends AbstractComponent {
}
@Override
- public XContentBuilder newBuilder(@Nullable BytesReference autoDetectSource, boolean useFiltering) throws IOException {
- return delegate.newBuilder(autoDetectSource, useFiltering);
+ public XContentBuilder newBuilder(@Nullable XContentType xContentType, boolean useFiltering) throws IOException {
+ return delegate.newBuilder(xContentType, useFiltering);
}
@Override