Add close() and isActive() methods to Tairon::Net::HTTPClient.
[tairon.git] / src / net-http / httpclient.cpp
blobf71753c42393b7c4f993349c05a85fce31becf03
1 /***************************************************************************
2 * *
3 * Copyright (C) 2006 David Brodsky *
4 * *
5 * This library is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU Library General Public *
7 * License as published by the Free Software Foundation and appearing *
8 * in the file LICENSE.LGPL included in the packaging of this file. *
9 * *
10 * This library is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
13 * Library General Public License for more details. *
14 * *
15 ***************************************************************************/
17 #include <cctype>
18 #include <sstream>
20 #include <tairon/core/log.h>
21 #include <tairon/core/thread.h>
22 #include <tairon/net/socket.h>
24 #include "httpclient.h"
26 /* {{{ static const char hexTable[] */
27 static const char hexTable[] = {
28 '0', '1', '2', '3', '4', '5', '6', '7',
29 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
31 /* }}} */
33 namespace Tairon
36 namespace Net
39 /* {{{ HTTPClient::HTTPClient(const String &, uint16_t */
40 HTTPClient::HTTPClient(const String &h, uint16_t p) : host(h), port(p), socket(0)
43 /* }}} */
45 /* {{{ HTTPClient::~HTTPClient() */
46 HTTPClient::~HTTPClient()
49 /* }}} */
51 /* {{{ HTTPClient::clearParameters() */
52 void HTTPClient::clearParameters()
54 parameters.clear();
56 /* }}} */
58 /* {{{ HTTPClient::close() */
59 void HTTPClient::close()
61 if (!socket) // there is nothing to do
62 return;
64 delete socket;
65 socket = 0;
67 /* }}} */
69 /* {{{ HTTPClient::connected(Tairon::Net::Socket *) */
70 void HTTPClient::connected(Tairon::Net::Socket *)
72 uri += prepareParameters();
74 socket->write("GET " + uri + " HTTP/1.0\r\nConnection: close\r\nHost: " + host + "\r\n\r\n");
76 dataReader = &HTTPClient::readStatus;
78 /* }}} */
80 /* {{{ HTTPClient::delParameter(const String &) */
81 void HTTPClient::delParameter(const String &key)
83 parameters.erase(key);
85 /* }}} */
87 /* {{{ HTTPClient::error(Tairon::Core::Socket *, int) */
88 void HTTPClient::error(Tairon::Net::Socket *, int err)
90 socket = 0;
91 errorSignal.emit(this, "Socket error", err);
93 /* }}} */
95 /* {{{ HTTPClient::escape(const String &) */
96 String HTTPClient::escape(const String &what)
98 unsigned int len = what.length();
99 String out;
100 out.reserve(len * 3);
102 for (unsigned int i = 0; i < len; ++i)
103 if (isalnum(what[i]) || (what[i] == '-'))
104 out += what[i];
105 else
106 out += String("%", 1) + hexTable[(what[i] >> 4) & 0x0f] + hexTable[what[i] & 0x0F];
108 return out;
110 /* }}} */
112 /* {{{ HTTPClient::get(const String &) */
113 void HTTPClient::get(const String &what)
115 if (socket) {
116 WARNING("Trying to get data when there is an active socket");
117 return;
120 uri = what;
122 // clear data, it may contain something from previous request
123 data.clear();
125 Tairon::Core::Thread *current = Tairon::Core::Thread::current();
127 socket = new Socket(Socket::Stream);
128 socket->connectedSignal.connect(Tairon::Core::threadMethodFunctor(current, this, &HTTPClient::connected));
129 socket->errorSignal.connect(Tairon::Core::threadMethodFunctor(current, this, &HTTPClient::error));
130 socket->readyReadSignal.connect(Tairon::Core::threadMethodFunctor(current, this, &HTTPClient::readyRead));
131 socket->connect(host, port);
133 /* }}} */
135 /* {{{ HTTPClient::isActive() */
136 bool HTTPClient::isActive()
138 return socket != 0;
140 /* }}} */
142 /* {{{ HTTPClient::prepareParameters() */
143 String HTTPClient::prepareParameters()
145 String ret("?");
146 for (std::map<String, String>::const_iterator it = parameters.begin(); it != parameters.end(); ++it)
147 ret += it->first + "=" + it->second + "&";
149 ret.erase(ret.length() - 1); // remove trailing &
151 return ret;
153 /* }}} */
155 /* {{{ HTTPClient::readData() */
156 void HTTPClient::readData()
158 // just empty function, body of a message from the server is incoming and
159 // there is no need to parse it, the data is already stored
161 /* }}} */
163 /* {{{ HTTPClient::readHeader() */
164 void HTTPClient::readHeader()
166 String line;
167 while (data.canReadLine()) {
168 line = data.readLine();
169 line.rstrip();
170 if (line.empty()) {
171 dataReader = &HTTPClient::readData;
172 break;
176 /* }}} */
178 /* {{{ HTTPClient::readStatus() */
179 void HTTPClient::readStatus()
181 String line = data.readLine();
182 if (line.empty())
183 return;
185 line.rstrip();
187 std::stringstream s(line);
188 String protocol;
189 int code;
191 s >> protocol;
192 s >> code;
194 if (protocol.find("HTTP") != 0) // unsupported protocol
195 throw "Unsupported protocol";
197 if (code != 200) // invalid return code
198 throw "Invalid return code";
199 // TODO: handle redirect codes
201 dataReader = &HTTPClient::readHeader;
202 readHeader(); // there may be another data, process them
204 /* }}} */
206 /* {{{ HTTPClient::readyRead(Tairon::Net::Socket *) */
207 void HTTPClient::readyRead(Tairon::Net::Socket *)
209 try {
210 data += socket->read();
211 } catch (const Tairon::Net::SocketException &) {
212 // connection has been closed or some other error occured
213 socket = 0;
216 try {
217 (this->*dataReader)();
218 if (!socket) // connection has been closed
219 dataReadSignal.emit(this);
220 } catch (const char *e) { // invalid data received
221 close();
222 errorSignal.emit(this, e, 0);
225 /* }}} */
227 /* {{{ HTTPClient::setParameter(const String &, const String &) */
228 void HTTPClient::setParameter(const String &key, const String &value)
230 parameters[key] = escape(value);
232 /* }}} */
234 }; // namespace Net
236 }; // namespace Tairon
238 // vim: ai sw=4 ts=4 noet fdm=marker