diff --git a/build.gradle b/build.gradle
index ec81047e3e6..9bb08cf29db 100644
--- a/build.gradle
+++ b/build.gradle
@@ -53,9 +53,23 @@ subprojects {
description = "Elasticsearch subproject ${project.path}"
}
+apply plugin: 'nebula.info-scm'
+String licenseCommit
+if (VersionProperties.elasticsearch.toString().endsWith('-SNAPSHOT')) {
+ licenseCommit = scminfo.change ?: "master" // leniency for non git builds
+} else {
+ licenseCommit = "v${version}"
+}
+String elasticLicenseUrl = "https://raw.githubusercontent.com/elastic/elasticsearch/${licenseCommit}/licenses/ELASTIC-LICENSE.txt"
+
subprojects {
+ // Default to the apache license
project.ext.licenseName = 'The Apache Software License, Version 2.0'
project.ext.licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+
+ // But stick the Elastic license url in project.ext so we can get it if we need to switch to it
+ project.ext.elasticLicenseUrl = elasticLicenseUrl
+
// we only use maven publish to add tasks for pom generation
plugins.withType(MavenPublishPlugin).whenPluginAdded {
publishing {
diff --git a/distribution/archives/build.gradle b/distribution/archives/build.gradle
index c1097b68b89..71606c2c027 100644
--- a/distribution/archives/build.gradle
+++ b/distribution/archives/build.gradle
@@ -228,6 +228,8 @@ subprojects {
check.dependsOn checkNotice
if (project.name == 'zip' || project.name == 'tar') {
+ project.ext.licenseName = 'Elastic License'
+ project.ext.licenseUrl = ext.elasticLicenseUrl
task checkMlCppNotice {
dependsOn buildDist, checkExtraction
onlyIf toolExists
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java
index cb31d444544..473985d2109 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpChannel.java
@@ -19,252 +19,58 @@
package org.elasticsearch.http.netty4;
-import io.netty.buffer.ByteBuf;
-import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
-import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelPromise;
-import io.netty.handler.codec.http.DefaultFullHttpResponse;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpResponseStatus;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
-import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
-import org.elasticsearch.common.bytes.BytesReference;
-import org.elasticsearch.common.io.stream.BytesStreamOutput;
-import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
-import org.elasticsearch.common.lease.Releasable;
-import org.elasticsearch.common.util.concurrent.ThreadContext;
-import org.elasticsearch.http.HttpHandlingSettings;
-import org.elasticsearch.http.netty4.cors.Netty4CorsHandler;
-import org.elasticsearch.rest.AbstractRestChannel;
-import org.elasticsearch.rest.RestResponse;
-import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.action.ActionListener;
+import org.elasticsearch.http.HttpChannel;
+import org.elasticsearch.http.HttpResponse;
import org.elasticsearch.transport.netty4.Netty4Utils;
-import java.util.Collections;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.net.InetSocketAddress;
-final class Netty4HttpChannel extends AbstractRestChannel {
+public class Netty4HttpChannel implements HttpChannel {
- private final Netty4HttpServerTransport transport;
private final Channel channel;
- private final FullHttpRequest nettyRequest;
- private final int sequence;
- private final ThreadContext threadContext;
- private final HttpHandlingSettings handlingSettings;
- /**
- * @param transport The corresponding NettyHttpServerTransport
where this channel belongs to.
- * @param request The request that is handled by this channel.
- * @param sequence The pipelining sequence number for this request
- * @param handlingSettings true if error messages should include stack traces.
- * @param threadContext the thread context for the channel
- */
- Netty4HttpChannel(Netty4HttpServerTransport transport, Netty4HttpRequest request, int sequence, HttpHandlingSettings handlingSettings,
- ThreadContext threadContext) {
- super(request, handlingSettings.getDetailedErrorsEnabled());
- this.transport = transport;
- this.channel = request.getChannel();
- this.nettyRequest = request.request();
- this.sequence = sequence;
- this.threadContext = threadContext;
- this.handlingSettings = handlingSettings;
+ Netty4HttpChannel(Channel channel) {
+ this.channel = channel;
}
@Override
- protected BytesStreamOutput newBytesOutput() {
- return new ReleasableBytesStreamOutput(transport.bigArrays);
+ public void sendResponse(HttpResponse response, ActionListener listener) {
+ ChannelPromise writePromise = channel.newPromise();
+ writePromise.addListener(f -> {
+ if (f.isSuccess()) {
+ listener.onResponse(null);
+ } else {
+ final Throwable cause = f.cause();
+ Netty4Utils.maybeDie(cause);
+ if (cause instanceof Error) {
+ listener.onFailure(new Exception(cause));
+ } else {
+ listener.onFailure((Exception) cause);
+ }
+ }
+ });
+ channel.writeAndFlush(response, writePromise);
}
@Override
- public void sendResponse(RestResponse response) {
- // if the response object was created upstream, then use it;
- // otherwise, create a new one
- ByteBuf buffer = Netty4Utils.toByteBuf(response.content());
- final FullHttpResponse resp;
- if (HttpMethod.HEAD.equals(nettyRequest.method())) {
- resp = newResponse(Unpooled.EMPTY_BUFFER);
- } else {
- resp = newResponse(buffer);
- }
- resp.setStatus(getStatus(response.status()));
-
- Netty4CorsHandler.setCorsResponseHeaders(nettyRequest, resp, transport.getCorsConfig());
-
- String opaque = nettyRequest.headers().get("X-Opaque-Id");
- if (opaque != null) {
- setHeaderField(resp, "X-Opaque-Id", opaque);
- }
-
- // Add all custom headers
- addCustomHeaders(resp, response.getHeaders());
- addCustomHeaders(resp, threadContext.getResponseHeaders());
-
- BytesReference content = response.content();
- boolean releaseContent = content instanceof Releasable;
- boolean releaseBytesStreamOutput = bytesOutputOrNull() instanceof ReleasableBytesStreamOutput;
- try {
- // If our response doesn't specify a content-type header, set one
- setHeaderField(resp, HttpHeaderNames.CONTENT_TYPE.toString(), response.contentType(), false);
- // If our response has no content-length, calculate and set one
- setHeaderField(resp, HttpHeaderNames.CONTENT_LENGTH.toString(), String.valueOf(buffer.readableBytes()), false);
-
- addCookies(resp);
-
- final ChannelPromise promise = channel.newPromise();
-
- if (releaseContent) {
- promise.addListener(f -> ((Releasable) content).close());
- }
-
- if (releaseBytesStreamOutput) {
- promise.addListener(f -> bytesOutputOrNull().close());
- }
-
- if (isCloseConnection()) {
- promise.addListener(ChannelFutureListener.CLOSE);
- }
-
- Netty4HttpResponse newResponse = new Netty4HttpResponse(sequence, resp);
-
- channel.writeAndFlush(newResponse, promise);
- releaseContent = false;
- releaseBytesStreamOutput = false;
- } finally {
- if (releaseContent) {
- ((Releasable) content).close();
- }
- if (releaseBytesStreamOutput) {
- bytesOutputOrNull().close();
- }
- }
+ public InetSocketAddress getLocalAddress() {
+ return (InetSocketAddress) channel.localAddress();
}
- private void setHeaderField(HttpResponse resp, String headerField, String value) {
- setHeaderField(resp, headerField, value, true);
+ @Override
+ public InetSocketAddress getRemoteAddress() {
+ return (InetSocketAddress) channel.remoteAddress();
}
- private void setHeaderField(HttpResponse resp, String headerField, String value, boolean override) {
- if (override || !resp.headers().contains(headerField)) {
- resp.headers().add(headerField, value);
- }
+ @Override
+ public void close() {
+ channel.close();
}
- private void addCookies(HttpResponse resp) {
- if (handlingSettings.isResetCookies()) {
- String cookieString = nettyRequest.headers().get(HttpHeaderNames.COOKIE);
- if (cookieString != null) {
- Set cookies = ServerCookieDecoder.STRICT.decode(cookieString);
- if (!cookies.isEmpty()) {
- // Reset the cookies if necessary.
- resp.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookies));
- }
- }
- }
- }
-
- private void addCustomHeaders(HttpResponse response, Map> customHeaders) {
- if (customHeaders != null) {
- for (Map.Entry> headerEntry : customHeaders.entrySet()) {
- for (String headerValue : headerEntry.getValue()) {
- setHeaderField(response, headerEntry.getKey(), headerValue);
- }
- }
- }
- }
-
- // Determine if the request protocol version is HTTP 1.0
- private boolean isHttp10() {
- return nettyRequest.protocolVersion().equals(HttpVersion.HTTP_1_0);
- }
-
- // Determine if the request connection should be closed on completion.
- private boolean isCloseConnection() {
- final boolean http10 = isHttp10();
- return HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(nettyRequest.headers().get(HttpHeaderNames.CONNECTION)) ||
- (http10 && !HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(nettyRequest.headers().get(HttpHeaderNames.CONNECTION)));
- }
-
- // Create a new {@link HttpResponse} to transmit the response for the netty request.
- private FullHttpResponse newResponse(ByteBuf buffer) {
- final boolean http10 = isHttp10();
- final boolean close = isCloseConnection();
- // Build the response object.
- final HttpResponseStatus status = HttpResponseStatus.OK; // default to initialize
- final FullHttpResponse response;
- if (http10) {
- response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status, buffer);
- if (!close) {
- response.headers().add(HttpHeaderNames.CONNECTION, "Keep-Alive");
- }
- } else {
- response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buffer);
- }
- return response;
- }
-
- private static Map MAP;
-
- static {
- EnumMap map = new EnumMap<>(RestStatus.class);
- map.put(RestStatus.CONTINUE, HttpResponseStatus.CONTINUE);
- map.put(RestStatus.SWITCHING_PROTOCOLS, HttpResponseStatus.SWITCHING_PROTOCOLS);
- map.put(RestStatus.OK, HttpResponseStatus.OK);
- map.put(RestStatus.CREATED, HttpResponseStatus.CREATED);
- map.put(RestStatus.ACCEPTED, HttpResponseStatus.ACCEPTED);
- map.put(RestStatus.NON_AUTHORITATIVE_INFORMATION, HttpResponseStatus.NON_AUTHORITATIVE_INFORMATION);
- map.put(RestStatus.NO_CONTENT, HttpResponseStatus.NO_CONTENT);
- map.put(RestStatus.RESET_CONTENT, HttpResponseStatus.RESET_CONTENT);
- map.put(RestStatus.PARTIAL_CONTENT, HttpResponseStatus.PARTIAL_CONTENT);
- map.put(RestStatus.MULTI_STATUS, HttpResponseStatus.INTERNAL_SERVER_ERROR); // no status for this??
- map.put(RestStatus.MULTIPLE_CHOICES, HttpResponseStatus.MULTIPLE_CHOICES);
- map.put(RestStatus.MOVED_PERMANENTLY, HttpResponseStatus.MOVED_PERMANENTLY);
- map.put(RestStatus.FOUND, HttpResponseStatus.FOUND);
- map.put(RestStatus.SEE_OTHER, HttpResponseStatus.SEE_OTHER);
- map.put(RestStatus.NOT_MODIFIED, HttpResponseStatus.NOT_MODIFIED);
- map.put(RestStatus.USE_PROXY, HttpResponseStatus.USE_PROXY);
- map.put(RestStatus.TEMPORARY_REDIRECT, HttpResponseStatus.TEMPORARY_REDIRECT);
- map.put(RestStatus.BAD_REQUEST, HttpResponseStatus.BAD_REQUEST);
- map.put(RestStatus.UNAUTHORIZED, HttpResponseStatus.UNAUTHORIZED);
- map.put(RestStatus.PAYMENT_REQUIRED, HttpResponseStatus.PAYMENT_REQUIRED);
- map.put(RestStatus.FORBIDDEN, HttpResponseStatus.FORBIDDEN);
- map.put(RestStatus.NOT_FOUND, HttpResponseStatus.NOT_FOUND);
- map.put(RestStatus.METHOD_NOT_ALLOWED, HttpResponseStatus.METHOD_NOT_ALLOWED);
- map.put(RestStatus.NOT_ACCEPTABLE, HttpResponseStatus.NOT_ACCEPTABLE);
- map.put(RestStatus.PROXY_AUTHENTICATION, HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED);
- map.put(RestStatus.REQUEST_TIMEOUT, HttpResponseStatus.REQUEST_TIMEOUT);
- map.put(RestStatus.CONFLICT, HttpResponseStatus.CONFLICT);
- map.put(RestStatus.GONE, HttpResponseStatus.GONE);
- map.put(RestStatus.LENGTH_REQUIRED, HttpResponseStatus.LENGTH_REQUIRED);
- map.put(RestStatus.PRECONDITION_FAILED, HttpResponseStatus.PRECONDITION_FAILED);
- map.put(RestStatus.REQUEST_ENTITY_TOO_LARGE, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE);
- map.put(RestStatus.REQUEST_URI_TOO_LONG, HttpResponseStatus.REQUEST_URI_TOO_LONG);
- map.put(RestStatus.UNSUPPORTED_MEDIA_TYPE, HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE);
- map.put(RestStatus.REQUESTED_RANGE_NOT_SATISFIED, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
- map.put(RestStatus.EXPECTATION_FAILED, HttpResponseStatus.EXPECTATION_FAILED);
- map.put(RestStatus.UNPROCESSABLE_ENTITY, HttpResponseStatus.BAD_REQUEST);
- map.put(RestStatus.LOCKED, HttpResponseStatus.BAD_REQUEST);
- map.put(RestStatus.FAILED_DEPENDENCY, HttpResponseStatus.BAD_REQUEST);
- map.put(RestStatus.TOO_MANY_REQUESTS, HttpResponseStatus.TOO_MANY_REQUESTS);
- map.put(RestStatus.INTERNAL_SERVER_ERROR, HttpResponseStatus.INTERNAL_SERVER_ERROR);
- map.put(RestStatus.NOT_IMPLEMENTED, HttpResponseStatus.NOT_IMPLEMENTED);
- map.put(RestStatus.BAD_GATEWAY, HttpResponseStatus.BAD_GATEWAY);
- map.put(RestStatus.SERVICE_UNAVAILABLE, HttpResponseStatus.SERVICE_UNAVAILABLE);
- map.put(RestStatus.GATEWAY_TIMEOUT, HttpResponseStatus.GATEWAY_TIMEOUT);
- map.put(RestStatus.HTTP_VERSION_NOT_SUPPORTED, HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
- MAP = Collections.unmodifiableMap(map);
- }
-
- private static HttpResponseStatus getStatus(RestStatus status) {
- return MAP.getOrDefault(status, HttpResponseStatus.INTERNAL_SERVER_ERROR);
+ public Channel getNettyChannel() {
+ return channel;
}
}
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpPipeliningHandler.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpPipeliningHandler.java
index 12c2e9a6857..e6436ccea1a 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpPipeliningHandler.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpPipeliningHandler.java
@@ -66,7 +66,7 @@ public class Netty4HttpPipeliningHandler extends ChannelDuplexHandler {
try {
List> readyResponses = aggregator.write(response, promise);
for (Tuple readyResponse : readyResponses) {
- ctx.write(readyResponse.v1().getResponse(), readyResponse.v2());
+ ctx.write(readyResponse.v1(), readyResponse.v2());
}
success = true;
} catch (IllegalStateException e) {
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequest.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequest.java
index 2ce6ffada67..ffabe5cbbe2 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequest.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequest.java
@@ -19,17 +19,22 @@
package org.elasticsearch.http.netty4;
-import io.netty.channel.Channel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.cookie.Cookie;
+import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
+import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
-import org.elasticsearch.common.xcontent.NamedXContentRegistry;
+import org.elasticsearch.http.HttpRequest;
import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.RestStatus;
import org.elasticsearch.transport.netty4.Netty4Utils;
-import java.net.SocketAddress;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.Collections;
@@ -38,25 +43,16 @@ import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
-public class Netty4HttpRequest extends RestRequest {
-
+public class Netty4HttpRequest implements HttpRequest {
private final FullHttpRequest request;
- private final Channel channel;
private final BytesReference content;
+ private final HttpHeadersMap headers;
+ private final int sequence;
- /**
- * Construct a new request.
- *
- * @param xContentRegistry the content registry
- * @param request the underlying request
- * @param channel the channel for the request
- * @throws BadParameterException if the parameters can not be decoded
- * @throws ContentTypeHeaderException if the Content-Type header can not be parsed
- */
- Netty4HttpRequest(NamedXContentRegistry xContentRegistry, FullHttpRequest request, Channel channel) {
- super(xContentRegistry, request.uri(), new HttpHeadersMap(request.headers()));
+ Netty4HttpRequest(FullHttpRequest request, int sequence) {
this.request = request;
- this.channel = channel;
+ headers = new HttpHeadersMap(request.headers());
+ this.sequence = sequence;
if (request.content().isReadable()) {
this.content = Netty4Utils.toBytesReference(request.content());
} else {
@@ -64,71 +60,39 @@ public class Netty4HttpRequest extends RestRequest {
}
}
- /**
- * Construct a new request. In contrast to
- * {@link Netty4HttpRequest#Netty4HttpRequest(NamedXContentRegistry, Map, String, FullHttpRequest, Channel)}, the URI is not decoded so
- * this constructor will not throw a {@link BadParameterException}.
- *
- * @param xContentRegistry the content registry
- * @param params the parameters for the request
- * @param uri the path for the request
- * @param request the underlying request
- * @param channel the channel for the request
- * @throws ContentTypeHeaderException if the Content-Type header can not be parsed
- */
- Netty4HttpRequest(
- final NamedXContentRegistry xContentRegistry,
- final Map params,
- final String uri,
- final FullHttpRequest request,
- final Channel channel) {
- super(xContentRegistry, params, uri, new HttpHeadersMap(request.headers()));
- this.request = request;
- this.channel = channel;
- if (request.content().isReadable()) {
- this.content = Netty4Utils.toBytesReference(request.content());
- } else {
- this.content = BytesArray.EMPTY;
- }
- }
-
- public FullHttpRequest request() {
- return this.request;
- }
-
@Override
- public Method method() {
+ public RestRequest.Method method() {
HttpMethod httpMethod = request.method();
if (httpMethod == HttpMethod.GET)
- return Method.GET;
+ return RestRequest.Method.GET;
if (httpMethod == HttpMethod.POST)
- return Method.POST;
+ return RestRequest.Method.POST;
if (httpMethod == HttpMethod.PUT)
- return Method.PUT;
+ return RestRequest.Method.PUT;
if (httpMethod == HttpMethod.DELETE)
- return Method.DELETE;
+ return RestRequest.Method.DELETE;
if (httpMethod == HttpMethod.HEAD) {
- return Method.HEAD;
+ return RestRequest.Method.HEAD;
}
if (httpMethod == HttpMethod.OPTIONS) {
- return Method.OPTIONS;
+ return RestRequest.Method.OPTIONS;
}
if (httpMethod == HttpMethod.PATCH) {
- return Method.PATCH;
+ return RestRequest.Method.PATCH;
}
if (httpMethod == HttpMethod.TRACE) {
- return Method.TRACE;
+ return RestRequest.Method.TRACE;
}
if (httpMethod == HttpMethod.CONNECT) {
- return Method.CONNECT;
+ return RestRequest.Method.CONNECT;
}
throw new IllegalArgumentException("Unexpected http method: " + httpMethod);
@@ -139,40 +103,64 @@ public class Netty4HttpRequest extends RestRequest {
return request.uri();
}
- @Override
- public boolean hasContent() {
- return content.length() > 0;
- }
-
@Override
public BytesReference content() {
return content;
}
- /**
- * Returns the remote address where this rest request channel is "connected to". The
- * returned {@link SocketAddress} is supposed to be down-cast into more
- * concrete type such as {@link java.net.InetSocketAddress} to retrieve
- * the detailed information.
- */
+
@Override
- public SocketAddress getRemoteAddress() {
- return channel.remoteAddress();
+ public final Map> getHeaders() {
+ return headers;
}
- /**
- * Returns the local address where this request channel is bound to. The returned
- * {@link SocketAddress} is supposed to be down-cast into more concrete
- * type such as {@link java.net.InetSocketAddress} to retrieve the detailed
- * information.
- */
@Override
- public SocketAddress getLocalAddress() {
- return channel.localAddress();
+ public List strictCookies() {
+ String cookieString = request.headers().get(HttpHeaderNames.COOKIE);
+ if (cookieString != null) {
+ Set cookies = ServerCookieDecoder.STRICT.decode(cookieString);
+ if (!cookies.isEmpty()) {
+ return ServerCookieEncoder.STRICT.encode(cookies);
+ }
+ }
+ return Collections.emptyList();
}
- public Channel getChannel() {
- return channel;
+ @Override
+ public HttpVersion protocolVersion() {
+ if (request.protocolVersion().equals(io.netty.handler.codec.http.HttpVersion.HTTP_1_0)) {
+ return HttpRequest.HttpVersion.HTTP_1_0;
+ } else if (request.protocolVersion().equals(io.netty.handler.codec.http.HttpVersion.HTTP_1_1)) {
+ return HttpRequest.HttpVersion.HTTP_1_1;
+ } else {
+ throw new IllegalArgumentException("Unexpected http protocol version: " + request.protocolVersion());
+ }
+ }
+
+ @Override
+ public HttpRequest removeHeader(String header) {
+ HttpHeaders headersWithoutContentTypeHeader = new DefaultHttpHeaders();
+ headersWithoutContentTypeHeader.add(request.headers());
+ headersWithoutContentTypeHeader.remove(header);
+ HttpHeaders trailingHeaders = new DefaultHttpHeaders();
+ trailingHeaders.add(request.trailingHeaders());
+ trailingHeaders.remove(header);
+ FullHttpRequest requestWithoutHeader = new DefaultFullHttpRequest(request.protocolVersion(), request.method(), request.uri(),
+ request.content(), headersWithoutContentTypeHeader, trailingHeaders);
+ return new Netty4HttpRequest(requestWithoutHeader, sequence);
+ }
+
+ @Override
+ public Netty4HttpResponse createResponse(RestStatus status, BytesReference content) {
+ return new Netty4HttpResponse(this, status, content);
+ }
+
+ public FullHttpRequest nettyRequest() {
+ return request;
+ }
+
+ int sequence() {
+ return sequence;
}
/**
@@ -249,7 +237,7 @@ public class Netty4HttpRequest extends RestRequest {
@Override
public Set>> entrySet() {
return httpHeaders.names().stream().map(k -> new AbstractMap.SimpleImmutableEntry<>(k, httpHeaders.getAll(k)))
- .collect(Collectors.toSet());
+ .collect(Collectors.toSet());
}
}
}
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequestHandler.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequestHandler.java
index c3a010226a4..4547a63a9a2 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequestHandler.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpRequestHandler.java
@@ -20,112 +20,51 @@
package org.elasticsearch.http.netty4;
import io.netty.buffer.Unpooled;
-import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpRequest;
-import io.netty.handler.codec.http.DefaultHttpHeaders;
import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.HttpHeaders;
-import org.elasticsearch.common.util.concurrent.ThreadContext;
-import org.elasticsearch.http.HttpHandlingSettings;
+import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.http.HttpPipelinedRequest;
-import org.elasticsearch.rest.RestRequest;
import org.elasticsearch.transport.netty4.Netty4Utils;
-import java.util.Collections;
-
@ChannelHandler.Sharable
class Netty4HttpRequestHandler extends SimpleChannelInboundHandler> {
private final Netty4HttpServerTransport serverTransport;
- private final HttpHandlingSettings handlingSettings;
- private final ThreadContext threadContext;
- Netty4HttpRequestHandler(Netty4HttpServerTransport serverTransport, HttpHandlingSettings handlingSettings,
- ThreadContext threadContext) {
+ Netty4HttpRequestHandler(Netty4HttpServerTransport serverTransport) {
this.serverTransport = serverTransport;
- this.handlingSettings = handlingSettings;
- this.threadContext = threadContext;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, HttpPipelinedRequest msg) throws Exception {
- final FullHttpRequest request = msg.getRequest();
+ Netty4HttpChannel channel = ctx.channel().attr(Netty4HttpServerTransport.HTTP_CHANNEL_KEY).get();
+ FullHttpRequest request = msg.getRequest();
try {
+ final FullHttpRequest copiedRequest =
+ new DefaultFullHttpRequest(
+ request.protocolVersion(),
+ request.method(),
+ request.uri(),
+ Unpooled.copiedBuffer(request.content()),
+ request.headers(),
+ request.trailingHeaders());
- final FullHttpRequest copy =
- new DefaultFullHttpRequest(
- request.protocolVersion(),
- request.method(),
- request.uri(),
- Unpooled.copiedBuffer(request.content()),
- request.headers(),
- request.trailingHeaders());
-
- Exception badRequestCause = null;
-
- /*
- * We want to create a REST request from the incoming request from Netty. However, creating this request could fail if there
- * are incorrectly encoded parameters, or the Content-Type header is invalid. If one of these specific failures occurs, we
- * attempt to create a REST request again without the input that caused the exception (e.g., we remove the Content-Type header,
- * or skip decoding the parameters). Once we have a request in hand, we then dispatch the request as a bad request with the
- * underlying exception that caused us to treat the request as bad.
- */
- final Netty4HttpRequest httpRequest;
- {
- Netty4HttpRequest innerHttpRequest;
- try {
- innerHttpRequest = new Netty4HttpRequest(serverTransport.xContentRegistry, copy, ctx.channel());
- } catch (final RestRequest.ContentTypeHeaderException e) {
- badRequestCause = e;
- innerHttpRequest = requestWithoutContentTypeHeader(copy, ctx.channel(), badRequestCause);
- } catch (final RestRequest.BadParameterException e) {
- badRequestCause = e;
- innerHttpRequest = requestWithoutParameters(copy, ctx.channel());
- }
- httpRequest = innerHttpRequest;
- }
-
- /*
- * We now want to create a channel used to send the response on. However, creating this channel can fail if there are invalid
- * parameter values for any of the filter_path, human, or pretty parameters. We detect these specific failures via an
- * IllegalArgumentException from the channel constructor and then attempt to create a new channel that bypasses parsing of these
- * parameter values.
- */
- final Netty4HttpChannel channel;
- {
- Netty4HttpChannel innerChannel;
- try {
- innerChannel =
- new Netty4HttpChannel(serverTransport, httpRequest, msg.getSequence(), handlingSettings, threadContext);
- } catch (final IllegalArgumentException e) {
- if (badRequestCause == null) {
- badRequestCause = e;
- } else {
- badRequestCause.addSuppressed(e);
- }
- final Netty4HttpRequest innerRequest =
- new Netty4HttpRequest(
- serverTransport.xContentRegistry,
- Collections.emptyMap(), // we are going to dispatch the request as a bad request, drop all parameters
- copy.uri(),
- copy,
- ctx.channel());
- innerChannel =
- new Netty4HttpChannel(serverTransport, innerRequest, msg.getSequence(), handlingSettings, threadContext);
- }
- channel = innerChannel;
- }
+ Netty4HttpRequest httpRequest = new Netty4HttpRequest(copiedRequest, msg.getSequence());
if (request.decoderResult().isFailure()) {
- serverTransport.dispatchBadRequest(httpRequest, channel, request.decoderResult().cause());
- } else if (badRequestCause != null) {
- serverTransport.dispatchBadRequest(httpRequest, channel, badRequestCause);
+ Throwable cause = request.decoderResult().cause();
+ if (cause instanceof Error) {
+ ExceptionsHelper.dieOnError(cause);
+ serverTransport.incomingRequestError(httpRequest, channel, new Exception(cause));
+ } else {
+ serverTransport.incomingRequestError(httpRequest, channel, (Exception) cause);
+ }
} else {
- serverTransport.dispatchRequest(httpRequest, channel);
+ serverTransport.incomingRequest(httpRequest, channel);
}
} finally {
// As we have copied the buffer, we can release the request
@@ -133,32 +72,6 @@ class Netty4HttpRequestHandler extends SimpleChannelInboundHandler MAP;
+
+ static {
+ EnumMap map = new EnumMap<>(RestStatus.class);
+ map.put(RestStatus.CONTINUE, HttpResponseStatus.CONTINUE);
+ map.put(RestStatus.SWITCHING_PROTOCOLS, HttpResponseStatus.SWITCHING_PROTOCOLS);
+ map.put(RestStatus.OK, HttpResponseStatus.OK);
+ map.put(RestStatus.CREATED, HttpResponseStatus.CREATED);
+ map.put(RestStatus.ACCEPTED, HttpResponseStatus.ACCEPTED);
+ map.put(RestStatus.NON_AUTHORITATIVE_INFORMATION, HttpResponseStatus.NON_AUTHORITATIVE_INFORMATION);
+ map.put(RestStatus.NO_CONTENT, HttpResponseStatus.NO_CONTENT);
+ map.put(RestStatus.RESET_CONTENT, HttpResponseStatus.RESET_CONTENT);
+ map.put(RestStatus.PARTIAL_CONTENT, HttpResponseStatus.PARTIAL_CONTENT);
+ map.put(RestStatus.MULTI_STATUS, HttpResponseStatus.INTERNAL_SERVER_ERROR); // no status for this??
+ map.put(RestStatus.MULTIPLE_CHOICES, HttpResponseStatus.MULTIPLE_CHOICES);
+ map.put(RestStatus.MOVED_PERMANENTLY, HttpResponseStatus.MOVED_PERMANENTLY);
+ map.put(RestStatus.FOUND, HttpResponseStatus.FOUND);
+ map.put(RestStatus.SEE_OTHER, HttpResponseStatus.SEE_OTHER);
+ map.put(RestStatus.NOT_MODIFIED, HttpResponseStatus.NOT_MODIFIED);
+ map.put(RestStatus.USE_PROXY, HttpResponseStatus.USE_PROXY);
+ map.put(RestStatus.TEMPORARY_REDIRECT, HttpResponseStatus.TEMPORARY_REDIRECT);
+ map.put(RestStatus.BAD_REQUEST, HttpResponseStatus.BAD_REQUEST);
+ map.put(RestStatus.UNAUTHORIZED, HttpResponseStatus.UNAUTHORIZED);
+ map.put(RestStatus.PAYMENT_REQUIRED, HttpResponseStatus.PAYMENT_REQUIRED);
+ map.put(RestStatus.FORBIDDEN, HttpResponseStatus.FORBIDDEN);
+ map.put(RestStatus.NOT_FOUND, HttpResponseStatus.NOT_FOUND);
+ map.put(RestStatus.METHOD_NOT_ALLOWED, HttpResponseStatus.METHOD_NOT_ALLOWED);
+ map.put(RestStatus.NOT_ACCEPTABLE, HttpResponseStatus.NOT_ACCEPTABLE);
+ map.put(RestStatus.PROXY_AUTHENTICATION, HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED);
+ map.put(RestStatus.REQUEST_TIMEOUT, HttpResponseStatus.REQUEST_TIMEOUT);
+ map.put(RestStatus.CONFLICT, HttpResponseStatus.CONFLICT);
+ map.put(RestStatus.GONE, HttpResponseStatus.GONE);
+ map.put(RestStatus.LENGTH_REQUIRED, HttpResponseStatus.LENGTH_REQUIRED);
+ map.put(RestStatus.PRECONDITION_FAILED, HttpResponseStatus.PRECONDITION_FAILED);
+ map.put(RestStatus.REQUEST_ENTITY_TOO_LARGE, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE);
+ map.put(RestStatus.REQUEST_URI_TOO_LONG, HttpResponseStatus.REQUEST_URI_TOO_LONG);
+ map.put(RestStatus.UNSUPPORTED_MEDIA_TYPE, HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE);
+ map.put(RestStatus.REQUESTED_RANGE_NOT_SATISFIED, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
+ map.put(RestStatus.EXPECTATION_FAILED, HttpResponseStatus.EXPECTATION_FAILED);
+ map.put(RestStatus.UNPROCESSABLE_ENTITY, HttpResponseStatus.BAD_REQUEST);
+ map.put(RestStatus.LOCKED, HttpResponseStatus.BAD_REQUEST);
+ map.put(RestStatus.FAILED_DEPENDENCY, HttpResponseStatus.BAD_REQUEST);
+ map.put(RestStatus.TOO_MANY_REQUESTS, HttpResponseStatus.TOO_MANY_REQUESTS);
+ map.put(RestStatus.INTERNAL_SERVER_ERROR, HttpResponseStatus.INTERNAL_SERVER_ERROR);
+ map.put(RestStatus.NOT_IMPLEMENTED, HttpResponseStatus.NOT_IMPLEMENTED);
+ map.put(RestStatus.BAD_GATEWAY, HttpResponseStatus.BAD_GATEWAY);
+ map.put(RestStatus.SERVICE_UNAVAILABLE, HttpResponseStatus.SERVICE_UNAVAILABLE);
+ map.put(RestStatus.GATEWAY_TIMEOUT, HttpResponseStatus.GATEWAY_TIMEOUT);
+ map.put(RestStatus.HTTP_VERSION_NOT_SUPPORTED, HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
+ MAP = Collections.unmodifiableMap(map);
+ }
+
+ private static HttpResponseStatus getStatus(RestStatus status) {
+ return MAP.getOrDefault(status, HttpResponseStatus.INTERNAL_SERVER_ERROR);
+ }
+
}
+
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java
index 0e18232e01c..6bfd8168dbe 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/Netty4HttpServerTransport.java
@@ -39,6 +39,7 @@ import io.netty.handler.codec.http.HttpRequestDecoder;
import io.netty.handler.codec.http.HttpResponseEncoder;
import io.netty.handler.timeout.ReadTimeoutException;
import io.netty.handler.timeout.ReadTimeoutHandler;
+import io.netty.util.AttributeKey;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.elasticsearch.common.Strings;
@@ -53,9 +54,7 @@ import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.common.util.BigArrays;
import org.elasticsearch.common.util.concurrent.EsExecutors;
-import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
-import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.http.AbstractHttpServerTransport;
import org.elasticsearch.http.BindHttpException;
import org.elasticsearch.http.HttpHandlingSettings;
@@ -149,38 +148,29 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport {
public static final Setting SETTING_HTTP_NETTY_RECEIVE_PREDICTOR_SIZE =
Setting.byteSizeSetting("http.netty.receive_predictor_size", new ByteSizeValue(64, ByteSizeUnit.KB), Property.NodeScope);
- protected final BigArrays bigArrays;
+ private final ByteSizeValue maxInitialLineLength;
+ private final ByteSizeValue maxHeaderSize;
+ private final ByteSizeValue maxChunkSize;
- protected final ByteSizeValue maxInitialLineLength;
- protected final ByteSizeValue maxHeaderSize;
- protected final ByteSizeValue maxChunkSize;
+ private final int workerCount;
- protected final int workerCount;
+ private final int pipeliningMaxEvents;
- protected final int pipeliningMaxEvents;
+ private final boolean tcpNoDelay;
+ private final boolean tcpKeepAlive;
+ private final boolean reuseAddress;
- /**
- * The registry used to construct parsers so they support {@link XContentParser#namedObject(Class, String, Object)}.
- */
- protected final NamedXContentRegistry xContentRegistry;
-
- protected final boolean tcpNoDelay;
- protected final boolean tcpKeepAlive;
- protected final boolean reuseAddress;
-
- protected final ByteSizeValue tcpSendBufferSize;
- protected final ByteSizeValue tcpReceiveBufferSize;
- protected final RecvByteBufAllocator recvByteBufAllocator;
+ private final ByteSizeValue tcpSendBufferSize;
+ private final ByteSizeValue tcpReceiveBufferSize;
+ private final RecvByteBufAllocator recvByteBufAllocator;
private final int readTimeoutMillis;
- protected final int maxCompositeBufferComponents;
+ private final int maxCompositeBufferComponents;
protected volatile ServerBootstrap serverBootstrap;
protected final List serverChannels = new ArrayList<>();
- protected final HttpHandlingSettings httpHandlingSettings;
-
// package private for testing
Netty4OpenChannelsHandler serverOpenChannels;
@@ -189,16 +179,13 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport {
public Netty4HttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, ThreadPool threadPool,
NamedXContentRegistry xContentRegistry, Dispatcher dispatcher) {
- super(settings, networkService, threadPool, dispatcher);
+ super(settings, networkService, bigArrays, threadPool, xContentRegistry, dispatcher);
Netty4Utils.setAvailableProcessors(EsExecutors.PROCESSORS_SETTING.get(settings));
- this.bigArrays = bigArrays;
- this.xContentRegistry = xContentRegistry;
this.maxChunkSize = SETTING_HTTP_MAX_CHUNK_SIZE.get(settings);
this.maxHeaderSize = SETTING_HTTP_MAX_HEADER_SIZE.get(settings);
this.maxInitialLineLength = SETTING_HTTP_MAX_INITIAL_LINE_LENGTH.get(settings);
this.pipeliningMaxEvents = SETTING_PIPELINING_MAX_EVENTS.get(settings);
- this.httpHandlingSettings = HttpHandlingSettings.fromSettings(settings);
this.maxCompositeBufferComponents = SETTING_HTTP_NETTY_MAX_COMPOSITE_BUFFER_COMPONENTS.get(settings);
this.workerCount = SETTING_HTTP_WORKER_COUNT.get(settings);
@@ -398,26 +385,27 @@ public class Netty4HttpServerTransport extends AbstractHttpServerTransport {
}
public ChannelHandler configureServerChannelHandler() {
- return new HttpChannelHandler(this, httpHandlingSettings, threadPool.getThreadContext());
+ return new HttpChannelHandler(this, handlingSettings);
}
+ static final AttributeKey HTTP_CHANNEL_KEY = AttributeKey.newInstance("es-http-channel");
+
protected static class HttpChannelHandler extends ChannelInitializer {
private final Netty4HttpServerTransport transport;
private final Netty4HttpRequestHandler requestHandler;
private final HttpHandlingSettings handlingSettings;
- protected HttpChannelHandler(
- final Netty4HttpServerTransport transport,
- final HttpHandlingSettings handlingSettings,
- final ThreadContext threadContext) {
+ protected HttpChannelHandler(final Netty4HttpServerTransport transport, final HttpHandlingSettings handlingSettings) {
this.transport = transport;
this.handlingSettings = handlingSettings;
- this.requestHandler = new Netty4HttpRequestHandler(transport, handlingSettings, threadContext);
+ this.requestHandler = new Netty4HttpRequestHandler(transport);
}
@Override
protected void initChannel(Channel ch) throws Exception {
+ Netty4HttpChannel nettyTcpChannel = new Netty4HttpChannel(ch);
+ ch.attr(HTTP_CHANNEL_KEY).set(nettyTcpChannel);
ch.pipeline().addLast("openChannels", transport.serverOpenChannels);
ch.pipeline().addLast("read_timeout", new ReadTimeoutHandler(transport.readTimeoutMillis, TimeUnit.MILLISECONDS));
final HttpRequestDecoder decoder = new HttpRequestDecoder(
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/cors/Netty4CorsHandler.java b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/cors/Netty4CorsHandler.java
index 779eb4fe2e4..38d832d6080 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/cors/Netty4CorsHandler.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/cors/Netty4CorsHandler.java
@@ -22,6 +22,7 @@ package org.elasticsearch.http.netty4.cors;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
+import io.netty.channel.ChannelPromise;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders;
@@ -30,6 +31,7 @@ import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import org.elasticsearch.common.Strings;
+import org.elasticsearch.http.netty4.Netty4HttpResponse;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@@ -76,6 +78,14 @@ public class Netty4CorsHandler extends ChannelDuplexHandler {
ctx.fireChannelRead(msg);
}
+ @Override
+ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
+ assert msg instanceof Netty4HttpResponse : "Invalid message type: " + msg.getClass();
+ Netty4HttpResponse response = (Netty4HttpResponse) msg;
+ setCorsResponseHeaders(response.getRequest().nettyRequest(), response, config);
+ ctx.write(response, promise);;
+ }
+
public static void setCorsResponseHeaders(HttpRequest request, HttpResponse resp, Netty4CorsConfig config) {
if (!config.isCorsSupportEnabled()) {
return;
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java
index f4818a2e567..466c4b68bfa 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/Netty4Transport.java
@@ -333,10 +333,10 @@ public class Netty4Transport extends TcpTransport {
addClosedExceptionLogger(ch);
NettyTcpChannel nettyTcpChannel = new NettyTcpChannel(ch, name);
ch.attr(CHANNEL_KEY).set(nettyTcpChannel);
- serverAcceptedChannel(nettyTcpChannel);
ch.pipeline().addLast("logging", new ESLoggingHandler());
ch.pipeline().addLast("size", new Netty4SizeHeaderFrameDecoder());
ch.pipeline().addLast("dispatcher", new Netty4MessageChannelHandler(Netty4Transport.this, name));
+ serverAcceptedChannel(nettyTcpChannel);
}
@Override
diff --git a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/NettyTcpChannel.java b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/NettyTcpChannel.java
index f650e757e7a..89fabdcd763 100644
--- a/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/NettyTcpChannel.java
+++ b/modules/transport-netty4/src/main/java/org/elasticsearch/transport/netty4/NettyTcpChannel.java
@@ -98,8 +98,11 @@ public class NettyTcpChannel implements TcpChannel {
} else {
final Throwable cause = f.cause();
Netty4Utils.maybeDie(cause);
- assert cause instanceof Exception;
- listener.onFailure((Exception) cause);
+ if (cause instanceof Error) {
+ listener.onFailure(new Exception(cause));
+ } else {
+ listener.onFailure((Exception) cause);
+ }
}
});
channel.writeAndFlush(Netty4Utils.toByteBuf(reference), writePromise);
diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4CorsTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4CorsTests.java
new file mode 100644
index 00000000000..15a0850f64d
--- /dev/null
+++ b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4CorsTests.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to Elasticsearch under one or more contributor
+ * license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright
+ * ownership. Elasticsearch licenses this file to you 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
+ *
+ * http://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 org.elasticsearch.http.netty4;
+
+import io.netty.channel.embedded.EmbeddedChannel;
+import io.netty.handler.codec.http.DefaultFullHttpRequest;
+import io.netty.handler.codec.http.FullHttpRequest;
+import io.netty.handler.codec.http.FullHttpResponse;
+import io.netty.handler.codec.http.HttpHeaderNames;
+import io.netty.handler.codec.http.HttpMethod;
+import io.netty.handler.codec.http.HttpResponse;
+import io.netty.handler.codec.http.HttpVersion;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.http.HttpTransportSettings;
+import org.elasticsearch.http.netty4.cors.Netty4CorsHandler;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.test.ESTestCase;
+
+import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
+import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
+import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
+import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+public class Netty4CorsTests extends ESTestCase {
+
+ public void testCorsEnabledWithoutAllowOrigins() {
+ // Set up a HTTP transport with only the CORS enabled setting
+ Settings settings = Settings.builder()
+ .put(HttpTransportSettings.SETTING_CORS_ENABLED.getKey(), true)
+ .build();
+ HttpResponse response = executeRequest(settings, "remote-host", "request-host");
+ // inspect response and validate
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), nullValue());
+ }
+
+ public void testCorsEnabledWithAllowOrigins() {
+ final String originValue = "remote-host";
+ // create a http transport with CORS enabled and allow origin configured
+ Settings settings = Settings.builder()
+ .put(SETTING_CORS_ENABLED.getKey(), true)
+ .put(SETTING_CORS_ALLOW_ORIGIN.getKey(), originValue)
+ .build();
+ HttpResponse response = executeRequest(settings, originValue, "request-host");
+ // inspect response and validate
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
+ String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat(allowedOrigins, is(originValue));
+ }
+
+ public void testCorsAllowOriginWithSameHost() {
+ String originValue = "remote-host";
+ String host = "remote-host";
+ // create a http transport with CORS enabled
+ Settings settings = Settings.builder()
+ .put(SETTING_CORS_ENABLED.getKey(), true)
+ .build();
+ HttpResponse response = executeRequest(settings, originValue, host);
+ // inspect response and validate
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
+ String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat(allowedOrigins, is(originValue));
+
+ originValue = "http://" + originValue;
+ response = executeRequest(settings, originValue, host);
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
+ allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat(allowedOrigins, is(originValue));
+
+ originValue = originValue + ":5555";
+ host = host + ":5555";
+ response = executeRequest(settings, originValue, host);
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
+ allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat(allowedOrigins, is(originValue));
+
+ originValue = originValue.replace("http", "https");
+ response = executeRequest(settings, originValue, host);
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
+ allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat(allowedOrigins, is(originValue));
+ }
+
+ public void testThatStringLiteralWorksOnMatch() {
+ final String originValue = "remote-host";
+ Settings settings = Settings.builder()
+ .put(SETTING_CORS_ENABLED.getKey(), true)
+ .put(SETTING_CORS_ALLOW_ORIGIN.getKey(), originValue)
+ .put(SETTING_CORS_ALLOW_METHODS.getKey(), "get, options, post")
+ .put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
+ .build();
+ HttpResponse response = executeRequest(settings, originValue, "request-host");
+ // inspect response and validate
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
+ String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat(allowedOrigins, is(originValue));
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS), equalTo("true"));
+ }
+
+ public void testThatAnyOriginWorks() {
+ final String originValue = Netty4CorsHandler.ANY_ORIGIN;
+ Settings settings = Settings.builder()
+ .put(SETTING_CORS_ENABLED.getKey(), true)
+ .put(SETTING_CORS_ALLOW_ORIGIN.getKey(), originValue)
+ .build();
+ HttpResponse response = executeRequest(settings, originValue, "request-host");
+ // inspect response and validate
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
+ String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
+ assertThat(allowedOrigins, is(originValue));
+ assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS), nullValue());
+ }
+
+ private FullHttpResponse executeRequest(final Settings settings, final String originValue, final String host) {
+ // construct request and send it over the transport layer
+ final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
+ if (originValue != null) {
+ httpRequest.headers().add(HttpHeaderNames.ORIGIN, originValue);
+ }
+ httpRequest.headers().add(HttpHeaderNames.HOST, host);
+ EmbeddedChannel embeddedChannel = new EmbeddedChannel();
+ embeddedChannel.pipeline().addLast(new Netty4CorsHandler(Netty4HttpServerTransport.buildCorsConfig(settings)));
+ Netty4HttpRequest nettyRequest = new Netty4HttpRequest(httpRequest, 0);
+ embeddedChannel.writeOutbound(nettyRequest.createResponse(RestStatus.OK, new BytesArray("content")));
+ return embeddedChannel.readOutbound();
+ }
+}
diff --git a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpChannelTests.java b/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpChannelTests.java
deleted file mode 100644
index 7c5b35a3229..00000000000
--- a/modules/transport-netty4/src/test/java/org/elasticsearch/http/netty4/Netty4HttpChannelTests.java
+++ /dev/null
@@ -1,616 +0,0 @@
-/*
- * Licensed to Elasticsearch under one or more contributor
- * license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright
- * ownership. Elasticsearch licenses this file to you 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
- *
- * http://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 org.elasticsearch.http.netty4;
-
-import io.netty.buffer.ByteBufAllocator;
-import io.netty.buffer.Unpooled;
-import io.netty.channel.Channel;
-import io.netty.channel.ChannelConfig;
-import io.netty.channel.ChannelFuture;
-import io.netty.channel.ChannelId;
-import io.netty.channel.ChannelMetadata;
-import io.netty.channel.ChannelPipeline;
-import io.netty.channel.ChannelProgressivePromise;
-import io.netty.channel.ChannelPromise;
-import io.netty.channel.EventLoop;
-import io.netty.channel.embedded.EmbeddedChannel;
-import io.netty.handler.codec.http.DefaultFullHttpRequest;
-import io.netty.handler.codec.http.FullHttpRequest;
-import io.netty.handler.codec.http.FullHttpResponse;
-import io.netty.handler.codec.http.HttpHeaderNames;
-import io.netty.handler.codec.http.HttpHeaderValues;
-import io.netty.handler.codec.http.HttpMethod;
-import io.netty.handler.codec.http.HttpResponse;
-import io.netty.handler.codec.http.HttpVersion;
-import io.netty.util.Attribute;
-import io.netty.util.AttributeKey;
-import org.elasticsearch.common.bytes.BytesReference;
-import org.elasticsearch.common.bytes.ReleasablePagedBytesReference;
-import org.elasticsearch.common.io.stream.BytesStreamOutput;
-import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
-import org.elasticsearch.common.lease.Releasable;
-import org.elasticsearch.common.lease.Releasables;
-import org.elasticsearch.common.network.NetworkService;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.util.BigArrays;
-import org.elasticsearch.common.util.ByteArray;
-import org.elasticsearch.common.util.MockBigArrays;
-import org.elasticsearch.common.util.MockPageCacheRecycler;
-import org.elasticsearch.common.xcontent.NamedXContentRegistry;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.json.JsonXContent;
-import org.elasticsearch.http.HttpHandlingSettings;
-import org.elasticsearch.http.HttpTransportSettings;
-import org.elasticsearch.http.NullDispatcher;
-import org.elasticsearch.http.netty4.cors.Netty4CorsHandler;
-import org.elasticsearch.indices.breaker.NoneCircuitBreakerService;
-import org.elasticsearch.rest.BytesRestResponse;
-import org.elasticsearch.rest.RestResponse;
-import org.elasticsearch.rest.RestStatus;
-import org.elasticsearch.test.ESTestCase;
-import org.elasticsearch.threadpool.TestThreadPool;
-import org.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.transport.netty4.Netty4Utils;
-import org.junit.After;
-import org.junit.Before;
-
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.SocketAddress;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
-import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
-import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
-import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
-import static org.hamcrest.Matchers.equalTo;
-import static org.hamcrest.Matchers.instanceOf;
-import static org.hamcrest.Matchers.is;
-import static org.hamcrest.Matchers.not;
-import static org.hamcrest.Matchers.notNullValue;
-import static org.hamcrest.Matchers.nullValue;
-
-public class Netty4HttpChannelTests extends ESTestCase {
-
- private NetworkService networkService;
- private ThreadPool threadPool;
- private MockBigArrays bigArrays;
-
- @Before
- public void setup() throws Exception {
- networkService = new NetworkService(Collections.emptyList());
- threadPool = new TestThreadPool("test");
- bigArrays = new MockBigArrays(new MockPageCacheRecycler(Settings.EMPTY), new NoneCircuitBreakerService());
- }
-
- @After
- public void shutdown() throws Exception {
- if (threadPool != null) {
- threadPool.shutdownNow();
- }
- }
-
- public void testResponse() {
- final FullHttpResponse response = executeRequest(Settings.EMPTY, "request-host");
- assertThat(response.content(), equalTo(Netty4Utils.toByteBuf(new TestResponse().content())));
- }
-
- public void testCorsEnabledWithoutAllowOrigins() {
- // Set up a HTTP transport with only the CORS enabled setting
- Settings settings = Settings.builder()
- .put(HttpTransportSettings.SETTING_CORS_ENABLED.getKey(), true)
- .build();
- HttpResponse response = executeRequest(settings, "remote-host", "request-host");
- // inspect response and validate
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), nullValue());
- }
-
- public void testCorsEnabledWithAllowOrigins() {
- final String originValue = "remote-host";
- // create a http transport with CORS enabled and allow origin configured
- Settings settings = Settings.builder()
- .put(SETTING_CORS_ENABLED.getKey(), true)
- .put(SETTING_CORS_ALLOW_ORIGIN.getKey(), originValue)
- .build();
- HttpResponse response = executeRequest(settings, originValue, "request-host");
- // inspect response and validate
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
- String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowedOrigins, is(originValue));
- }
-
- public void testCorsAllowOriginWithSameHost() {
- String originValue = "remote-host";
- String host = "remote-host";
- // create a http transport with CORS enabled
- Settings settings = Settings.builder()
- .put(SETTING_CORS_ENABLED.getKey(), true)
- .build();
- HttpResponse response = executeRequest(settings, originValue, host);
- // inspect response and validate
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
- String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowedOrigins, is(originValue));
-
- originValue = "http://" + originValue;
- response = executeRequest(settings, originValue, host);
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
- allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowedOrigins, is(originValue));
-
- originValue = originValue + ":5555";
- host = host + ":5555";
- response = executeRequest(settings, originValue, host);
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
- allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowedOrigins, is(originValue));
-
- originValue = originValue.replace("http", "https");
- response = executeRequest(settings, originValue, host);
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
- allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowedOrigins, is(originValue));
- }
-
- public void testThatStringLiteralWorksOnMatch() {
- final String originValue = "remote-host";
- Settings settings = Settings.builder()
- .put(SETTING_CORS_ENABLED.getKey(), true)
- .put(SETTING_CORS_ALLOW_ORIGIN.getKey(), originValue)
- .put(SETTING_CORS_ALLOW_METHODS.getKey(), "get, options, post")
- .put(SETTING_CORS_ALLOW_CREDENTIALS.getKey(), true)
- .build();
- HttpResponse response = executeRequest(settings, originValue, "request-host");
- // inspect response and validate
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
- String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowedOrigins, is(originValue));
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS), equalTo("true"));
- }
-
- public void testThatAnyOriginWorks() {
- final String originValue = Netty4CorsHandler.ANY_ORIGIN;
- Settings settings = Settings.builder()
- .put(SETTING_CORS_ENABLED.getKey(), true)
- .put(SETTING_CORS_ALLOW_ORIGIN.getKey(), originValue)
- .build();
- HttpResponse response = executeRequest(settings, originValue, "request-host");
- // inspect response and validate
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN), notNullValue());
- String allowedOrigins = response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN);
- assertThat(allowedOrigins, is(originValue));
- assertThat(response.headers().get(HttpHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS), nullValue());
- }
-
- public void testHeadersSet() {
- Settings settings = Settings.builder().build();
- try (Netty4HttpServerTransport httpServerTransport =
- new Netty4HttpServerTransport(settings, networkService, bigArrays, threadPool, xContentRegistry(),
- new NullDispatcher())) {
- httpServerTransport.start();
- final FullHttpRequest httpRequest = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, "/");
- httpRequest.headers().add(HttpHeaderNames.ORIGIN, "remote");
- final WriteCapturingChannel writeCapturingChannel = new WriteCapturingChannel();
- final Netty4HttpRequest request = new Netty4HttpRequest(xContentRegistry(), httpRequest, writeCapturingChannel);
- HttpHandlingSettings handlingSettings = httpServerTransport.httpHandlingSettings;
-
- // send a response
- Netty4HttpChannel channel =
- new Netty4HttpChannel(httpServerTransport, request, 1, handlingSettings, threadPool.getThreadContext());
- TestResponse resp = new TestResponse();
- final String customHeader = "custom-header";
- final String customHeaderValue = "xyz";
- resp.addHeader(customHeader, customHeaderValue);
- channel.sendResponse(resp);
-
- // inspect what was written
- List