写HTTP客户端
QHttp类在Qt中实现了HTTP协议的客户端程序。它提供了各种各样的函数来执行绝大多数普通HTTP操作,包括get()和post(),并且还提供了一个发送任意HTTP请求指令的方式。会发现QFtp和QHttp之间存在很多相似之处。
QHttp类是异步工作的。当调用一个像get()或者post( )这样的函数时,它会立即返回,并且当控制权回到Qt事件循环时才会开始传输数据。这样就确保了在处理HTTP请求时,应用程序的用户界面可以始终保持响应。
我们将查看一个名为httpget 的控制台应用程序,以说明如何利用HTTP协议下载一个文件。这里将不再显示出头文件,因为它和前一节所使用的那个ftpget例子,不论在功能上还是实现过程上,都非常相似。
HttpGet::HttpGet(QObject *parent)
: QObject(parent)
{
connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool)));
}
在构造函数中,我们把QHttp对象的done(bool)信号与httpDone(bool)私有槽连接起来。
bool HttpGet::getFile(const QUrl &url)
{
if (!url.isValid()) {
std::cerr << "Error: Invalid URL" << std::endl;
return false;
}
if (url.scheme() != "http") {
std::cerr << "Error: URL must start with 'http:'" << std::endl;
return false;
}
if (url.path().isEmpty()) {
std::cerr << "Error: URL has no path" << std::endl;
return false;
}
QString localFileName = QFileInfo(url.path()).fileName();
if (localFileName.isEmpty())
localFileName = "httpget.out";
file.setFileName(localFileName);
if (!file.open(QIODevice::WriteOnly)) {
std::cerr << "Error: Cannot write file "
<< qPrintable(file.fileName()) << ": "
<< qPrintable(file.errorString()) << std::endl;
return false;
}
http.setHost(url.host(), url.port(80));
http.get(url.path(), &file);
http.close();
return true;
}
getFile()函数执行与之前所给出的FtpGet::getFile()一样可以执行同种类型的错误检查,并且采用相同的方式来命名文件的本地文件名。当从网站获得文件时,由于不必登录,所以只要设置主机和端口号(如果在URL中没有指定端口号,则采用默认的HTTP端口号80),就可将数据下载到文件中,因为QHttp::get()的第二个参数指定了输出信号的输人输出(IO)设备。
这些HTTP请求在Qt的事件循环中排队并且被异步执行。QHttp 的done(bool)信号表明了这些请求的完成情况,在构造函数中已经把这个信号与httpDone(bool)连接起来了。
void HttpGet::httpDone(bool error)
{
if (error) {
std::cerr << "Error: " << qPrintable(http.errorString())
<< std::endl;
} else {
std::cerr << "File downloaded as "
<< qPrintable(file.fileName()) << std::endl;
}
file.close();
emit done();
}
一旦完成了这些HTTP请求,就关闭这个文件,并在有错误发生时通知用户。
它的main()函数与ftpget中曾使用过的主函数非常相似:
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = QCoreApplication::arguments();
if (args.count() != 2) {
std::cerr << "Usage: httpget url" << std::endl
<< "Example:" << std::endl
<< " httpget http://doc.trolltech.com/index.html"
<< std::endl;
return 1;
}
HttpGet getter;
if (!getter.getFile(QUrl(args[1])))
return 1;
QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}
QHtp类提供了多种操作,包括setHost()、 get()、post()和head()。如果站点需要认证,则setUser()可以用来提供用户名和口令。QHttp 可以用程序员自编的套接字装置而不用其内部自带的QTcpSocket。这就使利用可靠的QtSslSocket来在SSL(加密套接字协议层)或TLS(加密传输协议层)上实现HTTP成为可能。
为了向CGI脚本发送一列"name=value"值对,可以使用post()函数:
http.setHost("www.example.com");
http.post("/cgi/somescript.py", "x=200&y=3320", &file);
既可以用8位的字符串来传递数据,也可以像QFile一样,通过传递一个开放的QIODevice来传递数据。为了获得更多的控制权,可以使用request()函数,它接收任意一个HTTP的标题和数据。例如:
QHttpRequestHeader header("POST", "/search.html");
header.setValue("Host", "www.trolltech.com");
header.setContentType("application/X-www-form-urlencoded");
http.setHost("www.trolltech.com");
http.request(header, "qt-interest=on&search=opengl");
当QHttp开始执行请求时,它会发射requestStarted(int)信号,而当这个请求完成时,会发射requestFinished(int, bool)信号。int 参数是标识请求的ID号。如果我们对个别请求的结果感兴趣,就可以在调用这些指令的时候保存ID号。了解并记录这些ID号可以为用户提供详细的反馈
信息。
在绝大多数应用程序中,我们仅仅想知道整个系列的请求是否已成功地完成执行。通过与done(bool)信号连接,就可以很简单地实现这一点,当请求序列变空时,就会发射该信号。
当有错误发生时,这个请求队列会被自动清空。但是如果在错误发生之后使用相同的QHttp对象调用新的请求,这些请求将会照常排队并被发送执行。
与QFtp一样,QHttp不仅提供了read()和readAll()函数,它还提供readyRead()信号,使我们不必指定某一输入/输出设备。
httpget.h
#ifndef HTTPGET_H
#define HTTPGET_H
#include <QFile>
#include <QHttp>
class QUrl;
class HttpGet : public QObject
{
Q_OBJECT
public:
HttpGet(QObject *parent = 0);
bool getFile(const QUrl &url);
signals:
void done();
private slots:
void httpDone(bool error);
private:
QHttp http;
QFile file;
};
#endif
httpget.cpp
#include <QtCore>
#include <QtNetwork>
#include <iostream>
#include "httpget.h"
HttpGet::HttpGet(QObject *parent)
: QObject(parent)
{
connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool)));
}
bool HttpGet::getFile(const QUrl &url)
{
if (!url.isValid()) {
std::cerr << "Error: Invalid URL" << std::endl;
return false;
}
if (url.scheme() != "http") {
std::cerr << "Error: URL must start with 'http:'" << std::endl;
return false;
}
if (url.path().isEmpty()) {
std::cerr << "Error: URL has no path" << std::endl;
return false;
}
QString localFileName = QFileInfo(url.path()).fileName();
if (localFileName.isEmpty())
localFileName = "httpget.out";
file.setFileName(localFileName);
if (!file.open(QIODevice::WriteOnly)) {
std::cerr << "Error: Cannot write file "
<< qPrintable(file.fileName()) << ": "
<< qPrintable(file.errorString()) << std::endl;
return false;
}
http.setHost(url.host(), url.port(80));
http.get(url.path(), &file);
http.close();
return true;
}
void HttpGet::httpDone(bool error)
{
if (error) {
std::cerr << "Error: " << qPrintable(http.errorString())
<< std::endl;
} else {
std::cerr << "File downloaded as "
<< qPrintable(file.fileName()) << std::endl;
}
file.close();
emit done();
}
main.cpp
#include <QtCore>
#include <iostream>
#include "httpget.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
QStringList args = QCoreApplication::arguments();
if (args.count() != 2) {
std::cerr << "Usage: httpget url" << std::endl
<< "Example:" << std::endl
<< " httpget http://doc.trolltech.com/index.html"
<< std::endl;
return 1;
}
HttpGet getter;
if (!getter.getFile(QUrl(args[1])))
return 1;
QObject::connect(&getter, SIGNAL(done()), &app, SLOT(quit()));
return app.exec();
}