Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions vertx-core/src/main/asciidoc/http.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,17 @@ case you can just do:
{@link examples.HttpExamples#serverResponseSendFileFromOffset}
----

When the file cannot be transferred by the operating system, e.g. the connection is encrypted or the protocol is HTTP/2
or HTTP/3, the file is read and written to the response chunk per chunk. The size of these chunks is set with
{@link io.vertx.core.http.HttpServerConfig#setSendFileChunkSize} and applies to every file sent by the server. The
default value is a conservative one, a larger value can achieve a better throughput at the cost of a larger memory
footprint.

[source,$lang]
----
{@link examples.HttpExamples#serverResponseSendFileChunkSize}
----

==== Piping responses

The server response is a {@link io.vertx.core.streams.WriteStream} so you can pipe to it from any
Expand Down
11 changes: 11 additions & 0 deletions vertx-core/src/main/asciidoc/tcp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,17 @@ classpath resolution or disabling it.
{@link examples.TcpExamples#sendingAFile}
----

When the file cannot be transferred by the operating system, e.g. the connection is encrypted, the file is read and
written to the socket chunk per chunk. The size of these chunks is set with
{@link io.vertx.core.net.TcpServerConfig#setSendFileChunkSize} and applies to every file sent by the sockets the server
accepts. The default value is a conservative one, a larger value can achieve a better throughput at the cost of a
larger memory footprint.

[source,$lang]
----
{@link examples.TcpExamples#sendFileChunkSize}
----

=== Streaming sockets

Instances of {@link io.vertx.core.net.NetSocket} are also {@link io.vertx.core.streams.ReadStream} and
Expand Down
9 changes: 9 additions & 0 deletions vertx-core/src/main/java/examples/HttpExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,15 @@ public void serverResponseSendFileFromOffset(Vertx vertx) {
}).listen(8080);
}

public void serverResponseSendFileChunkSize(Vertx vertx) {
HttpServerConfig config = new HttpServerConfig()
.setSendFileChunkSize(32 * 1024);

vertx.createHttpServer(config).requestHandler(request -> {
request.response().sendFile("web/mybigfile.txt");
}).listen(8080);
}

public void serverResponsePiping(Vertx vertx) {
vertx.createHttpServer().requestHandler(request -> {
HttpServerResponse response = request.response();
Expand Down
9 changes: 9 additions & 0 deletions vertx-core/src/main/java/examples/TcpExamples.java
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,15 @@ public void sendingAFile(StreamChannel socket) {
.onFailure(err -> System.out.println("Could not send file: " + err.getMessage()));
}

public void sendFileChunkSize(Vertx vertx) {
TcpServerConfig config = new TcpServerConfig()
.setSendFileChunkSize(32 * 1024);

vertx.createNetServer(config).connectHandler(socket -> {
socket.sendFile("myfile.dat");
}).listen(1234, "localhost");
}

public void gracefullyShuttingDownAServer(NetServer server) {
server
.shutdown()
Expand Down
34 changes: 34 additions & 0 deletions vertx-core/src/main/java/io/vertx/core/http/HttpServerConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import io.vertx.codegen.annotations.DataObject;
import io.vertx.codegen.annotations.GenIgnore;
import io.vertx.codegen.annotations.Unstable;
import io.vertx.core.impl.Arguments;
import io.vertx.core.net.*;

import java.time.Duration;
Expand Down Expand Up @@ -42,6 +43,11 @@ public class HttpServerConfig {
public static final long DEFAULT_QUIC_INITIAL_MAX_STREAM_BIDI = 256L;
public static final long DEFAULT_QUIC_INITIAL_MAX_STREAM_UNI = 3L;

/**
* Default chunk size used to send a file = 8192
*/
public static final int DEFAULT_SEND_FILE_CHUNK_SIZE = 8192;

private static QuicServerConfig defaultQuicConfig() {
QuicServerConfig config = new QuicServerConfig();
config.getTransportConfig().setInitialMaxData(DEFAULT_QUIC_INITIAL_MAX_DATA);
Expand All @@ -65,6 +71,7 @@ private static TcpServerConfig defaultTcpServerConfig() {
private QueryParamDecoderConfig queryParamDecoderConfig;
private boolean handle100ContinueAutomatically;
private boolean strictThreadMode;
private int sendFileChunkSize;
private ObservabilityConfig observabilityConfig;
private Http1ServerConfig http1Config;
private Http2ServerConfig http2Config;
Expand Down Expand Up @@ -136,6 +143,7 @@ public HttpServerConfig(HttpServerOptions options) {
this.queryParamDecoderConfig = queryParamDecoderConfig;
this.handle100ContinueAutomatically = options.isHandle100ContinueAutomatically();
this.strictThreadMode = options.getStrictThreadMode();
this.sendFileChunkSize = DEFAULT_SEND_FILE_CHUNK_SIZE;
this.observabilityConfig = observabilityConfig;
this.http1Config = new Http1ServerConfig(options.getHttp1Config());
this.http2Config = new Http2ServerConfig(options.getHttp2Config());
Expand All @@ -154,6 +162,7 @@ public HttpServerConfig() {
this.queryParamDecoderConfig = null;
this.handle100ContinueAutomatically = HttpServerOptions.DEFAULT_HANDLE_100_CONTINE_AUTOMATICALLY;
this.strictThreadMode = HttpServerOptions.DEFAULT_STRICT_THREAD_MODE_STRICT;
this.sendFileChunkSize = DEFAULT_SEND_FILE_CHUNK_SIZE;
this.observabilityConfig = null;
this.http1Config = null;
this.http2Config = null;
Expand All @@ -175,6 +184,7 @@ public HttpServerConfig(HttpServerConfig other) {
this.queryParamDecoderConfig = other.queryParamDecoderConfig != null ? new QueryParamDecoderConfig(other.queryParamDecoderConfig) : null;
this.handle100ContinueAutomatically = other.handle100ContinueAutomatically;
this.strictThreadMode = other.strictThreadMode;
this.sendFileChunkSize = other.sendFileChunkSize;
this.observabilityConfig = other.observabilityConfig != null ? new ObservabilityConfig(other.observabilityConfig) : null;
this.http1Config = other.http1Config != null ? new Http1ServerConfig(other.http1Config) : null;
this.http2Config = other.http2Config != null ? new Http2ServerConfig(other.http2Config) : null;
Expand Down Expand Up @@ -442,6 +452,30 @@ public HttpServerConfig setStrictThreadMode(boolean strictThreadMode) {
return this;
}

/**
* @return the chunk size, in bytes, used to send files
*/
public int getSendFileChunkSize() {
return sendFileChunkSize;
}

/**
* <p>Set the chunk size, in bytes, used by {@link HttpServerResponse#sendFile} to read the file and write it to the
* response.</p>
*
* <p>This setting only applies when the file cannot be transferred with the zero-copy (file region) mechanism, e.g.
* when the connection is encrypted or when the protocol is HTTP/2 or HTTP/3. The default value is a conservative
* one, a larger value can achieve a better throughput at the cost of a larger memory footprint.</p>
*
* @param sendFileChunkSize the chunk size in bytes
* @return a reference to this, so the API can be used fluently
*/
public HttpServerConfig setSendFileChunkSize(int sendFileChunkSize) {
Arguments.require(sendFileChunkSize > 0, "sendFileChunkSize must be > 0");
this.sendFileChunkSize = sendFileChunkSize;
return this;
}

/**
* @return the server observability config.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ public interface HttpServerConnection extends HttpConnection {

boolean supportsSendFile();

/**
* @return the chunk size used to send a file when the zero-copy (file region) mechanism cannot be used
*/
int sendFileChunkSize();

/**
* @return the connection context
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,7 @@ private Future<Void> sendAsyncFile(String filename, long offset, long length) {
return HttpUtils
.resolveFile(context, filename, offset, length)
.compose(file -> {
file.setReadBufferSize(conn.sendFileChunkSize());
long fileLength = file.getReadLength();
long contentLength = Math.min(length, fileLength);
// fail early before status code/headers are written to the response
Expand Down Expand Up @@ -611,10 +612,11 @@ private Future<Void> sendFileInternal(long offset, long length, long size, Rando
if (file != null) {
channel = file.getChannel();
}
int chunkSize = conn.sendFileChunkSize();
if (close) {
chunkedFile = new ChunkedNioFile(channel, actualOffset, actualLength, 8192);
chunkedFile = new ChunkedNioFile(channel, actualOffset, actualLength, chunkSize);
} else {
chunkedFile = new UncloseableChunkedNioFile(channel, actualOffset, actualLength);
chunkedFile = new UncloseableChunkedNioFile(channel, actualOffset, actualLength, chunkSize);
}
} catch (IOException e) {
return context.failedFuture(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
package io.vertx.core.http.impl.http1;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
Expand Down Expand Up @@ -47,6 +48,7 @@
import io.vertx.core.spi.tracing.VertxTracer;
import io.vertx.core.tracing.TracingPolicy;

import java.nio.channels.FileChannel;
import java.time.Duration;
import java.util.function.Supplier;

Expand Down Expand Up @@ -82,6 +84,7 @@ public class Http1ServerConnection extends Http1Connection implements HttpServer
private final int maxFormBufferedBytes;
private final QueryParamDecoder queryParamDecoder;
private final Http1ServerConfig serverConfig;
private final int sendFileChunkSize;
private final boolean registerWebSocketWriteHandlers;
private final WebSocketServerConfig webSocketConfig;
private final ServerSSLOptions sslOptions;
Expand Down Expand Up @@ -109,6 +112,7 @@ public Http1ServerConnection(ThreadingModel threadingModel,
int maxFormBufferedBytes,
QueryParamDecoderConfig queryParamDecoderConfig,
Http1ServerConfig serverConfig,
int sendFileChunkSize,
boolean registerWebSocketWriteHandlers,
WebSocketServerConfig webSocketConfig,
ChannelHandlerContext chctx,
Expand All @@ -125,6 +129,7 @@ public Http1ServerConnection(ThreadingModel threadingModel,
this.maxFormBufferedBytes = maxFormBufferedBytes;
this.queryParamDecoder = new QueryParamDecoder(queryParamDecoderConfig);
this.serverConfig = serverConfig;
this.sendFileChunkSize = sendFileChunkSize;
this.registerWebSocketWriteHandlers = registerWebSocketWriteHandlers;
this.webSocketConfig = webSocketConfig;
this.sslContextManager = sslContextManager;
Expand Down Expand Up @@ -172,6 +177,16 @@ public boolean supportsSendFile() {
return true;
}

@Override
public int sendFileChunkSize() {
return sendFileChunkSize;
}

@Override
public ChannelFuture sendFile(FileChannel fc, long offset, long length) {
return sendFile(fc, offset, length, sendFileChunkSize);
}

TracingPolicy tracingPolicy() {
return tracingPolicy;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ public class Http2CodecServerChannelInitializer implements Http2ServerChannelIni
private final boolean useDecompression;
private final boolean useCompression;
private final Http2ServerConfig config;
private final int sendFileChunkSize;
private final CompressionManager compressionManager;
private final Supplier<ContextInternal> streamContextSupplier;
private final Handler<HttpServerConnection> connectionHandler;
Expand All @@ -49,6 +50,7 @@ public Http2CodecServerChannelInitializer(HttpServerConnectionInitializer initia
boolean useDecompression,
boolean useCompression,
Http2ServerConfig config,
int sendFileChunkSize,
CompressionManager compressionManager,
Supplier<ContextInternal> streamContextSupplier,
Handler<HttpServerConnection> connectionHandler,
Expand All @@ -61,6 +63,7 @@ public Http2CodecServerChannelInitializer(HttpServerConnectionInitializer initia
this.useDecompression = useDecompression;
this.useCompression = useCompression;
this.config = config;
this.sendFileChunkSize = sendFileChunkSize;
this.compressionManager = compressionManager;
this.streamContextSupplier = streamContextSupplier;
this.connectionHandler = connectionHandler;
Expand Down Expand Up @@ -95,7 +98,7 @@ public VertxHttp2ConnectionHandler<Http2ServerConnectionImpl> buildHttp2Connecti
.connectionFactory(connHandler -> {
Http2ServerConnectionImpl conn = new Http2ServerConnectionImpl(ctx, streamContextSupplier, connHandler,
compressionManager != null ? compressionManager::determineEncoding : null, tracingPolicy, httpMetrics,
transportMetrics);
transportMetrics, sendFileChunkSize);
conn.metric(metric);
return conn;
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ public class Http2ServerConnectionImpl extends Http2ConnectionImpl implements Ht
private final Function<String, String> encodingDetector;
private final Supplier<ContextInternal> streamContextSupplier;
private final VertxHttp2ConnectionHandler handler;
private final int sendFileChunkSize;

private Handler<HttpServerStream> streamHandler;
private int concurrentStreams;
Expand All @@ -67,7 +68,8 @@ public Http2ServerConnectionImpl(
Function<String, String> encodingDetector,
TracingPolicy tracingPolicy,
HttpServerMetrics<?, ?> httpMetrics,
TransportMetrics<?> transportMetrics) {
TransportMetrics<?> transportMetrics,
int sendFileChunkSize) {
super(context, connHandler);

this.tracingPolicy = tracingPolicy;
Expand All @@ -76,6 +78,7 @@ public Http2ServerConnectionImpl(
this.httpMetrics = httpMetrics;
this.transportMetrics = transportMetrics;
this.handler = connHandler;
this.sendFileChunkSize = sendFileChunkSize;
}

@Override
Expand Down Expand Up @@ -274,4 +277,9 @@ protected io.vertx.core.Future<Void> updateSettings(Http2Settings settingsUpdate
public boolean supportsSendFile() {
return false;
}

@Override
public int sendFileChunkSize() {
return sendFileChunkSize;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public Http2MultiplexServerChannelInitializer(ContextInternal context,
int rstFloodMaxRstFramePerWindow,
int rstFloodWindowDuration,
int maxSmallContinuationFrames,
int sendFileChunkSize,
boolean logEnabled) {
Http2MultiplexConnectionFactory connectionFactory = (handler, chctx) -> {
Http2MultiplexServerConnection connection = new Http2MultiplexServerConnection(
Expand All @@ -62,7 +63,8 @@ public Http2MultiplexServerChannelInitializer(ContextInternal context,
chctx,
context,
streamContextSupplier,
connectionHandler);
connectionHandler,
sendFileChunkSize);
connection.metric(connectionMetric);
return connection;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ public class Http2MultiplexServerConnection extends Http2MultiplexConnection<Htt
private final TransportMetrics<?> transportMetrics;
private final Supplier<ContextInternal> streamContextSupplier;
private final Handler<HttpServerConnection> connectionHandler;
private final int sendFileChunkSize;
private Handler<HttpServerStream> streamHandler;

public Http2MultiplexServerConnection(Http2MultiplexHandler handler,
Expand All @@ -47,21 +48,28 @@ public Http2MultiplexServerConnection(Http2MultiplexHandler handler,
ChannelHandlerContext chctx,
ContextInternal context,
Supplier<ContextInternal> streamContextSupplier,
Handler<HttpServerConnection> connectionHandler) {
Handler<HttpServerConnection> connectionHandler,
int sendFileChunkSize) {
super(handler, transportMetrics, chctx, context);

this.httpMetrics = httpMetrics;
this.transportMetrics = transportMetrics;
this.compressionManager = compressionManager;
this.streamContextSupplier = streamContextSupplier;
this.connectionHandler = connectionHandler;
this.sendFileChunkSize = sendFileChunkSize;
}

@Override
public Headers<CharSequence, CharSequence, ?> newHeaders() {
return new DefaultHttp2Headers();
}

@Override
public int sendFileChunkSize() {
return sendFileChunkSize;
}

@Override
public Http2ServerConnection streamHandler(Handler<HttpServerStream> handler) {
this.streamHandler = handler;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,19 @@ public class Http3ServerConnection extends Http3Connection implements HttpServer

private final Supplier<ContextInternal> streamContextProvider;
private final HttpServerMetrics<?, ?> httpMetrics;
private final int sendFileChunkSize;
private Handler<HttpServerStream> streamHandler;

public Http3ServerConnection(QuicConnectionInternal connection,
Http3Settings localSettings,
HttpServerMetrics<?, ?> httpMetrics,
Http3FrameLogger frameLogger) {
Http3FrameLogger frameLogger,
int sendFileChunkSize) {
super(connection, localSettings, frameLogger);

this.streamContextProvider = connection.context()::duplicate;
this.httpMetrics = httpMetrics;
this.sendFileChunkSize = sendFileChunkSize;
}

void handleRequestStream(QuicStreamInternal quicStream) {
Expand Down Expand Up @@ -110,6 +113,11 @@ public boolean supportsSendFile() {
return false;
}

@Override
public int sendFileChunkSize() {
return sendFileChunkSize;
}

@Override
public ContextInternal context() {
return context;
Expand Down
Loading