Xpoint
   [напомнить пароль]

Отправление файла с помощью XMLHttpRequest

2004-03-17 14:08:15 [обр] Михаил [досье]

Как отправить файл на сервер с xul-страницы (что-то вроде <input type="file">)? В описаниях указано, что нужно отправлять nsIInputStream, при этом указать соответствующие заголовки и добавить перевод строки. В связи с этим:

  1. Как добавить что-то к nsIInputStream?
  2. Как правильно указать параметры при инициализации потока?

void init ( nsIFile file , PRInt32 ioFlags , PRInt32 perm , PRInt32 behaviorFlags ). В описании (http://www.xulplanet.com/refer......ifaces/nsIFileInputStream.html) есть ссылка на какой-то prio.h, но где его взять?

Наконец код, который не работает:

netscape.security.PrivilegeManager.enablePrivilege("UniversalXPConnect");

var xmlhttpUpload = new XMLHttpRequest();
xmlhttpUpload.open("POST", "/jsps/comp.jsp", false);

var file = Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile);
file.initWithPath( "C:\\g1.tif" );

xmlhttpUpload.setRequestHeader("Content-type", "image/tif");
xmlhttpUpload.setRequestHeader("Content-length", file.fileSize);

var obj_InputStream = Components.classes["@mozilla.org/network/file-input-stream;1"].createInstance(Components.interfaces.nsIFileInputStream);
obj_InputStream.init(file, 0x01, 00004, null);

xmlhttpUpload.send(obj_InputStream);

В последней строке получаем сообщение

