|
19 | 19 |
|
20 | 20 | package org.elasticsearch.http.netty4;
|
21 | 21 |
|
22 |
| -import io.netty.buffer.ByteBuf; |
23 |
| -import io.netty.buffer.Unpooled; |
24 | 22 | import io.netty.channel.Channel;
|
25 |
| -import io.netty.channel.ChannelFutureListener; |
26 | 23 | 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; |
48 | 27 | import org.elasticsearch.transport.netty4.Netty4Utils;
|
49 | 28 |
|
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; |
55 | 30 |
|
56 |
| -final class Netty4HttpChannel extends AbstractRestChannel { |
| 31 | +public class Netty4HttpChannel implements HttpChannel { |
57 | 32 |
|
58 |
| - private final Netty4HttpServerTransport transport; |
59 | 33 | private final Channel channel;
|
60 |
| - private final FullHttpRequest nettyRequest; |
61 |
| - private final int sequence; |
62 |
| - private final ThreadContext threadContext; |
63 |
| - private final HttpHandlingSettings handlingSettings; |
64 | 34 |
|
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; |
81 | 37 | }
|
82 | 38 |
|
83 | 39 | @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); |
180 | 52 | }
|
181 | 53 | }
|
182 |
| - } |
| 54 | + }); |
| 55 | + channel.writeAndFlush(response, writePromise); |
183 | 56 | }
|
184 | 57 |
|
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(); |
195 | 61 | }
|
196 | 62 |
|
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(); |
213 | 66 | }
|
214 | 67 |
|
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(); |
265 | 71 | }
|
266 | 72 |
|
267 |
| - private static HttpResponseStatus getStatus(RestStatus status) { |
268 |
| - return MAP.getOrDefault(status, HttpResponseStatus.INTERNAL_SERVER_ERROR); |
| 73 | + public Channel getNettyChannel() { |
| 74 | + return channel; |
269 | 75 | }
|
270 | 76 | }
|
0 commit comments