All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.easimart.vertx.http.HttpServerMultipartRequest Maven / Gradle / Ivy

package co.easimart.vertx.http;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.net.ssl.SSLPeerUnverifiedException;
import javax.security.cert.X509Certificate;

import io.netty.buffer.ByteBuf;
import io.netty.handler.codec.DecoderResult;
import io.netty.handler.codec.http.DefaultHttpContent;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.multipart.Attribute;
import io.netty.handler.codec.http.multipart.DefaultHttpDataFactory;
import io.netty.handler.codec.http.multipart.FileUpload;
import io.netty.handler.codec.http.multipart.HttpPostRequestDecoder;
import io.netty.handler.codec.http.multipart.InterfaceHttpData;
import io.netty.handler.codec.http.multipart.MemoryAttribute;
import io.vertx.core.Handler;
import io.vertx.core.MultiMap;
import io.vertx.core.Vertx;
import io.vertx.core.buffer.Buffer;
import io.vertx.core.http.HttpMethod;
import io.vertx.core.http.HttpServerFileUpload;
import io.vertx.core.http.HttpServerRequest;
import io.vertx.core.http.HttpServerResponse;
import io.vertx.core.http.HttpVersion;
import io.vertx.core.http.ServerWebSocket;
import io.vertx.core.net.NetSocket;
import io.vertx.core.net.SocketAddress;

/**
 * HttpServerRequest wrapper which detects multipart http request and fire event
 * for each part received without reading the whole http request.
 */
public abstract class HttpServerMultipartRequest implements HttpServerRequest {

    private final Vertx vertx;

    private HttpServerRequest request;
    private HttpPostRequestDecoder decoder;
    private Handler dataHandler;
    private Handler uploadHandler;
    private Handler multipartHandler;
    private Handler exceptionHandler;

    public HttpServerMultipartRequest(Vertx vertx, HttpServerRequest request) {
        this.vertx = vertx;
        this.request = request;

        // Check if the request is really multipart request
        if (isMultipartRequest(request)) {
            this.decoder = new HttpPostRequestDecoder(
                    new DataFactory(),
                    new NettyHttpRequest()
            );
        } else {
            throw new IllegalArgumentException("Input request is not multipart request");
        }

        this.request.handler(buffer -> {
            try {
                // Feed data to decoder
                if (this.decoder != null) {
                    this.decoder.offer(new DefaultHttpContent(buffer.getByteBuf().duplicate()));
                }

                // Pass on the data to real data handler
                if (this.dataHandler != null) {
                    this.dataHandler.handle(buffer);
                }
            } catch (Throwable t) {
                notifyExceptionHandler(t);
            }
        });
    }

    public static boolean isMultipartRequest(HttpServerRequest request) {
        String contentType = request.getHeader("Content-Type");
        if (contentType != null) {
            HttpMethod method = request.method();
            String lowerCaseContentType = contentType.toLowerCase();
            boolean isURLEncoded = lowerCaseContentType.startsWith("application/x-www-form-urlencoded");
            return (lowerCaseContentType.startsWith("multipart/form-data") || isURLEncoded)
                    && (method.equals(HttpMethod.POST) || method.equals(HttpMethod.PUT) || method.equals(HttpMethod.PATCH) || method.equals(HttpMethod.DELETE));
        }
        return false;
    }

    private void notifyExceptionHandler(Throwable t) {
        if (this.exceptionHandler != null) {
            this.exceptionHandler.handle(t);
        }
    }

    public HttpServerRequest multipartHandler(Handler handler) {
        this.multipartHandler = handler;
        return this;
    }

    @Override
    public HttpServerRequest exceptionHandler(Handler handler) {
        this.exceptionHandler = handler;
        this.request.exceptionHandler(handler);
        return this;
    }

    @Override
    public HttpServerRequest handler(Handler handler) {
        this.dataHandler = handler;
        return this;
    }

    @Override
    public HttpServerRequest pause() {
        this.request.pause();
        return this;
    }

    @Override
    public HttpServerRequest resume() {
        this.request.resume();
        return this;
    }

