Tiny HTTP client layer on top of NetChat - and CTest support for some SimGear tests.
This commit is contained in:
parent
116c487384
commit
f06f25532c
@ -142,6 +142,11 @@ configure_file (
|
||||
"${PROJECT_BINARY_DIR}/simgear/simgear_config.h"
|
||||
)
|
||||
|
||||
# enable CTest / make test target
|
||||
|
||||
include (Dart)
|
||||
enable_testing()
|
||||
|
||||
install (FILES ${PROJECT_BINARY_DIR}/simgear/simgear_config.h DESTINATION include/simgear/)
|
||||
add_subdirectory(simgear)
|
||||
|
||||
@ -156,3 +161,4 @@ ADD_CUSTOM_TARGET(uninstall
|
||||
"${CMAKE_COMMAND}" -P "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake")
|
||||
|
||||
|
||||
|
||||
|
@ -14,6 +14,8 @@ set(HEADERS
|
||||
sg_serial.hxx
|
||||
sg_socket.hxx
|
||||
sg_socket_udp.hxx
|
||||
HTTPClient.hxx
|
||||
HTTPRequest.hxx
|
||||
)
|
||||
|
||||
set(SOURCES
|
||||
@ -28,6 +30,15 @@ set(SOURCES
|
||||
sg_serial.cxx
|
||||
sg_socket.cxx
|
||||
sg_socket_udp.cxx
|
||||
HTTPClient.cxx
|
||||
HTTPRequest.cxx
|
||||
)
|
||||
|
||||
simgear_component(io io "${SOURCES}" "${HEADERS}")
|
||||
simgear_component(io io "${SOURCES}" "${HEADERS}")
|
||||
|
||||
add_executable(test_sock socktest.cxx)
|
||||
target_link_libraries(test_sock sgio sgstructure sgdebug)
|
||||
|
||||
add_executable(test_http test_HTTP.cxx)
|
||||
target_link_libraries(test_http sgio sgstructure sgtiming sgmisc sgdebug)
|
||||
|
||||
|
240
simgear/io/HTTPClient.cxx
Normal file
240
simgear/io/HTTPClient.cxx
Normal file
@ -0,0 +1,240 @@
|
||||
#include "HTTPClient.hxx"
|
||||
|
||||
#include <sstream>
|
||||
#include <cassert>
|
||||
#include <list>
|
||||
|
||||
#include <boost/foreach.hpp>
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include <simgear/io/sg_netChat.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
#include <simgear/version.h>
|
||||
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
using std::vector;
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
|
||||
class Connection : public NetChat
|
||||
{
|
||||
public:
|
||||
Connection(Client* pr) :
|
||||
client(pr),
|
||||
state(STATE_IDLE)
|
||||
{
|
||||
setTerminator("\r\n");
|
||||
}
|
||||
|
||||
void connectToHost(const string& host)
|
||||
{
|
||||
open();
|
||||
|
||||
int colonPos = host.find(':');
|
||||
if (colonPos > 0) {
|
||||
string h = host.substr(0, colonPos);
|
||||
int port = strutils::to_int(host.substr(colonPos + 1));
|
||||
connect(h.c_str(), port);
|
||||
} else {
|
||||
connect(host.c_str(), 80 /* default port */);
|
||||
}
|
||||
}
|
||||
|
||||
void queueRequest(const Request_ptr& r)
|
||||
{
|
||||
if (!activeRequest) {
|
||||
startRequest(r);
|
||||
} else {
|
||||
queuedRequests.push_back(r);
|
||||
}
|
||||
}
|
||||
|
||||
void startRequest(const Request_ptr& r)
|
||||
{
|
||||
activeRequest = r;
|
||||
state = STATE_IDLE;
|
||||
bodyTransferSize = 0;
|
||||
|
||||
stringstream headerData;
|
||||
string path = r->path();
|
||||
if (!client->proxyHost().empty()) {
|
||||
path = "http://" + r->host() + path;
|
||||
}
|
||||
|
||||
int requestTime;
|
||||
headerData << r->method() << " " << path << " HTTP/1.1 " << client->userAgent() << "\r\n";
|
||||
headerData << "Host: " << r->host() << "\r\n";
|
||||
headerData << "X-Time: " << requestTime << "\r\n";
|
||||
|
||||
if (!client->proxyAuth().empty()) {
|
||||
headerData << "Proxy-Authorization: " << client->proxyAuth() << "\r\n";
|
||||
}
|
||||
|
||||
BOOST_FOREACH(string h, r->requestHeaders()) {
|
||||
headerData << h << ": " << r->header(h) << "\r\n";
|
||||
}
|
||||
|
||||
headerData << "\r\n"; // final CRLF to terminate the headers
|
||||
|
||||
// TODO - add request body support for PUT, etc operations
|
||||
|
||||
push(headerData.str().c_str());
|
||||
cout << "sent request" << endl;
|
||||
}
|
||||
|
||||
virtual void collectIncomingData(const char* s, int n)
|
||||
{
|
||||
if (state == STATE_GETTING_BODY) {
|
||||
activeRequest->gotBodyData(s, n);
|
||||
} else {
|
||||
buffer += string(s, n);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void foundTerminator(void)
|
||||
{
|
||||
switch (state) {
|
||||
case STATE_IDLE:
|
||||
activeRequest->responseStart(buffer);
|
||||
state = STATE_GETTING_HEADERS;
|
||||
buffer.clear();
|
||||
break;
|
||||
|
||||
case STATE_GETTING_HEADERS:
|
||||
processHeader();
|
||||
buffer.clear();
|
||||
break;
|
||||
|
||||
case STATE_GETTING_BODY:
|
||||
responseComplete();
|
||||
state = STATE_IDLE;
|
||||
setTerminator("\r\n");
|
||||
|
||||
if (!queuedRequests.empty()) {
|
||||
Request_ptr next = queuedRequests.front();
|
||||
queuedRequests.pop_front();
|
||||
startRequest(next);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void processHeader()
|
||||
{
|
||||
string h = strutils::simplify(buffer);
|
||||
if (h.empty()) { // blank line terminates headers
|
||||
headersComplete();
|
||||
|
||||
if (bodyTransferSize > 0) {
|
||||
state = STATE_GETTING_BODY;
|
||||
cout << "getting body:" << bodyTransferSize << endl;
|
||||
setByteCount(bodyTransferSize);
|
||||
} else {
|
||||
responseComplete();
|
||||
state = STATE_IDLE; // no response body, we're done
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int colonPos = buffer.find(':');
|
||||
if (colonPos < 0) {
|
||||
SG_LOG(SG_IO, SG_WARN, "malformed HTTP response header:" << h);
|
||||
return;
|
||||
}
|
||||
|
||||
string key = strutils::simplify(buffer.substr(0, colonPos));
|
||||
string lkey = boost::to_lower_copy(key);
|
||||
string value = strutils::strip(buffer.substr(colonPos + 1));
|
||||
|
||||
if (lkey == "content-length" && (bodyTransferSize <= 0)) {
|
||||
bodyTransferSize = strutils::to_int(value);
|
||||
} else if (lkey == "transfer-length") {
|
||||
bodyTransferSize = strutils::to_int(value);
|
||||
}
|
||||
|
||||
activeRequest->responseHeader(lkey, value);
|
||||
}
|
||||
|
||||
void headersComplete()
|
||||
{
|
||||
activeRequest->responseHeadersComplete();
|
||||
}
|
||||
|
||||
void responseComplete()
|
||||
{
|
||||
activeRequest->responseComplete();
|
||||
client->requestFinished(this);
|
||||
activeRequest = NULL;
|
||||
}
|
||||
|
||||
enum ConnectionState {
|
||||
STATE_IDLE = 0,
|
||||
STATE_GETTING_HEADERS,
|
||||
STATE_GETTING_BODY
|
||||
};
|
||||
|
||||
Client* client;
|
||||
Request_ptr activeRequest;
|
||||
ConnectionState state;
|
||||
std::string buffer;
|
||||
int bodyTransferSize;
|
||||
|
||||
std::list<Request_ptr> queuedRequests;
|
||||
};
|
||||
|
||||
Client::Client()
|
||||
{
|
||||
setUserAgent("SimGear-" SG_STRINGIZE(SIMGEAR_VERSION));
|
||||
}
|
||||
|
||||
void Client::makeRequest(const Request_ptr& r)
|
||||
{
|
||||
string host = r->host();
|
||||
if (!_proxy.empty()) {
|
||||
host = _proxy;
|
||||
}
|
||||
|
||||
if (_connections.find(host) == _connections.end()) {
|
||||
Connection* con = new Connection(this);
|
||||
con->connectToHost(host);
|
||||
_connections[host] = con;
|
||||
}
|
||||
|
||||
_connections[host]->queueRequest(r);
|
||||
}
|
||||
|
||||
void Client::requestFinished(Connection* con)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Client::setUserAgent(const string& ua)
|
||||
{
|
||||
_userAgent = ua;
|
||||
}
|
||||
|
||||
void Client::setProxy(const string& proxy, const string& auth)
|
||||
{
|
||||
_proxy = proxy;
|
||||
_proxyAuth = auth;
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
51
simgear/io/HTTPClient.hxx
Normal file
51
simgear/io/HTTPClient.hxx
Normal file
@ -0,0 +1,51 @@
|
||||
#ifndef SG_HTTP_CLIENT_HXX
|
||||
#define SG_HTTP_CLIENT_HXX
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <simgear/io/HTTPRequest.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class Connection;
|
||||
|
||||
class Client
|
||||
{
|
||||
public:
|
||||
Client();
|
||||
|
||||
void makeRequest(const Request_ptr& r);
|
||||
|
||||
void setUserAgent(const std::string& ua);
|
||||
void setProxy(const std::string& proxy, const std::string& auth = "");
|
||||
|
||||
const std::string& userAgent() const
|
||||
{ return _userAgent; }
|
||||
|
||||
const std::string& proxyHost() const
|
||||
{ return _proxy; }
|
||||
|
||||
const std::string& proxyAuth() const
|
||||
{ return _proxyAuth; }
|
||||
private:
|
||||
void requestFinished(Connection* con);
|
||||
|
||||
friend class Connection;
|
||||
|
||||
std::string _userAgent;
|
||||
std::string _proxy;
|
||||
std::string _proxyAuth;
|
||||
|
||||
// connections by host
|
||||
std::map<std::string, Connection*> _connections;
|
||||
};
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_HTTP_CLIENT_HXX
|
133
simgear/io/HTTPRequest.cxx
Normal file
133
simgear/io/HTTPRequest.cxx
Normal file
@ -0,0 +1,133 @@
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/compiler.h>
|
||||
#include <simgear/debug/logstream.hxx>
|
||||
|
||||
using std::string;
|
||||
using std::map;
|
||||
|
||||
#include <iostream>
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
Request::Request(const string& url, const string method) :
|
||||
_method(method),
|
||||
_url(url)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
Request::~Request()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
string_list Request::requestHeaders() const
|
||||
{
|
||||
string_list r;
|
||||
return r;
|
||||
}
|
||||
|
||||
string Request::header(const std::string& name) const
|
||||
{
|
||||
return string();
|
||||
}
|
||||
|
||||
void Request::responseStart(const string& r)
|
||||
{
|
||||
const int maxSplit = 2; // HTTP/1.1 nnn status code
|
||||
string_list parts = strutils::split(r, NULL, maxSplit);
|
||||
_responseStatus = strutils::to_int(parts[1]);
|
||||
_responseReason = parts[2];
|
||||
}
|
||||
|
||||
void Request::responseHeader(const string& key, const string& value)
|
||||
{
|
||||
_responseHeaders[key] = value;
|
||||
}
|
||||
|
||||
void Request::responseHeadersComplete()
|
||||
{
|
||||
// no op
|
||||
}
|
||||
|
||||
void Request::gotBodyData(const char* s, int n)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void Request::responseComplete()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
string Request::scheme() const
|
||||
{
|
||||
int firstColon = url().find(":");
|
||||
if (firstColon > 0) {
|
||||
return url().substr(0, firstColon);
|
||||
}
|
||||
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
string Request::path() const
|
||||
{
|
||||
string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
int hostEnd = u.find('/', schemeEnd + 3);
|
||||
if (hostEnd < 0) {
|
||||
return ""; // couldn't parse host
|
||||
}
|
||||
|
||||
int query = u.find('?', hostEnd + 1);
|
||||
if (query < 0) {
|
||||
// all remainder of URL is path
|
||||
return u.substr(hostEnd);
|
||||
}
|
||||
|
||||
return u.substr(hostEnd, query - hostEnd);
|
||||
}
|
||||
|
||||
string Request::host() const
|
||||
{
|
||||
string u(url());
|
||||
int schemeEnd = u.find("://");
|
||||
if (schemeEnd < 0) {
|
||||
return ""; // couldn't parse scheme
|
||||
}
|
||||
|
||||
int hostEnd = u.find('/', schemeEnd + 3);
|
||||
if (hostEnd < 0) { // all remainder of URL is host
|
||||
return u.substr(schemeEnd + 3);
|
||||
}
|
||||
|
||||
return u.substr(schemeEnd + 3, hostEnd - (schemeEnd + 3));
|
||||
}
|
||||
|
||||
int Request::contentLength() const
|
||||
{
|
||||
HeaderDict::const_iterator it = _responseHeaders.find("content-length");
|
||||
if (it == _responseHeaders.end()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return strutils::to_int(it->second);
|
||||
}
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
69
simgear/io/HTTPRequest.hxx
Normal file
69
simgear/io/HTTPRequest.hxx
Normal file
@ -0,0 +1,69 @@
|
||||
#ifndef SG_HTTP_REQUEST_HXX
|
||||
#define SG_HTTP_REQUEST_HXX
|
||||
|
||||
#include <map>
|
||||
|
||||
#include <simgear/structure/SGReferenced.hxx>
|
||||
#include <simgear/structure/SGSharedPtr.hxx>
|
||||
#include <simgear/math/sg_types.hxx>
|
||||
|
||||
namespace simgear
|
||||
{
|
||||
|
||||
namespace HTTP
|
||||
{
|
||||
|
||||
class Request : public SGReferenced
|
||||
{
|
||||
public:
|
||||
virtual ~Request();
|
||||
|
||||
virtual std::string method() const
|
||||
{ return _method; }
|
||||
virtual std::string url() const
|
||||
{ return _url; }
|
||||
|
||||
virtual std::string scheme() const;
|
||||
virtual std::string path() const;
|
||||
virtual std::string host() const; // host, including port
|
||||
|
||||
virtual string_list requestHeaders() const;
|
||||
virtual std::string header(const std::string& name) const;
|
||||
|
||||
virtual int responseCode() const
|
||||
{ return _responseStatus; }
|
||||
|
||||
virtual std::string resposeReason() const
|
||||
{ return _responseReason; }
|
||||
|
||||
virtual int contentLength() const;
|
||||
protected:
|
||||
friend class Connection;
|
||||
|
||||
Request(const std::string& url, const std::string method = "get");
|
||||
|
||||
virtual void responseStart(const std::string& r);
|
||||
virtual void responseHeader(const std::string& key, const std::string& value);
|
||||
virtual void responseHeadersComplete();
|
||||
virtual void responseComplete();
|
||||
|
||||
virtual void gotBodyData(const char* s, int n);
|
||||
private:
|
||||
|
||||
std::string _method;
|
||||
std::string _url;
|
||||
int _responseStatus;
|
||||
std::string _responseReason;
|
||||
|
||||
typedef std::map<std::string, std::string> HeaderDict;
|
||||
HeaderDict _responseHeaders;
|
||||
};
|
||||
|
||||
typedef SGSharedPtr<Request> Request_ptr;
|
||||
|
||||
} // of namespace HTTP
|
||||
|
||||
} // of namespace simgear
|
||||
|
||||
#endif // of SG_HTTP_REQUEST_HXX
|
||||
|
@ -26,14 +26,15 @@
|
||||
#include <simgear/io/sg_netChat.hxx>
|
||||
|
||||
#include <cstring> // for strdup
|
||||
|
||||
|
||||
namespace simgear {
|
||||
|
||||
void
|
||||
NetChat::setTerminator (const char* t)
|
||||
{
|
||||
if (terminator) delete[] terminator;
|
||||
if (terminator) free(terminator);
|
||||
terminator = strdup(t);
|
||||
bytesToCollect = -1;
|
||||
}
|
||||
|
||||
const char*
|
||||
@ -42,6 +43,15 @@ NetChat::getTerminator (void)
|
||||
return terminator;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
NetChat::setByteCount(int count)
|
||||
{
|
||||
if (terminator) free(terminator);
|
||||
terminator = NULL;
|
||||
bytesToCollect = count;
|
||||
}
|
||||
|
||||
// return the size of the largest prefix of needle at the end
|
||||
// of haystack
|
||||
|
||||
@ -89,12 +99,22 @@ NetChat::handleBufferRead (NetBuffer& in_buffer)
|
||||
// necessary because we might read several data+terminator combos
|
||||
// with a single recv().
|
||||
|
||||
while (in_buffer.getLength()) {
|
||||
|
||||
while (in_buffer.getLength()) {
|
||||
// special case where we're not using a terminator
|
||||
if (terminator == 0 || *terminator == 0) {
|
||||
collectIncomingData (in_buffer.getData(),in_buffer.getLength());
|
||||
in_buffer.remove ();
|
||||
if (terminator == 0 || *terminator == 0) {
|
||||
if ( bytesToCollect > 0) {
|
||||
const int toRead = std::min(in_buffer.getLength(), bytesToCollect);
|
||||
collectIncomingData(in_buffer.getData(), toRead);
|
||||
in_buffer.remove(0, toRead);
|
||||
bytesToCollect -= toRead;
|
||||
if (bytesToCollect == 0) { // read all requested bytes
|
||||
foundTerminator();
|
||||
}
|
||||
} else { // read the whole lot
|
||||
collectIncomingData (in_buffer.getData(),in_buffer.getLength());
|
||||
in_buffer.remove ();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -61,6 +61,7 @@
|
||||
#ifndef SG_NET_CHAT_H
|
||||
#define SG_NET_CHAT_H
|
||||
|
||||
#include <memory>
|
||||
#include <simgear/io/sg_netBuffer.hxx>
|
||||
|
||||
namespace simgear
|
||||
@ -69,16 +70,25 @@ namespace simgear
|
||||
class NetChat : public NetBufferChannel
|
||||
{
|
||||
char* terminator;
|
||||
|
||||
int bytesToCollect;
|
||||
virtual void handleBufferRead (NetBuffer& buffer) ;
|
||||
|
||||
public:
|
||||
|
||||
NetChat () : terminator (0) {}
|
||||
NetChat () :
|
||||
terminator (NULL),
|
||||
bytesToCollect(-1)
|
||||
{}
|
||||
|
||||
void setTerminator (const char* t);
|
||||
const char* getTerminator (void);
|
||||
|
||||
/**
|
||||
* set byte count to collect - 'foundTerminator' will be called once
|
||||
* this many bytes have been collected
|
||||
*/
|
||||
void setByteCount(int bytes);
|
||||
|
||||
bool push (const char* s);
|
||||
|
||||
virtual void collectIncomingData (const char* s, int n) {}
|
||||
|
274
simgear/io/test_HTTP.cxx
Normal file
274
simgear/io/test_HTTP.cxx
Normal file
@ -0,0 +1,274 @@
|
||||
#include <cstdlib>
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
|
||||
#include <boost/algorithm/string/case_conv.hpp>
|
||||
|
||||
#include "HTTPClient.hxx"
|
||||
#include "HTTPRequest.hxx"
|
||||
|
||||
#include <simgear/io/sg_netChat.hxx>
|
||||
#include <simgear/misc/strutils.hxx>
|
||||
#include <simgear/timing/timestamp.hxx>
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
using std::string;
|
||||
using std::stringstream;
|
||||
|
||||
using namespace simgear;
|
||||
|
||||
const char* BODY1 = "The quick brown fox jumps over a lazy dog.";
|
||||
|
||||
const int body2Size = 8 * 1024;
|
||||
char body2[body2Size];
|
||||
|
||||
#define COMPARE(a, b) \
|
||||
if ((a) != (b)) { \
|
||||
cerr << "failed:" << #a << " != " << #b << endl; \
|
||||
cerr << "\tgot:" << a << endl; \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
#define VERIFY(a) \
|
||||
if (!(a)) { \
|
||||
cerr << "failed:" << #a << endl; \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
class TestRequest : public HTTP::Request
|
||||
{
|
||||
public:
|
||||
bool complete;
|
||||
string bodyData;
|
||||
|
||||
TestRequest(const std::string& url) :
|
||||
HTTP::Request(url),
|
||||
complete(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual void responseHeadersComplete()
|
||||
{
|
||||
}
|
||||
|
||||
virtual void responseComplete()
|
||||
{
|
||||
complete = true;
|
||||
}
|
||||
|
||||
virtual void gotBodyData(const char* s, int n)
|
||||
{
|
||||
bodyData += string(s, n);
|
||||
}
|
||||
};
|
||||
|
||||
class TestServerChannel : public NetChat
|
||||
{
|
||||
public:
|
||||
enum State
|
||||
{
|
||||
STATE_IDLE = 0,
|
||||
STATE_HEADERS,
|
||||
STATE_REQUEST_BODY
|
||||
};
|
||||
|
||||
TestServerChannel()
|
||||
{
|
||||
state = STATE_IDLE;
|
||||
setTerminator("\r\n");
|
||||
}
|
||||
|
||||
virtual void collectIncomingData(const char* s, int n)
|
||||
{
|
||||
buffer += string(s, n);
|
||||
}
|
||||
|
||||
virtual void foundTerminator(void)
|
||||
{
|
||||
if (state == STATE_IDLE) {
|
||||
state = STATE_HEADERS;
|
||||
string_list line = strutils::split(buffer, NULL, 3);
|
||||
if (line.size() < 4) {
|
||||
cerr << "malformed request:" << buffer << endl;
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
method = line[0];
|
||||
path = line[1];
|
||||
httpVersion = line[2];
|
||||
userAgent = line[3];
|
||||
requestHeaders.clear();
|
||||
buffer.clear();
|
||||
} else if (state == STATE_HEADERS) {
|
||||
string s = strutils::simplify(buffer);
|
||||
if (s.empty()) {
|
||||
buffer.clear();
|
||||
receivedRequestHeaders();
|
||||
return;
|
||||
}
|
||||
|
||||
int colonPos = buffer.find(':');
|
||||
if (colonPos < 0) {
|
||||
cerr << "malformed HTTP response header:" << buffer << endl;
|
||||
buffer.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
string key = strutils::simplify(buffer.substr(0, colonPos));
|
||||
string lkey = boost::to_lower_copy(key);
|
||||
string value = strutils::strip(buffer.substr(colonPos + 1));
|
||||
requestHeaders[lkey] = value;
|
||||
buffer.clear();
|
||||
} else if (state == STATE_REQUEST_BODY) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void receivedRequestHeaders()
|
||||
{
|
||||
state = STATE_IDLE;
|
||||
if (path == "/test1") {
|
||||
string contentStr(BODY1);
|
||||
stringstream d;
|
||||
d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
|
||||
d << "Content-Length:" << contentStr.size() << "\r\n";
|
||||
d << "\r\n"; // final CRLF to terminate the headers
|
||||
d << contentStr;
|
||||
push(d.str().c_str());
|
||||
} else if (path == "/test2") {
|
||||
stringstream d;
|
||||
d << "HTTP1.1 " << 200 << " " << reasonForCode(200) << "\r\n";
|
||||
d << "Content-Length:" << body2Size << "\r\n";
|
||||
d << "\r\n"; // final CRLF to terminate the headers
|
||||
push(d.str().c_str());
|
||||
bufferSend(body2, body2Size);
|
||||
cout << "sent body2" << endl;
|
||||
} else {
|
||||
sendErrorResponse(404);
|
||||
}
|
||||
}
|
||||
|
||||
void sendErrorResponse(int code)
|
||||
{
|
||||
cerr << "sending error " << code << " for " << path << endl;
|
||||
stringstream headerData;
|
||||
headerData << "HTTP1.1 " << code << " " << reasonForCode(code) << "\r\n";
|
||||
headerData << "\r\n"; // final CRLF to terminate the headers
|
||||
push(headerData.str().c_str());
|
||||
}
|
||||
|
||||
string reasonForCode(int code)
|
||||
{
|
||||
switch (code) {
|
||||
case 200: return "OK";
|
||||
case 404: return "not found";
|
||||
default: return "unknown code";
|
||||
}
|
||||
}
|
||||
|
||||
State state;
|
||||
string buffer;
|
||||
string method;
|
||||
string path;
|
||||
string httpVersion;
|
||||
string userAgent;
|
||||
std::map<string, string> requestHeaders;
|
||||
};
|
||||
|
||||
class TestServer : public NetChannel
|
||||
{
|
||||
public:
|
||||
TestServer()
|
||||
{
|
||||
open();
|
||||
bind(NULL, 2000); // localhost, any port
|
||||
listen(5);
|
||||
}
|
||||
|
||||
virtual ~TestServer()
|
||||
{
|
||||
}
|
||||
|
||||
virtual bool writable (void) { return false ; }
|
||||
|
||||
virtual void handleAccept (void)
|
||||
{
|
||||
simgear::IPAddress addr ;
|
||||
int handle = accept ( &addr ) ;
|
||||
|
||||
TestServerChannel* chan = new TestServerChannel();
|
||||
chan->setHandle(handle);
|
||||
}
|
||||
};
|
||||
|
||||
void waitForComplete(TestRequest* tr)
|
||||
{
|
||||
SGTimeStamp start(SGTimeStamp::now());
|
||||
while (start.elapsedMSec() < 1000) {
|
||||
NetChannel::poll(10);
|
||||
if (tr->complete) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
cerr << "timed out" << endl;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
TestServer s;
|
||||
|
||||
HTTP::Client cl;
|
||||
|
||||
// test URL parsing
|
||||
TestRequest* tr1 = new TestRequest("http://localhost:2000/test1?foo=bar");
|
||||
COMPARE(tr1->scheme(), "http");
|
||||
COMPARE(tr1->host(), "localhost:2000");
|
||||
COMPARE(tr1->path(), "/test1");
|
||||
|
||||
// basic get request
|
||||
{
|
||||
TestRequest* tr = new TestRequest("http://localhost:2000/test1");
|
||||
HTTP::Request_ptr own(tr);
|
||||
cl.makeRequest(tr);
|
||||
|
||||
waitForComplete(tr);
|
||||
COMPARE(tr->responseCode(), 200);
|
||||
COMPARE(tr->contentLength(), strlen(BODY1));
|
||||
COMPARE(tr->bodyData, string(BODY1));
|
||||
}
|
||||
|
||||
// larger get request
|
||||
for (int i=0; i<body2Size; ++i) {
|
||||
body2[i] = (i << 4) | (i >> 2);
|
||||
}
|
||||
|
||||
{
|
||||
TestRequest* tr = new TestRequest("http://localhost:2000/test2");
|
||||
HTTP::Request_ptr own(tr);
|
||||
cl.makeRequest(tr);
|
||||
waitForComplete(tr);
|
||||
COMPARE(tr->responseCode(), 200);
|
||||
COMPARE(tr->contentLength(), body2Size);
|
||||
COMPARE(tr->bodyData, string(body2, body2Size));
|
||||
}
|
||||
|
||||
// test 404
|
||||
{
|
||||
TestRequest* tr = new TestRequest("http://localhost:2000/not-found");
|
||||
HTTP::Request_ptr own(tr);
|
||||
cl.makeRequest(tr);
|
||||
waitForComplete(tr);
|
||||
COMPARE(tr->responseCode(), 404);
|
||||
COMPARE(tr->contentLength(), 0);
|
||||
}
|
||||
|
||||
cout << "all tests passed ok" << endl;
|
||||
return EXIT_SUCCESS;
|
||||
}
|
@ -33,3 +33,11 @@ set(SOURCES
|
||||
)
|
||||
|
||||
simgear_component(misc misc "${SOURCES}" "${HEADERS}")
|
||||
|
||||
add_executable(test_tabbed_values tabbed_values_test.cxx)
|
||||
add_test(tabbed_values ${EXECUTABLE_OUTPUT_PATH}/test_tabbed_values)
|
||||
target_link_libraries(test_tabbed_values sgmisc)
|
||||
|
||||
add_executable(test_strings strutils_test.cxx )
|
||||
add_test(test_strings ${EXECUTABLE_OUTPUT_PATH}/test_strings)
|
||||
target_link_libraries(test_strings sgmisc)
|
||||
|
@ -213,5 +213,41 @@ namespace simgear {
|
||||
return (n != string::npos) && (n == s.length() - substr.length());
|
||||
}
|
||||
|
||||
string simplify(const string& s)
|
||||
{
|
||||
string result; // reserve size of 's'?
|
||||
string::const_iterator it = s.begin(),
|
||||
end = s.end();
|
||||
|
||||
// advance to first non-space char - simplifes logic in main loop,
|
||||
// since we can always prepend a single space when we see a
|
||||
// space -> non-space transition
|
||||
for (; (it != end) && isspace(*it); ++it) { /* nothing */ }
|
||||
|
||||
bool lastWasSpace = false;
|
||||
for (; it != end; ++it) {
|
||||
char c = *it;
|
||||
if (isspace(c)) {
|
||||
lastWasSpace = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (lastWasSpace) {
|
||||
result.push_back(' ');
|
||||
}
|
||||
|
||||
lastWasSpace = false;
|
||||
result.push_back(c);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int to_int(const std::string& s)
|
||||
{
|
||||
return atoi(s.c_str());
|
||||
}
|
||||
|
||||
} // end namespace strutils
|
||||
|
||||
} // end namespace simgear
|
||||
|
@ -116,6 +116,14 @@ namespace simgear {
|
||||
*/
|
||||
bool ends_with( const std::string & s, const std::string & substr );
|
||||
|
||||
/**
|
||||
* Strip all leading/trailing whitespace, and transform all interal
|
||||
* whitespace into a single ' ' character - i.e newlines/carriage returns/
|
||||
* tabs/multiple spaces will be condensed.
|
||||
*/
|
||||
std::string simplify(const std::string& s);
|
||||
|
||||
int to_int(const std::string& s);
|
||||
} // end namespace strutils
|
||||
} // end namespace simgear
|
||||
|
||||
|
58
simgear/misc/strutils_test.cxx
Normal file
58
simgear/misc/strutils_test.cxx
Normal file
@ -0,0 +1,58 @@
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
// Test harness.
|
||||
////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include <simgear/compiler.h>
|
||||
|
||||
#include <iostream>
|
||||
#include "strutils.hxx"
|
||||
|
||||
using std::cout;
|
||||
using std::cerr;
|
||||
using std::endl;
|
||||
|
||||
using namespace simgear::strutils;
|
||||
|
||||
#define COMPARE(a, b) \
|
||||
if ((a) != (b)) { \
|
||||
cerr << "failed:" << #a << " != " << #b << endl; \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
#define VERIFY(a) \
|
||||
if (!(a)) { \
|
||||
cerr << "failed:" << #a << endl; \
|
||||
exit(1); \
|
||||
}
|
||||
|
||||
int main (int ac, char ** av)
|
||||
{
|
||||
std::string a("abcd");
|
||||
COMPARE(strip(a), a);
|
||||
COMPARE(strip(" a "), "a");
|
||||
COMPARE(lstrip(" a "), "a ");
|
||||
COMPARE(rstrip("\ta "), "\ta");
|
||||
// check internal spacing is preserved
|
||||
COMPARE(strip("\t \na \t b\r \n "), "a \t b");
|
||||
|
||||
|
||||
VERIFY(starts_with("banana", "ban"));
|
||||
VERIFY(!starts_with("abanana", "ban"));
|
||||
VERIFY(starts_with("banana", "banana")); // pass - string starts with itself
|
||||
VERIFY(!starts_with("ban", "banana")); // fail - original string is prefix of
|
||||
|
||||
VERIFY(ends_with("banana", "ana"));
|
||||
VERIFY(ends_with("foo.text", ".text"));
|
||||
VERIFY(!ends_with("foo.text", ".html"));
|
||||
|
||||
COMPARE(simplify("\ta\t b \nc\n\r \r\n"), "a b c");
|
||||
COMPARE(simplify("The quick - brown dog!"), "The quick - brown dog!");
|
||||
COMPARE(simplify("\r\n \r\n \t \r"), "");
|
||||
|
||||
COMPARE(to_int("999"), 999);
|
||||
COMPARE(to_int("0000000"), 0);
|
||||
COMPARE(to_int("-10000"), -10000);
|
||||
|
||||
cout << "all tests passed successfully!" << endl;
|
||||
return 0;
|
||||
}
|
@ -20,3 +20,11 @@ set(SOURCES
|
||||
)
|
||||
|
||||
simgear_component(props props "${SOURCES}" "${HEADERS}")
|
||||
|
||||
add_executable(test_props props_test.cxx)
|
||||
target_link_libraries(test_props sgprops sgxml sgstructure sgmisc sgdebug)
|
||||
add_test(test_props ${EXECUTABLE_OUTPUT_PATH}/test_props)
|
||||
|
||||
add_executable(test_propertyObject propertyObject_test.cxx)
|
||||
target_link_libraries(test_propertyObject sgprops sgstructure sgdebug)
|
||||
add_test(test_propertyObject ${EXECUTABLE_OUTPUT_PATH}/test_propertyObject)
|
||||
|
@ -104,3 +104,10 @@ void SGTimeStamp::stamp() {
|
||||
#endif
|
||||
}
|
||||
|
||||
int SGTimeStamp::elapsedMSec() const
|
||||
{
|
||||
SGTimeStamp now;
|
||||
now.stamp();
|
||||
|
||||
return static_cast<int>((now - *this).toMSecs());
|
||||
}
|
||||
|
@ -195,6 +195,10 @@ public:
|
||||
static SGTimeStamp now()
|
||||
{ SGTimeStamp ts; ts.stamp(); return ts; }
|
||||
|
||||
/**
|
||||
* elapsed time since the stamp was taken, in msec
|
||||
*/
|
||||
int elapsedMSec() const;
|
||||
private:
|
||||
SGTimeStamp(sec_type sec, nsec_type nsec)
|
||||
{ setTime(sec, nsec); }
|
||||
|
Loading…
Reference in New Issue
Block a user