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}