    @Override
    public HttpServerRequest endHandler(Handler handler) {
        this.request.endHandler(handler);
        return this;
    }

    @Override
    public HttpVersion version() {
        return this.request.version();
    }

    @Override
    public HttpMethod method() {
        return this.request.method();
    }

    @Override
    public String uri() {
        return this.request.uri();
    }

    @Override
    public String path() {
        return this.request.path();
    }

    @Override
    public String query() {
        return this.request.query();
    }

    @Override
    public HttpServerResponse response() {
        return this.request.response();
    }

    @Override
    public MultiMap headers() {
        return this.request.headers();
    }

    @Override
    public String getHeader(String s) {
        return this.request.getHeader(s);
    }

    @Override
    public String getHeader(CharSequence charSequence) {
        return this.request.getHeader(charSequence);
    }

    @Override
    public MultiMap params() {
        return this.request.params();
    }

    @Override
    public String getParam(String s) {
        return this.request.getParam(s);
    }

    @Override
    public SocketAddress remoteAddress() {
        return this.request.remoteAddress();
    }

    @Override
    public SocketAddress localAddress() {
        return this.request.localAddress();
    }

    @Override
    public X509Certificate[] peerCertificateChain() throws SSLPeerUnverifiedException {
        return this.request.peerCertificateChain();
    }

    @Override
    public String absoluteURI() {
        return this.request.absoluteURI();
    }

    @Override
    public HttpServerRequest bodyHandler(Handler handler) {
        this.request.bodyHandler(handler);
        return this;
    }

    @Override
    public NetSocket netSocket() {
        return this.request.netSocket();
    }

    @Override
    public HttpServerRequest setExpectMultipart(boolean b) {
        throw new UnsupportedOperationException("Method setExpectMultipart() is not supported. It always expects multipart.");
    }

    @Override
    public boolean isExpectMultipart() {
        return true;
    }

    @Override
    public HttpServerRequest uploadHandler(Handler handler) {
        this.uploadHandler = handler;
        return this;
    }

    @Override
    public MultiMap formAttributes() {
        return this.request.formAttributes();
    }

    @Override
    public String getFormAttribute(String s) {
        return this.request.getFormAttribute(s);
    }

    @Override
    public ServerWebSocket upgrade() {
        return this.request.upgrade();
    }

    @Override
    public boolean isEnded() {
        return this.request.isEnded();
    }

    private class DataFactory extends DefaultHttpDataFactory {
        DataFactory() {
            super(false);
        }

        @Override
        public Attribute createAttribute(HttpRequest req, String name) {
            // Handling normal attribute in memory
            return new MemoryAttribute(name) {

                @Override
                public void addContent(ByteBuf buffer, boolean last) throws IOException {
                    super.addContent(buffer, last);
                    if (last) {
                        // Add new attribute to form attributes
                        request.formAttributes().add(this.getName(), this.getValue());
                        if (multipartHandler != null) {
                            multipartHandler.handle(this);
                        }
                    }
                }
            };
        }

        @Override
        public FileUpload createFileUpload(HttpRequest req,
                                           String name,
                                           String filename,
                                           String contentType,
                                           String contentTransferEncoding,
                                           Charset charset,
                                           long size) {

            StreamFileUpload upload = new StreamFileUpload(
                    vertx, request, name, filename, contentType, contentTransferEncoding, charset, size
            );
            if (uploadHandler != null) {
                uploadHandler.handle(upload);
            }
            return upload;
        }
    }

    private class NettyHttpRequest implements HttpRequest {

        public NettyHttpRequest() {
        }

        @Override
        public io.netty.handler.codec.http.HttpMethod getMethod() {
            throw new UnsupportedOperationException("Method getMethod() is not yet implemented");
        }

        @Override
        public io.netty.handler.codec.http.HttpMethod method() {
            return null;
        }

        @Override
        public HttpRequest setMethod(io.netty.handler.codec.http.HttpMethod httpMethod) {
            throw new UnsupportedOperationException("Method setMethod() is not yet implemented");
        }

