Prześlij plik przez formularz HTTP, przez MultipartEntityBuilder, z paskiem postępu

Wersja skrócona - org.apache...MultipartEntity jest przestarzały, a jego aktualizacja, MultipartEntityBuilder, wydaje się niewystarczająco reprezentowana na naszych forach internetowych. Naprawmy to. Jak zarejestrować callback, aby moja (Android) aplikacja może wyświetlać pasek postępu podczas przesyłania pliku?

The long version - Oto "brakujący Brud-prosty przykład" MultipartEntityBuilder:

public static void postFile(String fileName) throws Exception {
    // Based on: https://stackoverflow.com/questions/2017414/post-multipart-request-with-android-sdk

    HttpClient client = new DefaultHttpClient();
    HttpPost post = new HttpPost(SERVER + "uploadFile");
    MultipartEntityBuilder builder = MultipartEntityBuilder.create();        
    builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    builder.addPart("file", new FileBody(new File(fileName)));
    builder.addTextBody("userName", userName);
    builder.addTextBody("password", password);
    builder.addTextBody("macAddress", macAddress);
    post.setEntity(builder.build());
    HttpResponse response = client.execute(post);
    HttpEntity entity = response.getEntity();

    // response.getStatusLine();  // CONSIDER  Detect server complaints

    entity.consumeContent();
    client.getConnectionManager().shutdown(); 

}  // FIXME  Hook up a progress bar!

Musimy to naprawić FIXME. (Dodatkową korzyścią byłoby przerywane przesyłanie.) Ale (proszę popraw mnie czy się mylę czy nie), wszystkie przykłady online wydają się niewystarczające.

Ten, http://pastebin.com/M0uNZ6SB , na przykład, przesyła plik jako "binary / octet-stream", a nie "multipart / form-data". Potrzebuję prawdziwych pól.

Ten przykład, Przesyłanie pliku za pomocą Javy (z paskiem postępu) , pokazuje, jak nadpisać *Entity lub *Stream. Więc może powiem MultipartEntityBuilder .create() nadpisanemu bytu, który mierzy jego postęp wysyłania?

Więc jeśli chcę coś zastąpić, i zastąpić wbudowany strumień ze strumieniem zliczającym, który wysyła sygnał na każde 1000 bajtów, może uda mi się rozszerzyć FileBody część i nadpisać jej getInputStream i/lub writeTo.

Ale kiedy próbuję class ProgressiveFileBody extends FileBody {...}, dostaję niesławny java.lang.NoClassDefFoundError.

Więc kiedy będę spelunkować po moich plikach .jar, szukając brakującego Def, czy ktoś może sprawdzić moją matmę i może wskazać prostszą poprawkę, którą przeoczyłem?

Author: Community, 2013-09-23

3 answers

Zwycięski kod (w stylu Java-Herezja (tm)) to:

public static String postFile(String fileName, String userName, String password, String macAddress) throws Exception {

    HttpClient client = new DefaultHttpClient();
    HttpPost post = new HttpPost(SERVER + "uploadFile");
    MultipartEntityBuilder builder = MultipartEntityBuilder.create();        
    builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);

    final File file = new File(fileName);
    FileBody fb = new FileBody(file);

    builder.addPart("file", fb);  
    builder.addTextBody("userName", userName);
    builder.addTextBody("password", password);
    builder.addTextBody("macAddress",  macAddress);
    final HttpEntity yourEntity = builder.build();

    class ProgressiveEntity implements HttpEntity {
        @Override
        public void consumeContent() throws IOException {
            yourEntity.consumeContent();                
        }
        @Override
        public InputStream getContent() throws IOException,
                IllegalStateException {
            return yourEntity.getContent();
        }
        @Override
        public Header getContentEncoding() {             
            return yourEntity.getContentEncoding();
        }
        @Override
        public long getContentLength() {
            return yourEntity.getContentLength();
        }
        @Override
        public Header getContentType() {
            return yourEntity.getContentType();
        }
        @Override
        public boolean isChunked() {             
            return yourEntity.isChunked();
        }
        @Override
        public boolean isRepeatable() {
            return yourEntity.isRepeatable();
        }
        @Override
        public boolean isStreaming() {             
            return yourEntity.isStreaming();
        } // CONSIDER put a _real_ delegator into here!

        @Override
        public void writeTo(OutputStream outstream) throws IOException {

            class ProxyOutputStream extends FilterOutputStream {
                /**
                 * @author Stephen Colebourne
                 */

                public ProxyOutputStream(OutputStream proxy) {
                    super(proxy);    
                }
                public void write(int idx) throws IOException {
                    out.write(idx);
                }
                public void write(byte[] bts) throws IOException {
                    out.write(bts);
                }
                public void write(byte[] bts, int st, int end) throws IOException {
                    out.write(bts, st, end);
                }
                public void flush() throws IOException {
                    out.flush();
                }
                public void close() throws IOException {
                    out.close();
                }
            } // CONSIDER import this class (and risk more Jar File Hell)

            class ProgressiveOutputStream extends ProxyOutputStream {
                public ProgressiveOutputStream(OutputStream proxy) {
                    super(proxy);
                }
                public void write(byte[] bts, int st, int end) throws IOException {

                    // FIXME  Put your progress bar stuff here!

                    out.write(bts, st, end);
                }
            }

            yourEntity.writeTo(new ProgressiveOutputStream(outstream));
        }

    };
    ProgressiveEntity myEntity = new ProgressiveEntity();

    post.setEntity(myEntity);
    HttpResponse response = client.execute(post);        

    return getContent(response);

} 

