using std::stringstream;
using std::vector;
-//#include <iostream>
-//using namespace std;
-
namespace simgear
{
{
extern const int DEFAULT_HTTP_PORT = 80;
+const char* CONTENT_TYPE_URL_ENCODED = "application/x-www-form-urlencoded";
class Connection : public NetChat
{
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()) {
}
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");
activeRequest->responseStart(buffer);
state = STATE_GETTING_HEADERS;
buffer.clear();
+ if (activeRequest->responseCode() == 204) {
+ noMessageBody = true;
+ }
+
break;
case STATE_GETTING_HEADERS:
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;
{
activeRequest->responseComplete();
client->requestFinished(this);
- //cout << "response complete: " << activeRequest->url() << endl;
bool doClose = activeRequest->closeAfterComplete();
activeRequest = NULL;
int bodyTransferSize;
SGTimeStamp idleTime;
bool chunkedTransfer;
+ bool noMessageBody;
std::list<Request_ptr> queuedRequests;
};
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)
{
}
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();
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;
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"));
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;
}
}
{
switch (code) {
case 200: return "OK";
+ case 204: return "no content";
case 404: return "not found";
default: return "unknown code";
}
string path;
string httpVersion;
std::map<string, string> requestHeaders;
+ std::map<string, string> args;
+ int requestContentLength;
};
class TestServer : public NetChannel
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);
COMPARE(tr->bodyData, string(body2, body2Size));
}
+ cerr << "testing chunked" << endl;
{
TestRequest* tr = new TestRequest("http://localhost:2000/testchunked");
HTTP::Request_ptr own(tr);
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
{
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;
}