mirror of https://github.com/apache/nifi.git
NIFI-13661 Added Multipart Form Data Builder to web-client-api
This closes #9183 Signed-off-by: dan-s1 <dstieg1@gmail.com>
This commit is contained in:
parent
fa6e55f1de
commit
6013b93cee
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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
|
||||||
|
*
|
||||||
|
* http://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.apache.nifi.web.client.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content Type value for HTTP headers
|
||||||
|
*/
|
||||||
|
public interface HttpContentType {
|
||||||
|
/**
|
||||||
|
* Get Content Type value for HTTP header
|
||||||
|
*
|
||||||
|
* @return Content Type
|
||||||
|
*/
|
||||||
|
String getContentType();
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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
|
||||||
|
*
|
||||||
|
* http://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.apache.nifi.web.client.api;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multipart Form Data Stream Builder supports construction of an Input Stream with form-data sections according to RFC 7578
|
||||||
|
*/
|
||||||
|
public interface MultipartFormDataStreamBuilder {
|
||||||
|
/**
|
||||||
|
* Build Input Stream based on current component elements
|
||||||
|
*
|
||||||
|
* @return Input Stream
|
||||||
|
*/
|
||||||
|
InputStream build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Content-Type Header value containing multipart/form-data with boundary
|
||||||
|
*
|
||||||
|
* @return Multipart HTTP Content-Type
|
||||||
|
*/
|
||||||
|
HttpContentType getHttpContentType();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Part using specified Name with Content-Type and Stream
|
||||||
|
*
|
||||||
|
* @param name Name field of part to be added
|
||||||
|
* @param httpContentType Content-Type of part to be added
|
||||||
|
* @param inputStream Stream content of part to be added
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
MultipartFormDataStreamBuilder addPart(String name, HttpContentType httpContentType, InputStream inputStream);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Part using specified Name with Content-Type and byte array
|
||||||
|
*
|
||||||
|
* @param name Name field of part to be added
|
||||||
|
* @param httpContentType Content-Type of part to be added
|
||||||
|
* @param bytes Byte array content of part to be added
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
MultipartFormDataStreamBuilder addPart(String name, HttpContentType httpContentType, byte[] bytes);
|
||||||
|
}
|
|
@ -0,0 +1,48 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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
|
||||||
|
*
|
||||||
|
* http://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.apache.nifi.web.client.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Enumeration of standard registered Content Types applicable to most HTTP requests and responses
|
||||||
|
*/
|
||||||
|
public enum StandardHttpContentType implements HttpContentType {
|
||||||
|
/** Defined in RFC 8259 */
|
||||||
|
APPLICATION_JSON("application/json"),
|
||||||
|
|
||||||
|
/** Defined in RFC 2046 */
|
||||||
|
APPLICATION_OCTET_STREAM("application/octet-stream"),
|
||||||
|
|
||||||
|
/** Defined in RFF 7303 */
|
||||||
|
APPLICATION_XML("application/xml"),
|
||||||
|
|
||||||
|
/** Defined according to W3C */
|
||||||
|
TEXT_HTML("text/html"),
|
||||||
|
|
||||||
|
/** Defined in RFC 2046 */
|
||||||
|
TEXT_PLAIN("text/plain");
|
||||||
|
|
||||||
|
private final String contentType;
|
||||||
|
|
||||||
|
StandardHttpContentType(final String contentType) {
|
||||||
|
this.contentType = contentType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,187 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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
|
||||||
|
*
|
||||||
|
* http://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.apache.nifi.web.client.api;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.SequenceInputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard implementation of Multipart Form Data Stream Builder supporting form-data as described in RFC 7578
|
||||||
|
*/
|
||||||
|
public class StandardMultipartFormDataStreamBuilder implements MultipartFormDataStreamBuilder {
|
||||||
|
private static final String CONTENT_DISPOSITION_HEADER = "Content-Disposition: form-data; name=\"%s\"";
|
||||||
|
|
||||||
|
private static final String CONTENT_TYPE_HEADER = "Content-Type: %s";
|
||||||
|
|
||||||
|
private static final Pattern ALLOWED_NAME_PATTERN = Pattern.compile("^\\p{ASCII}+$");
|
||||||
|
|
||||||
|
private static final String CARRIAGE_RETURN_LINE_FEED = "\r\n";
|
||||||
|
|
||||||
|
private static final String BOUNDARY_SEPARATOR = "--";
|
||||||
|
|
||||||
|
private static final String BOUNDARY_FORMAT = "FormDataBoundary-%s";
|
||||||
|
|
||||||
|
private static final String MULTIPART_FORM_DATA_FORMAT = "multipart/form-data; boundary=\"%s\"";
|
||||||
|
|
||||||
|
private static final Charset HEADERS_CHARACTER_SET = StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
|
private final String boundary = BOUNDARY_FORMAT.formatted(UUID.randomUUID());
|
||||||
|
|
||||||
|
private final List<Part> parts = new ArrayList<>();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build Sequence Input Stream from collection of Form Data Parts formatted with boundaries
|
||||||
|
*
|
||||||
|
* @return Input Stream
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public InputStream build() {
|
||||||
|
if (parts.isEmpty()) {
|
||||||
|
throw new IllegalStateException("Parts required");
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<InputStream> partInputStreams = new ArrayList<>();
|
||||||
|
|
||||||
|
final Iterator<Part> selectedParts = parts.iterator();
|
||||||
|
while (selectedParts.hasNext()) {
|
||||||
|
final Part part = selectedParts.next();
|
||||||
|
final String footer = getFooter(selectedParts);
|
||||||
|
|
||||||
|
final InputStream partInputStream = getPartInputStream(part, footer);
|
||||||
|
partInputStreams.add(partInputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Enumeration<InputStream> enumeratedPartInputStreams = Collections.enumeration(partInputStreams);
|
||||||
|
return new SequenceInputStream(enumeratedPartInputStreams);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Content-Type Header value containing multipart/form-data with boundary
|
||||||
|
*
|
||||||
|
* @return Multipart HTTP Content-Type
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public HttpContentType getHttpContentType() {
|
||||||
|
final String contentType = MULTIPART_FORM_DATA_FORMAT.formatted(boundary);
|
||||||
|
return new MultipartHttpContentType(contentType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Part with field name and stream source
|
||||||
|
*
|
||||||
|
* @param name Name field of part to be added
|
||||||
|
* @param httpContentType Content-Type of part to be added
|
||||||
|
* @param inputStream Stream content of part to be added
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MultipartFormDataStreamBuilder addPart(final String name, final HttpContentType httpContentType, final InputStream inputStream) {
|
||||||
|
Objects.requireNonNull(name, "Name required");
|
||||||
|
Objects.requireNonNull(httpContentType, "Content Type required");
|
||||||
|
Objects.requireNonNull(inputStream, "Input Stream required");
|
||||||
|
|
||||||
|
final Matcher nameMatcher = ALLOWED_NAME_PATTERN.matcher(name);
|
||||||
|
if (nameMatcher.matches()) {
|
||||||
|
final Part part = new Part(name, httpContentType, inputStream);
|
||||||
|
parts.add(part);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Name contains characters outside of ASCII character set");
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add Part with field name and byte array source
|
||||||
|
*
|
||||||
|
* @param name Name field of part to be added
|
||||||
|
* @param httpContentType Content-Type of part to be added
|
||||||
|
* @param bytes Byte array content of part to be added
|
||||||
|
* @return Builder
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public MultipartFormDataStreamBuilder addPart(final String name, final HttpContentType httpContentType, final byte[] bytes) {
|
||||||
|
Objects.requireNonNull(bytes, "Byte Array required");
|
||||||
|
final InputStream inputStream = new ByteArrayInputStream(bytes);
|
||||||
|
return addPart(name, httpContentType, inputStream);
|
||||||
|
}
|
||||||
|
|
||||||
|
private InputStream getPartInputStream(final Part part, final String footer) {
|
||||||
|
final String partHeaders = getPartHeaders(part);
|
||||||
|
final InputStream headersInputStream = new ByteArrayInputStream(partHeaders.getBytes(HEADERS_CHARACTER_SET));
|
||||||
|
final InputStream footerInputStream = new ByteArrayInputStream(footer.getBytes(HEADERS_CHARACTER_SET));
|
||||||
|
final Enumeration<InputStream> inputStreams = Collections.enumeration(List.of(headersInputStream, part.inputStream, footerInputStream));
|
||||||
|
return new SequenceInputStream(inputStreams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getPartHeaders(final Part part) {
|
||||||
|
final StringBuilder headersBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
final String contentDispositionHeader = CONTENT_DISPOSITION_HEADER.formatted(part.name);
|
||||||
|
headersBuilder.append(contentDispositionHeader);
|
||||||
|
headersBuilder.append(CARRIAGE_RETURN_LINE_FEED);
|
||||||
|
|
||||||
|
final String contentType = part.httpContentType.getContentType();
|
||||||
|
final String contentTypeHeader = CONTENT_TYPE_HEADER.formatted(contentType);
|
||||||
|
headersBuilder.append(contentTypeHeader);
|
||||||
|
headersBuilder.append(CARRIAGE_RETURN_LINE_FEED);
|
||||||
|
|
||||||
|
headersBuilder.append(CARRIAGE_RETURN_LINE_FEED);
|
||||||
|
return headersBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getFooter(final Iterator<Part> selectedParts) {
|
||||||
|
final StringBuilder footerBuilder = new StringBuilder();
|
||||||
|
footerBuilder.append(CARRIAGE_RETURN_LINE_FEED);
|
||||||
|
footerBuilder.append(BOUNDARY_SEPARATOR);
|
||||||
|
footerBuilder.append(boundary);
|
||||||
|
if (selectedParts.hasNext()) {
|
||||||
|
footerBuilder.append(CARRIAGE_RETURN_LINE_FEED);
|
||||||
|
} else {
|
||||||
|
// Add boundary separator after last part indicating end
|
||||||
|
footerBuilder.append(BOUNDARY_SEPARATOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
return footerBuilder.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private record MultipartHttpContentType(String contentType) implements HttpContentType {
|
||||||
|
@Override
|
||||||
|
public String getContentType() {
|
||||||
|
return contentType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record Part(
|
||||||
|
String name,
|
||||||
|
HttpContentType httpContentType,
|
||||||
|
InputStream inputStream
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,157 @@
|
||||||
|
/*
|
||||||
|
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||||
|
* contributor license agreements. See the NOTICE file distributed with
|
||||||
|
* this work for additional information regarding copyright ownership.
|
||||||
|
* The ASF licenses this file to You 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
|
||||||
|
*
|
||||||
|
* http://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.apache.nifi.web.client.api;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class StandardMultipartFormDataStreamBuilderTest {
|
||||||
|
|
||||||
|
private static final Pattern MUTLIPART_FORM_DATA_PATTERN = Pattern.compile("^multipart/form-data; boundary=\"([a-zA-Z0-9-]+)\"$");
|
||||||
|
|
||||||
|
private static final int BOUNDARY_GROUP = 1;
|
||||||
|
|
||||||
|
private static final String PART_BOUNDARY = "\r\n--%s\r\n";
|
||||||
|
|
||||||
|
private static final String LAST_BOUNDARY = "\r\n--%s--";
|
||||||
|
|
||||||
|
private static final String UPLOADED = "uploaded";
|
||||||
|
|
||||||
|
private static final String FIELD = "field";
|
||||||
|
|
||||||
|
private static final String CONTENT_DISPOSITION_HEADER = "Content-Disposition: form-data; name=\"%s\"\r\n";
|
||||||
|
|
||||||
|
private static final Charset HEADERS_CHARACTER_SET = StandardCharsets.US_ASCII;
|
||||||
|
|
||||||
|
private static final byte[] UNICODE_ENCODED = new byte[]{-50, -111, -50, -87};
|
||||||
|
|
||||||
|
private static final String UNICODE_STRING = new String(UNICODE_ENCODED, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
private StandardMultipartFormDataStreamBuilder builder;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setBuilder() {
|
||||||
|
builder = new StandardMultipartFormDataStreamBuilder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testGetHttpContentType() {
|
||||||
|
final HttpContentType httpContentType = builder.getHttpContentType();
|
||||||
|
final String contentType = httpContentType.getContentType();
|
||||||
|
|
||||||
|
assertNotNull(contentType);
|
||||||
|
final Matcher matcher = MUTLIPART_FORM_DATA_PATTERN.matcher(contentType);
|
||||||
|
assertTrue(matcher.matches());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testAddPartNameDisallowed() {
|
||||||
|
final String uploaded = String.class.getName();
|
||||||
|
final byte[] uploadedBytes = uploaded.getBytes(StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
assertThrows(IllegalArgumentException.class, () -> builder.addPart(UNICODE_STRING, StandardHttpContentType.APPLICATION_OCTET_STREAM, uploadedBytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuildException() {
|
||||||
|
assertThrows(IllegalStateException.class, builder::build);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuildTextPlain() throws IOException {
|
||||||
|
final String value = String.class.getName();
|
||||||
|
final byte[] bytes = value.getBytes(HEADERS_CHARACTER_SET);
|
||||||
|
|
||||||
|
final InputStream inputStream = builder.addPart(UPLOADED, StandardHttpContentType.TEXT_PLAIN, bytes).build();
|
||||||
|
|
||||||
|
final String body = readInputStream(inputStream);
|
||||||
|
assertContentDispositionFound(body, UPLOADED);
|
||||||
|
|
||||||
|
final String boundary = getBoundary(builder);
|
||||||
|
assertLastBoundaryFound(body, boundary);
|
||||||
|
|
||||||
|
assertTrue(body.contains(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testBuildMultipleParts() throws IOException {
|
||||||
|
final String uploaded = String.class.getName();
|
||||||
|
final byte[] uploadedBytes = uploaded.getBytes(StandardCharsets.UTF_8);
|
||||||
|
final InputStream inputStream = new ByteArrayInputStream(uploadedBytes);
|
||||||
|
builder.addPart(UPLOADED, StandardHttpContentType.APPLICATION_OCTET_STREAM, inputStream);
|
||||||
|
|
||||||
|
final String field = Integer.class.getName();
|
||||||
|
final byte[] fieldBytes = field.getBytes(HEADERS_CHARACTER_SET);
|
||||||
|
builder.addPart(FIELD, StandardHttpContentType.TEXT_PLAIN, fieldBytes);
|
||||||
|
|
||||||
|
final InputStream stream = builder.build();
|
||||||
|
|
||||||
|
final String body = readInputStream(stream);
|
||||||
|
assertContentDispositionFound(body, UPLOADED);
|
||||||
|
assertContentDispositionFound(body, FIELD);
|
||||||
|
|
||||||
|
final String boundary = getBoundary(builder);
|
||||||
|
assertPartBoundaryFound(body, boundary);
|
||||||
|
assertLastBoundaryFound(body, boundary);
|
||||||
|
|
||||||
|
assertTrue(body.contains(uploaded));
|
||||||
|
assertTrue(body.contains(field));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertContentDispositionFound(final String body, final String name) {
|
||||||
|
final String contentDispositionHeader = CONTENT_DISPOSITION_HEADER.formatted(name);
|
||||||
|
assertTrue(body.contains(contentDispositionHeader));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPartBoundaryFound(final String body, final String boundary) {
|
||||||
|
final String partBoundary = PART_BOUNDARY.formatted(boundary);
|
||||||
|
assertTrue(body.contains(partBoundary));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertLastBoundaryFound(final String body, final String boundary) {
|
||||||
|
final String lastBoundary = LAST_BOUNDARY.formatted(boundary);
|
||||||
|
assertTrue(body.endsWith(lastBoundary));
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getBoundary(final MultipartFormDataStreamBuilder builder) {
|
||||||
|
final HttpContentType httpContentType = builder.getHttpContentType();
|
||||||
|
final String contentType = httpContentType.getContentType();
|
||||||
|
final Matcher boundaryMatcher = MUTLIPART_FORM_DATA_PATTERN.matcher(contentType);
|
||||||
|
assertTrue(boundaryMatcher.matches());
|
||||||
|
return boundaryMatcher.group(BOUNDARY_GROUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String readInputStream(final InputStream inputStream) throws IOException {
|
||||||
|
final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
inputStream.transferTo(outputStream);
|
||||||
|
inputStream.close();
|
||||||
|
return outputStream.toString();
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue