1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package io.netty5.handler.codec.http2;
16
17 import io.netty5.buffer.api.Buffer;
18 import io.netty5.channel.ChannelHandler;
19 import io.netty5.channel.ChannelHandlerContext;
20 import io.netty5.handler.codec.base64.Base64;
21 import io.netty5.handler.codec.http.FullHttpRequest;
22 import io.netty5.handler.codec.http.HttpHeaders;
23 import io.netty5.handler.codec.http.HttpServerUpgradeHandler;
24 import io.netty5.util.internal.UnstableApi;
25 import io.netty5.util.internal.logging.InternalLogger;
26 import io.netty5.util.internal.logging.InternalLoggerFactory;
27
28 import java.nio.charset.StandardCharsets;
29 import java.util.Collection;
30 import java.util.Collections;
31 import java.util.List;
32
33 import static io.netty5.handler.codec.base64.Base64Dialect.URL_SAFE;
34 import static io.netty5.handler.codec.http2.Http2CodecUtil.FRAME_HEADER_LENGTH;
35 import static io.netty5.handler.codec.http2.Http2CodecUtil.HTTP_UPGRADE_SETTINGS_HEADER;
36 import static io.netty5.handler.codec.http2.Http2CodecUtil.writeFrameHeader;
37 import static io.netty5.handler.codec.http2.Http2FrameTypes.SETTINGS;
38
39
40
41
42 @UnstableApi
43 public class Http2ServerUpgradeCodec implements HttpServerUpgradeHandler.UpgradeCodec {
44
45 private static final InternalLogger logger = InternalLoggerFactory.getInstance(Http2ServerUpgradeCodec.class);
46 private static final List<CharSequence> REQUIRED_UPGRADE_HEADERS =
47 Collections.singletonList(HTTP_UPGRADE_SETTINGS_HEADER);
48 private static final ChannelHandler[] EMPTY_HANDLERS = new ChannelHandler[0];
49
50 private final String handlerName;
51 private final Http2ConnectionHandler connectionHandler;
52 private final ChannelHandler[] handlers;
53 private final Http2FrameReader frameReader;
54
55 private Http2Settings settings;
56
57
58
59
60
61
62
63 public Http2ServerUpgradeCodec(Http2ConnectionHandler connectionHandler) {
64 this(null, connectionHandler, EMPTY_HANDLERS);
65 }
66
67
68
69
70
71
72
73
74 public Http2ServerUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler) {
75 this(handlerName, connectionHandler, EMPTY_HANDLERS);
76 }
77
78
79
80
81
82
83
84
85 public Http2ServerUpgradeCodec(Http2FrameCodec http2Codec, ChannelHandler... handlers) {
86 this(null, http2Codec, handlers);
87 }
88
89 private Http2ServerUpgradeCodec(String handlerName, Http2ConnectionHandler connectionHandler,
90 ChannelHandler... handlers) {
91 this.handlerName = handlerName;
92 this.connectionHandler = connectionHandler;
93 this.handlers = handlers;
94 frameReader = new DefaultHttp2FrameReader();
95 }
96
97 @Override
98 public Collection<CharSequence> requiredUpgradeHeaders() {
99 return REQUIRED_UPGRADE_HEADERS;
100 }
101
102 @Override
103 public boolean prepareUpgradeResponse(ChannelHandlerContext ctx, FullHttpRequest upgradeRequest,
104 HttpHeaders headers) {
105 try {
106
107
108 List<String> upgradeHeaders = upgradeRequest.headers().getAll(HTTP_UPGRADE_SETTINGS_HEADER);
109 if (upgradeHeaders.size() != 1) {
110 throw new IllegalArgumentException("There must be 1 and only 1 "
111 + HTTP_UPGRADE_SETTINGS_HEADER + " header.");
112 }
113 settings = decodeSettingsHeader(ctx, upgradeHeaders.get(0));
114
115 return true;
116 } catch (Throwable cause) {
117 logger.info("Error during upgrade to HTTP/2", cause);
118 return false;
119 }
120 }
121
122 @Override
123 public void upgradeTo(final ChannelHandlerContext ctx, FullHttpRequest upgradeRequest) {
124 try {
125
126 ctx.pipeline().addAfter(ctx.name(), handlerName, connectionHandler);
127
128
129
130 if (handlers != null) {
131 final String name = ctx.pipeline().context(connectionHandler).name();
132 for (int i = handlers.length - 1; i >= 0; i--) {
133 ctx.pipeline().addAfter(name, null, handlers[i]);
134 }
135 }
136 connectionHandler.onHttpServerUpgrade(settings);
137 } catch (Http2Exception e) {
138 ctx.fireChannelExceptionCaught(e);
139 ctx.close();
140 }
141 }
142
143
144
145
146 private Http2Settings decodeSettingsHeader(ChannelHandlerContext ctx, CharSequence settingsHeader)
147 throws Http2Exception {
148 try (Buffer header = ctx.bufferAllocator().allocate(settingsHeader.length())) {
149 header.writeCharSequence(settingsHeader, StandardCharsets.UTF_8);
150
151 try (Buffer payload = Base64.decode(header, URL_SAFE)) {
152
153 Buffer frame = createSettingsFrame(ctx, payload);
154
155
156 return decodeSettings(ctx, frame);
157 }
158 }
159 }
160
161
162
163
164 private Http2Settings decodeSettings(ChannelHandlerContext ctx, Buffer frame) throws Http2Exception {
165 try (frame) {
166 final Http2Settings decodedSettings = new Http2Settings();
167 frameReader.readFrame(ctx, frame, new Http2FrameAdapter() {
168 @Override
169 public void onSettingsRead(ChannelHandlerContext ctx, Http2Settings settings) {
170 decodedSettings.copyFrom(settings);
171 }
172 });
173 return decodedSettings;
174 }
175 }
176
177
178
179
180 private static Buffer createSettingsFrame(ChannelHandlerContext ctx, Buffer payload) {
181 Buffer frame = ctx.bufferAllocator().allocate(FRAME_HEADER_LENGTH + payload.readableBytes());
182 writeFrameHeader(frame, payload.readableBytes(), SETTINGS, new Http2Flags(), 0);
183 frame.writeBytes(payload);
184 return frame;
185 }
186 }