Возобновляемая загрузка из Java-клиента в веб-приложение Grails?

После почти 2 рабочих дней поиска в Google и пробования нескольких различных возможностей, которые я нашел в Интернете, я задаю этот вопрос здесь, надеясь, что, наконец, смогу получить ответ.

Прежде всего, вот что я хочу сделать:

Я разрабатываю клиентское и серверное приложение с целью обмена большими файлами между несколькими клиентами на одном сервере. Клиент разработан на чистом Java (JDK 1.6), а веб-приложение на Grails (2.0.0).

Поскольку целью клиента является предоставление пользователям возможности обмениваться большим количеством больших файлов (обычно около 2 ГБ каждый), я должен реализовать его таким образом, чтобы загрузки были возобновляемыми, то есть пользователи могли останавливать и возобновлять загрузки в любое время.

Вот что я сделал до сих пор:

Мне действительно удалось сделать то, что я хотел, и передавать большие файлы на сервер, но при этом я мог приостанавливать и возобновлять загрузку с использованием необработанных сокетов. Я бы отправлял на сервер обычный запрос (используя библиотеку Apache HttpClient), чтобы сервер отправил мне порт, который я мог использовать бесплатно, затем открыл ServerSocket на сервере и подключился к этому конкретному сокету от клиента.

Вот проблема с этим:

На самом деле есть как минимум две проблемы с этим:

  1. Я сам открываю эти порты, поэтому мне приходится самостоятельно управлять открытыми и используемыми портами. Это довольно подвержено ошибкам.
  2. Я фактически обхожу способность Grails управлять огромным количеством (одновременных) соединений.

Наконец, вот что я должен сделать сейчас и проблема:

Поскольку проблемы, о которых я упоминал выше, неприемлемы, я теперь должен использовать классы Java URLConnection/HttpURLConnection, но при этом придерживаться Grails.

Подключение к серверу и отправка простых запросов вообще не проблема, все работало нормально. Проблемы начались, когда я попытался использовать потоки (OutputStream соединения на клиенте и InputStream запроса на сервере). Открыть OutputStream клиента и записать в него данные так же просто, как и получить. Но чтение из InputStream запроса кажется мне невозможным, так как этот поток всегда пуст, как кажется.

Пример кода

Вот пример серверной части (Groovy контроллер):

def test() {
    InputStream inStream = request.inputStream

    if(inStream != null) {
        int read = 0;
        byte[] buffer = new byte[4096];
        long total = 0;

        println "Start reading"

        while((read = inStream.read(buffer)) != -1) {
            println "Read " + read + " bytes from input stream buffer"      //<-- this is NEVER called
        }       

        println "Reading finished"
        println "Read a total of " + total + " bytes"   // <-- 'total' will always be 0 (zero)
    } else {
        println "Input Stream is null"      // <-- This is NEVER called
    }
}

Это то, что я сделал на стороне клиента (класс Java):

public void connect() {
    final URL url = new URL("myserveraddress");
    final byte[] message = "someMessage".getBytes();        // Any byte[] - will be a file one day
    HttpURLConnection connection = url.openConnection();
    connection.setRequestMethod("GET");                     // other methods - same result

    // Write message 
    DataOutputStream out = new DataOutputStream(connection.getOutputStream());
    out.writeBytes(message);
    out.flush();
    out.close();

    // Actually connect
    connection.connect();                                   // is this placed correctly?

    // Get response
    BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));

    String line = null;
    while((line = in.readLine()) != null) {
        System.out.println(line);                   // Prints the whole server response as expected
    }

    in.close();
}

Как я уже говорил, проблема в том, что request.inputStream всегда выдает пустой InputStream, поэтому я никогда не смогу прочитать что-либо из него (конечно). Но так как это именно то, что я пытаюсь сделать (поэтому я могу передать файл для загрузки на сервер, прочитать из InputStream и сохранить его в файл), это довольно разочаровывает.

Я пробовал разные методы HTTP, разные полезные данные, а также перестраивал код снова и снова, но, похоже, не смог решить проблему.

Что я надеюсь найти

Я надеюсь найти решение моей проблемы, конечно. Все высоко ценится: подсказки, фрагменты кода, предложения библиотеки и так далее. Может быть, я даже все неправильно понимаю, и мне нужно идти в совершенно другом направлении.

Итак, как я могу реализовать возобновляемую загрузку файлов для довольно больших (двоичных) файлов из Java-клиента в веб-приложение Grails, не открывая вручную порты на стороне сервера?

2 ответа

Java-программирование на стороне клиента:

    public class NonFormFileUploader {
    static final String UPLOAD_URL= "http://localhost:8080/v2/mobileApp/fileUploadForEOL";


    static final int BUFFER_SIZE = 4096;

    public static void main(String[] args) throws IOException {
        // takes file path from first program's argument
        String filePath = "G:/study/GettingStartedwithGrailsFinalInfoQ.pdf";
        File uploadFile = new File(filePath);

        System.out.println("File to upload: " + filePath);

        // creates a HTTP connection
        URL url = new URL(UPLOAD_URL);
        HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
        httpConn.setDoOutput(true);
        httpConn.setRequestMethod("POST");
        // sets file name as a HTTP header
        httpConn.setRequestProperty("fileName", uploadFile.getName());

        // opens output stream of the HTTP connection for writing data
        OutputStream outputStream = httpConn.getOutputStream();

        // Opens input stream of the file for reading data
        FileInputStream inputStream = new FileInputStream(uploadFile);

        byte[] buffer = new byte[BUFFER_SIZE];
        int bytesRead = -1;

        while ((bytesRead = inputStream.read(buffer)) != -1) {
            System.out.println("bytesRead:"+bytesRead);
            outputStream.write(buffer, 0, bytesRead);
            outputStream.flush();
        }

        System.out.println("Data was written.");
        outputStream.flush();
        outputStream.close();
        inputStream.close();

        int responseCode = httpConn.getResponseCode();
        if (responseCode == HttpURLConnection.HTTP_OK) {
            // reads server's response
            BufferedReader reader = new BufferedReader(new InputStreamReader(
                    httpConn.getInputStream()));
            String response = reader.readLine();
            System.out.println("Server's response: " + response);
        } else {
            System.out.println("Server returned non-OK code: " + responseCode);
        }
    }
 }

Программа Grails на стороне сервера:

Внутри контроллера:

def fileUploadForEOL(){
    def result
    try{
        result = mobileAppService.fileUploadForEOL(request);
    }catch(Exception e){
        log.error "Exception in fileUploadForEOL service",e
    }
    render result as JSON
}

Внутри класса обслуживания:

    def fileUploadForEOL(request){
    def status = false;
    int code = 500
    def map = [:]
    try{
    String fileName = request.getHeader("fileName");
    File saveFile = new File(SAVE_DIR + fileName);

    System.out.println("===== Begin headers =====");
    Enumeration<String> names = request.getHeaderNames();
    while (names.hasMoreElements()) {
        String headerName = names.nextElement();
        System.out.println(headerName + " = " + request.getHeader(headerName));        
    }
    System.out.println("===== End headers =====\n");

    // opens input stream of the request for reading data
    InputStream inputStream = request.getInputStream();

    // opens an output stream for writing file
    FileOutputStream outputStream = new FileOutputStream(saveFile);

    byte[] buffer = new byte[BUFFER_SIZE];
    int bytesRead = inputStream.read(buffer);

    long count =  bytesRead

    while(bytesRead != -1) {
        outputStream.write(buffer, 0, bytesRead);
      bytesRead = inputStream.read(buffer);
      count += bytesRead
    }
     println "count:"+count
    System.out.println("Data received.");
    outputStream.close();
    inputStream.close();

    System.out.println("File written to: " + saveFile.getAbsolutePath());

    code = 200

    }catch(Exception e){
        mLogger.log(java.util.logging.Level.SEVERE,"Exception in fileUploadForEOL",e);
    }finally{
        map <<["code":code]
    }
    return map
}

Я пытался с вышеупомянутым кодом, он работает для меня (только для файлов размером от 3 до 4 МБ, но для файлов небольшого размера некоторые байты кода отсутствуют или даже не приходят, но в заголовке запроса длина содержимого приходит, не знаю, почему это происходит.)

Метод HTTP GET имеет специальные заголовки для извлечения диапазона: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html Он используется большинством загрузчиков для возобновляемой загрузки с сервера.

Как я понимаю, нет стандартной практики использования этих заголовков для запроса POST/PUT, но решать вам, не так ли? Вы можете сделать довольно стандартный контроллер Grails, который будет принимать стандартную загрузку http, с заголовком вроде Range: bytes=500-999, И контроллер должен поместить эти 500 загруженных байтов из клиента в файл, начиная с позиции 500

В этом случае вам не нужно открывать сокеты, создавать собственные протоколы и т. Д.

PS 500 байтов - это всего лишь пример, возможно, вы используете гораздо большие детали.

Другие вопросы по тегам