[Exception... "Component returned failure code: 0x80004005 (NS_ERROR_FAILURE)
[nsiXMLHttpRequest.send] nsresult: "0x80004005(NS_ERROR_FAILURE)" location:  ........." data: no]
спустя 9 минут [обр] Владимир Палант [досье]
Выдержка из prio.h:
/*
...
 *     PRIntn flags
 *         The file status flags.
 *         It is a bitwise OR of the following bit flags (only one of
 *         the first three flags below may be used):
 *             PR_RDONLY        Open for reading only.
 *             PR_WRONLY        Open for writing only.
 *             PR_RDWR          Open for reading and writing.
 *             PR_CREATE_FILE   If the file does not exist, the file is created
 *                              If the file exists, this flag has no effect.
 *             PR_SYNC          If set, each write will wait for both the file data
 *                              and file status to be physically updated.
 *             PR_APPEND        The file pointer is set to the end of
 *                              the file prior to each write.
 *             PR_TRUNCATE      If the file exists, its length is truncated to 0.
 *             PR_EXCL          With PR_CREATE_FILE, if the file does not exist,
 *                              the file is created. If the file already 
 *                              exists, no action and NULL is returned
 *
 *     PRIntn mode
 *         The access permission bits of the file mode, if the file is
 *         created when PR_CREATE_FILE is on.
...
 */

/* Open flags */
#define PR_RDONLY       0x01
#define PR_WRONLY       0x02
#define PR_RDWR         0x04
#define PR_CREATE_FILE  0x08
#define PR_APPEND       0x10
#define PR_TRUNCATE     0x20
#define PR_SYNC         0x40
#define PR_EXCL         0x80

/*
** File modes ....
**
** CAVEAT: 'mode' is currently only applicable on UNIX platforms.
** The 'mode' argument may be ignored by PR_Open on other platforms.
**
**   00400   Read by owner.
**   00200   Write by owner.
**   00100   Execute (search if a directory) by owner.
**   00040   Read by group.
**   00020   Write by group.
**   00010   Execute by group.
**   00004   Read by others.
**   00002   Write by others
**   00001   Execute by others.
**
*/
спустя 14 минут [обр] Владимир Палант [досье]
Что касается того, чтобы послать файл, как из формы — вы знаете формат multipart/formdata (RFC1867(ietf), RFC2388(ietf))? Просто послать файл недостаточно, нужно закодировать данные в соответствии с этим форматом и проставить нужные заголовки...
спустя 46 минут [обр] Михаил [досье]
Спасибо за ссылки.
Относительно отправки файла. У меня на самом деле не стоит задача полностью воспроизвести <input type="file">, нужно просто отправить файл, необязательно в формате multipart/formdata. И, как я понимаю, проблема не в том, что формат неправильный, а в том, что файл вообще не отправляется. Или я ошибаюсь, и в другом формате отправить нельзя?
спустя 25 минут [обр] Михаил [досье]
На всякий случай. Адрес сервера при отправке правильный. Если вместо obj_InputStream вставляю простой текст, то он благополучно добирается до места.
спустя 12 минут [обр] Владимир Палант [досье]
Михаил[досье]
Почему не отправляется, если ему дать поток — непонятно, похоже на баг. Если отправлять так, то работает:
...

var scriptableStream = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(Components.interfaces.nsIScriptableInputStream);
scriptableStream.init(obj_InputStream);
var data = "";
while (scriptableStream.available() > 0)
  data += scriptableStream.read(scriptableStream.available());
scriptableStream.close();

xmlhttpUpload.send(data);
спустя 27 минут [обр] Владимир Палант [досье]
Запустил дебаговую версию, она сразу показала, в чём дело — XMLHttpRequest (а точнее: nsIUploadChannel) использует nsIInputStream.ReadSegments() для чтения, а для nsIFileInputStream этот метод не реализован. Не знаю, сочтут ли это багом, а если сочтут, то кто на самом деле виноват...
спустя 10 часов [обр] Михаил [досье]
К сожалению фокус не удался, хотя ошибка и пропала. Причина в том, что scriptableStream затыкается на первом же нуле во входном файле. Все остальное отрезает. В моем конкретном случае передаются только три первых символа.
По большому счету, даже если бы получилось, все равно это не решило бы проблемы, т.к. передавать большие файлы по такой схеме нельзя. Впрочем для начала и это бы устроило.
Я порылся в jar-ах в chrome, надеялся там что-нибудь подходящее найти, ведь файлы все-таки как то отправляются, - безуспешно. Вероятно для этого привлекаются средства операционной системы, а не XUL/XPCOM.
Одновременно с этим форумом отправил свой вопрос Нилу Дикину (Neil Deakin) и на форум xulplanet - эффект пока нулевой.
Куда посоветуете обратиться, чтобы был результат? Где-то Вы писали о быстрой реакции Мозиллы на сообщения о багах. Куда надо писать?
спустя 11 часов [обр] Владимир Палант [досье]
Михаил[досье]
Самое главное правило, если вы хотите, чтобы вам помогли: убедитесь, что вы действительно нашли баг. nsIScriptableStream предназначен исключительно для текстовых данных (см. интерфейс — он возвращает char*, то есть ничего удивительного, что он считает знак NUL концом строки). Для чтения двоичных данных используют nsIBinaryStream.
спустя 6 минут [обр] Владимир Палант [досье]
Посмотрел ещё раз на всё это — nsIFileInputStream не поддерживает ReadSegments(), но этот метод поддерживает nsIBufferedInputStream (в этом есть своя логика). То есть надо всего лишь написать:
var bufferedStream = Components.classes["@mozilla.org/network/buffered-input-stream;1"]
                               .createInstance(Components.interfaces.nsIBufferedInputStream);
bufferedStream.init(obj_InputStream, 16384);
xmlhttpUpload.send(bufferedStream);
спустя 21 час [обр] Михаил [досье]

Все-таки еще не конец (см. п.1 наверху).
Для того, чтобы сервер понял, что ему передается, нужно отделить header запроса от body парой <CR><LF>.

Делается это так:

var multiplexStream = Components.classes["@mozilla.org/io/multiplex-input-stream;1"]
                                .createInstance(Components.interfaces.nsIMultiplexInputStream);

var stringStream = Components.classes["@mozilla.org/io/string-input-stream;1"]
                             .createInstance(Components.interfaces.nsIStringInputStream);
stringStream.setData("\r", 2);

var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"]
                           .createInstance(Components.interfaces.nsIFileInputStream);
fileStream.init(dFile, 0x01, 0444, null);

multiplexStream.appendStream(stringStream);
multiplexStream.appendStream(fileStream);

var bufferedStream = Components.classes["@mozilla.org/network/buffered-input-stream;1"]
                               .createInstance(Components.interfaces.nsIBufferedInputStream);

bufferedStream.init(multiplexStream, fileSize);
xmlhttpUpload.send(bufferedStream);

Теперь, вроде, все.
Не считая того, что файл должен быть chrome, или подписан, но это отдельный разговор, и, наверное, эта тема уже обсуждалась.

спустя 41 минуту [обр] Владимир Палант [досье]
Михаил[досье]
Вы же сами говорили, что собираетесь пересылать большие файлы. Так зачем же вы устанавливаете размер буфера равным размеру файла? В один пакет всё равно больше 1500 байтов не влезет...
спустя 3 минуты [обр] Михаил [досье]
Спасибо. Не подумал об этом.
Powered by POEM™ Engine Copyright © 2002-2005