2 * Copyright (c) 2024 PANTHEON.tech s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netconf.transport.http;
10 import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
11 import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH;
12 import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE;
13 import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN;
14 import static io.netty.handler.codec.http.HttpResponseStatus.INTERNAL_SERVER_ERROR;
15 import static org.opendaylight.netconf.transport.http.Http2Utils.copyStreamId;
17 import com.google.common.util.concurrent.FutureCallback;
18 import com.google.common.util.concurrent.Futures;
19 import com.google.common.util.concurrent.ListenableFuture;
20 import com.google.common.util.concurrent.MoreExecutors;
21 import com.google.common.util.concurrent.SettableFuture;
22 import io.netty.buffer.Unpooled;
23 import io.netty.channel.Channel;
24 import io.netty.channel.ChannelHandler;
25 import io.netty.channel.ChannelHandlerContext;
26 import io.netty.channel.ChannelInitializer;
27 import io.netty.channel.ChannelPipeline;
28 import io.netty.channel.SimpleChannelInboundHandler;
29 import io.netty.handler.codec.http.DefaultFullHttpResponse;
30 import io.netty.handler.codec.http.FullHttpRequest;
31 import io.netty.handler.codec.http.FullHttpResponse;
32 import io.netty.handler.codec.http.HttpMessage;
33 import io.netty.handler.codec.http.HttpObjectAggregator;
34 import io.netty.handler.codec.http.HttpServerCodec;
35 import io.netty.handler.codec.http.HttpServerKeepAliveHandler;
36 import io.netty.handler.codec.http.HttpServerUpgradeHandler;
37 import io.netty.handler.codec.http2.CleartextHttp2ServerUpgradeHandler;
38 import io.netty.handler.codec.http2.Http2CodecUtil;
39 import io.netty.handler.codec.http2.Http2ConnectionHandler;
40 import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
41 import io.netty.handler.ssl.ApplicationProtocolNames;
42 import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
43 import io.netty.handler.ssl.SslHandler;
44 import io.netty.util.AsciiString;
45 import io.netty.util.ReferenceCountUtil;
46 import java.nio.charset.StandardCharsets;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.http.server.rev240208.HttpServerGrouping;
50 * Netty channel initializer for Http Server.
52 class ServerChannelInitializer extends ChannelInitializer<Channel> implements HttpChannelInitializer {
53 private static final int MAX_HTTP_CONTENT_LENGTH = 16 * 1024;
55 private final SettableFuture<Void> completeFuture = SettableFuture.create();
56 private final ChannelHandler authHandler;
57 private final RequestDispatcher dispatcher;
59 ServerChannelInitializer(final HttpServerGrouping httpParams, final RequestDispatcher dispatcher) {
61 authHandler = BasicAuthHandler.ofNullable(httpParams);
62 this.dispatcher = dispatcher;
66 public ListenableFuture<Void> completeFuture() {
67 return completeFuture;
71 protected void initChannel(final Channel channel) throws Exception {
72 final var pipeline = channel.pipeline();
73 final var ssl = pipeline.get(SslHandler.class) != null;
75 // External HTTP 2 to internal HTTP 1.1 adapter handler
76 final var connectionHandler = Http2Utils.connectionHandler(true, MAX_HTTP_CONTENT_LENGTH);
78 // Application protocol negotiator over TLS
79 pipeline.addLast(apnHandler(connectionHandler));
81 // Cleartext upgrade flow
82 final var sourceCodec = new HttpServerCodec();
83 final var upgradeHandler =
84 new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory(connectionHandler));
85 pipeline.addLast(new CleartextHttp2ServerUpgradeHandler(sourceCodec, upgradeHandler, connectionHandler),
86 upgradeResultHandler());
89 // signal server transport is ready to accept requests
90 completeFuture.set(null);
93 private void configureEndOfPipeline(final ChannelPipeline pipeline) {
94 if (authHandler != null) {
95 pipeline.addLast(authHandler);
97 pipeline.addLast(serverHandler(dispatcher));
100 private ChannelHandler apnHandler(final ChannelHandler connectionHandler) {
101 return new ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_1_1) {
103 protected void configurePipeline(final ChannelHandlerContext ctx, final String protocol) throws Exception {
104 final var pipeline = ctx.pipeline();
105 if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
106 pipeline.addLast(connectionHandler);
107 configureEndOfPipeline(pipeline);
110 if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
111 pipeline.addLast(new HttpServerCodec(),
112 new HttpServerKeepAliveHandler(),
113 new HttpObjectAggregator(MAX_HTTP_CONTENT_LENGTH));
114 configureEndOfPipeline(pipeline);
117 throw new IllegalStateException("unknown protocol: " + protocol);
122 private ChannelHandler upgradeResultHandler() {
123 // the handler processes cleartext upgrade result
125 return new SimpleChannelInboundHandler<HttpMessage>() {
127 protected void channelRead0(final ChannelHandlerContext ctx, final HttpMessage request) throws Exception {
128 // if there was no upgrade to HTTP/2 the incoming message is accepted via channel read;
129 // configure HTTP 1.1 flow, pass the message further the pipeline, remove self as no longer required
130 final var pipeline = ctx.pipeline();
131 pipeline.addLast(new HttpServerKeepAliveHandler(), new HttpObjectAggregator(MAX_HTTP_CONTENT_LENGTH));
132 configureEndOfPipeline(pipeline);
133 ctx.fireChannelRead(ReferenceCountUtil.retain(request));
134 pipeline.remove(this);
138 public void userEventTriggered(final ChannelHandlerContext ctx, final Object event) throws Exception {
139 // if there was upgrade to HTTP/2 the upgrade event is fired further the pipeline;
140 // on event occurrence it's only required to complete the configuration for future requests,
141 // then remove self as no longer required
142 if (event instanceof HttpServerUpgradeHandler.UpgradeEvent) {
143 final var pipeline = ctx.pipeline();
144 configureEndOfPipeline(pipeline);
145 pipeline.remove(this);
151 private static HttpServerUpgradeHandler.UpgradeCodecFactory upgradeCodecFactory(
152 final Http2ConnectionHandler connectionHandler) {
153 return protocol -> AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)
154 ? new Http2ServerUpgradeCodec(connectionHandler) : null;
157 private static ChannelHandler serverHandler(final RequestDispatcher dispatcher) {
158 return new SimpleChannelInboundHandler<FullHttpRequest>() {
160 protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest request) {
161 Futures.addCallback(dispatcher.dispatch(request.retain()), new FutureCallback<>() {
163 public void onSuccess(final FullHttpResponse response) {
164 copyStreamId(request, response);
166 ctx.writeAndFlush(response);
170 public void onFailure(final Throwable throwable) {
171 final var message = throwable.getMessage();
172 final var content = message == null ? EMPTY_BUFFER
173 : Unpooled.wrappedBuffer(message.getBytes(StandardCharsets.UTF_8));
174 final var response = new DefaultFullHttpResponse(request.protocolVersion(),
175 INTERNAL_SERVER_ERROR, content);
177 .set(CONTENT_TYPE, TEXT_PLAIN)
178 .setInt(CONTENT_LENGTH, response.content().readableBytes());
181 }, MoreExecutors.directExecutor());