
reactor.netty.http.server.HttpServerOperations Maven / Gradle / Ivy
/*
* Copyright (c) 2011-Present VMware, Inc. or its affiliates, All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package reactor.netty.http.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.BiPredicate;
import java.util.function.Consumer;
import java.util.function.Function;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.TooLongFrameException;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaderValues;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMessage;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpUtil;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.codec.http.cookie.Cookie;
import io.netty.handler.codec.http.cookie.ServerCookieDecoder;
import io.netty.handler.codec.http.cookie.ServerCookieEncoder;
import io.netty.handler.codec.http2.HttpConversionUtil;
import io.netty.util.AsciiString;
import io.netty.util.ReferenceCountUtil;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscription;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.netty.Connection;
import reactor.netty.ConnectionObserver;
import reactor.netty.FutureMono;
import reactor.netty.NettyOutbound;
import reactor.netty.NettyPipeline;
import reactor.netty.channel.ChannelOperations;
import reactor.netty.http.Cookies;
import reactor.netty.http.HttpOperations;
import reactor.netty.http.websocket.WebsocketInbound;
import reactor.netty.http.websocket.WebsocketOutbound;
import reactor.util.Logger;
import reactor.util.Loggers;
import reactor.util.annotation.Nullable;
import reactor.util.context.Context;
import static io.netty.buffer.Unpooled.EMPTY_BUFFER;
import static reactor.netty.ReactorNetty.format;
import static reactor.netty.http.server.HttpServerState.REQUEST_DECODING_FAILED;
/**
* Conversion between Netty types and Reactor types ({@link HttpOperations}.
*
* @author Stephane Maldini1
*/
class HttpServerOperations extends HttpOperations
implements HttpServerRequest, HttpServerResponse {
final HttpResponse nettyResponse;
final HttpHeaders responseHeaders;
final Cookies cookieHolder;
final HttpRequest nettyRequest;
final String path;
final ConnectionInfo connectionInfo;
final ServerCookieEncoder cookieEncoder;
final ServerCookieDecoder cookieDecoder;
final BiPredicate compressionPredicate;
final BiFunction super Mono, ? super Connection, ? extends Mono> mapHandle;
Function super String, Map> paramsResolver;
HttpServerOperations(HttpServerOperations replaced) {
super(replaced);
this.cookieHolder = replaced.cookieHolder;
this.connectionInfo = replaced.connectionInfo;
this.responseHeaders = replaced.responseHeaders;
this.nettyResponse = replaced.nettyResponse;
this.paramsResolver = replaced.paramsResolver;
this.nettyRequest = replaced.nettyRequest;
this.path = replaced.path;
this.compressionPredicate = replaced.compressionPredicate;
this.cookieEncoder = replaced.cookieEncoder;
this.cookieDecoder = replaced.cookieDecoder;
this.mapHandle = replaced.mapHandle;
}
HttpServerOperations(Connection c,
ConnectionObserver listener,
@Nullable BiPredicate compressionPredicate,
HttpRequest nettyRequest,
@Nullable ConnectionInfo connectionInfo,
ServerCookieEncoder encoder,
ServerCookieDecoder decoder,
@Nullable BiFunction super Mono, ? super Connection, ? extends Mono> mapHandle) {
this(c, listener, compressionPredicate, nettyRequest, connectionInfo, encoder, decoder, mapHandle, true);
}
HttpServerOperations(Connection c,
ConnectionObserver listener,
@Nullable BiPredicate compressionPredicate,
HttpRequest nettyRequest,
@Nullable ConnectionInfo connectionInfo,
ServerCookieEncoder encoder,
ServerCookieDecoder decoder,
@Nullable BiFunction super Mono, ? super Connection, ? extends Mono> mapHandle,
boolean resolvePath) {
super(c, listener);
this.nettyRequest = nettyRequest;
if (resolvePath) {
this.path = resolvePath(nettyRequest.uri());
}
else {
this.path = null;
}
this.nettyResponse = new DefaultHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK);
this.responseHeaders = nettyResponse.headers();
this.responseHeaders.set(HttpHeaderNames.TRANSFER_ENCODING, HttpHeaderValues.CHUNKED);
this.compressionPredicate = compressionPredicate;
this.cookieHolder = Cookies.newServerRequestHolder(requestHeaders(), decoder);
this.connectionInfo = connectionInfo;
this.cookieEncoder = encoder;
this.cookieDecoder = decoder;
this.mapHandle = mapHandle;
}
@Override
public NettyOutbound sendHeaders() {
if (hasSentHeaders()) {
return this;
}
return then(Mono.empty());
}
@Override
public HttpServerOperations withConnection(Consumer super Connection> withConnection) {
Objects.requireNonNull(withConnection, "withConnection");
withConnection.accept(this);
return this;
}
@Override
protected HttpMessage newFullBodyMessage(ByteBuf body) {
HttpResponse res =
new DefaultFullHttpResponse(version(), status(), body);
if (!HttpMethod.HEAD.equals(method())) {
responseHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
if (!HttpResponseStatus.NOT_MODIFIED.equals(status())) {
if (HttpUtil.getContentLength(nettyResponse, -1) == -1) {
responseHeaders.setInt(HttpHeaderNames.CONTENT_LENGTH, body.readableBytes());
}
}
}
// For HEAD requests:
// - if there is Transfer-Encoding and Content-Length, Transfer-Encoding will be removed
// - if there is only Transfer-Encoding, it will be kept and not replaced by
// Content-Length: body.readableBytes()
// For HEAD requests, the I/O handler may decide to provide only the headers and complete
// the response. In that case body will be EMPTY_BUFFER and if we set Content-Length: 0,
// this will not be correct
// https://github.com/reactor/reactor-netty/issues/1333
else if (HttpUtil.getContentLength(nettyResponse, -1) != -1) {
responseHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
}
res.headers().set(responseHeaders);
return res;
}
@Override
public HttpServerResponse addCookie(Cookie cookie) {
if (!hasSentHeaders()) {
this.responseHeaders.add(HttpHeaderNames.SET_COOKIE,
cookieEncoder.encode(cookie));
}
else {
throw new IllegalStateException("Status and headers already sent");
}
return this;
}
@Override
public HttpServerResponse addHeader(CharSequence name, CharSequence value) {
if (!hasSentHeaders()) {
this.responseHeaders.add(name, value);
}
else {
throw new IllegalStateException("Status and headers already sent");
}
return this;
}
@Override
public HttpServerOperations chunkedTransfer(boolean chunked) {
if (!hasSentHeaders() && HttpUtil.isTransferEncodingChunked(nettyResponse) != chunked) {
responseHeaders.remove(HttpHeaderNames.TRANSFER_ENCODING);
HttpUtil.setTransferEncodingChunked(nettyResponse, chunked);
}
return this;
}
@Override
public Map> cookies() {
if (cookieHolder != null) {
return cookieHolder.getCachedCookies();
}
throw new IllegalStateException("request not parsed");
}
@Override
public HttpServerResponse header(CharSequence name, CharSequence value) {
if (!hasSentHeaders()) {
this.responseHeaders.set(name, value);
}
else {
throw new IllegalStateException("Status and headers already sent");
}
return this;
}
@Override
public HttpServerResponse headers(HttpHeaders headers) {
if (!hasSentHeaders()) {
this.responseHeaders.set(headers);
}
else {
throw new IllegalStateException("Status and headers already sent");
}
return this;
}
@Override
public boolean isKeepAlive() {
return HttpUtil.isKeepAlive(nettyRequest);
}
@Override
public boolean isWebsocket() {
return get(channel()) instanceof WebsocketServerOperations;
}
final boolean isHttp2() {
return requestHeaders().contains(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text());
}
@Override
public HttpServerResponse keepAlive(boolean keepAlive) {
HttpUtil.setKeepAlive(nettyResponse, keepAlive);
return this;
}
@Override
public HttpMethod method() {
return nettyRequest.method();
}
@Override
@Nullable
public String param(CharSequence key) {
Objects.requireNonNull(key, "key");
Map params = null;
if (paramsResolver != null) {
params = this.paramsResolver.apply(uri());
}
return null != params ? params.get(key.toString()) : null;
}
@Override
@Nullable
public Map params() {
return null != paramsResolver ? paramsResolver.apply(uri()) : null;
}
@Override
public HttpServerRequest paramsResolver(Function super String, Map> paramsResolver) {
this.paramsResolver = paramsResolver;
return this;
}
@Override
public Flux> receiveObject() {
// Handle the 'Expect: 100-continue' header if necessary.
// TODO: Respond with 413 Request Entity Too Large
// and discard the traffic or close the connection.
// No need to notify the upstream handlers - just log.
// If decoding a response, just throw an error.
if (HttpUtil.is100ContinueExpected(nettyRequest)) {
return FutureMono.deferFuture(() -> {
if(!hasSentHeaders()) {
return channel().writeAndFlush(CONTINUE);
}
return channel().newSucceededFuture();
})
.thenMany(super.receiveObject());
}
else {
return super.receiveObject();
}
}
@Override
@Nullable
public InetSocketAddress hostAddress() {
if (connectionInfo != null) {
return this.connectionInfo.getHostAddress();
}
else {
return null;
}
}
@Override
@Nullable
public InetSocketAddress remoteAddress() {
if (connectionInfo != null) {
return this.connectionInfo.getRemoteAddress();
}
else {
return null;
}
}
@Override
public HttpHeaders requestHeaders() {
if (nettyRequest != null) {
return nettyRequest.headers();
}
throw new IllegalStateException("request not parsed");
}
@Override
public String scheme() {
return this.connectionInfo.getScheme();
}
@Override
public HttpHeaders responseHeaders() {
return responseHeaders;
}
@Override
public Mono send() {
if (markSentHeaderAndBody()) {
HttpMessage response = newFullBodyMessage(EMPTY_BUFFER);
return FutureMono.deferFuture(() -> channel().writeAndFlush(response));
}
else {
return Mono.empty();
}
}
@Override
public NettyOutbound sendFile(Path file) {
try {
return sendFile(file, 0L, Files.size(file));
}
catch (IOException e) {
if (log.isDebugEnabled()) {
log.debug(format(channel(), "Path not resolved"), e);
}
return then(sendNotFound());
}
}
@Override
public Mono sendNotFound() {
return this.status(HttpResponseStatus.NOT_FOUND)
.send();
}
@Override
public Mono sendRedirect(String location) {
Objects.requireNonNull(location, "location");
return this.status(HttpResponseStatus.FOUND)
.header(HttpHeaderNames.LOCATION, location)
.send();
}
/**
* @return the Transfer setting SSE for this http connection (e.g. event-stream)
*/
@Override
public HttpServerResponse sse() {
header(HttpHeaderNames.CONTENT_TYPE, EVENT_STREAM);
return this;
}
@Override
public HttpResponseStatus status() {
return this.nettyResponse.status();
}
@Override
public HttpServerResponse status(HttpResponseStatus status) {
if (!hasSentHeaders()) {
this.nettyResponse.setStatus(status);
}
else {
throw new IllegalStateException("Status and headers already sent");
}
return this;
}
@Override
public Mono sendWebsocket(
BiFunction super WebsocketInbound, ? super WebsocketOutbound, ? extends Publisher> websocketHandler,
WebsocketServerSpec configurer) {
return withWebsocketSupport(uri(), configurer, websocketHandler);
}
@Override
public String uri() {
if (nettyRequest != null) {
return nettyRequest.uri();
}
throw new IllegalStateException("request not parsed");
}
@Override
public String fullPath() {
if (path != null) {
return path;
}
throw new IllegalStateException("request not parsed");
}
@Override
public HttpVersion version() {
if (nettyRequest != null) {
return nettyRequest.protocolVersion();
}
throw new IllegalStateException("request not parsed");
}
@Override
public HttpServerResponse compression(boolean compress) {
if (!compress) {
removeHandler(NettyPipeline.CompressionHandler);
}
else if (channel().pipeline()
.get(NettyPipeline.CompressionHandler) == null) {
SimpleCompressionHandler handler = new SimpleCompressionHandler();
try {
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy