Возобновляемая загрузка из Java-клиента в веб-приложение Grails?
После почти 2 рабочих дней поиска в Google и пробования нескольких различных возможностей, которые я нашел в Интернете, я задаю этот вопрос здесь, надеясь, что, наконец, смогу получить ответ.
Прежде всего, вот что я хочу сделать:
Я разрабатываю клиентское и серверное приложение с целью обмена большими файлами между несколькими клиентами на одном сервере. Клиент разработан на чистом Java (JDK 1.6), а веб-приложение на Grails (2.0.0).
Поскольку целью клиента является предоставление пользователям возможности обмениваться большим количеством больших файлов (обычно около 2 ГБ каждый), я должен реализовать его таким образом, чтобы загрузки были возобновляемыми, то есть пользователи могли останавливать и возобновлять загрузки в любое время.
Вот что я сделал до сих пор:
Мне действительно удалось сделать то, что я хотел, и передавать большие файлы на сервер, но при этом я мог приостанавливать и возобновлять загрузку с использованием необработанных сокетов. Я бы отправлял на сервер обычный запрос (используя библиотеку Apache HttpClient), чтобы сервер отправил мне порт, который я мог использовать бесплатно, затем открыл ServerSocket на сервере и подключился к этому конкретному сокету от клиента.
Вот проблема с этим:
На самом деле есть как минимум две проблемы с этим:
- Я сам открываю эти порты, поэтому мне приходится самостоятельно управлять открытыми и используемыми портами. Это довольно подвержено ошибкам.
- Я фактически обхожу способность 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 байтов - это всего лишь пример, возможно, вы используете гораздо большие детали.