        @Override
        public String getUri() {
            throw new UnsupportedOperationException("Method getUri() is not yet implemented");
        }

        @Override
        public String uri() {
            return null;
        }

        @Override
        public HttpRequest setUri(String s) {
            throw new UnsupportedOperationException("Method setUri() is not yet implemented");
        }

        @Override
        public io.netty.handler.codec.http.HttpVersion getProtocolVersion() {
            throw new UnsupportedOperationException("Method getProtocolVersion() is not yet implemented");
        }

        @Override
        public io.netty.handler.codec.http.HttpVersion protocolVersion() {
            return null;
        }

        @Override
        public HttpRequest setProtocolVersion(io.netty.handler.codec.http.HttpVersion httpVersion) {
            throw new UnsupportedOperationException("Method setProtocolVersion() is not yet implemented");
        }

        @Override
        public HttpHeaders headers() {
            return new HttpHeaders() {
                @Override
                public String get(String s) {
                    return request.getHeader(s);
                }

                @Override
                public Integer getInt(CharSequence name) {
                    return null;
                }

                @Override
                public int getInt(CharSequence name, int defaultValue) {
                    return 0;
                }

                @Override
                public Short getShort(CharSequence name) {
                    return null;
                }

                @Override
                public short getShort(CharSequence name, short defaultValue) {
                    return 0;
                }

                @Override
                public Long getTimeMillis(CharSequence name) {
                    return null;
                }

                @Override
                public long getTimeMillis(CharSequence name, long defaultValue) {
                    return 0;
                }

                @Override
                public List getAll(String s) {
                    throw new UnsupportedOperationException("Method getAll() is not yet implemented");
                }

                @Override
                public List> entries() {
                    throw new UnsupportedOperationException("Method entries() is not yet implemented");
                }

                @Override
                public boolean contains(String s) {
                    return request.headers().contains(s);
                }

                @Override
                public boolean isEmpty() {
                    throw new UnsupportedOperationException("Method isEmpty() is not yet implemented");
                }

                @Override
                public int size() {
                    return 0;
                }

                @Override
                public Set names() {
                    throw new UnsupportedOperationException("Method names() is not yet implemented");
                }

                @Override
                public HttpHeaders add(String s, Object o) {
                    throw new UnsupportedOperationException("Method add() is not yet implemented");
                }

                @Override
                public HttpHeaders add(String s, Iterable iterable) {
                    throw new UnsupportedOperationException("Method add() is not yet implemented");
                }

                @Override
                public HttpHeaders addInt(CharSequence name, int value) {
                    return null;
                }

                @Override
                public HttpHeaders addShort(CharSequence name, short value) {
                    return null;
                }

                @Override
                public HttpHeaders set(String s, Object o) {
                    throw new UnsupportedOperationException("Method set() is not yet implemented");
                }

                @Override
                public HttpHeaders set(String s, Iterable iterable) {
                    throw new UnsupportedOperationException("Method set() is not yet implemented");
                }

                @Override
                public HttpHeaders setInt(CharSequence name, int value) {
                    return null;
                }

                @Override
                public HttpHeaders setShort(CharSequence name, short value) {
                    return null;
                }

                @Override
                public HttpHeaders remove(String s) {
                    throw new UnsupportedOperationException("Method remove() is not yet implemented");
                }

                @Override
                public HttpHeaders clear() {
                    throw new UnsupportedOperationException("Method clear() is not yet implemented");
                }

                @Override
                public Iterator> iterator() {
                    throw new UnsupportedOperationException("Method iterator() is not yet implemented");
                }

                @Override
                public Iterator> iteratorCharSequence() {
                    return null;
                }
            };
        }

        @Override
        public DecoderResult getDecoderResult() {
            throw new UnsupportedOperationException("Method getDecoderResult() is not yet implemented");
        }

        @Override
        public DecoderResult decoderResult() {
            return null;
        }

        @Override
        public void setDecoderResult(DecoderResult decoderResult) {
            throw new UnsupportedOperationException("Method setDecoderResult() is not yet implemented");
        }
    }

