Skip to content

Commit fcf1e41

Browse files
authored
Extract common http logic to server (#31311)
This is related to #28898. With the addition of the http nio transport, we now have two different modules that provide http transports. Currently most of the http logic lives at the module level. However, some of this logic can live in server. In particular, some of the setting of headers, cors, and pipelining. This commit begins this moving in that direction by introducing lower level abstraction (HttpChannel, HttpRequest, and HttpResonse) that is implemented by the modules. The higher level rest request and rest channel work can live entirely in server.
1 parent 6dd81ea commit fcf1e41

File tree

51 files changed

+2101
-2267
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+2101
-2267
lines changed

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

Lines changed: 32 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -19,252 +19,58 @@
1919

2020
package org.elasticsearch.http.netty4;
2121

22-
import io.netty.buffer.ByteBuf;
23-
import io.netty.buffer.Unpooled;
2422
import io.netty.channel.Channel;
25-
import io.netty.channel.ChannelFutureListener;
2623
import io.netty.channel.ChannelPromise;
27-
import io.netty.handler.codec.http.DefaultFullHttpResponse;
28-
import io.netty.handler.codec.http.FullHttpRequest;
29-
import io.netty.handler.codec.http.FullHttpResponse;
30-
import io.netty.handler.codec.http.HttpHeaderNames;
31-
import io.netty.handler.codec.http.HttpHeaderValues;
32-
import io.netty.handler.codec.http.HttpMethod;
33-
import io.netty.handler.codec.http.HttpResponse;
34-
import io.netty.handler.codec.http.HttpResponseStatus;
35-
import io.netty.handler.codec.http.HttpVersion;
36-
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
37-
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
38-
import org.elasticsearch.common.bytes.BytesReference;
39-
import org.elasticsearch.common.io.stream.BytesStreamOutput;
40-
import org.elasticsearch.common.io.stream.ReleasableBytesStreamOutput;
41-
import org.elasticsearch.common.lease.Releasable;
42-
import org.elasticsearch.common.util.concurrent.ThreadContext;
43-
import org.elasticsearch.http.HttpHandlingSettings;
44-
import org.elasticsearch.http.netty4.cors.Netty4CorsHandler;
45-
import org.elasticsearch.rest.AbstractRestChannel;
46-
import org.elasticsearch.rest.RestResponse;
47-
import org.elasticsearch.rest.RestStatus;
24+
import org.elasticsearch.action.ActionListener;
25+
import org.elasticsearch.http.HttpChannel;
26+
import org.elasticsearch.http.HttpResponse;
4827
import org.elasticsearch.transport.netty4.Netty4Utils;
4928

50-
import java.util.Collections;
51-
import java.util.EnumMap;
52-
import java.util.List;
53-
import java.util.Map;
54-
import java.util.Set;
29+
import java.net.InetSocketAddress;
5530

56-
final class Netty4HttpChannel extends AbstractRestChannel {
31+
public class Netty4HttpChannel implements HttpChannel {
5732

58-
private final Netty4HttpServerTransport transport;
5933
private final Channel channel;
60-
private final FullHttpRequest nettyRequest;
61-
private final int sequence;
62-
private final ThreadContext threadContext;
63-
private final HttpHandlingSettings handlingSettings;
6434

65-
/**
66-
* @param transport The corresponding <code>NettyHttpServerTransport</code> where this channel belongs to.
67-
* @param request The request that is handled by this channel.
68-
* @param sequence The pipelining sequence number for this request
69-
* @param handlingSettings true if error messages should include stack traces.
70-
* @param threadContext the thread context for the channel
71-
*/
72-
Netty4HttpChannel(Netty4HttpServerTransport transport, Netty4HttpRequest request, int sequence, HttpHandlingSettings handlingSettings,
73-
ThreadContext threadContext) {
74-
super(request, handlingSettings.getDetailedErrorsEnabled());
75-
this.transport = transport;
76-
this.channel = request.getChannel();
77-
this.nettyRequest = request.request();
78-
this.sequence = sequence;
79-
this.threadContext = threadContext;
80-
this.handlingSettings = handlingSettings;
35+
Netty4HttpChannel(Channel channel) {
36+
this.channel = channel;
8137
}
8238

8339
@Override
84-
protected BytesStreamOutput newBytesOutput() {
85-
return new ReleasableBytesStreamOutput(transport.bigArrays);
86-
}
87-
88-
@Override
89-
public void sendResponse(RestResponse response) {
90-
// if the response object was created upstream, then use it;
91-
// otherwise, create a new one
92-
ByteBuf buffer = Netty4Utils.toByteBuf(response.content());
93-
final FullHttpResponse resp;
94-
if (HttpMethod.HEAD.equals(nettyRequest.method())) {
95-
resp = newResponse(Unpooled.EMPTY_BUFFER);
96-
} else {
97-
resp = newResponse(buffer);
98-
}
99-
resp.setStatus(getStatus(response.status()));
100-
101-
Netty4CorsHandler.setCorsResponseHeaders(nettyRequest, resp, transport.getCorsConfig());
102-
103-
String opaque = nettyRequest.headers().get("X-Opaque-Id");
104-
if (opaque != null) {
105-
setHeaderField(resp, "X-Opaque-Id", opaque);
106-
}
107-
108-
// Add all custom headers
109-
addCustomHeaders(resp, response.getHeaders());
110-
addCustomHeaders(resp, threadContext.getResponseHeaders());
111-
112-
BytesReference content = response.content();
113-
boolean releaseContent = content instanceof Releasable;
114-
boolean releaseBytesStreamOutput = bytesOutputOrNull() instanceof ReleasableBytesStreamOutput;
115-
try {
116-
// If our response doesn't specify a content-type header, set one
117-
setHeaderField(resp, HttpHeaderNames.CONTENT_TYPE.toString(), response.contentType(), false);
118-
// If our response has no content-length, calculate and set one
119-
setHeaderField(resp, HttpHeaderNames.CONTENT_LENGTH.toString(), String.valueOf(buffer.readableBytes()), false);
120-
121-
addCookies(resp);
122-
123-
final ChannelPromise promise = channel.newPromise();
124-
125-
if (releaseContent) {
126-
promise.addListener(f -> ((Releasable) content).close());
127-
}
128-
129-
if (releaseBytesStreamOutput) {
130-
promise.addListener(f -> bytesOutputOrNull().close());
131-
}
132-
133-
if (isCloseConnection()) {
134-
promise.addListener(ChannelFutureListener.CLOSE);
135-
}
136-
137-
Netty4HttpResponse newResponse = new Netty4HttpResponse(sequence, resp);
138-
139-
channel.writeAndFlush(newResponse, promise);
140-
releaseContent = false;
141-
releaseBytesStreamOutput = false;
142-
} finally {
143-
if (releaseContent) {
144-
((Releasable) content).close();
145-
}
146-
if (releaseBytesStreamOutput) {
147-
bytesOutputOrNull().close();
148-
}
149-
}
150-
}
151-
152-
private void setHeaderField(HttpResponse resp, String headerField, String value) {
153-
setHeaderField(resp, headerField, value, true);
154-
}
155-
156-
private void setHeaderField(HttpResponse resp, String headerField, String value, boolean override) {
157-
if (override || !resp.headers().contains(headerField)) {
158-
resp.headers().add(headerField, value);
159-
}
160-
}
161-
162-
private void addCookies(HttpResponse resp) {
163-
if (handlingSettings.isResetCookies()) {
164-
String cookieString = nettyRequest.headers().get(HttpHeaderNames.COOKIE);
165-
if (cookieString != null) {
166-
Set<io.netty.handler.codec.http.cookie.Cookie> cookies = ServerCookieDecoder.STRICT.decode(cookieString);
167-
if (!cookies.isEmpty()) {
168-
// Reset the cookies if necessary.
169-
resp.headers().set(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.STRICT.encode(cookies));
170-
}
171-
}
172-
}
173-
}
174-
175-
private void addCustomHeaders(HttpResponse response, Map<String, List<String>> customHeaders) {
176-
if (customHeaders != null) {
177-
for (Map.Entry<String, List<String>> headerEntry : customHeaders.entrySet()) {
178-
for (String headerValue : headerEntry.getValue()) {
179-
setHeaderField(response, headerEntry.getKey(), headerValue);
40+
public void sendResponse(HttpResponse response, ActionListener<Void> listener) {
41+
ChannelPromise writePromise = channel.newPromise();
42+
writePromise.addListener(f -> {
43+
if (f.isSuccess()) {
44+
listener.onResponse(null);
45+
} else {
46+
final Throwable cause = f.cause();
47+
Netty4Utils.maybeDie(cause);
48+
if (cause instanceof Error) {
49+
listener.onFailure(new Exception(cause));
50+
} else {
51+
listener.onFailure((Exception) cause);
18052
}
18153
}
182-
}
54+
});
55+
channel.writeAndFlush(response, writePromise);
18356
}
18457

185-
// Determine if the request protocol version is HTTP 1.0
186-
private boolean isHttp10() {
187-
return nettyRequest.protocolVersion().equals(HttpVersion.HTTP_1_0);
188-
}
189-
190-
// Determine if the request connection should be closed on completion.
191-
private boolean isCloseConnection() {
192-
final boolean http10 = isHttp10();
193-
return HttpHeaderValues.CLOSE.contentEqualsIgnoreCase(nettyRequest.headers().get(HttpHeaderNames.CONNECTION)) ||
194-
(http10 && !HttpHeaderValues.KEEP_ALIVE.contentEqualsIgnoreCase(nettyRequest.headers().get(HttpHeaderNames.CONNECTION)));
58+
@Override
59+
public InetSocketAddress getLocalAddress() {
60+
return (InetSocketAddress) channel.localAddress();
19561
}
19662

197-
// Create a new {@link HttpResponse} to transmit the response for the netty request.
198-
private FullHttpResponse newResponse(ByteBuf buffer) {
199-
final boolean http10 = isHttp10();
200-
final boolean close = isCloseConnection();
201-
// Build the response object.
202-
final HttpResponseStatus status = HttpResponseStatus.OK; // default to initialize
203-
final FullHttpResponse response;
204-
if (http10) {
205-
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_0, status, buffer);
206-
if (!close) {
207-
response.headers().add(HttpHeaderNames.CONNECTION, "Keep-Alive");
208-
}
209-
} else {
210-
response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buffer);
211-
}
212-
return response;
63+
@Override
64+
public InetSocketAddress getRemoteAddress() {
65+
return (InetSocketAddress) channel.remoteAddress();
21366
}
21467

215-
private static Map<RestStatus, HttpResponseStatus> MAP;
216-
217-
static {
218-
EnumMap<RestStatus, HttpResponseStatus> map = new EnumMap<>(RestStatus.class);
219-
map.put(RestStatus.CONTINUE, HttpResponseStatus.CONTINUE);
220-
map.put(RestStatus.SWITCHING_PROTOCOLS, HttpResponseStatus.SWITCHING_PROTOCOLS);
221-
map.put(RestStatus.OK, HttpResponseStatus.OK);
222-
map.put(RestStatus.CREATED, HttpResponseStatus.CREATED);
223-
map.put(RestStatus.ACCEPTED, HttpResponseStatus.ACCEPTED);
224-
map.put(RestStatus.NON_AUTHORITATIVE_INFORMATION, HttpResponseStatus.NON_AUTHORITATIVE_INFORMATION);
225-
map.put(RestStatus.NO_CONTENT, HttpResponseStatus.NO_CONTENT);
226-
map.put(RestStatus.RESET_CONTENT, HttpResponseStatus.RESET_CONTENT);
227-
map.put(RestStatus.PARTIAL_CONTENT, HttpResponseStatus.PARTIAL_CONTENT);
228-
map.put(RestStatus.MULTI_STATUS, HttpResponseStatus.INTERNAL_SERVER_ERROR); // no status for this??
229-
map.put(RestStatus.MULTIPLE_CHOICES, HttpResponseStatus.MULTIPLE_CHOICES);
230-
map.put(RestStatus.MOVED_PERMANENTLY, HttpResponseStatus.MOVED_PERMANENTLY);
231-
map.put(RestStatus.FOUND, HttpResponseStatus.FOUND);
232-
map.put(RestStatus.SEE_OTHER, HttpResponseStatus.SEE_OTHER);
233-
map.put(RestStatus.NOT_MODIFIED, HttpResponseStatus.NOT_MODIFIED);
234-
map.put(RestStatus.USE_PROXY, HttpResponseStatus.USE_PROXY);
235-
map.put(RestStatus.TEMPORARY_REDIRECT, HttpResponseStatus.TEMPORARY_REDIRECT);
236-
map.put(RestStatus.BAD_REQUEST, HttpResponseStatus.BAD_REQUEST);
237-
map.put(RestStatus.UNAUTHORIZED, HttpResponseStatus.UNAUTHORIZED);
238-
map.put(RestStatus.PAYMENT_REQUIRED, HttpResponseStatus.PAYMENT_REQUIRED);
239-
map.put(RestStatus.FORBIDDEN, HttpResponseStatus.FORBIDDEN);
240-
map.put(RestStatus.NOT_FOUND, HttpResponseStatus.NOT_FOUND);
241-
map.put(RestStatus.METHOD_NOT_ALLOWED, HttpResponseStatus.METHOD_NOT_ALLOWED);
242-
map.put(RestStatus.NOT_ACCEPTABLE, HttpResponseStatus.NOT_ACCEPTABLE);
243-
map.put(RestStatus.PROXY_AUTHENTICATION, HttpResponseStatus.PROXY_AUTHENTICATION_REQUIRED);
244-
map.put(RestStatus.REQUEST_TIMEOUT, HttpResponseStatus.REQUEST_TIMEOUT);
245-
map.put(RestStatus.CONFLICT, HttpResponseStatus.CONFLICT);
246-
map.put(RestStatus.GONE, HttpResponseStatus.GONE);
247-
map.put(RestStatus.LENGTH_REQUIRED, HttpResponseStatus.LENGTH_REQUIRED);
248-
map.put(RestStatus.PRECONDITION_FAILED, HttpResponseStatus.PRECONDITION_FAILED);
249-
map.put(RestStatus.REQUEST_ENTITY_TOO_LARGE, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE);
250-
map.put(RestStatus.REQUEST_URI_TOO_LONG, HttpResponseStatus.REQUEST_URI_TOO_LONG);
251-
map.put(RestStatus.UNSUPPORTED_MEDIA_TYPE, HttpResponseStatus.UNSUPPORTED_MEDIA_TYPE);
252-
map.put(RestStatus.REQUESTED_RANGE_NOT_SATISFIED, HttpResponseStatus.REQUESTED_RANGE_NOT_SATISFIABLE);
253-
map.put(RestStatus.EXPECTATION_FAILED, HttpResponseStatus.EXPECTATION_FAILED);
254-
map.put(RestStatus.UNPROCESSABLE_ENTITY, HttpResponseStatus.BAD_REQUEST);
255-
map.put(RestStatus.LOCKED, HttpResponseStatus.BAD_REQUEST);
256-
map.put(RestStatus.FAILED_DEPENDENCY, HttpResponseStatus.BAD_REQUEST);
257-
map.put(RestStatus.TOO_MANY_REQUESTS, HttpResponseStatus.TOO_MANY_REQUESTS);
258-
map.put(RestStatus.INTERNAL_SERVER_ERROR, HttpResponseStatus.INTERNAL_SERVER_ERROR);
259-
map.put(RestStatus.NOT_IMPLEMENTED, HttpResponseStatus.NOT_IMPLEMENTED);
260-
map.put(RestStatus.BAD_GATEWAY, HttpResponseStatus.BAD_GATEWAY);
261-
map.put(RestStatus.SERVICE_UNAVAILABLE, HttpResponseStatus.SERVICE_UNAVAILABLE);
262-
map.put(RestStatus.GATEWAY_TIMEOUT, HttpResponseStatus.GATEWAY_TIMEOUT);
263-
map.put(RestStatus.HTTP_VERSION_NOT_SUPPORTED, HttpResponseStatus.HTTP_VERSION_NOT_SUPPORTED);
264-
MAP = Collections.unmodifiableMap(map);
68+
@Override
69+
public void close() {
70+
channel.close();
26571
}
26672

267-
private static HttpResponseStatus getStatus(RestStatus status) {
268-
return MAP.getOrDefault(status, HttpResponseStatus.INTERNAL_SERVER_ERROR);
73+
public Channel getNettyChannel() {
74+
return channel;
26975
}
27076
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ public void write(final ChannelHandlerContext ctx, final Object msg, final Chann
6666
try {
6767
List<Tuple<Netty4HttpResponse, ChannelPromise>> readyResponses = aggregator.write(response, promise);
6868
for (Tuple<Netty4HttpResponse, ChannelPromise> readyResponse : readyResponses) {
69-
ctx.write(readyResponse.v1().getResponse(), readyResponse.v2());
69+
ctx.write(readyResponse.v1(), readyResponse.v2());
7070
}
7171
success = true;
7272
} catch (IllegalStateException e) {

0 commit comments

Comments
 (0)