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); } }
|