Skip to content

Commit

Permalink
Add request temp file buffer (#3479)
Browse files Browse the repository at this point in the history
  • Loading branch information
Milkdove committed Jan 7, 2025
1 parent cf916f3 commit 15e2f61
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 3 deletions.
4 changes: 3 additions & 1 deletion docs/modules/ROOT/partials/_configprops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

|spring.cloud.gateway.default-filters | | List of filter definitions that are applied to every route.
|spring.cloud.gateway.discovery.locator.enabled | `+++false+++` | Flag that enables DiscoveryClient gateway integration.
|spring.cloud.gateway.discovery.locator.filters | |
|spring.cloud.gateway.discovery.locator.filters | |
|spring.cloud.gateway.discovery.locator.include-expression | `+++true+++` | SpEL expression that will evaluate whether to include a service in gateway integration or not, defaults to: true.
|spring.cloud.gateway.discovery.locator.lower-case-service-id | `+++false+++` | Option to lower case serviceId in predicates and filters, defaults to false. Useful with eureka when it automatically uppercases serviceId. so MYSERIVCE, would match /myservice/**
|spring.cloud.gateway.discovery.locator.predicates | |
Expand Down Expand Up @@ -130,6 +130,8 @@
|spring.cloud.gateway.mvc.routes-map | | Map of Routes.
|spring.cloud.gateway.mvc.streaming-buffer-size | `+++16384+++` | Buffer size for streaming media mime-types.
|spring.cloud.gateway.mvc.streaming-media-types | | Mime-types that are streaming.
|spring.cloud.gateway.mvc.file-buffer-enabled | true | Enables the temp file buffer.
|spring.cloud.gateway.mvc.file-buffer-size-threshold | 1MB | The size threshold which a request will be written to disk.
|spring.cloud.gateway.mvc.transfer-encoding-normalization-request-headers-filter.enabled | `+++true+++` | Enables the transfer-encoding-normalization-request-headers-filter.
|spring.cloud.gateway.mvc.weight-calculator-filter.enabled | `+++true+++` | Enables the weight-calculator-filter.
|spring.cloud.gateway.mvc.x-forwarded-request-headers-filter.enabled | `+++true+++` | If the XForwardedHeadersFilter is enabled.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,9 @@ private int copyResponseBodyWithFlushing(InputStream inputStream, OutputStream o
return totalReadBytes;
}


protected boolean isWriteClientBodyToFile(Request request) {
return properties.isFileBufferEnabled() && request.getServerRequest().servletRequest().getContentLength() >= properties.getFileBufferSizeThreshold().toBytes();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package org.springframework.cloud.gateway.server.mvc.common;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
Expand Down Expand Up @@ -92,6 +93,11 @@ public abstract class MvcUtils {
*/
public static final String WEIGHT_ATTR = qualify("routeWeight");

/**
* Client request body temp file.
*/
public static final String CLIENT_BODY_TMP_ATTR = qualify("clientBodyTempFile");

private MvcUtils() {
}

Expand Down Expand Up @@ -242,6 +248,12 @@ public static void setRequestUrl(ServerRequest request, URI url) {
request.servletRequest().setAttribute(GATEWAY_REQUEST_URL_ATTR, url);
}

public static void setBodyTempFile(ServerRequest request, File file) {
request.attributes().put(GATEWAY_ROUTE_ID_ATTR, file);
request.servletRequest().setAttribute(GATEWAY_ROUTE_ID_ATTR, file);
}


private record ByteArrayInputMessage(ServerRequest request, ByteArrayInputStream body) implements HttpInputMessage {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.springframework.boot.context.properties.DeprecatedConfigurationProperty;
import org.springframework.core.style.ToStringCreator;
import org.springframework.http.MediaType;
import org.springframework.util.unit.DataSize;

@ConfigurationProperties(GatewayMvcProperties.PREFIX)
public class GatewayMvcProperties {
Expand Down Expand Up @@ -66,6 +67,16 @@ public class GatewayMvcProperties {
*/
private int streamingBufferSize = 16384;

/**
* Temp file buffer for request.
*/
private boolean fileBufferEnabled = true;

/**
* The size threshold which a request will be written to disk.
*/
private DataSize fileBufferSizeThreshold = DataSize.ofMegabytes(1L);

public List<RouteProperties> getRoutes() {
return routes;
}
Expand Down Expand Up @@ -102,13 +113,31 @@ public void setStreamingBufferSize(int streamingBufferSize) {
this.streamingBufferSize = streamingBufferSize;
}

public boolean isFileBufferEnabled() {
return fileBufferEnabled;
}

public void setFileBufferEnabled(boolean fileBufferEnabled) {
this.fileBufferEnabled = fileBufferEnabled;
}

public DataSize getFileBufferSizeThreshold() {
return fileBufferSizeThreshold;
}

public void setFileBufferSizeThreshold(DataSize fileBufferSizeThreshold) {
this.fileBufferSizeThreshold = fileBufferSizeThreshold;
}

@Override
public String toString() {
return new ToStringCreator(this).append("httpClient", httpClient)
.append("routes", routes)
.append("routesMap", routesMap)
.append("streamingMediaTypes", streamingMediaTypes)
.append("streamingBufferSize", streamingBufferSize)
.append("fileBufferEnabled", fileBufferEnabled)
.append("fileBufferSizeThreshold", fileBufferSizeThreshold)
.toString();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,18 @@

package org.springframework.cloud.gateway.server.mvc.handler;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;

import org.springframework.cloud.gateway.server.mvc.common.AbstractProxyExchange;
import org.springframework.cloud.gateway.server.mvc.common.MvcUtils;
import org.springframework.cloud.gateway.server.mvc.config.GatewayMvcProperties;
import org.springframework.core.io.FileSystemResource;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;
import org.springframework.web.client.RestClient;
Expand All @@ -50,9 +54,25 @@ public ServerResponse exchange(Request request) {
.uri(request.getUri())
.headers(httpHeaders -> httpHeaders.putAll(request.getHeaders()));
if (isBodyPresent(request)) {
requestSpec.body(outputStream -> copyBody(request, outputStream));
if (isWriteClientBodyToFile(request)) {
requestSpec.body(copyClientBodyToFile(request));
}
else {
requestSpec.body(outputStream -> copyBody(request, outputStream));
}
}
return requestSpec.exchange((clientRequest, clientResponse) -> {
ServerResponse serverResponse = doExchange(request, clientResponse);
clearTempFileIfExist(request);
return serverResponse;
}, false);
}

private void clearTempFileIfExist(Request request) {
File tempFile = MvcUtils.getAttribute(request.getServerRequest(), MvcUtils.CLIENT_BODY_TMP_ATTR);
if (tempFile != null && tempFile.exists()) {
tempFile.delete();
}
return requestSpec.exchange((clientRequest, clientResponse) -> doExchange(request, clientResponse), false);
}

private static boolean isBodyPresent(Request request) {
Expand All @@ -68,6 +88,23 @@ private static int copyBody(Request request, OutputStream outputStream) throws I
return StreamUtils.copy(request.getServerRequest().servletRequest().getInputStream(), outputStream);
}

private static FileSystemResource copyClientBodyToFile(Request request) {
File bodyTempFile = null;
try {
// TODO: customize temp dir
bodyTempFile = File.createTempFile("gateway_client_body", ".tmp");
Files.copy(request.getServerRequest().servletRequest().getInputStream(), bodyTempFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
catch (IOException e) {
if (bodyTempFile != null && bodyTempFile.exists()) {
bodyTempFile.delete();
}
throw new UncheckedIOException(e);
}
MvcUtils.setBodyTempFile(request.getServerRequest(), bodyTempFile);
return new FileSystemResource(bodyTempFile);
}

private ServerResponse doExchange(Request request, ClientHttpResponse clientResponse) throws IOException {
InputStream body = clientResponse.getBody();
// put the body input stream in a request attribute so filters can read it.
Expand Down

0 comments on commit 15e2f61

Please sign in to comment.