public static String getContent(HttpResponse response) throws IOException {
    BufferedReader rd = new BufferedReader(new InputStreamReader(response.getEntity().getContent()));
    String body = "";
    String content = "";

    while ((body = rd.readLine()) != null) 
    {
        content += body + "\n";
    }
    return content.trim();
}

#  NOTE ADDED LATER: as this blasterpiece gets copied into various code lineages, 
#  The management reminds the peanut gallery that "Java-Heresy" crack was there
#  for a reason, and (as commented) most of that stuff can be farmed out to off-
#  the-shelf jar files and what-not. That's for the java lifers to tool up. This
#  pristine hack shall remain obviousized for education, and for use in a pinch.

#  What are the odds??
 66
Author: Phlip,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-06-30 04:34:41

Nie mogę podziękować Phlipowi za to rozwiązanie. Oto ostatnie poprawki do dodawania wsparcia progresbar. Uruchomiłem go wewnątrz AsyncTask - postęp poniżej pozwala opublikować postęp z powrotem do metody w AsyncTask, który wywołuje AsyncTask.publishProgress () dla twojej klasy działającej w Asynctasku. Pasek postępu nie jest dokładnie gładki, ale przynajmniej się porusza. Na Samsungu S4 wgrywanie 4MB imagefile po preambuły to było przenoszenie 4K kawałki.

     class ProgressiveOutputStream extends ProxyOutputStream {
            long totalSent;
            public ProgressiveOutputStream(OutputStream proxy) {
                   super(proxy);
                   totalSent = 0;
            }

            public void write(byte[] bts, int st, int end) throws IOException {

            // FIXME  Put your progress bar stuff here!
            // end is the amount being sent this time
            // st is always zero and end=bts.length()

                 totalSent += end;
                 progress.publish((int) ((totalSent / (float) totalSize) * 100));
                 out.write(bts, st, end);
            }
 6
Author: Mike Kogan,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-17 07:16:42

Po pierwsze: wielkie dzięki za oryginalne pytanie/odpowiedź. Ponieważ HttpPost jest teraz przestarzały, przerobiłem go trochę, używając dodatkowego wejścia z tego artykułu i zrobiłem z niego mikro bibliotekę: https://github.com/licryle/HTTPPoster

Owija całość w zadanie asynchroniczne; używa MultipartEntityBuilder & HttpURLConnection i pozwala słuchać wywołań zwrotnych.

Do użycia:

  1. Pobierz i wyodrębnij
  2. w Twojej budowie.moduł gradle Plik, Dodaj zależność:
dependencies 
{    
     compile project(':libs:HTTPPoster') 
}
  1. Potrzebujesz klasy do zaimplementowania interfejsu HttpListener, abyś mógł słuchać wywołań zwrotnych. Posiada cztery wywołania w HTTPListener:

    • onStartTransfer
    • onProgress
    • onFailure
    • onResponse
  2. Skonfiguruj ASyncTask i uruchom go. Oto szybkie użycie:

HashMap<String, String> mArgs = new HashMap<>();
mArgs.put("lat", "40.712784");
mArgs.put("lon", "-74.005941");

ArrayList<File> aFileList = getMyImageFiles();

HttpConfiguration mConf = new HttpConfiguration(
    "http://example.org/HttpPostEndPoint",
    mArgs,
    aFileList,
    this, // If this class implements HttpListener
    null,  // Boundary for Entities - Optional
    15000  // Timeout in ms for the connection operation
    10000, // Timeout in ms for the reading operation
);

new HttpPoster().execute(mConf);

Mam nadzieję, że to może pomóc :) Zapraszam również do sugerowania ulepszeń! To bardzo niedawno, a ja przedłużam tak, jak tego potrzebuję.

Cheers

 2
Author: licryle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-11-09 01:18:19