reactor.netty.http.client.HttpClientFormEncoder Maven / Gradle / Ivy
Show all versions of reactor-netty-http Show documentation
/*
* Copyright (c) 2011-2023 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.client;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.regex.Pattern;
import io.netty.buffer.ByteBufAllocator;
import io.netty.handler.codec.http.HttpContent;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.multipart.DiskAttribute;
import io.netty.handler.codec.http.multipart.DiskFileUpload;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpDataFactory;
import io.netty.handler.codec.http.multipart.HttpPostRequestEncoder;
import io.netty.handler.stream.ChunkedInput;
import reactor.core.Exceptions;
import reactor.core.publisher.Sinks;
import reactor.util.annotation.Nullable;
/**
* Modified {@link io.netty.handler.codec.http.multipart.HttpPostRequestEncoder} for
* optional filename and builder support
*
* This encoder will help to encode Request for a FORM as POST.
*/
final class HttpClientFormEncoder extends HttpPostRequestEncoder
implements ChunkedInput, Runnable, HttpClientForm {
final Sinks.Many progressSink;
final HttpRequest request;
boolean needNewEncoder;
HttpDataFactory newFactory;
boolean cleanOnTerminate;
Charset newCharset;
boolean newMultipart;
EncoderMode newMode;
/**
* Constructs a new {@link HttpClientFormEncoder}.
*
* @param factory the factory used to create InterfaceHttpData
* @param request the request to encode
* @param multipart True if the FORM is a ENCTYPE="multipart/form-data"
* @param charset the charset to use as default
* @param encoderMode the mode for the encoder to use. See {@link EncoderMode} for the
* details.
*
* @throws NullPointerException for request or charset or factory
* @throws ErrorDataEncoderException if the request is not a POST
*/
HttpClientFormEncoder(HttpDataFactory factory,
HttpRequest request,
boolean multipart,
Charset charset,
EncoderMode encoderMode) throws ErrorDataEncoderException {
super(factory, request, multipart, charset, encoderMode);
this.newCharset = charset;
this.request = request;
this.cleanOnTerminate = true;
this.progressSink = Sinks.many().unicast().onBackpressureError();
this.newMode = encoderMode;
this.newFactory = factory;
this.newMultipart = multipart;
}
@Override
public HttpContent readChunk(ByteBufAllocator allocator) throws Exception {
HttpContent c = super.readChunk(allocator);
if (c == null) {
progressSink.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST);
}
else {
progressSink.emitNext(progress(), Sinks.EmitFailureHandler.FAIL_FAST);
if (isEndOfInput()) {
progressSink.emitComplete(Sinks.EmitFailureHandler.FAIL_FAST);
}
}
return c;
}
@Override
public HttpClientForm attr(String name, String value) {
try {
addBodyAttribute(name, value);
}
catch (ErrorDataEncoderException e) {
throw Exceptions.propagate(e);
}
return this;
}
@Override
public HttpClientForm charset(Charset charset) {
this.newCharset = Objects.requireNonNull(charset, "charset");
this.needNewEncoder = true;
return this;
}
@Override
public HttpClientForm cleanOnTerminate(boolean clean) {
this.cleanOnTerminate = clean;
return this;
}
@Override
public HttpClientForm factory(HttpDataFactory factory) {
if (!getBodyListAttributes().isEmpty()) {
throw new IllegalStateException("Cannot set a new HttpDataFactory after " +
"starting appending Parts, call factory(f) at the earliest occasion" +
" offered");
}
this.newFactory = Objects.requireNonNull(factory, "factory");
this.needNewEncoder = true;
return this;
}
@Override
public HttpClientForm file(String name, File file) {
file(name, file, null);
return this;
}
@Override
public HttpClientForm file(String name, InputStream inputStream) {
file(name, inputStream, null);
return this;
}
@Override
public HttpClientForm file(String name,
String filename,
File file,
@Nullable String contentType) {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(file, "file");
Objects.requireNonNull(filename, "filename");
String scontentType = contentType;
if (contentType == null) {
scontentType = DEFAULT_BINARY_CONTENT_TYPE;
}
FileUpload fileUpload = newFactory.createFileUpload(request,
name,
filename,
scontentType,
DEFAULT_TRANSFER_ENCODING,
null,
file.length());
try {
fileUpload.setContent(file);
addBodyHttpData(fileUpload);
}
catch (ErrorDataEncoderException e) {
throw Exceptions.propagate(e);
}
catch (IOException e) {
throw Exceptions.propagate(new ErrorDataEncoderException(e));
}
return this;
}
@Override
public HttpClientForm file(String name,
String filename,
InputStream stream,
@Nullable String contentType) {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(stream, "stream");
try {
String scontentType = contentType;
if (contentType == null) {
scontentType = DEFAULT_BINARY_CONTENT_TYPE;
}
FileUpload fileUpload = newFactory.createFileUpload(request,
name,
filename,
scontentType,
DEFAULT_TRANSFER_ENCODING,
newCharset,
-1);
fileUpload.setMaxSize(-1);
fileUpload.setContent(stream);
addBodyHttpData(fileUpload);
}
catch (ErrorDataEncoderException e) {
throw Exceptions.propagate(e);
}
catch (IOException e) {
throw Exceptions.propagate(new ErrorDataEncoderException(e));
}
return this;
}
@Override
public HttpClientForm files(String name,
File[] files,
String[] contentTypes) {
for (int i = 0; i < files.length; i++) {
file(name, files[i], contentTypes[i]);
}
return this;
}
@Override
public HttpClientForm files(String name,
File[] files,
String[] contentTypes,
boolean[] textFiles) {
try {
addBodyFileUploads(name, files, contentTypes, textFiles);
}
catch (ErrorDataEncoderException e) {
throw Exceptions.propagate(e);
}
return this;
}
@Override
public HttpClientForm encoding(EncoderMode mode) {
this.newMode = Objects.requireNonNull(mode, "mode");
this.needNewEncoder = true;
return this;
}
@Override
public HttpClientForm multipart(boolean isMultipart) {
this.needNewEncoder = isChunked() != isMultipart;
this.newMultipart = isMultipart;
return this;
}
@Override
public HttpClientForm textFile(String name, File file) {
textFile(name, file, null);
return this;
}
@Override
public HttpClientForm textFile(String name, File file, @Nullable String contentType) {
try {
addBodyFileUpload(name, file, contentType, true);
}
catch (ErrorDataEncoderException e) {
throw Exceptions.propagate(e);
}
return this;
}
@Override
public HttpClientForm textFile(String name, InputStream stream) {
textFile(name, stream, null);
return this;
}
@Override
public HttpClientForm textFile(String name,
InputStream stream,
@Nullable String contentType) {
Objects.requireNonNull(name, "name");
Objects.requireNonNull(stream, "stream");
try {
String scontentType = contentType;
if (contentType == null) {
scontentType = DEFAULT_TEXT_CONTENT_TYPE;
}
FileUpload fileUpload = newFactory.createFileUpload(request,
name,
"",
scontentType,
null,
newCharset,
-1);
fileUpload.setMaxSize(-1);
fileUpload.setContent(stream);
addBodyHttpData(fileUpload);
}
catch (ErrorDataEncoderException e) {
throw Exceptions.propagate(e);
}
catch (IOException e) {
throw Exceptions.propagate(new ErrorDataEncoderException(e));
}
return this;
}
@Override
public void run() {
cleanFiles();
}
final HttpClientFormEncoder applyChanges(HttpRequest request) {
if (!needNewEncoder) {
return this;
}
try {
HttpClientFormEncoder encoder = new HttpClientFormEncoder(newFactory,
request,
newMultipart,
newCharset,
newMode);
encoder.setBodyHttpDatas(getBodyListAttributes());
needNewEncoder = false;
return encoder;
}
catch (ErrorDataEncoderException ee) {
throw Exceptions.propagate(ee);
}
}
static final Map percentEncodings = new HashMap<>();
static final String DEFAULT_BINARY_CONTENT_TYPE =
"application/octet-stream";
static final String DEFAULT_TRANSFER_ENCODING = "binary";
static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain";
static {
DiskFileUpload.deleteOnExitTemporaryFile = true; // should delete file
// on exit (in normal
// exit)
DiskFileUpload.baseDirectory = null; // system temp directory
DiskAttribute.deleteOnExitTemporaryFile = true; // should delete file on
// exit (in normal exit)
DiskAttribute.baseDirectory = null; // system temp directory
percentEncodings.put(Pattern.compile("\\*"), "%2A");
percentEncodings.put(Pattern.compile("\\+"), "%20");
percentEncodings.put(Pattern.compile("%7E"), "~");
}
}