1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
| public class MultipartWriter {
private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; private static final byte[] FIELD_SEP = ": ".getBytes(StandardCharsets.ISO_8859_1); private static final byte[] CR_LF = "\r\n".getBytes(StandardCharsets.ISO_8859_1); private static final String TWO_HYPHENS_TEXT = "--"; private static final byte[] TWO_HYPHENS = TWO_HYPHENS_TEXT.getBytes(StandardCharsets.ISO_8859_1); private static final String CONTENT_DISPOSITION_KEY = "Content-Disposition"; private static final String CONTENT_TYPE_KEY = "Content-Type"; private static final String DEFAULT_CONTENT_TYPE = "multipart/form-data; boundary="; private static final String DEFAULT_BINARY_CONTENT_TYPE = "application/octet-stream"; private static final String DEFAULT_TEXT_CONTENT_TYPE = "text/plain;charset=UTF-8"; private static final String DEFAULT_CONTENT_DISPOSITION_VALUE = "form-data; name=\"%s\""; private static final String FILE_CONTENT_DISPOSITION_VALUE = "form-data; name=\"%s\"; filename=\"%s\"";
private final Map<String, String> headers = new HashMap<>(8); private final List<AbstractMultipartPart> parts = new ArrayList<>(); private final String boundary;
private MultipartWriter(String boundary) { this.boundary = Objects.isNull(boundary) ? TWO_HYPHENS_TEXT + UUID.randomUUID().toString().replace("-", "") : boundary; this.headers.put(CONTENT_TYPE_KEY, DEFAULT_CONTENT_TYPE + this.boundary); }
public static MultipartWriter newMultipartWriter(String boundary) { return new MultipartWriter(boundary); }
public static MultipartWriter newMultipartWriter() { return new MultipartWriter(null); }
public MultipartWriter addHeader(String key, String value) { if (!CONTENT_TYPE_KEY.equalsIgnoreCase(key)) { headers.put(key, value); } return this; }
public MultipartWriter addTextPart(String name, String text) { parts.add(new TextPart(String.format(DEFAULT_CONTENT_DISPOSITION_VALUE, name), DEFAULT_TEXT_CONTENT_TYPE, this.boundary, text)); return this; }
public MultipartWriter addBinaryPart(String name, byte[] bytes) { parts.add(new BinaryPart(String.format(DEFAULT_CONTENT_DISPOSITION_VALUE, name), DEFAULT_BINARY_CONTENT_TYPE, this.boundary, bytes)); return this; }
public MultipartWriter addFilePart(String name, File file) { parts.add(new FilePart(String.format(FILE_CONTENT_DISPOSITION_VALUE, name, file.getName()), DEFAULT_BINARY_CONTENT_TYPE, this.boundary, file)); return this; }
private static void writeHeader(String key, String value, OutputStream out) throws IOException { writeBytes(key, out); writeBytes(FIELD_SEP, out); writeBytes(value, out); writeBytes(CR_LF, out); }
private static void writeBytes(String text, OutputStream out) throws IOException { out.write(text.getBytes(DEFAULT_CHARSET)); }
private static void writeBytes(byte[] bytes, OutputStream out) throws IOException { out.write(bytes); }
interface MultipartPart {
void writeBody(OutputStream os) throws IOException; }
@RequiredArgsConstructor public static abstract class AbstractMultipartPart implements MultipartPart {
protected final String contentDispositionValue; protected final String contentTypeValue; protected final String boundary;
protected String getContentDispositionValue() { return contentDispositionValue; }
protected String getContentTypeValue() { return contentTypeValue; }
protected String getBoundary() { return boundary; }
public final void write(OutputStream out) throws IOException { writeBytes(TWO_HYPHENS, out); writeBytes(getBoundary(), out); writeBytes(CR_LF, out); writeHeader(CONTENT_DISPOSITION_KEY, getContentDispositionValue(), out); writeHeader(CONTENT_TYPE_KEY, getContentTypeValue(), out); writeBytes(CR_LF, out); writeBody(out); writeBytes(CR_LF, out); } }
public static class TextPart extends AbstractMultipartPart {
private final String text;
public TextPart(String contentDispositionValue, String contentTypeValue, String boundary, String text) { super(contentDispositionValue, contentTypeValue, boundary); this.text = text; }
@Override public void writeBody(OutputStream os) throws IOException { os.write(text.getBytes(DEFAULT_CHARSET)); }
@Override protected String getContentDispositionValue() { return contentDispositionValue; }
@Override protected String getContentTypeValue() { return contentTypeValue; } }
public static class BinaryPart extends AbstractMultipartPart {
private final byte[] content;
public BinaryPart(String contentDispositionValue, String contentTypeValue, String boundary, byte[] content) { super(contentDispositionValue, contentTypeValue, boundary); this.content = content; }
@Override public void writeBody(OutputStream out) throws IOException { out.write(content); } }
public static class FilePart extends AbstractMultipartPart {
private final File file;
public FilePart(String contentDispositionValue, String contentTypeValue, String boundary, File file) { super(contentDispositionValue, contentTypeValue, boundary); this.file = file; }
@Override public void writeBody(OutputStream out) throws IOException { try (InputStream in = new FileInputStream(file)) { final byte[] buffer = new byte[4096]; int l; while ((l = in.read(buffer)) != -1) { out.write(buffer, 0, l); } out.flush(); } } }
public void forEachHeader(BiConsumer<String, String> consumer) { headers.forEach(consumer); }
public void write(OutputStream out) throws IOException { if (!parts.isEmpty()) { for (AbstractMultipartPart part : parts) { part.write(out); } } writeBytes(TWO_HYPHENS, out); writeBytes(this.boundary, out); writeBytes(TWO_HYPHENS, out); writeBytes(CR_LF, out); } }
|