Support auth* service integration for HttpTransport
[netconf.git] / transport / transport-http / src / main / java / org / opendaylight / netconf / transport / http / ServerChannelInitializer.java
1 /*
2  * Copyright (c) 2024 PANTHEON.tech s.r.o. and others. All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netconf.transport.http;
9
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;
16
17 import com.google.common.util.concurrent.FutureCallback;
18 import com.google.common.util.concurrent.ListenableFuture;
19 import com.google.common.util.concurrent.SettableFuture;
20 import io.netty.buffer.Unpooled;
21 import io.netty.channel.Channel;
22 import io.netty.channel.ChannelHandler;
23 import io.netty.channel.ChannelHandlerContext;
24 import io.netty.channel.ChannelInitializer;
25 import io.netty.channel.ChannelPipeline;
26 import io.netty.channel.SimpleChannelInboundHandler;
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.HttpMessage;
31 import io.netty.handler.codec.http.HttpObjectAggregator;
32 import io.netty.handler.codec.http.HttpServerCodec;
33 import io.netty.handler.codec.http.HttpServerKeepAliveHandler;
34 import io.netty.handler.codec.http.HttpServerUpgradeHandler;
35 import io.netty.handler.codec.http2.CleartextHttp2ServerUpgradeHandler;
36 import io.netty.handler.codec.http2.Http2CodecUtil;
37 import io.netty.handler.codec.http2.Http2ConnectionHandler;
38 import io.netty.handler.codec.http2.Http2ServerUpgradeCodec;
39 import io.netty.handler.ssl.ApplicationProtocolNames;
40 import io.netty.handler.ssl.ApplicationProtocolNegotiationHandler;
41 import io.netty.handler.ssl.SslHandler;
42 import io.netty.util.AsciiString;
43 import io.netty.util.ReferenceCountUtil;
44 import java.nio.charset.StandardCharsets;
45 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.http.server.rev240208.HttpServerGrouping;
46
47 /**
48  * Netty channel initializer for Http Server.
49  */
50 class ServerChannelInitializer extends ChannelInitializer<Channel> implements HttpChannelInitializer {
51     private static final int MAX_HTTP_CONTENT_LENGTH = 16 * 1024;
52
53     private final SettableFuture<Void> completeFuture = SettableFuture.create();
54     private final AuthHandlerFactory authHandlerFactory;
55     private final RequestDispatcher dispatcher;
56
57     ServerChannelInitializer(final AuthHandlerFactory authHandlerFactory, final RequestDispatcher dispatcher) {
58         this.authHandlerFactory = authHandlerFactory;
59         this.dispatcher = dispatcher;
60     }
61
62     ServerChannelInitializer(final HttpServerGrouping httpParams, final RequestDispatcher dispatcher) {
63         authHandlerFactory = BasicAuthHandlerFactory.ofNullable(httpParams);
64         this.dispatcher = dispatcher;
65     }
66
67     @Override
68     public ListenableFuture<Void> completeFuture() {
69         return completeFuture;
70     }
71
72     @Override
73     protected void initChannel(final Channel channel) throws Exception {
74         final var pipeline = channel.pipeline();
75         final var ssl = pipeline.get(SslHandler.class) != null;
76
77         // External HTTP 2 to internal HTTP 1.1 adapter handler
78         final var connectionHandler = Http2Utils.connectionHandler(true, MAX_HTTP_CONTENT_LENGTH);
79         if (ssl) {
80             // Application protocol negotiator over TLS
81             pipeline.addLast(apnHandler(connectionHandler));
82         } else {
83             // Cleartext upgrade flow
84             final var sourceCodec = new HttpServerCodec();
85             final var upgradeHandler =
86                 new HttpServerUpgradeHandler(sourceCodec, upgradeCodecFactory(connectionHandler));
87             pipeline.addLast(new CleartextHttp2ServerUpgradeHandler(sourceCodec, upgradeHandler, connectionHandler),
88                 upgradeResultHandler());
89         }
90
91         // signal server transport is ready to accept requests
92         completeFuture.set(null);
93     }
94
95     private void configureEndOfPipeline(final ChannelPipeline pipeline) {
96         if (authHandlerFactory != null) {
97             pipeline.addLast(authHandlerFactory.create());
98         }
99         pipeline.addLast(serverHandler(dispatcher));
100     }
101
102     private ChannelHandler apnHandler(final ChannelHandler connectionHandler) {
103         return new ApplicationProtocolNegotiationHandler(ApplicationProtocolNames.HTTP_1_1) {
104             @Override
105             protected void configurePipeline(final ChannelHandlerContext ctx, final String protocol) throws Exception {
106                 final var pipeline = ctx.pipeline();
107                 if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {
108                     pipeline.addLast(connectionHandler);
109                     configureEndOfPipeline(pipeline);
110                     return;
111                 }
112                 if (ApplicationProtocolNames.HTTP_1_1.equals(protocol)) {
113                     pipeline.addLast(new HttpServerCodec(),
114                         new HttpServerKeepAliveHandler(),
115                         new HttpObjectAggregator(MAX_HTTP_CONTENT_LENGTH));
116                     configureEndOfPipeline(pipeline);
117                     return;
118                 }
119                 throw new IllegalStateException("unknown protocol: " + protocol);
120             }
121         };
122     }
123
124     private ChannelHandler upgradeResultHandler() {
125         // the handler processes cleartext upgrade result
126
127         return new SimpleChannelInboundHandler<HttpMessage>() {
128             @Override
129             protected void channelRead0(final ChannelHandlerContext ctx, final HttpMessage request) throws Exception {
130                 // if there was no upgrade to HTTP/2 the incoming message is accepted via channel read;
131                 // configure HTTP 1.1 flow, pass the message further the pipeline, remove self as no longer required
132                 final var pipeline = ctx.pipeline();
133                 pipeline.addLast(new HttpServerKeepAliveHandler(), new HttpObjectAggregator(MAX_HTTP_CONTENT_LENGTH));
134                 configureEndOfPipeline(pipeline);
135                 ctx.fireChannelRead(ReferenceCountUtil.retain(request));
136                 pipeline.remove(this);
137             }
138
139             @Override
140             public void userEventTriggered(final ChannelHandlerContext ctx, final Object event) throws Exception {
141                 // if there was upgrade to HTTP/2 the upgrade event is fired further the pipeline;
142                 // on event occurrence it's only required to complete the configuration for future requests,
143                 // then remove self as no longer required
144                 if (event instanceof HttpServerUpgradeHandler.UpgradeEvent) {
145                     final var pipeline = ctx.pipeline();
146                     configureEndOfPipeline(pipeline);
147                     pipeline.remove(this);
148                 }
149             }
150         };
151     }
152
153     private static HttpServerUpgradeHandler.UpgradeCodecFactory upgradeCodecFactory(
154             final Http2ConnectionHandler connectionHandler) {
155         return protocol -> AsciiString.contentEquals(Http2CodecUtil.HTTP_UPGRADE_PROTOCOL_NAME, protocol)
156             ? new Http2ServerUpgradeCodec(connectionHandler) : null;
157     }
158
159     private static ChannelHandler serverHandler(final RequestDispatcher dispatcher) {
160         return new SimpleChannelInboundHandler<FullHttpRequest>() {
161             @Override
162             protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpRequest request) {
163                 dispatcher.dispatch(request.retain(), new FutureCallback<>() {
164                     @Override
165                     public void onSuccess(final FullHttpResponse response) {
166                         copyStreamId(request, response);
167                         request.release();
168                         ctx.writeAndFlush(response);
169                     }
170
171                     @Override
172                     public void onFailure(final Throwable throwable) {
173                         final var message = throwable.getMessage();
174                         final var content = message == null ? EMPTY_BUFFER
175                             : Unpooled.wrappedBuffer(message.getBytes(StandardCharsets.UTF_8));
176                         final var response = new DefaultFullHttpResponse(request.protocolVersion(),
177                             INTERNAL_SERVER_ERROR, content);
178                         response.headers()
179                             .set(CONTENT_TYPE, TEXT_PLAIN)
180                             .setInt(CONTENT_LENGTH, response.content().readableBytes());
181                         onSuccess(response);
182                     }
183                 });
184             }
185         };
186     }
187 }