    private static final class StreamFileUpload implements FileUpload, HttpServerFileUpload {
        private final Vertx vertx;
        private final HttpServerRequest request;
        private final String name;

        private String contentType;
        private String filename;
        private String contentTransferEncoding;
        private Charset charset;
        private long size;
        private boolean completed;

        private Handler dataHandler;
        private Handler endHandler;
        private Handler exceptionHandler;
        private boolean paused;
        private Buffer pauseBuff;
        private boolean lazyCalculateSize;

        StreamFileUpload(Vertx vertx, HttpServerRequest request, String name, String filename, String contentType, String contentTransferEncoding, Charset charset, long size) {
            this.vertx = vertx;
            this.request = request;
            this.name = name;
            this.setFilename(filename);
            this.setContentType(contentType);
            this.setContentTransferEncoding(contentTransferEncoding);
            this.setCharset(charset);
            this.size = size;
            if (size == 0L) {
                this.lazyCalculateSize = true;
            }
        }

        @Override
        public String getFilename() {
            return this.filename;
        }

        @Override
        public void setFilename(String s) {
            this.filename = s;
        }

        @Override
        public void setContentType(String s) {
            this.contentType = s;
        }

        @Override
        public String getContentType() {
            return this.contentType;
        }

        @Override
        public void setContentTransferEncoding(String s) {
            this.contentTransferEncoding = s;
        }

        @Override
        public String getContentTransferEncoding() {
            return this.contentTransferEncoding;
        }

        @Override
        public long getMaxSize() {
            return 0;
        }

        @Override
        public void setMaxSize(long maxSize) {

        }

        @Override
        public void checkSize(long newSize) throws IOException {

        }

        @Override
        public void setContent(ByteBuf byteBuf) throws IOException {
            Buffer data = Buffer.buffer(byteBuf);
            this.size = data.length();
            this.completed = true;
            this.handleData(data);
            this.handleComplete();
        }

        @Override
        public void addContent(ByteBuf byteBuf, boolean last) throws IOException {
            Buffer data = Buffer.buffer(byteBuf);
            if (!this.completed) {
                this.completed = last;
            }
            if (data.length() != 0) {
                if (this.lazyCalculateSize) {
                    this.size += (long) data.length();
                }

                this.handleData(data);
            }
            if (this.completed) {
                this.handleComplete();
            }
        }

        @Override
        public void setContent(File file) throws IOException {
            throw new UnsupportedOperationException("Method setContent() is not yet implemented");
        }

        @Override
        public void setContent(InputStream inputStream) throws IOException {
            throw new UnsupportedOperationException("Method setContent() is not yet implemented");
        }

        @Override
        public boolean isCompleted() {
            return this.completed;
        }

        @Override
        public long length() {
            return this.size();
        }

        @Override
        public long definedLength() {
            return 0;
        }

        @Override
        public void delete() {
            throw new UnsupportedOperationException("Method delete() is not yet implemented");
        }

        @Override
        public byte[] get() throws IOException {
            throw new UnsupportedOperationException("Method get() is not yet implemented");
        }

        @Override
        public ByteBuf getByteBuf() throws IOException {
            throw new UnsupportedOperationException("Method getByteBuf() is not yet implemented");
        }

        @Override
        public ByteBuf getChunk(int i) throws IOException {
            throw new UnsupportedOperationException("Method getChunk() is not yet implemented");
        }

        @Override
        public String getString() throws IOException {
            throw new UnsupportedOperationException("Method getString() is not yet implemented");
        }

        @Override
        public String getString(Charset charset) throws IOException {
            throw new UnsupportedOperationException("Method getString() is not yet implemented");
        }

        @Override
        public void setCharset(Charset charset) {
            this.charset = charset;
        }

        @Override
        public Charset getCharset() {
            return this.charset;
        }

        @Override
        public boolean renameTo(File file) throws IOException {
            throw new UnsupportedOperationException("Method renameTo() is not yet implemented");
        }

