- Writing FTP Clients
- Writing HTTP Clients
- Writing TCP Client-Server Applications
- Sending and Receiving UDP Datagrams
Writing HTTP Clients
The QHttp class implements the client side of the HTTP protocol in Qt. It provides various functions to perform the most common HTTP operations, including get() and post(), and provides a means of sending arbitrary HTTP requests. If you read the previous section about QFtp, you will find that there are many similarities between QFtp and QHttp.
The QHttp class works asynchronously. When we call a function such as get() or post(), the function returns immediately, and the data transfer occurs later, when control returns to Qt's event loop. This ensures that the application's user interface remains responsive while HTTP requests are being processed.
We will review a console application example called httpget that shows how to download a file using the HTTP protocol. It is very similar to the ftpget example from the previous section, in both functionality and implementation, so we will not show the header file.
HttpGet::HttpGet(QObject *parent) : QObject(parent) { connect(&http, SIGNAL(done(bool)), this, SLOT(httpDone(bool))); }
In the constructor, we connect the QHttp object's done(bool) signal to the private httpDone(bool) slot.
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; }
The getFile() function performs the same kind of error checks as the FtpGet::getFile() shown earlier and uses the same approach to giving the file a local name. When retrieving from web sites, no login is necessary, so we simply set the host and port (using the default HTTP port 80 if none is specified in the URL) and download the data into the file, since the second argument to QHttp::get() specifies the output I/O device.
The HTTP requests are queued and executed asynchronously in Qt's event loop. The completion of the requests is indicated by QHttp's done(bool) signal, which we connected to httpDone(bool) in the constructor.
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(); }
Once the HTTP requests are finished, we close the file, notifying the user if an error occurred.
The main() function is very similar to the one used by 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(); }
The QHttp class provides many operations, including setHost(), get(), post(), and head(). If a site requires authentication, setUser() can be used to supply a user name and password. QHttp can use a socket supplied by the programmer rather than its own internal QTcpSocket. This makes it possible to use a secure QSslSocket to achieve HTTP over SSL or TLS.
To send a list of "name = value" pairs to a CGI script, we can use post():
http.setHost("www.example.com"); http.post("/cgi/somescript.py", "x=200&y=320", &file);
We can pass the data either as an 8-bit string or by supplying an open QIODevice, such as a QFile. For more control, we can use the request() function, which accepts an arbitrary HTTP header and data. For example:
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 emits the requestStarted(int) signal when it starts executing a request, and it emits the requestFinished(int, bool) signal when the request has finished. The int parameter is an ID number that identifies a request. If we are interested in the fate of individual requests, we can store the ID numbers when we schedule the requests. Keeping track of the ID numbers allows us to provide detailed feedback to the user.
In most applications, we only want to know whether the entire sequence of requests completed successfully or not. This is easily achieved by connecting to the done(bool) signal, which is emitted when the request queue becomes empty.
When an error occurs, the request queue is automatically cleared. But if we schedule new requests after the error has occurred using the same QHttp object, these requests will be queued and sent as usual.
Like QFtp, QHttp provides a readyRead() signal as well as the read() and readAll() functions that we can use instead of specifying an I/O device.