All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.springframework.http.codec.multipart.PartEventHttpMessageReader Maven / Gradle / Ivy

/*
 * Copyright 2002-2022 the original author or authors.
 *
 * 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 org.springframework.http.codec.multipart;

import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import org.springframework.core.ResolvableType;
import org.springframework.core.codec.DecodingException;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferLimitException;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ReactiveHttpInputMessage;
import org.springframework.http.codec.HttpMessageReader;
import org.springframework.http.codec.LoggingCodecSupport;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;

/**
 * {@code HttpMessageReader} for parsing {@code "multipart/form-data"} requests
 * to a stream of {@link PartEvent} elements.
 *
 * @author Arjen Poutsma
 * @since 6.0
 * @see PartEvent
 */
public class PartEventHttpMessageReader extends LoggingCodecSupport implements HttpMessageReader {

	private int maxInMemorySize = 256 * 1024;

	private int maxHeadersSize = 10 * 1024;

	private Charset headersCharset = StandardCharsets.UTF_8;


	/**
	 * Get the {@link #setMaxInMemorySize configured} maximum in-memory size.
	 */
	public int getMaxInMemorySize() {
		return this.maxInMemorySize;
	}

	/**
	 * Configure the maximum amount of memory allowed for form fields.
	 * When the limit is exceeded, form fields parts are rejected with
	 * {@link DataBufferLimitException}.

	 * 

By default this is set to 256K. * @param maxInMemorySize the in-memory limit in bytes; if set to -1 the entire * contents will be stored in memory */ public void setMaxInMemorySize(int maxInMemorySize) { this.maxInMemorySize = maxInMemorySize; } /** * Configure the maximum amount of memory that is allowed per headers section of each part. * Defaults to 10K. * @param byteCount the maximum amount of memory for headers */ public void setMaxHeadersSize(int byteCount) { this.maxHeadersSize = byteCount; } /** * Set the character set used to decode headers. *

Defaults to UTF-8 as per RFC 7578. * @param headersCharset the charset to use for decoding headers * @see RFC-7578 Section 5.1 */ public void setHeadersCharset(Charset headersCharset) { Assert.notNull(headersCharset, "Charset must not be null"); this.headersCharset = headersCharset; } @Override public List getReadableMediaTypes() { return Collections.singletonList(MediaType.MULTIPART_FORM_DATA); } @Override public boolean canRead(ResolvableType elementType, @Nullable MediaType mediaType) { return PartEvent.class.equals(elementType.toClass()) && (mediaType == null || MediaType.MULTIPART_FORM_DATA.isCompatibleWith(mediaType)); } @Override public Mono readMono(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) { return Mono.error( new UnsupportedOperationException("Cannot read multipart request body into single PartEvent")); } @Override public Flux read(ResolvableType elementType, ReactiveHttpInputMessage message, Map hints) { return Flux.defer(() -> { byte[] boundary = MultipartUtils.boundary(message, this.headersCharset); if (boundary == null) { return Flux.error(new DecodingException("No multipart boundary found in Content-Type: \"" + message.getHeaders().getContentType() + "\"")); } return MultipartParser.parse(message.getBody(), boundary, this.maxHeadersSize, this.headersCharset) .windowUntil(t -> t instanceof MultipartParser.HeadersToken, true) .concatMap(tokens -> tokens.switchOnFirst((signal, flux) -> { if (signal.hasValue()) { MultipartParser.HeadersToken headersToken = (MultipartParser.HeadersToken) signal.get(); Assert.state(headersToken != null, "Signal should be headers token"); HttpHeaders headers = headersToken.headers(); Flux bodyTokens = flux.filter(t -> t instanceof MultipartParser.BodyToken) .cast(MultipartParser.BodyToken.class); return createEvents(headers, bodyTokens); } else { // complete or error signal return flux.cast(PartEvent.class); } })); }); } private Publisher createEvents(HttpHeaders headers, Flux bodyTokens) { if (MultipartUtils.isFormField(headers)) { Flux contents = bodyTokens.map(MultipartParser.BodyToken::buffer); return DataBufferUtils.join(contents, this.maxInMemorySize) .map(content -> { String value = content.toString(MultipartUtils.charset(headers)); DataBufferUtils.release(content); return DefaultPartEvents.form(headers, value); }) .switchIfEmpty(Mono.fromCallable(() -> DefaultPartEvents.form(headers))); } else if (headers.getContentDisposition().getFilename() != null) { return bodyTokens .map(body -> DefaultPartEvents.file(headers, body.buffer(), body.isLast())) .switchIfEmpty(Mono.fromCallable(() -> DefaultPartEvents.file(headers))); } else { return bodyTokens .map(body -> DefaultPartEvents.create(headers, body.buffer(), body.isLast())) .switchIfEmpty(Mono.fromCallable(() -> DefaultPartEvents.create(headers))); // empty body } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy