Skip to content

Commit 0039b22

Browse files
committed
Add org.apache.hc.client5.http.entity.mime.PathBody
- Add org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder.addBinaryBody(String, File) - Add org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder.addBinaryBody(String, Path, ContentType, String)
1 parent 3500661 commit 0039b22

File tree

9 files changed

+391
-6
lines changed

9 files changed

+391
-6
lines changed

RELEASE_NOTES.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
Release 5.5.1
1+
Release 5.6-alpha1
22
------------------
33

44
Change Log
@@ -10,6 +10,11 @@ Change Log
1010
* Bump testcontainers.version from 1.20.6 to 1.21.1 #638.
1111
Contributed by Gary Gregory <garydgregory at gmail.com>
1212

13+
* Add org.apache.hc.client5.http.entity.mime.PathBody
14+
Add org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder.addBinaryBody(String, File)
15+
Add org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder.addBinaryBody(String, Path, ContentType, String)
16+
Contributed by Gary Gregory <garydgregory at gmail.com>
17+
1318

1419
Release 5.5
1520
------------------

httpclient5/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@
103103
<artifactId>mockito-core</artifactId>
104104
<scope>test</scope>
105105
</dependency>
106+
<dependency>
107+
<groupId>commons-io</groupId>
108+
<artifactId>commons-io</artifactId>
109+
<scope>test</scope>
110+
</dependency>
106111
</dependencies>
107112

108113
<build>

httpclient5/src/main/java/org/apache/hc/client5/http/entity/mime/MultipartEntityBuilder.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import java.io.InputStream;
3232
import java.nio.charset.Charset;
3333
import java.nio.charset.StandardCharsets;
34+
import java.nio.file.Path;
3435
import java.util.ArrayList;
3536
import java.util.Collections;
3637
import java.util.List;
@@ -215,6 +216,32 @@ public MultipartEntityBuilder addBinaryBody(
215216
return addPart(name, new FileBody(file, contentType, filename));
216217
}
217218

219+
/**
220+
* Adds body with contents from the given source Path.
221+
*
222+
* @param name The part name.
223+
* @param path The source path.
224+
* @return {@code this} instance.
225+
* @since 5.6
226+
*/
227+
public MultipartEntityBuilder addBinaryBody(final String name, final Path path) {
228+
return addBinaryBody(name, path, ContentType.DEFAULT_BINARY, path != null ? path.getFileName().toString() : null);
229+
}
230+
231+
/**
232+
* Adds body with contents from the given source Path.
233+
*
234+
* @param name The part name.
235+
* @param path The source path.
236+
* @param contentType The content type.
237+
* @param fileName The file name to override the Path's file name.
238+
* @return {@code this} instance.
239+
* @since 5.6
240+
*/
241+
public MultipartEntityBuilder addBinaryBody(final String name, final Path path, final ContentType contentType, final String fileName) {
242+
return addPart(name, new PathBody(path, contentType, fileName));
243+
}
244+
218245
public MultipartEntityBuilder addBinaryBody(
219246
final String name, final File file) {
220247
return addBinaryBody(name, file, ContentType.DEFAULT_BINARY, file != null ? file.getName() : null);
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* ====================================================================
3+
* Licensed to the Apache Software Foundation (ASF) under one
4+
* or more contributor license agreements. See the NOTICE file
5+
* distributed with this work for additional information
6+
* regarding copyright ownership. The ASF licenses this file
7+
* to you under the Apache License, Version 2.0 (the
8+
* "License"); you may not use this file except in compliance
9+
* with the License. You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing,
14+
* software distributed under the License is distributed on an
15+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16+
* KIND, either express or implied. See the License for the
17+
* specific language governing permissions and limitations
18+
* under the License.
19+
* ====================================================================
20+
*
21+
* This software consists of voluntary contributions made by many
22+
* individuals on behalf of the Apache Software Foundation. For more
23+
* information on the Apache Software Foundation, please see
24+
* <http://www.apache.org/>.
25+
*
26+
*/
27+
28+
package org.apache.hc.client5.http.entity.mime;
29+
30+
import java.io.IOException;
31+
import java.io.InputStream;
32+
import java.io.OutputStream;
33+
import java.io.UncheckedIOException;
34+
import java.nio.file.Files;
35+
import java.nio.file.Path;
36+
import java.util.Objects;
37+
38+
import org.apache.hc.core5.http.ContentType;
39+
import org.apache.hc.core5.util.Args;
40+
41+
/**
42+
* Binary body part backed by an NIO {@link Path}.
43+
*
44+
* @see org.apache.hc.client5.http.entity.mime.MultipartEntityBuilder
45+
*
46+
* @since 5.6
47+
*/
48+
public class PathBody extends AbstractContentBody {
49+
50+
private static String getFileName(final Path path) {
51+
return path != null ? Objects.toString(path.getFileName(), null) : null;
52+
}
53+
54+
private final String fileName;
55+
56+
private final Path path;
57+
58+
/**
59+
* Constructs a new instance for a given Path.
60+
*
61+
* @param path the source Path.
62+
*/
63+
public PathBody(final Path path) {
64+
this(path, ContentType.DEFAULT_BINARY, getFileName(path));
65+
}
66+
67+
/**
68+
* Constructs a new instance for a given Path.
69+
*
70+
* @param path the source Path.
71+
* @param contentType the content type.
72+
*/
73+
public PathBody(final Path path, final ContentType contentType) {
74+
this(path, contentType, getFileName(path));
75+
}
76+
77+
/**
78+
* Constructs a new instance for a given Path.
79+
*
80+
* @param path the source Path.
81+
* @param contentType the content type.
82+
* @param fileName The file name to override the Path's file name.
83+
*/
84+
public PathBody(final Path path, final ContentType contentType, final String fileName) {
85+
super(contentType);
86+
this.path = Args.notNull(path, "path");
87+
this.fileName = fileName == null ? getFileName(path) : fileName;
88+
}
89+
90+
@Override
91+
public long getContentLength() {
92+
try {
93+
return Files.size(path);
94+
} catch (final IOException e) {
95+
throw new UncheckedIOException(e);
96+
}
97+
}
98+
99+
@Override
100+
public String getFilename() {
101+
return fileName;
102+
}
103+
104+
/**
105+
* Gets a new input stream.
106+
*
107+
* @return a new input stream.
108+
* @throws IOException if an I/O error occurs
109+
*/
110+
public InputStream getInputStream() throws IOException {
111+
return Files.newInputStream(path);
112+
}
113+
114+
/**
115+
* Gets the source Path.
116+
*
117+
* @return the source Path.
118+
*/
119+
public Path getPath() {
120+
return path;
121+
}
122+
123+
@Override
124+
public void writeTo(final OutputStream out) throws IOException {
125+
Files.copy(path, Args.notNull(out, "Output stream"));
126+
}
127+
128+
}

httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/FormBodyPartTest.java

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,31 @@
2828
package org.apache.hc.client5.http.entity.mime;
2929

3030
import java.io.File;
31+
import java.nio.file.Files;
32+
import java.nio.file.Path;
3133

3234
import org.apache.hc.core5.http.ContentType;
3335
import org.junit.jupiter.api.Assertions;
3436
import org.junit.jupiter.api.Test;
37+
import org.junit.jupiter.api.io.TempDir;
3538

3639
class FormBodyPartTest {
3740

41+
@TempDir
42+
Path tempDir;
43+
3844
@Test
39-
void testConstructorCompat() throws Exception {
40-
final File tmp = File.createTempFile("test", "test");
41-
tmp.deleteOnExit();
45+
void testFileConstructorCompat() throws Exception {
46+
final File tmp = Files.createTempFile(tempDir, "test-", "-file.bin").toFile();
4247
final FileBody obj = new FileBody(tmp, ContentType.APPLICATION_OCTET_STREAM);
4348
Assertions.assertEquals(tmp.getName(), obj.getFilename());
4449
}
4550

51+
@Test
52+
void testPathConstructorCompat() throws Exception {
53+
final Path tmp = Files.createTempFile(tempDir, "test-", "-path.bin");
54+
final PathBody obj = new PathBody(tmp, ContentType.APPLICATION_OCTET_STREAM);
55+
Assertions.assertEquals(tmp.getFileName().toString(), obj.getFilename());
56+
}
57+
4658
}

httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartEntityBuilder.java

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@
3030
import java.io.ByteArrayInputStream;
3131
import java.io.ByteArrayOutputStream;
3232
import java.io.File;
33+
import java.io.IOException;
3334
import java.nio.charset.StandardCharsets;
35+
import java.nio.file.Files;
36+
import java.nio.file.Path;
3437
import java.util.ArrayList;
3538
import java.util.List;
3639

@@ -42,9 +45,13 @@
4245
import org.apache.hc.core5.http.message.ParserCursor;
4346
import org.junit.jupiter.api.Assertions;
4447
import org.junit.jupiter.api.Test;
48+
import org.junit.jupiter.api.io.TempDir;
4549

4650
class TestMultipartEntityBuilder {
4751

52+
@TempDir
53+
Path tempDir;
54+
4855
@Test
4956
void testBasics() {
5057
final MultipartFormEntity entity = MultipartEntityBuilder.create().buildEntity();
@@ -67,7 +74,7 @@ void testMultipartOptions() {
6774
}
6875

6976
@Test
70-
void testAddBodyParts() {
77+
void testAddBodyPartsFile() {
7178
final MultipartFormEntity entity = MultipartEntityBuilder.create()
7279
.addTextBody("p1", "stuff")
7380
.addBinaryBody("p2", new File("stuff"))
@@ -81,6 +88,20 @@ void testAddBodyParts() {
8188
Assertions.assertEquals(5, bodyParts.size());
8289
}
8390

91+
@Test
92+
void testAddBodyPartsPath() throws IOException {
93+
final MultipartFormEntity entity = MultipartEntityBuilder.create()
94+
.addTextBody("p1", "stuff")
95+
.addBinaryBody("p2", Files.createTempFile(tempDir, "test-", ".bin"))
96+
.addBinaryBody("p3", new byte[]{})
97+
.addBinaryBody("p4", new ByteArrayInputStream(new byte[]{}))
98+
.addBinaryBody("p5", new ByteArrayInputStream(new byte[]{}), ContentType.DEFAULT_BINARY, "filename")
99+
.buildEntity();
100+
Assertions.assertNotNull(entity);
101+
final List<MultipartPart> bodyParts = entity.getMultipart().getParts();
102+
Assertions.assertNotNull(bodyParts);
103+
Assertions.assertEquals(5, bodyParts.size());
104+
}
84105

85106
@Test
86107
void testMultipartCustomContentType() {

httpclient5/src/test/java/org/apache/hc/client5/http/entity/mime/TestMultipartMixed.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ void testMultipartPartBinaryParts() throws Exception {
150150
}
151151

152152
@Test
153-
void testMultipartPartStrict() throws Exception {
153+
void testMultipartPartFileStrict() throws Exception {
154154
tmpfile = File.createTempFile("tmp", ".bin");
155155
try (Writer writer = new FileWriter(tmpfile)) {
156156
writer.append("some random whatever");
@@ -189,6 +189,46 @@ void testMultipartPartStrict() throws Exception {
189189
Assertions.assertEquals(-1, multipart.getTotalLength());
190190
}
191191

192+
@Test
193+
void testMultipartPartPathStrict() throws Exception {
194+
tmpfile = File.createTempFile("tmp", ".bin");
195+
try (Writer writer = new FileWriter(tmpfile)) {
196+
writer.append("some random whatever");
197+
}
198+
199+
final MultipartPart p1 = MultipartPartBuilder.create(
200+
new PathBody(tmpfile.toPath())).build();
201+
final MultipartPart p2 = MultipartPartBuilder.create(
202+
new PathBody(tmpfile.toPath(), ContentType.create("text/plain", "ANSI_X3.4-1968"), "test-file")).build();
203+
@SuppressWarnings("resource")
204+
final MultipartPart p3 = MultipartPartBuilder.create(
205+
new InputStreamBody(new FileInputStream(tmpfile), "file.tmp")).build();
206+
final HttpStrictMultipart multipart = new HttpStrictMultipart(null, "foo",
207+
Arrays.asList(p1, p2, p3));
208+
209+
final ByteArrayOutputStream out = new ByteArrayOutputStream();
210+
multipart.writeTo(out);
211+
out.close();
212+
213+
final String expected =
214+
"--foo\r\n" +
215+
"Content-Type: application/octet-stream\r\n" +
216+
"\r\n" +
217+
"some random whatever\r\n" +
218+
"--foo\r\n" +
219+
"Content-Type: text/plain; charset=US-ASCII\r\n" +
220+
"\r\n" +
221+
"some random whatever\r\n" +
222+
"--foo\r\n" +
223+
"Content-Type: application/octet-stream\r\n" +
224+
"\r\n" +
225+
"some random whatever\r\n" +
226+
"--foo--\r\n";
227+
final String s = out.toString("US-ASCII");
228+
Assertions.assertEquals(expected, s);
229+
Assertions.assertEquals(-1, multipart.getTotalLength());
230+
}
231+
192232
@Test
193233
void testMultipartPartRFC6532() throws Exception {
194234
tmpfile = File.createTempFile("tmp", ".bin");

0 commit comments

Comments
 (0)