Жасмин Бланшет - QT 4: программирование GUI на С++
get(file_N_1)
emit listInfo(file_N_2)
get(file_N_2)
…
emit done()
Если файл фактически оказывается каталогом, он добавляется в список pendingDirs и, когда завершается скачивание последнего файла, полученного текущей командой list(), выдается новая команда cd(), за которой следует новая команда list() для следующего каталога, ожидающего обработки, и весь процесс повторяется для нового каталога. Скачиваются новые файлы, и в список pendingDirs добавляются новые каталоги до тех пор, пока не будут скачаны все файлы из всех каталогов и список pendingDirs в результате не станет пустым.
Если возникнет сетевая ошибка при загрузке пятого файла, скажем, из двадцати файлов в каталоге, остальные файлы не будут скачаны. Если бы мы захотели скачать как можно больше файлов, то один из способов заключается в выполнении по одной операции GET и ожидании сигнала done(bool) перед выполнением новой операции GET. В функции listInfo() мы бы просто добавили имя файла в конец списка QStringList вместо немедленного вызова get(), а в слоте done(bool) мы бы вызывали функцию get() для следующего загружаемого файла из списка QStringList. Последовательность команд выглядела бы так:
connectToHost(host, port)
login()
cd(directory_1)
list()
…
cd(directory_N)
list()
emit listInfo(file_1_1)
emit listInfo(file_1_2)
…
emit listInfo(file_N_1)
emit listInfo(file_N_2)
…
emit done()
get(file_1_1)
emit done()
get(file_1_2)
emit done()
…
get(file_N_1)
emit done()
get(file_N_2)
emit done()
…
Еще одно решение могло бы заключаться в применении одного объекта QFtp для каждого файла. Это позволило бы нам скачивать файлы из сети параллельно, используя отдельные FTP—соединения.
01 int main(int argc, char *argv[])
02 {
03 QCoreApplication app(argc, argv);
04 QStringList args = app.arguments();
05 if (args.count() != 2) {
06 cerr << "Usage: spider url" << endl << "Example:" << endl
07 << " spider ftp://ftp.trolltech.com/freebies/leafnode" << endl;
08 return 1;
09 }
10 Spider spider;
11 if (!spider.getDirectory(QUrl(args[1])))
12 return 1;
13 QObject::connect(&spider, SIGNAL(done()), &app, SLOT(quit()));
14 return app.exec();
15 }
Функция main() завершает программу. Если пользователь не задает адрес URL в командной строке, мы выдаем сообщение об ошибке и завершаем программу.
В обоих примерах применения протокола FTP данные, полученные функцией get(), записывались в объект QFile. Это не обязательно должно быть так. Если бы мы захотели хранить данные в памяти, мы могли бы использовать QBuffer — подкласс QIODevice, являющийся оболочкой массива QByteArray. Например:
QBuffer *buffer= new QBuffer;
buffer->open(QIODevice::WriteOnly);
ftp.get(urlInfo.name(), buffer);
Мы могли бы также не задавать в функции get() аргумент с устройством ввода—вывода или передать нулевой указатель. Класс QFtp тогда генерирует сигнал readyRead() при поступлении каждой новой порции данных и данные могут считываться при помощи функции read() или readAll().
Написание НТТР—клиента
Класс QHttp реализует клиентскую часть протокола HTTP в Qt. Он содержит различные функции для выполнения самых распространенных операций протокола HTTP, включая get() и post(), и обеспечивает средство выполнения произвольных запросов HTTP. Если вы прочитали предыдущий раздел о классе QFtp, вы обнаружите, что существует много общего у классов QFtp и QHttp.
Класс QHttp работает асинхронно. Когда мы вызываем такие функции, как get() или post(), управление сразу же возвращается к нам, а пересылка данных осуществляется после передачи управления обратно в цикл обработки событий Qt. Это обеспечивает работоспособность интерфейса пользователя во время обработки запросов HTTP.
Мы рассмотрим пример консольного приложения с именем httpget, демонстрирующего способ загрузки файла с использованием протокола HTTP. Мы не приводим здесь заголовочный файл, поскольку данный пример очень напоминает пример ftpget, который мы использовали в предыдущем разделе.
01 HttpGet::HttpGet(QObject *parent)
02 : QObject(parent)
03 {
04 …
05 connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool)));
06 }
В конструкторе мы подсоединяем сигнал done(bool) объекта QHttp к закрытому слоту httpDone(bool).
01 bool HttpGet::getFile(const QUrl &url)
02 {
03 if (!url.isValid()) {
04 сегг << "Error: Invalid URL" << endl;
05 return false;
06 }
07 if (url.scheme() != "http") {
08 cerr << "Error: URL must start with 'http:'" << endl;
09 return false;
10 }
11 if (url.path().isEmpty()) {
12 cerr << "Error: URL has no path" << endl;
13 return false;
14 }
15 QString localFileName = QFileInfo(url.path()).fileName();
16 if (localFileName.isEmpty())
17 localFileName = "httpget.out";
18 file.setFileName(localFileName);
19 if (!file.open(QIODevice::WriteOnly)) {
20 cerr << "Error: Cannot open "
21 << qPrintable(file.fileName()) << " for writing: "
22 << qPrintable(file.errorString()) << endl;
23 return false;
24 }
25 http.setHost(url.host(), url.port(80));
26 http.get(url.path(), &file);
27 http.close();
28 return true;
29 }
Функция getFile() проверяет ошибочные ситуации так же, как рассмотренная ранее функция FtpGet::getFile(), и использует тот же подход при задании имени локального файла. При загрузке файлов с веб-сайта не требуется входить в систему, поэтому мы просто указываем хост и порт (используя стандартный для HTTP порт 80, если его нет в URL) и скачиваем данные в файл, заданный вторым аргументом функции QHttp::get().
Запросы HTTP ставятся в очередь и обрабатываются асинхронно в цикле обработки событий Qt. На завершение выполнения запросов указывает сигнал done(bool) объекта QHttp, который мы подсоединили к слоту httpDone(bool) в конструкторе.
01 void HttpGet::httpDone(bool еггог)
02 {
03 if (еггог) {
04 сегг << "Еггог: " << qPrintable(http.errorString()) << endl;
05 } else {
06 сегг << "File downloaded as " << qPrintable(file.fileName()) << endl;
07 }
08 file.close();
09 emit done();
10 }
После выполнения запросов HTTP мы файл закрываем, уведомляя пользователя о возникновении ошибки.
Функция main() очень похожа на такую же функцию в примере ftpget:
01 int main(int argc, char *argv[])
02 {
03 QCoreApplication app(argc, argv);
04 QStringList args = app.arguments();
05 if (args.count() != 2) {
06 cerr << "Usage: httpget url" << endl << "Example:" << endl
07 << " httpget http://doc.trolltech.com/qq/index.html" << endl;
08 return 1;
09 }
10 HttpGet getter;
11 if (!getter.getFile(QUrl(args[1])))
12 return 1;
13 QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
14 return app.exec();
15 }
Класс QHttp содержит много операций, включая setHost(), get(), post() и head(). Если для входа на сайт необходимо выполнить аутентификацию пользователя, setUser() может использоваться для установки имени пользователя и пароля. QHttp может использовать сокет, указанный программистом, а не свой собственный внутренний QTcpSocket. Это делает возможным применение безопасного сокета QtSslSocket (который предоставляется компонентом Qt Solution компании «Trolltech») для работы с HTTP через SSL.
Мы можем применять функцию post() для пересылки пар «имя = значение» в сценарий CGI:
http.setHost("www.example.com");
http.post("/cgi/somescript.py", "x=200&y=320", &file);
Мы можем передавать данные в виде 8-битовой строки либо передавать открытое устройство QIODevice, например QFile. Для обеспечения большего контроля мы можем использовать функцию request(), которая принимает произвольные заголовок и данные HTTP. Например:
QHttpRequestHeader header("POST", "/search.html");
header.setValue("Host", "www.trolltech.com");