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.io.IOException;
020
021import java.util.Objects;
022
023import java.util.function.Supplier;
024
025import java.util.logging.Level;
026import java.util.logging.Logger;
027
028import javax.ws.rs.HttpMethod;
029
030import javax.ws.rs.core.HttpHeaders;
031import javax.ws.rs.core.Response.Status;
032
033import io.netty.channel.Channel;
034import io.netty.channel.ChannelFuture;
035import io.netty.channel.ChannelHandlerContext;
036import io.netty.channel.ChannelPromise;
037
038import io.netty.handler.codec.http2.DefaultHttp2Headers;
039import io.netty.handler.codec.http2.DefaultHttp2HeadersFrame;
040import io.netty.handler.codec.http2.Http2DataFrame; // for javadoc only
041import io.netty.handler.codec.http2.Http2Headers;
042import io.netty.handler.codec.http2.Http2HeadersFrame; // for javadoc only
043
044import io.netty.util.concurrent.Future;
045import io.netty.util.concurrent.GenericFutureListener;
046
047import org.glassfish.jersey.server.ApplicationHandler;
048import org.glassfish.jersey.server.ContainerResponse;
049
050import org.microbean.jersey.netty.AbstractByteBufBackedChannelOutboundInvokingOutputStream.ByteBufCreator;
051
052/**
053 * An {@link AbstractContainerRequestHandlingResponseWriter}
054 * implemented in terms of {@link Http2Headers}, {@link
055 * Http2HeadersFrame}s, {@link Http2DataFrame}s and {@link
056 * ByteBufBackedChannelOutboundInvokingHttp2DataFrameOutputStream}s.
057 *
058 * @author <a href="https://about.me/lairdnelson" target="_parent">Laird Nelson</a>
059 *
060 * @see AbstractContainerRequestHandlingResponseWriter
061 *
062 * @see ByteBufBackedChannelOutboundInvokingHttpContentOutputStream
063 *
064 * @see #writeStatusAndHeaders(long, ContainerResponse)
065 *
066 * @see #createOutputStream(long, ContainerResponse)
067 */
068public final class Http2ContainerRequestHandlingResponseWriter extends AbstractContainerRequestHandlingResponseWriter<Http2DataFrame> {
069
070
071  /*
072   * Static fields.
073   */
074
075
076  private static final String cn = Http2ContainerRequestHandlingResponseWriter.class.getName();
077
078  private static final Logger logger = Logger.getLogger(cn);
079
080  private static final GenericFutureListener<? extends Future<? super Void>> listener = new LoggingWriteListener(logger);
081
082
083  /*
084   * Constructors.
085   */
086
087
088  /**
089   * Creates a new {@link Http2ContainerRequestHandlingResponseWriter}.
090   *
091   * @param applicationHandler an {@link ApplicationHandler}
092   * representing a <a
093   * href="https://jakarta.ee/specifications/restful-ws/"
094   * target="_parent">Jakarta RESTful Web Services application</a>
095   * whose {@link ApplicationHandler#handle(ContainerRequest)} method
096   * will serve as the bridge between Netty and Jersey; may be {@code
097   * null} somewhat pathologically but normally is not
098   *
099   * @see ApplicationHandler
100   *
101   * @see ApplicationHandler#handle(ContainerRequest)
102   */
103  public Http2ContainerRequestHandlingResponseWriter(final ApplicationHandler applicationHandler) {
104    super(new ImmutableSupplier<>(applicationHandler));
105  }
106
107  /**
108   * Creates a new {@link Http2ContainerRequestHandlingResponseWriter}.
109   *
110   * @param applicationHandler an {@link ApplicationHandler}
111   * representing a <a
112   * href="https://jakarta.ee/specifications/restful-ws/"
113   * target="_parent">Jakarta RESTful Web Services application</a>
114   * whose {@link ApplicationHandler#handle(ContainerRequest)} method
115   * will serve as the bridge between Netty and Jersey; may be {@code
116   * null} somewhat pathologically but normally is not
117   *
118   * @param flushThreshold the minimum number of bytes that an {@link
119   * AbstractChannelOutboundInvokingOutputStream} returned by the
120   * {@link #createOutputStream(long, ContainerResponse)} method must
121   * write before an automatic {@linkplain
122   * AbstractChannelOutboundInvokingOutputStream#flush() flush} may
123   * take place; if less than {@code 0} {@code 0} will be used
124   * instead; if {@link Integer#MAX_VALUE} then it is suggested that
125   * no automatic flushing will occur
126   *
127   * @param byteBufCreator a {@link ByteBufCreator} that will be used
128   * by the {@link #createOutputStream(long, ContainerResponse)}
129   * method; may be {@code null}
130   *
131   * @see ApplicationHandler
132   *
133   * @see ApplicationHandler#handle(ContainerRequest)
134   */
135  public Http2ContainerRequestHandlingResponseWriter(final ApplicationHandler applicationHandler,
136                                                     final int flushThreshold,
137                                                     final ByteBufCreator byteBufCreator) {
138    super(new ImmutableSupplier<>(applicationHandler), flushThreshold, byteBufCreator);
139  }
140
141  /**
142   * Creates a new {@link Http2ContainerRequestHandlingResponseWriter}.
143   *
144   * @param applicationHandlerSupplier a {@link Supplier} of an {@link
145   * ApplicationHandler} representing a <a
146   * href="https://jakarta.ee/specifications/restful-ws/"
147   * target="_parent">Jakarta RESTful Web Services application</a>
148   * whose {@link ApplicationHandler#handle(ContainerRequest)} method
149   * will serve as the bridge between Netty and Jersey; may be {@code
150   * null} somewhat pathologically but normally is not
151   *
152   * @see ApplicationHandler
153   *
154   * @see ApplicationHandler#handle(ContainerRequest)
155   */
156  public Http2ContainerRequestHandlingResponseWriter(final Supplier<? extends ApplicationHandler> applicationHandlerSupplier) {
157    super(applicationHandlerSupplier);
158  }
159
160  /**
161   * Creates a new {@link Http2ContainerRequestHandlingResponseWriter}.
162   *
163   * @param applicationHandlerSupplier a {@link Supplier} of an {@link
164   * ApplicationHandler} representing a <a
165   * href="https://jakarta.ee/specifications/restful-ws/"
166   * target="_parent">Jakarta RESTful Web Services application</a>
167   * whose {@link ApplicationHandler#handle(ContainerRequest)} method
168   * will serve as the bridge between Netty and Jersey; may be {@code
169   * null} somewhat pathologically but normally is not
170   *
171   * @param flushThreshold the minimum number of bytes that an {@link
172   * AbstractChannelOutboundInvokingOutputStream} returned by the
173   * {@link #createOutputStream(long, ContainerResponse)} method must
174   * write before an automatic {@linkplain
175   * AbstractChannelOutboundInvokingOutputStream#flush() flush} may
176   * take place; if less than {@code 0} {@code 0} will be used
177   * instead; if {@link Integer#MAX_VALUE} then it is suggested that
178   * no automatic flushing will occur
179   *
180   * @param byteBufCreator a {@link ByteBufCreator} that will be used
181   * by the {@link #createOutputStream(long, ContainerResponse)}
182   * method; may be {@code null}
183   *
184   * @see ApplicationHandler
185   *
186   * @see ApplicationHandler#handle(ContainerRequest)
187   */
188  public Http2ContainerRequestHandlingResponseWriter(final Supplier<? extends ApplicationHandler> applicationHandlerSupplier,
189                                                     final int flushThreshold,
190                                                     final ByteBufCreator byteBufCreator) {
191    super(applicationHandlerSupplier, flushThreshold, byteBufCreator);
192  }
193
194
195  /*
196   * Instance methods.
197   */
198
199
200  /**
201   * Writes the status and headers portion of the response present in
202   * the supplied {@link ContainerResponse} and returns {@code true}
203   * if further output is forthcoming.
204   *
205   * <p>This implementation writes an instance of {@link
206   * DefaultHttp2HeadersFrame}.</p>
207   *
208   * @param contentLength the content length as determined by the
209   * logic encapsulated by the {@link
210   * ApplicationHandler#handle(ContainerRequest)} method; a value less
211   * than zero indicates an unknown content length
212   *
213   * @param containerResponse the {@link ContainerResponse} containing
214   * status and headers information; must not be {@code null}
215   *
216   * @return {@code true} if the {@link #createOutputStream(long,
217   * ContainerResponse)} method should be invoked, <em>i.e.</em> if
218   * further output is forthcoming
219   *
220   * @exception NullPointerException if {@code containerResponse} is
221   * {@code null}
222   *
223   * @see ApplicationHandler#handle(ContainerRequest)
224   *
225   * @see #createOutputStream(long, ContainerResponse)
226   */
227  @Override
228  protected final boolean writeStatusAndHeaders(final long contentLength,
229                                                final ContainerResponse containerResponse) {
230    Objects.requireNonNull(containerResponse);
231
232    final ChannelHandlerContext channelHandlerContext = Objects.requireNonNull(this.getChannelHandlerContext());
233
234    final Http2Headers nettyHeaders = new DefaultHttp2Headers();
235    copyHeaders(containerResponse.getStringHeaders(), String::toLowerCase, nettyHeaders::add);
236    // See https://tools.ietf.org/html/rfc7540#section-8.1.2.4
237    nettyHeaders.status(Integer.toString(containerResponse.getStatus()));
238
239    final boolean needsOutputStream;
240    if (contentLength < 0L) {
241      needsOutputStream = !HttpMethod.HEAD.equalsIgnoreCase(containerResponse.getRequestContext().getMethod());
242      // Don't set the Content-Length: header because the content
243      // length is unknown.
244    } else if (contentLength == 0L) {
245      needsOutputStream = false;
246      nettyHeaders.set(HttpHeaders.CONTENT_LENGTH.toLowerCase(), "0");
247    } else {
248      needsOutputStream = !HttpMethod.HEAD.equalsIgnoreCase(containerResponse.getRequestContext().getMethod());
249      nettyHeaders.set(HttpHeaders.CONTENT_LENGTH.toLowerCase(), Long.toString(contentLength));
250    }
251
252    final Object message = new DefaultHttp2HeadersFrame(nettyHeaders, !needsOutputStream /* end of stream? */);
253
254    final ChannelPromise channelPromise = channelHandlerContext.newPromise();
255    assert channelPromise != null;
256    channelPromise.addListener(listener);
257
258    // Remember that
259    // AbstractContainerRequestHandlingResponseWriter#channelReadComplete(ChannelHandlerContext)
260    // will call ChannelHandlerContext#flush() in all cases.
261    channelHandlerContext.write(message, channelPromise);
262
263    return needsOutputStream;
264  }
265
266  /**
267   * Creates and returns a new {@link
268   * ByteBufBackedChannelOutboundInvokingHttp2DataFrameOutputStream}.
269   *
270   * @param contentLength the content length as determined by the
271   * logic encapsulated by the {@link
272   * ApplicationHandler#handle(ContainerRequest)} method; must not be
273   * {@code 0L}
274   *
275   * @param containerResponse a {@link ContainerResponse} for which an
276   * {@link AbstractChannelOutboundInvokingOutputStream} is being
277   * created and returned; must not be {@code null}; ignored by this
278   * implementation
279   *
280   * @return a new {@link
281   * ByteBufBackedChannelOutboundInvokingHttp2DataFrameOutputStream}
282   *
283   * @exception NullPointerException if {@code containerResponse} is
284   * {@code null} or if {@link #getChannelHandlerContext()} returns
285   * {@code null}
286   *
287   * @exception IllegalArgumentException if {@code contentLength} is
288   * {@code 0L}
289   *
290   * @see ByteBufBackedChannelOutboundInvokingHttpContentOutputStream
291   */
292  @Override
293  protected final AbstractChannelOutboundInvokingOutputStream<? extends Http2DataFrame> createOutputStream(final long contentLength,
294                                                                                                           final ContainerResponse containerResponse) {
295    if (contentLength == 0L) {
296      throw new IllegalArgumentException("contentLength == 0L");
297    }
298    return new ByteBufBackedChannelOutboundInvokingHttp2DataFrameOutputStream(this.getChannelHandlerContext(),
299                                                                              this.getFlushThreshold(),
300                                                                              false,
301                                                                              this.getByteBufCreator()) {
302      @Override
303      protected final ChannelPromise newPromise() {
304        final ChannelPromise returnValue = super.newPromise();
305        if (returnValue != null && !returnValue.isVoid()) {
306          returnValue.addListener(listener);
307        }
308        return returnValue;
309      }
310    };
311  }
312
313  /**
314   * Writes an appropriate failure message using the return value of
315   * the {@link #getChannelHandlerContext()} method.
316   *
317   * @param failureCause the {@link Throwable} responsible for this
318   * method's invocation; may be {@code null} in pathological cases;
319   * ignored by this implementation
320   *
321   * @exception NullPointerException if {@link
322   * #getChannelHandlerContext()} returns {@code null}
323   */
324  @Override
325  protected final void writeFailureMessage(final Throwable failureCause) {
326    final ChannelHandlerContext channelHandlerContext = Objects.requireNonNull(this.getChannelHandlerContext());
327    final ChannelPromise channelPromise = channelHandlerContext.newPromise();
328    assert channelPromise != null;
329    channelPromise.addListener(listener);
330    channelHandlerContext.write(new DefaultHttp2Headers().status(String.valueOf(Status.INTERNAL_SERVER_ERROR.getStatusCode())),
331                                channelPromise);
332  }
333
334}