View Javadoc
1   /*
2    * Copyright 2015 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  
17  package io.netty.handler.codec.http2;
18  
19  import io.netty.channel.Channel;
20  import io.netty.handler.codec.http2.Http2HeadersEncoder.SensitivityDetector;
21  import io.netty.util.internal.ObjectUtil;
22  import io.netty.util.internal.UnstableApi;
23  
24  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_HEADER_LIST_SIZE;
25  import static io.netty.handler.codec.http2.Http2CodecUtil.DEFAULT_MAX_RESERVED_STREAMS;
26  import static io.netty.handler.codec.http2.Http2PromisedRequestVerifier.ALWAYS_VERIFY;
27  import static io.netty.util.internal.ObjectUtil.checkNotNull;
28  import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
29  
30  /**
31   * Abstract base class which defines commonly used features required to build {@link Http2ConnectionHandler} instances.
32   *
33   * <h3>Three ways to build a {@link Http2ConnectionHandler}</h3>
34   * <h4>Let the builder create a {@link Http2ConnectionHandler}</h4>
35   * Simply call all the necessary setter methods, and then use {@link #build()} to build a new
36   * {@link Http2ConnectionHandler}. Setting the following properties are prohibited because they are used for
37   * other ways of building a {@link Http2ConnectionHandler}.
38   * conflicts with this option:
39   * <ul>
40   *   <li>{@link #connection(Http2Connection)}</li>
41   *   <li>{@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)}</li>
42   * </ul>
43   *
44   *
45   * <h4>Let the builder use the {@link Http2ConnectionHandler} you specified</h4>
46   * Call {@link #connection(Http2Connection)} to tell the builder that you want to build the handler from the
47   * {@link Http2Connection} you specified. Setting the following properties are prohibited and thus will trigger
48   * an {@link IllegalStateException} because they conflict with this option.
49   * <ul>
50   *   <li>{@link #server(boolean)}</li>
51   *   <li>{@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)}</li>
52   * </ul>
53   *
54   * <h4>Let the builder use the {@link Http2ConnectionDecoder} and {@link Http2ConnectionEncoder} you specified</h4>
55   * Call {@link #codec(Http2ConnectionDecoder, Http2ConnectionEncoder)} to tell the builder that you want to built the
56   * handler from the {@link Http2ConnectionDecoder} and {@link Http2ConnectionEncoder} you specified. Setting the
57   * following properties are prohibited and thus will trigger an {@link IllegalStateException} because they conflict
58   * with this option:
59   * <ul>
60   *   <li>{@link #server(boolean)}</li>
61   *   <li>{@link #connection(Http2Connection)}</li>
62   *   <li>{@link #frameLogger(Http2FrameLogger)}</li>
63   *   <li>{@link #headerSensitivityDetector(SensitivityDetector)}</li>
64   *   <li>{@link #encoderEnforceMaxConcurrentStreams(boolean)}</li>
65   *   <li>{@link #encoderIgnoreMaxHeaderListSize(boolean)}</li>
66   * </ul>
67   *
68   * <h3>Exposing necessary methods in a subclass</h3>
69   * {@link #build()} method and all property access methods are {@code protected}. Choose the methods to expose to the
70   * users of your builder implementation and make them {@code public}.
71   *
72   * @param <T> The type of handler created by this builder.
73   * @param <B> The concrete type of this builder.
74   */
75  @UnstableApi
76  public abstract class AbstractHttp2ConnectionHandlerBuilder<T extends Http2ConnectionHandler,
77                                                              B extends AbstractHttp2ConnectionHandlerBuilder<T, B>> {
78  
79      private static final SensitivityDetector DEFAULT_HEADER_SENSITIVITY_DETECTOR = Http2HeadersEncoder.NEVER_SENSITIVE;
80  
81      // The properties that can always be set.
82      private Http2Settings initialSettings = Http2Settings.defaultSettings();
83      private Http2FrameListener frameListener;
84      private long gracefulShutdownTimeoutMillis = Http2CodecUtil.DEFAULT_GRACEFUL_SHUTDOWN_TIMEOUT_MILLIS;
85      private boolean decoupleCloseAndGoAway;
86  
87      // The property that will prohibit connection() and codec() if set by server(),
88      // because this property is used only when this builder creates an Http2Connection.
89      private Boolean isServer;
90      private Integer maxReservedStreams;
91  
92      // The property that will prohibit server() and codec() if set by connection().
93      private Http2Connection connection;
94  
95      // The properties that will prohibit server() and connection() if set by codec().
96      private Http2ConnectionDecoder decoder;
97      private Http2ConnectionEncoder encoder;
98  
99      // The properties that are:
100     // * mutually exclusive against codec() and
101     // * OK to use with server() and connection()
102     private Boolean validateHeaders;
103     private Http2FrameLogger frameLogger;
104     private SensitivityDetector headerSensitivityDetector;
105     private Boolean encoderEnforceMaxConcurrentStreams;
106     private Boolean encoderIgnoreMaxHeaderListSize;
107     private Http2PromisedRequestVerifier promisedRequestVerifier = ALWAYS_VERIFY;
108     private boolean autoAckSettingsFrame = true;
109     private boolean autoAckPingFrame = true;
110     private int maxQueuedControlFrames = Http2CodecUtil.DEFAULT_MAX_QUEUED_CONTROL_FRAMES;
111     private int maxConsecutiveEmptyFrames = 2;
112 
113     /**
114      * Sets the {@link Http2Settings} to use for the initial connection settings exchange.
115      */
116     protected Http2Settings initialSettings() {
117         return initialSettings;
118     }
119 
120     /**
121      * Sets the {@link Http2Settings} to use for the initial connection settings exchange.
122      */
123     protected B initialSettings(Http2Settings settings) {
124         initialSettings = checkNotNull(settings, "settings");
125         return self();
126     }
127 
128     /**
129      * Returns the listener of inbound frames.
130      *
131      * @return {@link Http2FrameListener} if set, or {@code null} if not set.
132      */
133     protected Http2FrameListener frameListener() {
134         return frameListener;
135     }
136 
137     /**
138      * Sets the listener of inbound frames.
139      * This listener will only be set if the decoder's listener is {@code null}.
140      */
141     protected B frameListener(Http2FrameListener frameListener) {
142         this.frameListener = checkNotNull(frameListener, "frameListener");
143         return self();
144     }
145 
146     /**
147      * Returns the graceful shutdown timeout of the {@link Http2Connection} in milliseconds. Returns -1 if the
148      * timeout is indefinite.
149      */
150     protected long gracefulShutdownTimeoutMillis() {
151         return gracefulShutdownTimeoutMillis;
152     }
153 
154     /**
155      * Sets the graceful shutdown timeout of the {@link Http2Connection} in milliseconds.
156      */
157     protected B gracefulShutdownTimeoutMillis(long gracefulShutdownTimeoutMillis) {
158         if (gracefulShutdownTimeoutMillis < -1) {
159             throw new IllegalArgumentException("gracefulShutdownTimeoutMillis: " + gracefulShutdownTimeoutMillis +
160                                                " (expected: -1 for indefinite or >= 0)");
161         }
162         this.gracefulShutdownTimeoutMillis = gracefulShutdownTimeoutMillis;
163         return self();
164     }
165 
166     /**
167      * Returns if {@link #build()} will to create a {@link Http2Connection} in server mode ({@code true})
168      * or client mode ({@code false}).
169      */
170     protected boolean isServer() {
171         return isServer != null ? isServer : true;
172     }
173 
174     /**
175      * Sets if {@link #build()} will to create a {@link Http2Connection} in server mode ({@code true})
176      * or client mode ({@code false}).
177      */
178     protected B server(boolean isServer) {
179         enforceConstraint("server", "connection", connection);
180         enforceConstraint("server", "codec", decoder);
181         enforceConstraint("server", "codec", encoder);
182 
183         this.isServer = isServer;
184         return self();
185     }
186 
187     /**
188      * Get the maximum number of streams which can be in the reserved state at any given time.
189      * <p>
190      * By default this value will be ignored on the server for local endpoint. This is because the RFC provides
191      * no way to explicitly communicate a limit to how many states can be in the reserved state, and instead relies
192      * on the peer to send RST_STREAM frames when they will be rejected.
193      */
194     protected int maxReservedStreams() {
195         return maxReservedStreams != null ? maxReservedStreams : DEFAULT_MAX_RESERVED_STREAMS;
196     }
197 
198     /**
199      * Set the maximum number of streams which can be in the reserved state at any given time.
200      */
201     protected B maxReservedStreams(int maxReservedStreams) {
202         enforceConstraint("server", "connection", connection);
203         enforceConstraint("server", "codec", decoder);
204         enforceConstraint("server", "codec", encoder);
205 
206         this.maxReservedStreams = checkPositiveOrZero(maxReservedStreams, "maxReservedStreams");
207         return self();
208     }
209 
210     /**
211      * Returns the {@link Http2Connection} to use.
212      *
213      * @return {@link Http2Connection} if set, or {@code null} if not set.
214      */
215     protected Http2Connection connection() {
216         return connection;
217     }
218 
219     /**
220      * Sets the {@link Http2Connection} to use.
221      */
222     protected B connection(Http2Connection connection) {
223         enforceConstraint("connection", "maxReservedStreams", maxReservedStreams);
224         enforceConstraint("connection", "server", isServer);
225         enforceConstraint("connection", "codec", decoder);
226         enforceConstraint("connection", "codec", encoder);
227 
228         this.connection = checkNotNull(connection, "connection");
229 
230         return self();
231     }
232 
233     /**
234      * Returns the {@link Http2ConnectionDecoder} to use.
235      *
236      * @return {@link Http2ConnectionDecoder} if set, or {@code null} if not set.
237      */
238     protected Http2ConnectionDecoder decoder() {
239         return decoder;
240     }
241 
242     /**
243      * Returns the {@link Http2ConnectionEncoder} to use.
244      *
245      * @return {@link Http2ConnectionEncoder} if set, or {@code null} if not set.
246      */
247     protected Http2ConnectionEncoder encoder() {
248         return encoder;
249     }
250 
251     /**
252      * Sets the {@link Http2ConnectionDecoder} and {@link Http2ConnectionEncoder} to use.
253      */
254     protected B codec(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder) {
255         enforceConstraint("codec", "server", isServer);
256         enforceConstraint("codec", "maxReservedStreams", maxReservedStreams);
257         enforceConstraint("codec", "connection", connection);
258         enforceConstraint("codec", "frameLogger", frameLogger);
259         enforceConstraint("codec", "validateHeaders", validateHeaders);
260         enforceConstraint("codec", "headerSensitivityDetector", headerSensitivityDetector);
261         enforceConstraint("codec", "encoderEnforceMaxConcurrentStreams", encoderEnforceMaxConcurrentStreams);
262 
263         checkNotNull(decoder, "decoder");
264         checkNotNull(encoder, "encoder");
265 
266         if (decoder.connection() != encoder.connection()) {
267             throw new IllegalArgumentException("The specified encoder and decoder have different connections.");
268         }
269 
270         this.decoder = decoder;
271         this.encoder = encoder;
272 
273         return self();
274     }
275 
276     /**
277      * Returns if HTTP headers should be validated according to
278      * <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.6">RFC 7540, 8.1.2.6</a>.
279      */
280     protected boolean isValidateHeaders() {
281         return validateHeaders != null ? validateHeaders : true;
282     }
283 
284     /**
285      * Sets if HTTP headers should be validated according to
286      * <a href="https://tools.ietf.org/html/rfc7540#section-8.1.2.6">RFC 7540, 8.1.2.6</a>.
287      */
288     protected B validateHeaders(boolean validateHeaders) {
289         enforceNonCodecConstraints("validateHeaders");
290         this.validateHeaders = validateHeaders;
291         return self();
292     }
293 
294     /**
295      * Returns the logger that is used for the encoder and decoder.
296      *
297      * @return {@link Http2FrameLogger} if set, or {@code null} if not set.
298      */
299     protected Http2FrameLogger frameLogger() {
300         return frameLogger;
301     }
302 
303     /**
304      * Sets the logger that is used for the encoder and decoder.
305      */
306     protected B frameLogger(Http2FrameLogger frameLogger) {
307         enforceNonCodecConstraints("frameLogger");
308         this.frameLogger = checkNotNull(frameLogger, "frameLogger");
309         return self();
310     }
311 
312     /**
313      * Returns if the encoder should queue frames if the maximum number of concurrent streams
314      * would otherwise be exceeded.
315      */
316     protected boolean encoderEnforceMaxConcurrentStreams() {
317         return encoderEnforceMaxConcurrentStreams != null ? encoderEnforceMaxConcurrentStreams : false;
318     }
319 
320     /**
321      * Sets if the encoder should queue frames if the maximum number of concurrent streams
322      * would otherwise be exceeded.
323      */
324     protected B encoderEnforceMaxConcurrentStreams(boolean encoderEnforceMaxConcurrentStreams) {
325         enforceNonCodecConstraints("encoderEnforceMaxConcurrentStreams");
326         this.encoderEnforceMaxConcurrentStreams = encoderEnforceMaxConcurrentStreams;
327         return self();
328     }
329 
330     /**
331      * Returns the maximum number of queued control frames that are allowed before the connection is closed.
332      * This allows to protected against various attacks that can lead to high CPU / memory usage if the remote-peer
333      * floods us with frames that would have us produce control frames, but stops to read from the underlying socket.
334      *
335      * {@code 0} means no protection is in place.
336      */
337     protected int encoderEnforceMaxQueuedControlFrames() {
338         return maxQueuedControlFrames;
339     }
340 
341     /**
342      * Sets the maximum number of queued control frames that are allowed before the connection is closed.
343      * This allows to protected against various attacks that can lead to high CPU / memory usage if the remote-peer
344      * floods us with frames that would have us produce control frames, but stops to read from the underlying socket.
345      *
346      * {@code 0} means no protection should be applied.
347      */
348     protected B encoderEnforceMaxQueuedControlFrames(int maxQueuedControlFrames) {
349         enforceNonCodecConstraints("encoderEnforceMaxQueuedControlFrames");
350         this.maxQueuedControlFrames = ObjectUtil.checkPositiveOrZero(maxQueuedControlFrames, "maxQueuedControlFrames");
351         return self();
352     }
353 
354     /**
355      * Returns the {@link SensitivityDetector} to use.
356      */
357     protected SensitivityDetector headerSensitivityDetector() {
358         return headerSensitivityDetector != null ? headerSensitivityDetector : DEFAULT_HEADER_SENSITIVITY_DETECTOR;
359     }
360 
361     /**
362      * Sets the {@link SensitivityDetector} to use.
363      */
364     protected B headerSensitivityDetector(SensitivityDetector headerSensitivityDetector) {
365         enforceNonCodecConstraints("headerSensitivityDetector");
366         this.headerSensitivityDetector = checkNotNull(headerSensitivityDetector, "headerSensitivityDetector");
367         return self();
368     }
369 
370     /**
371      * Sets if the <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>
372      * should be ignored when encoding headers.
373      * @param ignoreMaxHeaderListSize {@code true} to ignore
374      * <a href="https://tools.ietf.org/html/rfc7540#section-6.5.2">SETTINGS_MAX_HEADER_LIST_SIZE</a>.
375      * @return this.
376      */
377     protected B encoderIgnoreMaxHeaderListSize(boolean ignoreMaxHeaderListSize) {
378         enforceNonCodecConstraints("encoderIgnoreMaxHeaderListSize");
379         this.encoderIgnoreMaxHeaderListSize = ignoreMaxHeaderListSize;
380         return self();
381     }
382 
383     /**
384      * Does nothing, do not call.
385      *
386      * @deprecated Huffman decoding no longer depends on having a decode capacity.
387      */
388     @Deprecated
389     protected B initialHuffmanDecodeCapacity(int initialHuffmanDecodeCapacity) {
390         return self();
391     }
392 
393     /**
394      * Set the {@link Http2PromisedRequestVerifier} to use.
395      * @return this.
396      */
397     protected B promisedRequestVerifier(Http2PromisedRequestVerifier promisedRequestVerifier) {
398         enforceNonCodecConstraints("promisedRequestVerifier");
399         this.promisedRequestVerifier = checkNotNull(promisedRequestVerifier, "promisedRequestVerifier");
400         return self();
401     }
402 
403     /**
404      * Get the {@link Http2PromisedRequestVerifier} to use.
405      * @return the {@link Http2PromisedRequestVerifier} to use.
406      */
407     protected Http2PromisedRequestVerifier promisedRequestVerifier() {
408         return promisedRequestVerifier;
409     }
410 
411     /**
412      * Returns the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed before
413      * the connection is closed. This allows to protected against the remote peer flooding us with such frames and
414      * so use up a lot of CPU. There is no valid use-case for empty DATA frames without end_of_stream flag.
415      *
416      * {@code 0} means no protection is in place.
417      */
418     protected int decoderEnforceMaxConsecutiveEmptyDataFrames() {
419         return maxConsecutiveEmptyFrames;
420     }
421 
422     /**
423      * Sets the maximum number of consecutive empty DATA frames (without end_of_stream flag) that are allowed before
424      * the connection is closed. This allows to protected against the remote peer flooding us with such frames and
425      * so use up a lot of CPU. There is no valid use-case for empty DATA frames without end_of_stream flag.
426      *
427      * {@code 0} means no protection should be applied.
428      */
429     protected B decoderEnforceMaxConsecutiveEmptyDataFrames(int maxConsecutiveEmptyFrames) {
430         enforceNonCodecConstraints("maxConsecutiveEmptyFrames");
431         this.maxConsecutiveEmptyFrames = ObjectUtil.checkPositiveOrZero(
432                 maxConsecutiveEmptyFrames, "maxConsecutiveEmptyFrames");
433         return self();
434     }
435 
436     /**
437      * Determine if settings frame should automatically be acknowledged and applied.
438      * @return this.
439      */
440     protected B autoAckSettingsFrame(boolean autoAckSettings) {
441         enforceNonCodecConstraints("autoAckSettingsFrame");
442         this.autoAckSettingsFrame = autoAckSettings;
443         return self();
444     }
445 
446     /**
447      * Determine if the SETTINGS frames should be automatically acknowledged and applied.
448      * @return {@code true} if the SETTINGS frames should be automatically acknowledged and applied.
449      */
450     protected boolean isAutoAckSettingsFrame() {
451         return autoAckSettingsFrame;
452     }
453 
454     /**
455      * Determine if PING frame should automatically be acknowledged or not.
456      * @return this.
457      */
458     protected B autoAckPingFrame(boolean autoAckPingFrame) {
459         enforceNonCodecConstraints("autoAckPingFrame");
460         this.autoAckPingFrame = autoAckPingFrame;
461         return self();
462     }
463 
464     /**
465      * Determine if the PING frames should be automatically acknowledged or not.
466      * @return {@code true} if the PING frames should be automatically acknowledged.
467      */
468     protected boolean isAutoAckPingFrame() {
469         return autoAckPingFrame;
470     }
471 
472     /**
473      * Determine if the {@link Channel#close()} should be coupled with goaway and graceful close.
474      * @param decoupleCloseAndGoAway {@code true} to make {@link Channel#close()} directly close the underlying
475      *   transport, and not attempt graceful closure via GOAWAY.
476      * @return {@code this}.
477      */
478     protected B decoupleCloseAndGoAway(boolean decoupleCloseAndGoAway) {
479         this.decoupleCloseAndGoAway = decoupleCloseAndGoAway;
480         return self();
481     }
482 
483     /**
484      * Determine if the {@link Channel#close()} should be coupled with goaway and graceful close.
485      */
486     protected boolean decoupleCloseAndGoAway() {
487         return decoupleCloseAndGoAway;
488     }
489 
490     /**
491      * Create a new {@link Http2ConnectionHandler}.
492      */
493     protected T build() {
494         if (encoder != null) {
495             assert decoder != null;
496             return buildFromCodec(decoder, encoder);
497         }
498 
499         Http2Connection connection = this.connection;
500         if (connection == null) {
501             connection = new DefaultHttp2Connection(isServer(), maxReservedStreams());
502         }
503 
504         return buildFromConnection(connection);
505     }
506 
507     private T buildFromConnection(Http2Connection connection) {
508         Long maxHeaderListSize = initialSettings.maxHeaderListSize();
509         Http2FrameReader reader = new DefaultHttp2FrameReader(new DefaultHttp2HeadersDecoder(isValidateHeaders(),
510                 maxHeaderListSize == null ? DEFAULT_HEADER_LIST_SIZE : maxHeaderListSize,
511                 /* initialHuffmanDecodeCapacity= */ -1));
512         Http2FrameWriter writer = encoderIgnoreMaxHeaderListSize == null ?
513                 new DefaultHttp2FrameWriter(headerSensitivityDetector()) :
514                 new DefaultHttp2FrameWriter(headerSensitivityDetector(), encoderIgnoreMaxHeaderListSize);
515 
516         if (frameLogger != null) {
517             reader = new Http2InboundFrameLogger(reader, frameLogger);
518             writer = new Http2OutboundFrameLogger(writer, frameLogger);
519         }
520 
521         Http2ConnectionEncoder encoder = new DefaultHttp2ConnectionEncoder(connection, writer);
522         boolean encoderEnforceMaxConcurrentStreams = encoderEnforceMaxConcurrentStreams();
523 
524         if (maxQueuedControlFrames != 0) {
525             encoder = new Http2ControlFrameLimitEncoder(encoder, maxQueuedControlFrames);
526         }
527         if (encoderEnforceMaxConcurrentStreams) {
528             if (connection.isServer()) {
529                 encoder.close();
530                 reader.close();
531                 throw new IllegalArgumentException(
532                         "encoderEnforceMaxConcurrentStreams: " + encoderEnforceMaxConcurrentStreams +
533                         " not supported for server");
534             }
535             encoder = new StreamBufferingEncoder(encoder);
536         }
537 
538         DefaultHttp2ConnectionDecoder decoder = new DefaultHttp2ConnectionDecoder(connection, encoder, reader,
539                 promisedRequestVerifier(), isAutoAckSettingsFrame(), isAutoAckPingFrame());
540         return buildFromCodec(decoder, encoder);
541     }
542 
543     private T buildFromCodec(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder) {
544         int maxConsecutiveEmptyDataFrames = decoderEnforceMaxConsecutiveEmptyDataFrames();
545         if (maxConsecutiveEmptyDataFrames > 0) {
546             decoder = new Http2EmptyDataFrameConnectionDecoder(decoder, maxConsecutiveEmptyDataFrames);
547         }
548         final T handler;
549         try {
550             // Call the abstract build method
551             handler = build(decoder, encoder, initialSettings);
552         } catch (Throwable t) {
553             encoder.close();
554             decoder.close();
555             throw new IllegalStateException("failed to build an Http2ConnectionHandler", t);
556         }
557 
558         // Setup post build options
559         handler.gracefulShutdownTimeoutMillis(gracefulShutdownTimeoutMillis);
560         if (handler.decoder().frameListener() == null) {
561             handler.decoder().frameListener(frameListener);
562         }
563         return handler;
564     }
565 
566     /**
567      * Implement this method to create a new {@link Http2ConnectionHandler} or its subtype instance.
568      * <p>
569      * The return of this method will be subject to the following:
570      * <ul>
571      *   <li>{@link #frameListener(Http2FrameListener)} will be set if not already set in the decoder</li>
572      *   <li>{@link #gracefulShutdownTimeoutMillis(long)} will always be set</li>
573      * </ul>
574      */
575     protected abstract T build(Http2ConnectionDecoder decoder, Http2ConnectionEncoder encoder,
576                                Http2Settings initialSettings) throws Exception;
577 
578     /**
579      * Returns {@code this}.
580      */
581     @SuppressWarnings("unchecked")
582     protected final B self() {
583         return (B) this;
584     }
585 
586     private void enforceNonCodecConstraints(String rejected) {
587         enforceConstraint(rejected, "server/connection", decoder);
588         enforceConstraint(rejected, "server/connection", encoder);
589     }
590 
591     private static void enforceConstraint(String methodName, String rejectorName, Object value) {
592         if (value != null) {
593             throw new IllegalStateException(
594                     methodName + "() cannot be called because " + rejectorName + "() has been called already.");
595         }
596     }
597 }