Skip to content

Commit 05ee0f8

Browse files
authored
Add cors support to NioHttpServerTransport (#30827)
This is related to #28898. This commit adds cors support to the nio http transport. Most of the work is copied directly from the netty module implementation. Additionally, this commit adds tests for the nio http channel.
1 parent 840a3bd commit 05ee0f8

File tree

11 files changed

+1332
-46
lines changed

11 files changed

+1332
-46
lines changed

modules/transport-netty4/src/main/java/org/elasticsearch/http/netty4/cors/Netty4CorsConfig.java

+10-9
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ public boolean isCorsSupportEnabled() {
7676
}
7777

7878
/**
79-
* Determines whether a wildcard origin, '*', is supported.
79+
* Determines whether a wildcard origin, '*', is supported. This also means that null origins are
80+
* supported.
8081
*
8182
* @return {@code boolean} true if any origin is allowed.
8283
*/
@@ -121,21 +122,21 @@ public boolean isNullOriginAllowed() {
121122
}
122123

123124
/**
124-
* Determines if cookies are supported for CORS requests.
125+
* Determines if credentials are supported for CORS requests.
125126
*
126-
* By default cookies are not included in CORS requests but if isCredentialsAllowed returns
127-
* true cookies will be added to CORS requests. Setting this value to true will set the
127+
* By default credentials are not included in CORS requests but if isCredentialsAllowed returns
128+
* true credentials will be added to CORS requests. Setting this value to true will set the
128129
* CORS 'Access-Control-Allow-Credentials' response header to true.
129130
*
130-
* Please note that cookie support needs to be enabled on the client side as well.
131-
* The client needs to opt-in to send cookies by calling:
131+
* Please note that credentials support needs to be enabled on the client side as well.
132+
* The client needs to opt-in to send credentials by calling:
132133
* <pre>
133134
* xhr.withCredentials = true;
134135
* </pre>
135-
* The default value for 'withCredentials' is false in which case no cookies are sent.
136-
* Setting this to true will included cookies in cross origin requests.
136+
* The default value for 'withCredentials' is false in which case no credentials are sent.
137+
* Setting this to true will included credentials in cross origin requests.
137138
*
138-
* @return {@code true} if cookies are supported.
139+
* @return {@code true} if credentials are supported.
139140
*/
140141
public boolean isCredentialsAllowed() {
141142
return allowCredentials;

plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/HttpReadWriteHandler.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@
3636
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
3737
import org.elasticsearch.http.HttpHandlingSettings;
3838
import org.elasticsearch.http.HttpPipelinedRequest;
39+
import org.elasticsearch.http.nio.cors.NioCorsConfig;
40+
import org.elasticsearch.http.nio.cors.NioCorsHandler;
3941
import org.elasticsearch.nio.FlushOperation;
4042
import org.elasticsearch.nio.InboundChannelBuffer;
4143
import org.elasticsearch.nio.NioSocketChannel;
@@ -50,21 +52,25 @@
5052
import java.util.List;
5153
import java.util.function.BiConsumer;
5254

55+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
56+
5357
public class HttpReadWriteHandler implements ReadWriteHandler {
5458

5559
private final NettyAdaptor adaptor;
5660
private final NioSocketChannel nioChannel;
5761
private final NioHttpServerTransport transport;
5862
private final HttpHandlingSettings settings;
5963
private final NamedXContentRegistry xContentRegistry;
64+
private final NioCorsConfig corsConfig;
6065
private final ThreadContext threadContext;
6166

6267
HttpReadWriteHandler(NioSocketChannel nioChannel, NioHttpServerTransport transport, HttpHandlingSettings settings,
63-
NamedXContentRegistry xContentRegistry, ThreadContext threadContext) {
68+
NamedXContentRegistry xContentRegistry, NioCorsConfig corsConfig, ThreadContext threadContext) {
6469
this.nioChannel = nioChannel;
6570
this.transport = transport;
6671
this.settings = settings;
6772
this.xContentRegistry = xContentRegistry;
73+
this.corsConfig = corsConfig;
6874
this.threadContext = threadContext;
6975

7076
List<ChannelHandler> handlers = new ArrayList<>(5);
@@ -78,6 +84,9 @@ public class HttpReadWriteHandler implements ReadWriteHandler {
7884
if (settings.isCompression()) {
7985
handlers.add(new HttpContentCompressor(settings.getCompressionLevel()));
8086
}
87+
if (settings.isCorsEnabled()) {
88+
handlers.add(new NioCorsHandler(corsConfig));
89+
}
8190
handlers.add(new NioHttpPipeliningHandler(transport.getLogger(), settings.getPipeliningMaxEvents()));
8291

8392
adaptor = new NettyAdaptor(handlers.toArray(new ChannelHandler[0]));
@@ -178,7 +187,7 @@ private void handleRequest(Object msg) {
178187
int sequence = pipelinedRequest.getSequence();
179188
BigArrays bigArrays = transport.getBigArrays();
180189
try {
181-
innerChannel = new NioHttpChannel(nioChannel, bigArrays, httpRequest, sequence, settings, threadContext);
190+
innerChannel = new NioHttpChannel(nioChannel, bigArrays, httpRequest, sequence, settings, corsConfig, threadContext);
182191
} catch (final IllegalArgumentException e) {
183192
if (badRequestCause == null) {
184193
badRequestCause = e;
@@ -191,7 +200,7 @@ private void handleRequest(Object msg) {
191200
Collections.emptyMap(), // we are going to dispatch the request as a bad request, drop all parameters
192201
copiedRequest.uri(),
193202
copiedRequest);
194-
innerChannel = new NioHttpChannel(nioChannel, bigArrays, innerRequest, sequence, settings, threadContext);
203+
innerChannel = new NioHttpChannel(nioChannel, bigArrays, innerRequest, sequence, settings, corsConfig, threadContext);
195204
}
196205
channel = innerChannel;
197206
}

plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NioHttpChannel.java

+7-1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
import org.elasticsearch.common.util.BigArrays;
4242
import org.elasticsearch.common.util.concurrent.ThreadContext;
4343
import org.elasticsearch.http.HttpHandlingSettings;
44+
import org.elasticsearch.http.nio.cors.NioCorsConfig;
45+
import org.elasticsearch.http.nio.cors.NioCorsHandler;
4446
import org.elasticsearch.nio.NioSocketChannel;
4547
import org.elasticsearch.rest.AbstractRestChannel;
4648
import org.elasticsearch.rest.RestResponse;
@@ -58,17 +60,19 @@ public class NioHttpChannel extends AbstractRestChannel {
5860

5961
private final BigArrays bigArrays;
6062
private final int sequence;
63+
private final NioCorsConfig corsConfig;
6164
private final ThreadContext threadContext;
6265
private final FullHttpRequest nettyRequest;
6366
private final NioSocketChannel nioChannel;
6467
private final boolean resetCookies;
6568

6669
NioHttpChannel(NioSocketChannel nioChannel, BigArrays bigArrays, NioHttpRequest request, int sequence,
67-
HttpHandlingSettings settings, ThreadContext threadContext) {
70+
HttpHandlingSettings settings, NioCorsConfig corsConfig, ThreadContext threadContext) {
6871
super(request, settings.getDetailedErrorsEnabled());
6972
this.nioChannel = nioChannel;
7073
this.bigArrays = bigArrays;
7174
this.sequence = sequence;
75+
this.corsConfig = corsConfig;
7276
this.threadContext = threadContext;
7377
this.nettyRequest = request.getRequest();
7478
this.resetCookies = settings.isResetCookies();
@@ -87,6 +91,8 @@ public void sendResponse(RestResponse response) {
8791
}
8892
resp.setStatus(getStatus(response.status()));
8993

94+
NioCorsHandler.setCorsResponseHeaders(nettyRequest, resp, corsConfig);
95+
9096
String opaque = nettyRequest.headers().get("X-Opaque-Id");
9197
if (opaque != null) {
9298
setHeaderField(resp, "X-Opaque-Id", opaque);

plugins/transport-nio/src/main/java/org/elasticsearch/http/nio/NioHttpServerTransport.java

+50-2
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.http.nio;
2121

22+
import io.netty.handler.codec.http.HttpMethod;
2223
import io.netty.handler.timeout.ReadTimeoutException;
2324
import org.apache.logging.log4j.Logger;
2425
import org.apache.logging.log4j.message.ParameterizedMessage;
@@ -28,6 +29,7 @@
2829
import org.elasticsearch.action.ActionFuture;
2930
import org.elasticsearch.action.ActionListener;
3031
import org.elasticsearch.action.support.PlainActionFuture;
32+
import org.elasticsearch.common.Strings;
3133
import org.elasticsearch.common.network.NetworkAddress;
3234
import org.elasticsearch.common.network.NetworkService;
3335
import org.elasticsearch.common.settings.Setting;
@@ -38,11 +40,13 @@
3840
import org.elasticsearch.common.util.BigArrays;
3941
import org.elasticsearch.common.util.concurrent.EsExecutors;
4042
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
43+
import org.elasticsearch.http.AbstractHttpServerTransport;
4144
import org.elasticsearch.http.BindHttpException;
4245
import org.elasticsearch.http.HttpHandlingSettings;
4346
import org.elasticsearch.http.HttpServerTransport;
4447
import org.elasticsearch.http.HttpStats;
45-
import org.elasticsearch.http.AbstractHttpServerTransport;
48+
import org.elasticsearch.http.nio.cors.NioCorsConfig;
49+
import org.elasticsearch.http.nio.cors.NioCorsConfigBuilder;
4650
import org.elasticsearch.nio.AcceptingSelector;
4751
import org.elasticsearch.nio.AcceptorEventHandler;
4852
import org.elasticsearch.nio.BytesChannelContext;
@@ -56,6 +60,7 @@
5660
import org.elasticsearch.nio.SocketChannelContext;
5761
import org.elasticsearch.nio.SocketEventHandler;
5862
import org.elasticsearch.nio.SocketSelector;
63+
import org.elasticsearch.rest.RestUtils;
5964
import org.elasticsearch.threadpool.ThreadPool;
6065

6166
import java.io.IOException;
@@ -64,15 +69,23 @@
6469
import java.nio.channels.ServerSocketChannel;
6570
import java.nio.channels.SocketChannel;
6671
import java.util.ArrayList;
72+
import java.util.Arrays;
6773
import java.util.Collections;
6874
import java.util.List;
6975
import java.util.Set;
7076
import java.util.concurrent.ConcurrentHashMap;
7177
import java.util.concurrent.atomic.AtomicReference;
7278
import java.util.function.Consumer;
79+
import java.util.regex.Pattern;
7380

7481
import static org.elasticsearch.common.settings.Setting.intSetting;
7582
import static org.elasticsearch.common.util.concurrent.EsExecutors.daemonThreadFactory;
83+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_CREDENTIALS;
84+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_HEADERS;
85+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_METHODS;
86+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ALLOW_ORIGIN;
87+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_ENABLED;
88+
import static org.elasticsearch.http.HttpTransportSettings.SETTING_CORS_MAX_AGE;
7689
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_COMPRESSION;
7790
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_COMPRESSION_LEVEL;
7891
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_DETAILED_ERRORS_ENABLED;
@@ -86,6 +99,7 @@
8699
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_REUSE_ADDRESS;
87100
import static org.elasticsearch.http.HttpTransportSettings.SETTING_HTTP_TCP_SEND_BUFFER_SIZE;
88101
import static org.elasticsearch.http.HttpTransportSettings.SETTING_PIPELINING_MAX_EVENTS;
102+
import static org.elasticsearch.http.nio.cors.NioCorsHandler.ANY_ORIGIN;
89103

90104
public class NioHttpServerTransport extends AbstractHttpServerTransport {
91105

@@ -115,6 +129,7 @@ public class NioHttpServerTransport extends AbstractHttpServerTransport {
115129
private final Set<NioSocketChannel> socketChannels = Collections.newSetFromMap(new ConcurrentHashMap<>());
116130
private NioGroup nioGroup;
117131
private HttpChannelFactory channelFactory;
132+
private final NioCorsConfig corsConfig;
118133

119134
public NioHttpServerTransport(Settings settings, NetworkService networkService, BigArrays bigArrays, ThreadPool threadPool,
120135
NamedXContentRegistry xContentRegistry, HttpServerTransport.Dispatcher dispatcher) {
@@ -136,6 +151,7 @@ public NioHttpServerTransport(Settings settings, NetworkService networkService,
136151
SETTING_HTTP_COMPRESSION_LEVEL.get(settings),
137152
SETTING_HTTP_DETAILED_ERRORS_ENABLED.get(settings),
138153
pipeliningMaxEvents);
154+
this.corsConfig = buildCorsConfig(settings);
139155

140156
this.tcpNoDelay = SETTING_HTTP_TCP_NO_DELAY.get(settings);
141157
this.tcpKeepAlive = SETTING_HTTP_TCP_KEEP_ALIVE.get(settings);
@@ -279,6 +295,38 @@ protected void nonChannelExceptionCaught(Exception ex) {
279295
logger.warn(new ParameterizedMessage("exception caught on transport layer [thread={}]", Thread.currentThread().getName()), ex);
280296
}
281297

298+
static NioCorsConfig buildCorsConfig(Settings settings) {
299+
if (SETTING_CORS_ENABLED.get(settings) == false) {
300+
return NioCorsConfigBuilder.forOrigins().disable().build();
301+
}
302+
String origin = SETTING_CORS_ALLOW_ORIGIN.get(settings);
303+
final NioCorsConfigBuilder builder;
304+
if (Strings.isNullOrEmpty(origin)) {
305+
builder = NioCorsConfigBuilder.forOrigins();
306+
} else if (origin.equals(ANY_ORIGIN)) {
307+
builder = NioCorsConfigBuilder.forAnyOrigin();
308+
} else {
309+
Pattern p = RestUtils.checkCorsSettingForRegex(origin);
310+
if (p == null) {
311+
builder = NioCorsConfigBuilder.forOrigins(RestUtils.corsSettingAsArray(origin));
312+
} else {
313+
builder = NioCorsConfigBuilder.forPattern(p);
314+
}
315+
}
316+
if (SETTING_CORS_ALLOW_CREDENTIALS.get(settings)) {
317+
builder.allowCredentials();
318+
}
319+
String[] strMethods = Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_METHODS.get(settings), ",");
320+
HttpMethod[] methods = Arrays.stream(strMethods)
321+
.map(HttpMethod::valueOf)
322+
.toArray(HttpMethod[]::new);
323+
return builder.allowedRequestMethods(methods)
324+
.maxAge(SETTING_CORS_MAX_AGE.get(settings))
325+
.allowedRequestHeaders(Strings.tokenizeToStringArray(SETTING_CORS_ALLOW_HEADERS.get(settings), ","))
326+
.shortCircuit()
327+
.build();
328+
}
329+
282330
private void closeChannels(List<NioChannel> channels) {
283331
List<ActionFuture<Void>> futures = new ArrayList<>(channels.size());
284332

@@ -315,7 +363,7 @@ private HttpChannelFactory() {
315363
public NioSocketChannel createChannel(SocketSelector selector, SocketChannel channel) throws IOException {
316364
NioSocketChannel nioChannel = new NioSocketChannel(channel);
317365
HttpReadWriteHandler httpReadWritePipeline = new HttpReadWriteHandler(nioChannel,NioHttpServerTransport.this,
318-
httpHandlingSettings, xContentRegistry, threadPool.getThreadContext());
366+
httpHandlingSettings, xContentRegistry, corsConfig, threadPool.getThreadContext());
319367
Consumer<Exception> exceptionHandler = (e) -> exceptionCaught(nioChannel, e);
320368
SocketChannelContext context = new BytesChannelContext(nioChannel, selector, exceptionHandler, httpReadWritePipeline,
321369
InboundChannelBuffer.allocatingInstance());

0 commit comments

Comments
 (0)