Merge commit 'refs/merge-requests/22' of git://gitorious.org/fg/simgear into merge-requests/22

This commit is contained in:
James Turner 2012-04-22 20:21:45 +01:00
parent 4abcf3215f
commit 69ef461e6d
4 changed files with 174 additions and 14 deletions

View File

@ -25,9 +25,6 @@ using std::string;
using std::stringstream;
using std::vector;
//#include <iostream>
//using namespace std;
namespace simgear
{
@ -35,6 +32,7 @@ namespace HTTP
{
extern const int DEFAULT_HTTP_PORT = 80;
const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
class Connection : public NetChat
{
@ -103,15 +101,27 @@ public:
state = STATE_SENT_REQUEST;
bodyTransferSize = -1;
chunkedTransfer = false;
noMessageBody = (r->method() == "HEAD");
setTerminator("\r\n");
stringstream headerData;
string path = r->path();
string query = r->query();
string bodyData;
if (!client->proxyHost().empty()) {
path = r->url();
path = r->scheme() + "://" + r->host() + r->path();
}
headerData << r->method() << " " << path << " HTTP/1.1\r\n";
if (r->method() == "POST") {
headerData << r->method() << " " << path << " HTTP/1.1\r\n";
bodyData = query.substr(1); // URL-encode, drop the leading '?'
headerData << "Content-Type:" << CONTENT_TYPE_URL_ENCODED << "\r\n";
headerData << "Content-Length:" << bodyData.size() << "\r\n";
} else {
headerData << r->method() << " " << path << query << " HTTP/1.1\r\n";
}
headerData << "Host: " << r->hostAndPort() << "\r\n";
headerData << "User-Agent:" << client->userAgent() << "\r\n";
if (!client->proxyAuth().empty()) {
@ -123,9 +133,10 @@ public:
}
headerData << "\r\n"; // final CRLF to terminate the headers
// TODO - add request body support for PUT, etc operations
if (!bodyData.empty()) {
headerData << bodyData;
}
bool ok = push(headerData.str().c_str());
if (!ok) {
SG_LOG(SG_IO, SG_WARN, "HTTP writing to socket failed");
@ -150,6 +161,10 @@ public:
activeRequest->responseStart(buffer);
state = STATE_GETTING_HEADERS;
buffer.clear();
if (activeRequest->responseCode() == 204) {
noMessageBody = true;
}
break;
case STATE_GETTING_HEADERS:
@ -233,6 +248,11 @@ private:
if (chunkedTransfer) {
state = STATE_GETTING_CHUNKED;
} else if (noMessageBody || (bodyTransferSize == 0)) {
// force the state to GETTING_BODY, to simplify logic in
// responseComplete and handleClose
state = STATE_GETTING_BODY;
responseComplete();
} else {
setByteCount(bodyTransferSize); // may be -1, that's fine
state = STATE_GETTING_BODY;
@ -327,7 +347,6 @@ private:
{
activeRequest->responseComplete();
client->requestFinished(this);
//cout << "response complete: " << activeRequest->url() << endl;
bool doClose = activeRequest->closeAfterComplete();
activeRequest = NULL;
@ -374,6 +393,7 @@ private:
int bodyTransferSize;
SGTimeStamp idleTime;
bool chunkedTransfer;
bool noMessageBody;
std::list<Request_ptr> queuedRequests;
};

View File

@ -125,6 +125,20 @@ string Request::path() const
return u.substr(hostEnd, query - hostEnd);
}
string Request::query() const
{
string u(url());
int query = u.find('?');
if (query < 0) {
return ""; //no query string found
}
return u.substr(query); //includes question mark
}
string Request::host() const
{
string hp(hostAndPort());

View File

@ -30,6 +30,7 @@ public:
virtual std::string host() const;
virtual std::string hostAndPort() const;
virtual unsigned short port() const;
virtual std::string query() const;
virtual string_list requestHeaders() const;
virtual std::string header(const std::string& name) const;

View File

@ -46,8 +46,8 @@ public:
bool failed;
string bodyData;
TestRequest(const std::string& url) :
HTTP::Request(url),
TestRequest(const std::string& url, const std::string method = "GET") :
HTTP::Request(url, method),
complete(false)
{
}
@ -133,6 +133,13 @@ public:
method = line[0];
path = line[1];
int queryPos = path.find('?');
if (queryPos != string::npos) {
parseArgs(path.substr(queryPos + 1));
path = path.substr(0, queryPos);
}
httpVersion = line[2];
requestHeaders.clear();
buffer.clear();
@ -156,10 +163,28 @@ public:
requestHeaders[key] = value;
buffer.clear();
} else if (state == STATE_REQUEST_BODY) {
cerr << "done getting requst body";
receivedBody();
setTerminator("\r\n");
}
}
void parseArgs(const string& argData)
{
string_list argv = strutils::split(argData, "&");
for (unsigned int a=0; a<argv.size(); ++a) {
int eqPos = argv[a].find('=');
if (eqPos < 0) {
cerr << "malformed HTTP argument:" << argv[a] << endl;
continue;
}
string key = argv[a].substr(0, eqPos);
string value = argv[a].substr(eqPos + 1);
args[key] = value;
}
}
void receivedRequestHeaders()
{
state = STATE_IDLE;
@ -172,6 +197,14 @@ public:
d << "\r\n"; // final CRLF to terminate the headers
d << contentStr;
push(d.str().c_str());
} else if (path == "/test_zero_length_content") {
string contentStr;
stringstream d;
d << "HTTP/1.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 == "/test_headers") {
COMPARE(requestHeaders["X-Foo"], string("Bar"));
COMPARE(requestHeaders["X-AnotherHeader"], string("A longer value"));
@ -239,8 +272,53 @@ public:
d << contentStr;
push(d.str().c_str());
closeWhenDone();
} else if (path == "/test_args") {
if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
sendErrorResponse(400, true, "bad arguments");
return;
}
string contentStr(BODY1);
stringstream d;
d << "HTTP/1.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 == "/test_post") {
if (requestHeaders["Content-Type"] != "application/x-www-form-urlencoded") {
cerr << "bad content type: '" << requestHeaders["Content-Type"] << "'" << endl;
sendErrorResponse(400, true, "bad content type");
return;
}
requestContentLength = strutils::to_int(requestHeaders["Content-Length"]);
setByteCount(requestContentLength);
state = STATE_REQUEST_BODY;
} else {
sendErrorResponse(404, true, "");
sendErrorResponse(404, false, "");
}
}
void receivedBody()
{
state = STATE_IDLE;
if (method == "POST") {
parseArgs(buffer);
}
if (path == "/test_post") {
if ((args["foo"] != "abc") || (args["bar"] != "1234") || (args["username"] != "johndoe")) {
sendErrorResponse(400, true, "bad arguments");
return;
}
stringstream d;
d << "HTTP/1.1 " << 204 << " " << reasonForCode(204) << "\r\n";
d << "\r\n"; // final CRLF to terminate the headers
push(d.str().c_str());
cerr << "sent 204 response ok" << endl;
}
}
@ -273,6 +351,7 @@ public:
{
switch (code) {
case 200: return "OK";
case 204: return "no content";
case 404: return "not found";
default: return "unknown code";
}
@ -284,6 +363,8 @@ public:
string path;
string httpVersion;
std::map<string, string> requestHeaders;
std::map<string, string> args;
int requestContentLength;
};
class TestServer : public NetChannel
@ -374,7 +455,17 @@ int main(int argc, char* argv[])
COMPARE(tr->responseBytesReceived(), strlen(BODY1));
COMPARE(tr->bodyData, string(BODY1));
}
{
TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
}
cerr << "done args" << endl;
{
TestRequest* tr = new TestRequest("http://localhost:2000/test_headers");
HTTP::Request_ptr own(tr);
@ -405,6 +496,7 @@ int main(int argc, char* argv[])
COMPARE(tr->bodyData, string(body2, body2Size));
}
cerr << "testing chunked" << endl;
{
TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
HTTP::Request_ptr own(tr);
@ -430,6 +522,16 @@ int main(int argc, char* argv[])
COMPARE(tr->responseLength(), 0);
}
cout << "done 404 test" << endl;
{
TestRequest* tr = new TestRequest("http://localhost:2000/test_args?foo=abc&bar=1234&username=johndoe");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
}
cout << "done1" << endl;
// test HTTP/1.0
{
@ -536,6 +638,29 @@ int main(int argc, char* argv[])
COMPARE(tr3->bodyData, string(BODY1));
}
// POST
{
cout << "POST" << endl;
TestRequest* tr = new TestRequest("http://localhost:2000/test_post?foo=abc&bar=1234&username=johndoe", "POST");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 204);
}
// test_zero_length_content
{
cout << "zero-length-content-response" << endl;
TestRequest* tr = new TestRequest("http://localhost:2000/test_zero_length_content");
HTTP::Request_ptr own(tr);
cl.makeRequest(tr);
waitForComplete(&cl, tr);
COMPARE(tr->responseCode(), 200);
COMPARE(tr->bodyData, string());
COMPARE(tr->responseBytesReceived(), 0);
}
cout << "all tests passed ok" << endl;
return EXIT_SUCCESS;
}