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.handler.codec.http2.HttpConversionUtil.ExtensionHeaderNames.SCHEME;
11 import static io.netty.handler.codec.http2.HttpConversionUtil.ExtensionHeaderNames.STREAM_ID;
13 import com.google.common.util.concurrent.FutureCallback;
14 import io.netty.channel.Channel;
15 import io.netty.channel.ChannelHandlerContext;
16 import io.netty.handler.codec.http.FullHttpRequest;
17 import io.netty.handler.codec.http.FullHttpResponse;
18 import io.netty.handler.codec.http.HttpScheme;
19 import io.netty.handler.ssl.SslHandler;
21 import java.util.concurrent.ConcurrentHashMap;
22 import java.util.concurrent.atomic.AtomicInteger;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
27 * Client side {@link RequestDispatcher} implementation for HTTP 2.
30 * Serves as gateway to Netty {@link Channel}, performs sending requests to server, returns server responses associated.
31 * Uses request to response mapping by stream identifier.
33 final class ClientHttp2RequestDispatcher extends ClientRequestDispatcher {
34 private static final Logger LOG = LoggerFactory.getLogger(ClientHttp2RequestDispatcher.class);
36 // TODO: we access the queue only from Netty callbacks: can we use a plain HashMap?
37 private final Map<Integer, FutureCallback<FullHttpResponse>> map = new ConcurrentHashMap<>();
38 // identifier for streams initiated from client require to be odd-numbered, 1 is reserved
39 // see https://datatracker.ietf.org/doc/html/rfc7540#section-5.1.1
40 private final AtomicInteger streamIdCounter = new AtomicInteger(3);
42 private boolean ssl = false;
45 public void handlerAdded(final ChannelHandlerContext ctx) throws Exception {
46 ssl = ctx.pipeline().get(SslHandler.class) != null;
47 super.handlerAdded(ctx);
51 public void dispatch(final Channel channel, final FullHttpRequest request,
52 final FutureCallback<FullHttpResponse> callback) {
53 final var streamId = streamIdCounter.getAndAdd(2);
55 .setInt(STREAM_ID.text(), streamId)
56 .set(SCHEME.text(), ssl ? HttpScheme.HTTPS.name() : HttpScheme.HTTP.name());
58 channel.writeAndFlush(request).addListener(sent -> {
59 final var cause = sent.cause();
61 map.put(streamId, callback);
63 callback.onFailure(cause);
69 protected void channelRead0(final ChannelHandlerContext ctx, final FullHttpResponse response) {
70 final var streamId = response.headers().getInt(STREAM_ID.text());
71 if (streamId == null) {
72 LOG.warn("Unexpected response with no stream ID -- Dropping response object {}", response);
75 final var callback = map.remove(streamId);
76 if (callback != null) {
77 callback.onSuccess(response);
79 LOG.warn("Unexpected response with unknown or expired stream ID {} -- Dropping response object {}",