]> git.mxchange.org Git - simgear.git/commitdiff
Tiny HTTP client layer on top of NetChat - and CTest support for some SimGear tests.
authorJames Turner <zakalawe@mac.com>
Tue, 19 Jul 2011 11:55:55 +0000 (12:55 +0100)
committerJames Turner <zakalawe@mac.com>
Tue, 19 Jul 2011 11:55:55 +0000 (12:55 +0100)
16 files changed:
CMakeLists.txt
simgear/io/CMakeLists.txt
simgear/io/HTTPClient.cxx [new file with mode: 0644]
simgear/io/HTTPClient.hxx [new file with mode: 0644]
simgear/io/HTTPRequest.cxx [new file with mode: 0644]
simgear/io/HTTPRequest.hxx [new file with mode: 0644]
simgear/io/sg_netChat.cxx
simgear/io/sg_netChat.hxx
simgear/io/test_HTTP.cxx [new file with mode: 0644]
simgear/misc/CMakeLists.txt
simgear/misc/strutils.cxx
simgear/misc/strutils.hxx
simgear/misc/strutils_test.cxx [new file with mode: 0644]
simgear/props/CMakeLists.txt
simgear/timing/timestamp.cxx
simgear/timing/timestamp.hxx

index 9374a3eef50916a8fb27b3c70237384f29de14e1..73bfdb5192eca4c53880521f4368e769a0f6e179 100644 (file)
@@ -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")
 
 
+
index e00b6945c2d230a3aa2236f44d8464868434f717..1d4600f6f1f8d52f99614ab6a11bcce75f88afa9 100644 (file)
@@ -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}")
\ No newline at end of file
+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)
+
diff --git a/simgear/io/HTTPClient.cxx b/simgear/io/HTTPClient.cxx
new file mode 100644 (file)
index 0000000..fb8b256
--- /dev/null
@@ -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
diff --git a/simgear/io/HTTPClient.hxx b/simgear/io/HTTPClient.hxx
new file mode 100644 (file)
index 0000000..0e04cdc
--- /dev/null
@@ -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
diff --git a/simgear/io/HTTPRequest.cxx b/simgear/io/HTTPRequest.cxx
new file mode 100644 (file)
index 0000000..e38f7aa
--- /dev/null
@@ -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
diff --git a/simgear/io/HTTPRequest.hxx b/simgear/io/HTTPRequest.hxx
new file mode 100644 (file)
index 0000000..4afb1c7
--- /dev/null
@@ -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
+
index fcced48eca9abb0963e8c23e13faf7dec6c008f3..597bb47e19c6dc589c2f36fa371ae0e7007669da 100644 (file)
 #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;
     }
     
index e9ab2e039d9384aa9936e45433dcf80083c93c7c..8c3467284b403d3cdee558713b83a8e8da421e9e 100644 (file)
@@ -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) {}
diff --git a/simgear/io/test_HTTP.cxx b/simgear/io/test_HTTP.cxx
new file mode 100644 (file)
index 0000000..3d53649
--- /dev/null
@@ -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;
+}
index f6c66277ea1b547f5b26177982fed0747a27a673..6b21c7ca47d0fe6a7165ad14482474227bf31e67 100644 (file)
@@ -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)
index a81adc5c22f5331cc7a0bfc9752ab9eeb0b3f275..d4ff839358effeb70bc804cd9461d9b7e6b37628 100644 (file)
@@ -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
index 935e3d4a1b37646a3bbba58d730b9985bd128ce1..eeb1bfaaa406cd9de583407b7cb88e3e82cf58bb 100644 (file)
@@ -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
 
diff --git a/simgear/misc/strutils_test.cxx b/simgear/misc/strutils_test.cxx
new file mode 100644 (file)
index 0000000..f6e08e0
--- /dev/null
@@ -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;
+}
index f57b9253dd647c87722bfa53f9ac71194cda6e25..f347df457d687ebadd1ec6b4d06bfea3d3c412e0 100644 (file)
@@ -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)
index bfed66446da1df1c972c6c84e94c365f90d4546c..83eff2fc10ea485c8039634132b958f1ac866cef 100644 (file)
@@ -104,3 +104,10 @@ void SGTimeStamp::stamp() {
 #endif
 }
 
+int SGTimeStamp::elapsedMSec() const
+{
+    SGTimeStamp now;
+    now.stamp();
+    
+    return static_cast<int>((now - *this).toMSecs());
+}
index e6aeb0a23657a2ec824e8869d222502e66ca2047..f195dc3740fbdedd4593b2d6c9ff6f574a648494 100644 (file)
@@ -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); }