        @Override
        public boolean isInMemory() {
            return true;
        }

        @Override
        public File getFile() throws IOException {
            throw new UnsupportedOperationException("Method getFile() is not yet implemented");
        }

        @Override
        public ByteBuf content() {
            throw new UnsupportedOperationException("Method content() is not yet implemented");
        }

        @Override
        public FileUpload copy() {
            throw new UnsupportedOperationException("Method copy() is not yet implemented");
        }

        @Override
        public FileUpload duplicate() {
            throw new UnsupportedOperationException("Method duplicate() is not yet implemented");
        }

        @Override
        public FileUpload retainedDuplicate() {
            return null;
        }

        @Override
        public FileUpload replace(ByteBuf content) {
            return null;
        }

        @Override
        public int refCnt() {
            return 1;
        }

        @Override
        public FileUpload retain() {
            return this;
        }

        @Override
        public FileUpload retain(int i) {
            return this;
        }

        @Override
        public FileUpload touch() {
            return null;
        }

        @Override
        public FileUpload touch(Object hint) {
            return null;
        }

        @Override
        public boolean release() {
            return false;
        }

        @Override
        public boolean release(int i) {
            return false;
        }

        @Override
        public String getName() {
            return this.name;
        }

        @Override
        public HttpDataType getHttpDataType() {
            return HttpDataType.FileUpload;
        }

        @Override
        public int compareTo(@SuppressWarnings("NullableProblems") InterfaceHttpData o) {
            return 0;
        }

        /// ------------------ HttpServerFileUpload

        @Override
        public HttpServerFileUpload exceptionHandler(Handler handler) {
            this.exceptionHandler = handler;
            return this;
        }

        @Override
        public HttpServerFileUpload handler(Handler handler) {
            this.dataHandler = handler;
            return this;
        }

        @Override
        public HttpServerFileUpload endHandler(Handler handler) {
            this.endHandler = handler;
            return this;
        }

        @Override
        public HttpServerFileUpload pause() {
            this.request.pause();
            this.paused = true;
            return this;
        }

        @Override
        public HttpServerFileUpload resume() {
            if (this.paused) {
                this.paused = false;
                Buffer pb = pauseBuff;
                this.pauseBuff = null;
                boolean cmp = completed;

                if (pb != null || cmp) {
                    vertx.runOnContext(x -> {
                        if (pb != null) handleData(pb);
                        if (cmp) this.handleComplete();
                    });
                }
                this.request.resume();
            }

            return this;
        }

        @Override
        public HttpServerFileUpload streamToFileSystem(String s) {
            throw new UnsupportedOperationException("Method streamToFileSystem() is not yet implemented");
        }

        @Override
        public String filename() {
            return this.filename;
        }

        @Override
        public String name() {
            return this.name;
        }

        @Override
        public String contentType() {
            return this.contentType;
        }

        @Override
        public String contentTransferEncoding() {
            return this.contentTransferEncoding;
        }

        @Override
        public String charset() {
            return this.charset.toString();
        }

        @Override
        public long size() {
            return this.size;
        }

        @Override
        public boolean isSizeAvailable() {
            return !this.lazyCalculateSize;
        }

        void handleData(Buffer data) {
            if (!this.paused) {
                if (this.dataHandler != null) {
                    try {
                        this.dataHandler.handle(data);
                    } catch (Throwable t) {
                        this.notifyExceptionHandler(t);
                    }
                }
            } else {
                if (this.pauseBuff == null) {
                    this.pauseBuff = Buffer.buffer();
                }

                this.pauseBuff.appendBuffer(data);
            }
        }

        private void handleComplete() {
            if (!this.paused) {
                this.lazyCalculateSize = false;
                this.notifyEndHandler();
            }
        }

        private void notifyExceptionHandler(Throwable t) {
            if (this.exceptionHandler != null) {
                this.exceptionHandler.handle(t);
            }
        }

        private void notifyEndHandler() {
            if (this.endHandler != null) {
                this.endHandler.handle(null);
            }
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy