Загрузка файла с Java (с индикатором выполнения)

Я чрезвычайно новичок в Java, и в основном я учил себя на ходу, поэтому я начал создавать апплет. Я хотел бы сделать тот, который может выбрать файл с локального диска и загрузить его как POST-запрос multipart / form-data, но с индикатором выполнения. Очевидно, что пользователь должен предоставить разрешение апплету Java для доступа к жесткому диску. Теперь у меня уже работает первая часть: пользователь может выбрать файл, используя JFileChooser объект, который удобно возвращает File объект. Но мне интересно, что будет дальше. я знаю это File.length() даст мне общий размер в байтах файла, но как мне отправить выбранный File в Интернет, и как я могу контролировать, сколько байтов было отправлено? Заранее спасибо.

11 ответов

Решение

Я наткнулся на апплет Java-загрузчика с открытым исходным кодом и нашел все, что мне нужно было знать в его коде. Вот ссылки на сообщение в блоге, описывающее его, а также источник:

Статья
Исходный код

Чтобы проверить прогресс с помощью HttpClient, оберните MultipartRequestEntity вокруг единицы, которая считает отправленные байты. Обертка ниже:

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import org.apache.commons.httpclient.methods.RequestEntity;

public class CountingMultipartRequestEntity implements RequestEntity {
    private final RequestEntity delegate;

    private final ProgressListener listener;

    public CountingMultipartRequestEntity(final RequestEntity entity,
            final ProgressListener listener) {
        super();
        this.delegate = entity;
        this.listener = listener;
    }

    public long getContentLength() {
        return this.delegate.getContentLength();
    }

    public String getContentType() {
        return this.delegate.getContentType();
    }

    public boolean isRepeatable() {
        return this.delegate.isRepeatable();
    }

    public void writeRequest(final OutputStream out) throws IOException {
        this.delegate.writeRequest(new CountingOutputStream(out, this.listener));
    }

    public static interface ProgressListener {
        void transferred(long num);
    }

    public static class CountingOutputStream extends FilterOutputStream {

        private final ProgressListener listener;

        private long transferred;

        public CountingOutputStream(final OutputStream out,
                final ProgressListener listener) {
            super(out);
            this.listener = listener;
            this.transferred = 0;
        }

        public void write(byte[] b, int off, int len) throws IOException {
            out.write(b, off, len);
            this.transferred += len;
            this.listener.transferred(this.transferred);
        }

        public void write(int b) throws IOException {
            out.write(b);
            this.transferred++;
            this.listener.transferred(this.transferred);
        }
    }
}

Затем реализует ProgressListener, который обновляет индикатор выполнения.
Помните, что обновление индикатора выполнения не должно выполняться в потоке диспетчеризации событий.

Более простой countingEntity не будет зависеть от конкретного типа объекта, а скорее будет расширяться HttpEntityWrapped:

package gr.phaistos.android.util;

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.http.HttpEntity;
import org.apache.http.entity.HttpEntityWrapper;

public class CountingHttpEntity extends HttpEntityWrapper {

    public static interface ProgressListener {
        void transferred(long transferedBytes);
    }


    static class CountingOutputStream extends FilterOutputStream {

        private final ProgressListener listener;
        private long transferred;

        CountingOutputStream(final OutputStream out, final ProgressListener listener) {
            super(out);
            this.listener = listener;
            this.transferred = 0;
        }

        @Override
        public void write(final byte[] b, final int off, final int len) throws IOException {
            //// NO, double-counting, as super.write(byte[], int, int) delegates to write(int).
            //super.write(b, off, len);
            out.write(b, off, len);
            this.transferred += len;
            this.listener.transferred(this.transferred);
        }

        @Override
        public void write(final int b) throws IOException {
            out.write(b);
            this.transferred++;
            this.listener.transferred(this.transferred);
        }

    }


    private final ProgressListener listener;

    public CountingHttpEntity(final HttpEntity entity, final ProgressListener listener) {
        super(entity);
        this.listener = listener;
    }

    @Override
    public void writeTo(final OutputStream out) throws IOException {
        this.wrappedEntity.writeTo(out instanceof CountingOutputStream? out: new CountingOutputStream(out, this.listener));
    }
}

Количество байтов, возвращаемых слушателем, отличается от исходного размера файла. Таким образом, вместо того, чтобы иметь transferred++Я изменил это так, чтобы transferred=len; это длина фактического количества байтов, записываемых в выходной поток. И когда я вычисляю сложение всех переданных байтов, оно равно фактическому ContentLength вернулся CountingMultiPartEntity.this.getContentLength();

public void write(byte[] b, int off, int len) throws IOException {
    wrappedOutputStream_.write(b,off,len);
    transferred=len;
    listener_.transferred(transferred);
}

Имейте в виду, что индикатор выполнения может вводить в заблуждение, когда промежуточный компонент в сети (например, HTTP-прокси интернет-провайдера или обратный HTTP-прокси перед сервером) потребляет загрузку быстрее, чем сервер.

