/* * Copyright 2016 The Netty Project * * The Netty Project licenses this file to you under the Apache License, * version 2.0 (the "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ package io.netty.handler.codec.http2; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandler.Sharable; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelOption; import io.netty.channel.ChannelPipeline; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.util.AttributeKey; import io.netty.util.internal.UnstableApi; import java.util.LinkedHashMap; import java.util.Map; import static io.netty.util.internal.ObjectUtil.checkNotNull; import static java.util.Collections.synchronizedMap; import static java.util.Collections.unmodifiableMap; /** * A class that makes it easy to bootstrap a new HTTP/2 stream as a {@link Channel}. * * <p>The bootstrap requires a registered parent {@link Channel} with a {@link ChannelPipeline} that contains the * {@link Http2MultiplexCodec}. * * <p>A child channel becomes active as soon as it is registered to an eventloop. Therefore, an active channel does not * map to an active HTTP/2 stream immediately. Only once a {@link Http2HeadersFrame} has been sent or received, does * the channel map to an active HTTP/2 stream. In case it was not possible to open a new HTTP/2 stream (i.e. due to * the maximum number of active streams being exceeded), the child channel receives an exception indicating the reason * and is closed immediately thereafter. * * <p>This class is thread-safe. */ // TODO(buchgr): Should we deliver a user event when the stream becomes active? For all stream states? @UnstableApi public class Http2StreamChannelBootstrap { private volatile ParentChannelAndMultiplexCodec channelAndCodec; private volatile ChannelHandler handler; private volatile EventLoopGroup group; private final Map<ChannelOption<?>, Object> options; private final Map<AttributeKey<?>, Object> attributes; public Http2StreamChannelBootstrap() { options = synchronizedMap(new LinkedHashMap<ChannelOption<?>, Object>()); attributes = synchronizedMap(new LinkedHashMap<AttributeKey<?>, Object>()); } // Copy constructor Http2StreamChannelBootstrap(Http2StreamChannelBootstrap bootstrap0) { checkNotNull(bootstrap0, "bootstrap must not be null"); channelAndCodec = bootstrap0.channelAndCodec; handler = bootstrap0.handler; group = bootstrap0.group; options = synchronizedMap(new LinkedHashMap<ChannelOption<?>, Object>(bootstrap0.options)); attributes = synchronizedMap(new LinkedHashMap<AttributeKey<?>, Object>(bootstrap0.attributes)); } /** * Creates a new channel that will eventually map to a local/outbound HTTP/2 stream. */ public ChannelFuture connect() { return connect(-1); } /** * Used by the {@link Http2MultiplexCodec} to instantiate incoming/remotely-created streams. */ ChannelFuture connect(int streamId) { validateState(); ParentChannelAndMultiplexCodec channelAndCodec0 = channelAndCodec; Channel parentChannel = channelAndCodec0.parentChannel; Http2MultiplexCodec multiplexCodec = channelAndCodec0.multiplexCodec; EventLoopGroup group0 = group; group0 = group0 == null ? parentChannel.eventLoop() : group0; return multiplexCodec.createStreamChannel(parentChannel, group0, handler, options, attributes, streamId); } /** * Sets the parent channel that must have the {@link Http2MultiplexCodec} in its pipeline. * * @param parent a registered channel with the {@link Http2MultiplexCodec} in its pipeline. This channel will * be the {@link Channel#parent()} of all channels created via {@link #connect()}. * @return {@code this} */ public Http2StreamChannelBootstrap parentChannel(Channel parent) { channelAndCodec = new ParentChannelAndMultiplexCodec(parent); return this; } /** * Sets the channel handler that should be added to the channels's pipeline. * * @param handler the channel handler to add to the channel's pipeline. The handler must be * {@link Sharable}. * @return {@code this} */ public Http2StreamChannelBootstrap handler(ChannelHandler handler) { this.handler = checkSharable(checkNotNull(handler, "handler")); return this; } /** * Sets the {@link EventLoop} to which channels created with this bootstrap are registered. * * @param group the eventloop or {@code null} if the eventloop of the parent channel should be used. * @return {@code this} */ public Http2StreamChannelBootstrap group(EventLoopGroup group) { this.group = group; return this; } /** * Specify {@link ChannelOption}s to be set on newly created channels. An option can be removed by specifying a * value of {@code null}. */ public <T> Http2StreamChannelBootstrap option(ChannelOption<T> option, T value) { checkNotNull(option, "option must not be null"); if (value == null) { options.remove(option); } else { options.put(option, value); } return this; } /** * Specify attributes with an initial value to be set on newly created channels. An attribute can be removed by * specifying a value of {@code null}. */ public <T> Http2StreamChannelBootstrap attr(AttributeKey<T> key, T value) { checkNotNull(key, "key must not be null"); if (value == null) { attributes.remove(key); } else { attributes.put(key, value); } return this; } public Channel parentChannel() { ParentChannelAndMultiplexCodec channelAndCodec0 = channelAndCodec; if (channelAndCodec0 != null) { return channelAndCodec0.parentChannel; } return null; } public ChannelHandler handler() { return handler; } public EventLoopGroup group() { return group; } public Map<ChannelOption<?>, Object> options() { return unmodifiableMap(new LinkedHashMap<ChannelOption<?>, Object>(options)); } public Map<AttributeKey<?>, Object> attributes() { return unmodifiableMap(new LinkedHashMap<AttributeKey<?>, Object>(attributes)); } private void validateState() { checkNotNull(handler, "handler must be set"); checkNotNull(channelAndCodec, "parent channel must be set"); } private static ChannelHandler checkSharable(ChannelHandler handler) { if (!handler.getClass().isAnnotationPresent(Sharable.class)) { throw new IllegalArgumentException("The handler must be Sharable"); } return handler; } private static class ParentChannelAndMultiplexCodec { final Channel parentChannel; final Http2MultiplexCodec multiplexCodec; ParentChannelAndMultiplexCodec(Channel parentChannel) { this.parentChannel = checkRegistered(checkNotNull(parentChannel, "parentChannel")); this.multiplexCodec = requireMultiplexCodec(parentChannel.pipeline()); } private static Http2MultiplexCodec requireMultiplexCodec(ChannelPipeline pipeline) { ChannelHandlerContext ctx = pipeline.context(Http2MultiplexCodec.class); if (ctx == null) { throw new IllegalArgumentException(Http2MultiplexCodec.class.getSimpleName() + " was not found in the channel pipeline."); } return (Http2MultiplexCodec) ctx.handler(); } private static Channel checkRegistered(Channel channel) { if (!channel.isRegistered()) { throw new IllegalArgumentException("The channel must be registered to an eventloop."); } return channel; } } }