001/* -*- mode: Java; c-basic-offset: 2; indent-tabs-mode: nil; coding: utf-8-unix -*-
002 *
003 * Copyright © 2019–2020 microBean™.
004 *
005 * Licensed under the Apache License, Version 2.0 (the "License");
006 * you may not use this file except in compliance with the License.
007 * You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
014 * implied.  See the License for the specific language governing
015 * permissions and limitations under the License.
016 */
017package org.microbean.jersey.netty;
018
019import java.net.URI;
020
021import java.util.Iterator;
022import java.util.Map.Entry;
023
024import java.util.function.Supplier;
025
026import javax.ws.rs.core.Configuration;
027
028import io.netty.channel.ChannelHandlerContext;
029
030import io.netty.handler.codec.http2.Http2DataFrame;
031import io.netty.handler.codec.http2.Http2Headers;
032import io.netty.handler.codec.http2.Http2HeadersFrame;
033import io.netty.handler.codec.http2.Http2StreamFrame;
034
035import org.glassfish.jersey.server.ContainerRequest;
036
037/**
038 * An {@link AbstractContainerRequestDecoder} that {@linkplain
039 * #decode(ChannelHandlerContext, Object, List) decodes} {@link
040 * Http2StreamFrame}s into {@link ContainerRequest}s.
041 *
042 * @author <a href="https://about.me/lairdnelson"
043 * target="_parent">Laird Nelson</a>
044 *
045 * @see #decode(ChannelHandlerContext, Object, List)
046 */
047public class Http2StreamFrameToContainerRequestDecoder extends AbstractContainerRequestDecoder<Http2StreamFrame, Http2HeadersFrame, Http2DataFrame> {
048
049
050  /*
051   * Constructors.
052   */
053
054
055  /**
056   * Creates a new {@link Http2StreamFrameToContainerRequestDecoder}.
057   *
058   * @param baseUri a {@link URI} that will serve as the {@linkplain
059   * ContainerRequest#getBaseUri() base <code>URI</code>} in a new
060   * {@link ContainerRequest}; may be {@code null} in which case the
061   * return value of {@link URI#create(String) URI.create("/")} will
062   * be used instead
063   *
064   * @see #Http2StreamFrameToContainerRequestDecoder(URI, Configuration)
065   *
066   * @deprecated Please use the {@link
067   * #Http2StreamFrameToContainerRequestDecoder(URI, Configuration)}
068   * constructor instead.
069   */
070  @Deprecated
071  public Http2StreamFrameToContainerRequestDecoder(final URI baseUri) {
072    this(baseUri, (Supplier<? extends Configuration>)null);
073  }
074
075  /**
076   * Creates a new {@link Http2StreamFrameToContainerRequestDecoder}.
077   *
078   * @param baseUri a {@link URI} that will serve as the {@linkplain
079   * ContainerRequest#getBaseUri() base <code>URI</code>} in a new
080   * {@link ContainerRequest}; may be {@code null} in which case the
081   * return value of {@link URI#create(String) URI.create("/")} will
082   * be used instead
083   *
084   * @param configuration a {@link Configuration} describing how the
085   * container is configured; may be {@code null}
086   */
087  public Http2StreamFrameToContainerRequestDecoder(final URI baseUri, final Configuration configuration) {
088    this(baseUri, configuration == null ? (Supplier<? extends Configuration>)null : new ImmutableSupplier<>(configuration));
089  }
090
091  /**
092   * Creates a new {@link Http2StreamFrameToContainerRequestDecoder}.
093   *
094   * @param baseUri a {@link URI} that will serve as the {@linkplain
095   * ContainerRequest#getBaseUri() base <code>URI</code>} in a new
096   * {@link ContainerRequest}; may be {@code null} in which case the
097   * return value of {@link URI#create(String) URI.create("/")} will
098   * be used instead
099   *
100   * @param configurationSupplier a {@link Supplier} of {@link
101   * Configuration} instances describing how the container is
102   * configured; may be {@code null}
103   */
104  public Http2StreamFrameToContainerRequestDecoder(final URI baseUri, final Supplier<? extends Configuration> configurationSupplier) {
105    super(baseUri, configurationSupplier, Http2HeadersFrame.class, Http2DataFrame.class);
106  }
107
108
109  /*
110   * Instance methods.
111   */
112
113
114  /**
115   * Extracts and returns a {@link String} representing a request URI
116   * from the supplied message, which is guaranteed to be a
117   * {@linkplain #isHeaders(Object) "headers" message}.
118   *
119   * <p>This implementation calls {@link Http2HeadersFrame#headers()
120   * http2HeadersFrame.headers().path().toString()} and returns the
121   * result.</p>
122   *
123   * @param http2HeadersFrame the message to interrogate; will not be
124   * {@code null}
125   *
126   * @return a {@link String} representing a request URI from the
127   * supplied message, or {@code null}
128   */
129  @Override
130  protected final String getRequestUriString(final Http2HeadersFrame http2HeadersFrame) {
131    return http2HeadersFrame.headers().path().toString();
132  }
133
134  /**
135   * Extracts and returns the name of the request method from the
136   * supplied message, which is guaranteed to be a {@linkplain
137   * #isHeaders(Object) "headers" message}.
138   *
139   * <p>This implementation calls {@link Http2HeadersFrame#headers()
140   * http2HeadersFrame.headers().method().toString()} and returns the
141   * result.</p>
142   *
143   * @param http2HeadersFrame the message to interrogate; will not be
144   * {@code null}
145   *
146   * @return a {@link String} representing the request method from the
147   * supplied message, or {@code null}
148   */
149  @Override
150  protected final String getMethod(final Http2HeadersFrame http2HeadersFrame) {
151    return http2HeadersFrame.headers().method().toString();
152  }
153
154  /**
155   * Overrides the {@link
156   * AbstractContainerRequestDecoder#installMessage(ChannelHandlerContext,
157   * Object, ContainerRequest)} method to
158   * <strong>additionally</strong> install incoming headers into the
159   * supplied {@link ContainerRequest}.
160   *
161   * @param channelHandlerContext the {@link ChannelHandlerContext} in
162   * effect; will not be {@code null}; supplied for convenience;
163   * overrides may (and often do) ignore this parameter
164   *
165   * @param message the message to install; will not be {@code null}
166   *
167   * @param containerRequest the just-constructed {@link
168   * ContainerRequest} into which to install the supplied {@code
169   * message}; will not be {@code null}
170   *
171   * @see Http2HeadersFrame#headers()
172   *
173   * @see
174   * org.glassfish.jersey.message.internal.InboundMessageContext#header(String,
175   * Object)
176   */
177  @Override
178  protected void installMessage(final ChannelHandlerContext channelHandlerContext,
179                                final Http2HeadersFrame message,
180                                final ContainerRequest containerRequest) {
181    super.installMessage(channelHandlerContext, message, containerRequest);
182    final Http2Headers headers = message.headers();
183    if (headers != null && !headers.isEmpty()) {
184      final Iterator<? extends Entry<?, ?>> iterator = headers.iterator();
185      while (iterator.hasNext()) {
186        final Entry<?, ?> entry = iterator.next();
187        containerRequest.header(entry.getKey().toString(), entry.getValue());
188      }
189    }
190  }
191
192  /**
193   * Returns {@code true} if the supplied {@link Http2StreamFrame} is the
194   * last of a stream of messages.
195   *
196   * <p>This implementation returns {@code true} if either:</p>
197   *
198   * <ul>
199   *
200   * <li>{@code http2StreamFrame} is an instance of {@link
201   * Http2HeadersFrame} and its {@link
202   * Http2HeadersFrame#isEndStream()} method returns {@code true},
203   * or</li>
204   *
205   * <li>{@code http2StreamFrame} is an instance of {@link
206   * Http2DataFrame} and its {@link Http2DataFrame#isEndStream()}
207   * method returns {@code true}</li>
208   *
209   * </ul>
210   *
211   * @param http2StreamFrame the message to interrogate; will not be
212   * {@code null}
213   *
214   * @return {@code true} if no further messages in the stream are
215   * forthcoming; {@code false} otherwise
216   */
217  @Override
218  protected final boolean isLast(final Http2StreamFrame http2StreamFrame) {
219    final boolean returnValue;
220    if (http2StreamFrame instanceof Http2HeadersFrame) {
221      returnValue = ((Http2HeadersFrame)http2StreamFrame).isEndStream();
222    } else if (http2StreamFrame instanceof Http2DataFrame) {
223      returnValue = ((Http2DataFrame)http2StreamFrame).isEndStream();
224    } else {
225      // Not possible in Netty 4.1 and earlier
226      returnValue = false;
227    }
228    return returnValue;
229  }
230
231}