Вы можете найти эту статью полезной. Он подробно объясняет, как HttpClient и FileUpload, оба проекта apache, делают то, что вы хотите. Он также включает примеры кода.

Как отмечается в статье, опубликованной Винсентом, вы можете использовать Apache Commons для этого.

Немного подрезал


DiskFileUpload upload = new DiskFileUpload();
upload.setHeaderEncoding(ConsoleConstants.UTF8_ENCODING);

upload.setSizeMax(1000000);
upload.setSizeThreshold(1000000);

Iterator it = upload.parseRequest((HttpServletRequest) request).iterator();
FileItem item;
while(it.hasNext()){
    item = (FileItem) it.next();
    if (item.getFieldName("UPLOAD FIELD"){
       String fileName = item.getString(ConsoleConstants.UTF8_ENCODING);
       byte[] fileBytes = item.get();
    }
}

Просто мой 2с стоит:

Это основано на ответе Тулера (есть ошибка во время написания). Я немного изменил его, так что вот моя версия ответа tuler и mmyers (кажется, я не могу отредактировать их ответ). Я хотел попытаться сделать это немного чище и быстрее. Помимо ошибки (которую я обсуждаю в комментариях к их ответу), у меня большая проблема с их версией в том, что она создает новую CountingOutputStream с каждой записью. Это может стать очень дорогим с точки зрения памяти - тонны выделений и сборщиков мусора. Меньшая проблема заключается в том, что используется делегат, когда он может просто расширить MultipartEntity, Не уверен, почему они выбрали это, поэтому я сделал это так, как мне было лучше знакомо. Если кто-нибудь знает плюсы / минусы двух подходов, это было бы здорово. Наконец, метод FilterOutputStream#write(byte[], int,int) просто вызывает в цикле запись FilterOutputStream#write(byte). Документация FOS рекомендует подклассам переопределять это поведение и делать его более эффективным. Лучший способ сделать это здесь - позволить исходному OutputStream обрабатывать запрос на запись.

import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;

import org.apache.http.entity.mime.HttpMultipartMode;
import org.apache.http.entity.mime.MultipartEntity;

public class CountingMultiPartEntity extends MultipartEntity {

    private UploadProgressListener listener_;
    private CountingOutputStream outputStream_;
    private OutputStream lastOutputStream_;

    // the parameter is the same as the ProgressListener class in tuler's answer
    public CountingMultiPartEntity(UploadProgressListener listener) {
        super(HttpMultipartMode.BROWSER_COMPATIBLE);
        listener_ = listener;
    }

    @Override
    public void writeTo(OutputStream out) throws IOException {
        // If we have yet to create the CountingOutputStream, or the
        // OutputStream being passed in is different from the OutputStream used
        // to create the current CountingOutputStream
        if ((lastOutputStream_ == null) || (lastOutputStream_ != out)) {
            lastOutputStream_ = out;
            outputStream_ = new CountingOutputStream(out);
        }

        super.writeTo(outputStream_);
    }

    private class CountingOutputStream extends FilterOutputStream {

        private long transferred = 0;
            private OutputStream wrappedOutputStream_;

        public CountingOutputStream(final OutputStream out) {
            super(out);
                    wrappedOutputStream_ = out;
        }

        public void write(byte[] b, int off, int len) throws IOException {
                    wrappedOutputStream_.write(b,off,len);
                    ++transferred;
            listener_.transferred(transferred);
        }

        public void write(int b) throws IOException {
            super.write(b);
        }
    }
}

Загляните в HTTP-клиент для загрузки файла в Интернет. Это должно быть в состоянии сделать это. Я не уверен, как получить индикатор выполнения, но это будет связано с тем, чтобы запросить этот API как-то.

Из других ответов вы можете просто переопределить AbstractHttpEntity класс детей или реализации public void writeTo(OutputStream outstream) метод, который вы используете, если не хотите создавать класс.

Пример использования FileEntity пример:

FileEntity fileEntity = new FileEntity(new File("img.jpg")){
    @Override
    public void writeTo(OutputStream outstream) throws IOException {
        super.writeTo(new BufferedOutputStream(outstream){
            int writedBytes = 0;

            @Override
            public synchronized void write(byte[] b, int off, int len) throws IOException {
                super.write(b, off, len);

                writedBytes+=len;
                System.out.println("wrote: "+writedBytes+"/"+getContentLength()); //Or anything you want [using other threads]
            }
        });
    }

};

Apache common - очень хороший вариант. Apache common позволяет настроить следующие вещи.

  1. Настройте (файл xml) максимальный размер файла / размер загружаемого файла
  2. Путь к месту назначения (где сохранить загруженный файл)
  3. Установите темп. папка, чтобы поменять файл, чтобы загрузка файла была быстрой.
Другие вопросы по тегам