added basic support for an embedded HTTP server. server
currently supports editing the screen map but changing the map won't behave correctly if there are connected screens.
This commit is contained in:
parent
2cc63e31aa
commit
70f5f9491d
|
@ -20,6 +20,9 @@
|
|||
#define CONFIG_PLATFORM_WIN32
|
||||
|
||||
#if (_MSC_VER >= 1200)
|
||||
// work around for statement scoping bug
|
||||
#define for if (false) { } else for
|
||||
|
||||
// turn off bonehead warnings
|
||||
#pragma warning(disable: 4786) // identifier truncated in debug info
|
||||
|
||||
|
|
|
@ -0,0 +1,611 @@
|
|||
#include "CHTTPProtocol.h"
|
||||
#include "CLog.h"
|
||||
#include "XHTTP.h"
|
||||
#include "IInputStream.h"
|
||||
#include "IOutputStream.h"
|
||||
#include <ctype.h>
|
||||
#include <locale.h>
|
||||
#include <time.h>
|
||||
#include <algorithm>
|
||||
#include <sstream>
|
||||
|
||||
//
|
||||
// CHTTPUtil::CaselessCmp
|
||||
//
|
||||
|
||||
inline
|
||||
bool CHTTPUtil::CaselessCmp::cmpEqual(
|
||||
const CString::value_type& a,
|
||||
const CString::value_type& b)
|
||||
{
|
||||
// FIXME -- use std::tolower
|
||||
return tolower(a) == tolower(b);
|
||||
}
|
||||
|
||||
inline
|
||||
bool CHTTPUtil::CaselessCmp::cmpLess(
|
||||
const CString::value_type& a,
|
||||
const CString::value_type& b)
|
||||
{
|
||||
// FIXME -- use std::tolower
|
||||
return tolower(a) < tolower(b);
|
||||
}
|
||||
|
||||
bool CHTTPUtil::CaselessCmp::less(
|
||||
const CString& a,
|
||||
const CString& b)
|
||||
{
|
||||
return std::lexicographical_compare(
|
||||
a.begin(), a.end(),
|
||||
b.begin(), b.end(),
|
||||
&CHTTPUtil::CaselessCmp::cmpLess);
|
||||
}
|
||||
|
||||
bool CHTTPUtil::CaselessCmp::equal(
|
||||
const CString& a,
|
||||
const CString& b)
|
||||
{
|
||||
return !(less(a, b) || less(b, a));
|
||||
}
|
||||
|
||||
bool CHTTPUtil::CaselessCmp::operator()(
|
||||
const CString& a,
|
||||
const CString& b) const
|
||||
{
|
||||
return less(a, b);
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// CHTTPProtocol
|
||||
//
|
||||
|
||||
CHTTPRequest* CHTTPProtocol::readRequest(IInputStream* stream)
|
||||
{
|
||||
CString scratch;
|
||||
|
||||
// parse request line by line
|
||||
CHTTPRequest* request = new CHTTPRequest;
|
||||
try {
|
||||
CString line;
|
||||
|
||||
// read request line. accept and discard leading empty lines.
|
||||
do {
|
||||
line = readLine(stream, scratch);
|
||||
} while (line.empty());
|
||||
|
||||
// parse request line: <method> <uri> <version>
|
||||
{
|
||||
std::istringstream s(line);
|
||||
s.exceptions(std::ios::goodbit);
|
||||
CString version;
|
||||
s >> request->m_method >> request->m_uri >> version;
|
||||
if (!s || request->m_uri.empty() || version.find("HTTP/") != 0) {
|
||||
log((CLOG_DEBUG1 "failed to parse HTTP request line: %s", line.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
// parse version
|
||||
char dot;
|
||||
s.str(version);
|
||||
s.ignore(5);
|
||||
s >> request->m_majorVersion;
|
||||
s.get(dot);
|
||||
s >> request->m_minorVersion;
|
||||
if (!s || dot != '.') {
|
||||
log((CLOG_DEBUG1 "failed to parse HTTP request line: %s", line.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
}
|
||||
if (!isValidToken(request->m_method)) {
|
||||
log((CLOG_DEBUG1 "invalid HTTP method: %s", line.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
if (request->m_majorVersion < 1 || request->m_minorVersion < 0) {
|
||||
log((CLOG_DEBUG1 "invalid HTTP version: %s", line.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
// parse headers
|
||||
readHeaders(stream, request, false, scratch);
|
||||
|
||||
// HTTP/1.1 requests must have a Host header
|
||||
if (request->m_majorVersion > 1 ||
|
||||
(request->m_majorVersion == 1 && request->m_minorVersion >= 1)) {
|
||||
if (request->m_headerIndexByName.count("Host") == 0) {
|
||||
log((CLOG_DEBUG1 "Host header missing"));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
}
|
||||
|
||||
// some methods may not have a body. ensure that the headers
|
||||
// that indicate the body length do not exist for those methods
|
||||
// and do exist for others.
|
||||
if ((request->m_headerIndexByName.count("Transfer-Encoding") == 0 &&
|
||||
request->m_headerIndexByName.count("Content-Length") == 0) !=
|
||||
(request->m_method == "GET" ||
|
||||
request->m_method == "HEAD")) {
|
||||
log((CLOG_DEBUG1 "HTTP method (%s)/body mismatch", request->m_method.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
// prepare to read the body. the length of the body is
|
||||
// determined using, in order:
|
||||
// 1. Transfer-Encoding indicates a "chunked" transfer
|
||||
// 2. Content-Length is present
|
||||
// Content-Length is ignored for "chunked" transfers.
|
||||
CHTTPRequest::CHeaderMap::iterator index = request->
|
||||
m_headerIndexByName.find("Transfer-Encoding");
|
||||
if (index != request->m_headerIndexByName.end()) {
|
||||
// we only understand "chunked" encodings
|
||||
if (!CHTTPUtil::CaselessCmp::equal(
|
||||
request->m_headers[index->second], "chunked")) {
|
||||
log((CLOG_DEBUG1 "unsupported Transfer-Encoding %s", request->m_headers[index->second].c_str()));
|
||||
throw XHTTP(501);
|
||||
}
|
||||
|
||||
// chunked encoding
|
||||
UInt32 oldSize;
|
||||
do {
|
||||
oldSize = request->m_body.size();
|
||||
request->m_body += readChunk(stream, scratch);
|
||||
} while (request->m_body.size() != oldSize);
|
||||
|
||||
// read footer
|
||||
readHeaders(stream, request, true, scratch);
|
||||
|
||||
// remove "chunked" from Transfer-Encoding and set the
|
||||
// Content-Length.
|
||||
// FIXME
|
||||
// FIXME -- note that just deleting Transfer-Encoding will
|
||||
// mess up indices in m_headerIndexByName, and replacing
|
||||
// it with Content-Length could lead to two of those.
|
||||
}
|
||||
else if ((index = request->m_headerIndexByName.
|
||||
find("Content-Length")) !=
|
||||
request->m_headerIndexByName.end()) {
|
||||
// FIXME -- check for overly-long requests
|
||||
|
||||
// parse content-length
|
||||
UInt32 length;
|
||||
{
|
||||
std::istringstream s(request->m_headers[index->second]);
|
||||
s.exceptions(std::ios::goodbit);
|
||||
s >> length;
|
||||
if (!s) {
|
||||
log((CLOG_DEBUG1 "cannot parse Content-Length", request->m_headers[index->second].c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
}
|
||||
|
||||
// use content length
|
||||
request->m_body = readBlock(stream, length, scratch);
|
||||
if (request->m_body.size() != length) {
|
||||
// length must match size of body
|
||||
log((CLOG_DEBUG1 "Content-Length/actual length mismatch (%d vs %d)", length, request->m_body.size()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (...) {
|
||||
delete request;
|
||||
throw;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
void CHTTPProtocol::reply(
|
||||
IOutputStream* stream,
|
||||
CHTTPReply& reply)
|
||||
{
|
||||
// suppress body for certain replies
|
||||
bool hasBody = true;
|
||||
if ((reply.m_status / 100) == 1 ||
|
||||
reply.m_status == 204 ||
|
||||
reply.m_status == 304) {
|
||||
hasBody = false;
|
||||
}
|
||||
|
||||
// adjust headers
|
||||
for (CHTTPReply::CHeaderList::iterator
|
||||
index = reply.m_headers.begin();
|
||||
index != reply.m_headers.end(); ) {
|
||||
const CString& header = index->first;
|
||||
|
||||
// remove certain headers
|
||||
if (CHTTPUtil::CaselessCmp::equal(header, "Content-Length") ||
|
||||
CHTTPUtil::CaselessCmp::equal(header, "Date") ||
|
||||
CHTTPUtil::CaselessCmp::equal(header, "Transfer-Encoding")) {
|
||||
// FIXME -- Transfer-Encoding should be left as-is if
|
||||
// not "chunked" and if the version is 1.1 or up.
|
||||
index = reply.m_headers.erase(index);
|
||||
}
|
||||
|
||||
// keep as-is
|
||||
else {
|
||||
++index;
|
||||
}
|
||||
}
|
||||
|
||||
// write reply header
|
||||
ostringstream s;
|
||||
s << "HTTP/" << reply.m_majorVersion << "." <<
|
||||
reply.m_minorVersion << " " <<
|
||||
reply.m_status << " " <<
|
||||
reply.m_reason << "\r\n";
|
||||
|
||||
// get date
|
||||
// FIXME -- should use C++ locale stuff but VC++ time_put is broken.
|
||||
// FIXME -- double check that VC++ is broken
|
||||
// FIXME -- should mutex gmtime() since the return value may not
|
||||
// be thread safe
|
||||
char date[30];
|
||||
{
|
||||
const char* oldLocale = setlocale(LC_TIME, "C");
|
||||
time_t t = time(NULL);
|
||||
struct tm* tm = gmtime(&t);
|
||||
strftime(date, sizeof(date), "%a, %d %b %Y %H:%M:%S GMT", tm);
|
||||
setlocale(LC_TIME, oldLocale);
|
||||
}
|
||||
|
||||
// write headers
|
||||
s << "Date: " << date << "\r\n";
|
||||
for (CHTTPReply::CHeaderList::const_iterator
|
||||
index = reply.m_headers.begin();
|
||||
index != reply.m_headers.end(); ++index) {
|
||||
s << index->first << ": " << index->second << "\r\n";
|
||||
}
|
||||
if (hasBody) {
|
||||
s << "Content-Length: " << reply.m_body.size() << "\r\n";
|
||||
}
|
||||
s << "Connection: close\r\n";
|
||||
|
||||
// write end of headers
|
||||
s << "\r\n";
|
||||
|
||||
// write to stream
|
||||
stream->write(s.str().data(), s.str().size());
|
||||
|
||||
// write body. replies to HEAD method never have a body (though
|
||||
// they do have the Content-Length header).
|
||||
if (hasBody && reply.m_method != "HEAD") {
|
||||
stream->write(reply.m_body.data(), reply.m_body.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool CHTTPProtocol::parseFormData(
|
||||
const CHTTPRequest& request,
|
||||
CFormParts& parts)
|
||||
{
|
||||
static const char formData[] = "multipart/form-data";
|
||||
static const char boundary[] = "boundary=";
|
||||
static const char disposition[] = "Content-Disposition:";
|
||||
static const char nameAttr[] = "name=";
|
||||
static const char quote[] = "\"";
|
||||
|
||||
// find the Content-Type header
|
||||
CHTTPRequest::CHeaderMap::const_iterator contentTypeIndex =
|
||||
request.m_headerIndexByName.find("Content-Type");
|
||||
if (contentTypeIndex == request.m_headerIndexByName.end()) {
|
||||
// missing required Content-Type header
|
||||
return false;
|
||||
}
|
||||
const CString contentType = request.m_headers[contentTypeIndex->second];
|
||||
|
||||
// parse type
|
||||
CString::const_iterator index = std::search(
|
||||
contentType.begin(), contentType.end(),
|
||||
formData, formData + sizeof(formData) - 1,
|
||||
CHTTPUtil::CaselessCmp::cmpEqual);
|
||||
if (index == contentType.end()) {
|
||||
// not form-data
|
||||
return false;
|
||||
}
|
||||
index += sizeof(formData) - 1;
|
||||
index = std::search(index, contentType.end(),
|
||||
boundary, boundary + sizeof(boundary) - 1,
|
||||
CHTTPUtil::CaselessCmp::cmpEqual);
|
||||
if (index == contentType.end()) {
|
||||
// no boundary
|
||||
return false;
|
||||
}
|
||||
CString delimiter = contentType.c_str() +
|
||||
(index - contentType.begin()) +
|
||||
sizeof(boundary) - 1;
|
||||
|
||||
// find first delimiter
|
||||
const CString& body = request.m_body;
|
||||
CString::size_type partIndex = body.find(delimiter);
|
||||
if (partIndex == CString::npos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// skip over it
|
||||
partIndex += delimiter.size();
|
||||
|
||||
// prepend CRLF-- to delimiter
|
||||
delimiter = "\r\n--" + delimiter;
|
||||
|
||||
// parse parts until there are no more
|
||||
for (;;) {
|
||||
// is it the last part?
|
||||
if (body.size() >= partIndex + 2 &&
|
||||
body[partIndex ] == '-' &&
|
||||
body[partIndex + 1] == '-') {
|
||||
// found last part. success if there's no trailing data.
|
||||
// FIXME -- check for trailing data (other than a single CRLF)
|
||||
return true;
|
||||
}
|
||||
|
||||
// find the end of this part
|
||||
CString::size_type nextPart = body.find(delimiter, partIndex);
|
||||
if (nextPart == CString::npos) {
|
||||
// no terminator
|
||||
return false;
|
||||
}
|
||||
|
||||
// find end of headers
|
||||
CString::size_type endOfHeaders = body.find("\r\n\r\n", partIndex);
|
||||
if (endOfHeaders == CString::npos || endOfHeaders > nextPart) {
|
||||
// bad part
|
||||
return false;
|
||||
}
|
||||
endOfHeaders += 2;
|
||||
|
||||
// now find Content-Disposition
|
||||
index = std::search(body.begin() + partIndex,
|
||||
body.begin() + endOfHeaders,
|
||||
disposition,
|
||||
disposition + sizeof(disposition) - 1,
|
||||
CHTTPUtil::CaselessCmp::cmpEqual);
|
||||
if (index == contentType.begin() + endOfHeaders) {
|
||||
// bad part
|
||||
return false;
|
||||
}
|
||||
|
||||
// find the name in the Content-Disposition
|
||||
CString::size_type endOfHeader = body.find("\r\n",
|
||||
index - body.begin());
|
||||
if (endOfHeader >= endOfHeaders) {
|
||||
// bad part
|
||||
return false;
|
||||
}
|
||||
index = std::search(index, body.begin() + endOfHeader,
|
||||
nameAttr, nameAttr + sizeof(nameAttr) - 1,
|
||||
CHTTPUtil::CaselessCmp::cmpEqual);
|
||||
if (index == body.begin() + endOfHeader) {
|
||||
// no name
|
||||
return false;
|
||||
}
|
||||
|
||||
// extract the name
|
||||
CString name;
|
||||
index += sizeof(nameAttr) - 1;
|
||||
if (*index == quote[0]) {
|
||||
// quoted name
|
||||
++index;
|
||||
CString::size_type namePos = index - body.begin();
|
||||
index = std::search(index, body.begin() + endOfHeader,
|
||||
quote, quote + 1,
|
||||
CHTTPUtil::CaselessCmp::cmpEqual);
|
||||
if (index == body.begin() + endOfHeader) {
|
||||
// missing close quote
|
||||
return false;
|
||||
}
|
||||
name = body.substr(namePos, index - body.begin() - namePos);
|
||||
}
|
||||
else {
|
||||
// unquoted name
|
||||
name = body.substr(index - body.begin(),
|
||||
body.find_first_of(" \t\r\n"));
|
||||
}
|
||||
|
||||
// save part. add 2 to endOfHeaders to skip CRLF.
|
||||
parts.insert(std::make_pair(name, body.substr(endOfHeaders + 2,
|
||||
nextPart - (endOfHeaders + 2))));
|
||||
|
||||
// move to next part
|
||||
partIndex = nextPart + delimiter.size();
|
||||
}
|
||||
|
||||
// should've found the last delimiter inside the loop but we did not
|
||||
return false;
|
||||
}
|
||||
|
||||
CString CHTTPProtocol::readLine(
|
||||
IInputStream* stream,
|
||||
CString& tmpBuffer)
|
||||
{
|
||||
// read up to and including a CRLF from stream, using whatever
|
||||
// is in tmpBuffer as if it were at the head of the stream.
|
||||
|
||||
for (;;) {
|
||||
// scan tmpBuffer for CRLF
|
||||
CString::size_type newline = tmpBuffer.find("\r\n");
|
||||
if (newline != CString::npos) {
|
||||
// copy line without the CRLF
|
||||
CString line = tmpBuffer.substr(0, newline);
|
||||
|
||||
// discard line and CRLF from tmpBuffer
|
||||
tmpBuffer.erase(0, newline + 2);
|
||||
return line;
|
||||
}
|
||||
|
||||
// read more from stream
|
||||
char buffer[4096];
|
||||
UInt32 n = stream->read(buffer, sizeof(buffer));
|
||||
if (n == 0) {
|
||||
// stream is empty. return what's leftover.
|
||||
CString line = tmpBuffer;
|
||||
tmpBuffer.erase();
|
||||
return line;
|
||||
}
|
||||
|
||||
// append stream data
|
||||
tmpBuffer.append(buffer, n);
|
||||
}
|
||||
}
|
||||
|
||||
CString CHTTPProtocol::readBlock(
|
||||
IInputStream* stream,
|
||||
UInt32 numBytes,
|
||||
CString& tmpBuffer)
|
||||
{
|
||||
CString data;
|
||||
|
||||
// read numBytes from stream, using whatever is in tmpBuffer as
|
||||
// if it were at the head of the stream.
|
||||
if (tmpBuffer.size() > 0) {
|
||||
// ignore stream if there's enough data in tmpBuffer
|
||||
if (tmpBuffer.size() >= numBytes) {
|
||||
data = tmpBuffer.substr(0, numBytes);
|
||||
tmpBuffer.erase(0, numBytes);
|
||||
return data;
|
||||
}
|
||||
|
||||
// move everything out of tmpBuffer into data
|
||||
data = tmpBuffer;
|
||||
tmpBuffer.erase();
|
||||
}
|
||||
|
||||
// account for bytes read so far
|
||||
assert(data.size() < numBytes);
|
||||
numBytes -= data.size();
|
||||
|
||||
// read until we have all the requested data
|
||||
while (numBytes > 0) {
|
||||
// read max(4096, bytes_left) bytes into buffer
|
||||
char buffer[4096];
|
||||
UInt32 n = sizeof(buffer);
|
||||
if (n > numBytes) {
|
||||
n = numBytes;
|
||||
}
|
||||
n = stream->read(buffer, n);
|
||||
|
||||
// if stream is empty then return what we've got so far
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// append stream data
|
||||
data.append(buffer, n);
|
||||
numBytes -= n;
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
CString CHTTPProtocol::readChunk(
|
||||
IInputStream* stream,
|
||||
CString& tmpBuffer)
|
||||
{
|
||||
CString line;
|
||||
|
||||
// get chunk header
|
||||
line = readLine(stream, tmpBuffer);
|
||||
|
||||
// parse chunk size
|
||||
UInt32 size;
|
||||
{
|
||||
std::istringstream s(line);
|
||||
s.exceptions(std::ios::goodbit);
|
||||
s >> std::hex >> size;
|
||||
if (!s) {
|
||||
log((CLOG_DEBUG1 "cannot parse chunk size", line.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
}
|
||||
if (size == 0) {
|
||||
return CString();
|
||||
}
|
||||
|
||||
// read size bytes
|
||||
// FIXME -- check for overly-long requests
|
||||
CString data = readBlock(stream, size, tmpBuffer);
|
||||
if (data.size() != size) {
|
||||
log((CLOG_DEBUG1 "expected/actual chunk size mismatch", size, data.size()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
// read an discard CRLF
|
||||
line = readLine(stream, tmpBuffer);
|
||||
if (!line.empty()) {
|
||||
log((CLOG_DEBUG1 "missing CRLF after chunk"));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
void CHTTPProtocol::readHeaders(
|
||||
IInputStream* stream,
|
||||
CHTTPRequest* request,
|
||||
bool isFooter,
|
||||
CString& tmpBuffer)
|
||||
{
|
||||
// parse headers. done with headers when we get a blank line.
|
||||
CString line = readLine(stream, tmpBuffer);
|
||||
while (!line.empty()) {
|
||||
// if line starts with space or tab then append it to the
|
||||
// previous header. if there is no previous header then
|
||||
// throw.
|
||||
if (line[0] == ' ' || line[0] == '\t') {
|
||||
if (request->m_headers.size() == 0) {
|
||||
log((CLOG_DEBUG1 "first header is a continuation"));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
request->m_headers.back() += ",";
|
||||
request->m_headers.back() == line;
|
||||
}
|
||||
|
||||
// line should have the form: <name>:[<value>]
|
||||
else {
|
||||
// parse
|
||||
CString name, value;
|
||||
std::istringstream s(line);
|
||||
s.exceptions(std::ios::goodbit);
|
||||
std::getline(s, name, ':');
|
||||
if (!s || !isValidToken(name)) {
|
||||
log((CLOG_DEBUG1 "invalid header: %s", line.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
std::getline(s, value);
|
||||
|
||||
// check validity of name
|
||||
if (isFooter) {
|
||||
// FIXME -- only certain names are allowed in footers
|
||||
}
|
||||
|
||||
// check if we've seen this header before
|
||||
CHTTPRequest::CHeaderMap::iterator index =
|
||||
request->m_headerIndexByName.find(name);
|
||||
if (index == request->m_headerIndexByName.end()) {
|
||||
// it's a new header
|
||||
request->m_headerIndexByName.insert(std::make_pair(name,
|
||||
request->m_headers.size()));
|
||||
request->m_headers.push_back(value);
|
||||
}
|
||||
else {
|
||||
// it's an existing header. append value to previous
|
||||
// header, separated by a comma.
|
||||
request->m_headers[index->second] += ',';
|
||||
request->m_headers[index->second] += value;
|
||||
}
|
||||
}
|
||||
|
||||
// next header
|
||||
line = readLine(stream, tmpBuffer);
|
||||
|
||||
// FIXME -- should check for overly-long requests
|
||||
}
|
||||
}
|
||||
|
||||
bool CHTTPProtocol::isValidToken(const CString& token)
|
||||
{
|
||||
return (token.find("()<>@,;:\\\"/[]?={} "
|
||||
"\0\1\2\3\4\5\6\7"
|
||||
"\10\11\12\13\14\15\16\17"
|
||||
"\20\21\22\23\24\25\26\27"
|
||||
"\30\31\32\33\34\35\36\37\177") == CString::npos);
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
#ifndef CHTTPPROTOCOL_H
|
||||
#define CHTTPPROTOCOL_H
|
||||
|
||||
#include "BasicTypes.h"
|
||||
#include "CString.h"
|
||||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
class IInputStream;
|
||||
class IOutputStream;
|
||||
|
||||
class CHTTPUtil {
|
||||
public:
|
||||
class CaselessCmp {
|
||||
public:
|
||||
bool operator()(const CString&, const CString&) const;
|
||||
static bool less(const CString&, const CString&);
|
||||
static bool equal(const CString&, const CString&);
|
||||
static bool cmpLess(const CString::value_type&,
|
||||
const CString::value_type&);
|
||||
static bool cmpEqual(const CString::value_type&,
|
||||
const CString::value_type&);
|
||||
};
|
||||
};
|
||||
|
||||
class CHTTPRequest {
|
||||
public:
|
||||
typedef std::map<CString, UInt32, CHTTPUtil::CaselessCmp> CHeaderMap;
|
||||
typedef std::vector<CString> CHeaderList;
|
||||
|
||||
CString m_method;
|
||||
CString m_uri;
|
||||
SInt32 m_majorVersion;
|
||||
SInt32 m_minorVersion;
|
||||
|
||||
CHeaderList m_headers;
|
||||
CHeaderMap m_headerIndexByName;
|
||||
|
||||
CString m_body;
|
||||
// FIXME -- need parts-of-body for POST messages
|
||||
};
|
||||
|
||||
class CHTTPReply {
|
||||
public:
|
||||
typedef std::vector<std::pair<CString, CString> > CHeaderList;
|
||||
|
||||
SInt32 m_majorVersion;
|
||||
SInt32 m_minorVersion;
|
||||
SInt32 m_status;
|
||||
CString m_reason;
|
||||
CString m_method;
|
||||
|
||||
CHeaderList m_headers;
|
||||
|
||||
CString m_body;
|
||||
};
|
||||
|
||||
class CHTTPProtocol {
|
||||
public:
|
||||
// read and parse an HTTP request. result is returned in a
|
||||
// CHTTPRequest which the client must delete. throws an
|
||||
// XHTTP if there was a parse error. throws an XIO
|
||||
// exception if there was a read error.
|
||||
static CHTTPRequest* readRequest(IInputStream*);
|
||||
|
||||
// send an HTTP reply on the stream
|
||||
static void reply(IOutputStream*, CHTTPReply&);
|
||||
|
||||
// parse a multipart/form-data body into its parts. returns true
|
||||
// iff the entire body was correctly parsed.
|
||||
// FIXME -- name/value pairs insufficient to save part headers
|
||||
typedef std::map<CString, CString> CFormParts;
|
||||
static bool parseFormData(const CHTTPRequest&,
|
||||
CFormParts& parts);
|
||||
|
||||
private:
|
||||
static CString readLine(IInputStream*, CString& tmpBuffer);
|
||||
static CString readBlock(IInputStream*,
|
||||
UInt32 numBytes, CString& tmpBuffer);
|
||||
static CString readChunk(IInputStream*, CString& tmpBuffer);
|
||||
static void readHeaders(IInputStream*,
|
||||
CHTTPRequest*, bool isFooter,
|
||||
CString& tmpBuffer);
|
||||
|
||||
static bool isValidToken(const CString&);
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,26 @@
|
|||
DEPTH=..
|
||||
include $(DEPTH)/Makecommon
|
||||
|
||||
#
|
||||
# target file
|
||||
#
|
||||
TARGET = http
|
||||
|
||||
#
|
||||
# source files
|
||||
#
|
||||
LCXXINCS = \
|
||||
-I$(DEPTH)/base \
|
||||
-I$(DEPTH)/mt \
|
||||
-I$(DEPTH)/io \
|
||||
$(NULL)
|
||||
CXXFILES = \
|
||||
XHTTP.cpp \
|
||||
CHTTPProtocol.cpp \
|
||||
$(NULL)
|
||||
|
||||
targets: $(LIBTARGET)
|
||||
|
||||
$(LIBTARGET): $(OBJECTS) $(DEPLIBS)
|
||||
if test ! -d $(LIBDIR); then $(MKDIR) $(LIBDIR); fi
|
||||
$(ARF) $(LIBTARGET) $(OBJECTS)
|
|
@ -0,0 +1,119 @@
|
|||
#include "XHTTP.h"
|
||||
#include "CHTTPProtocol.h"
|
||||
#include <stdio.h>
|
||||
|
||||
//
|
||||
// XHTTP
|
||||
//
|
||||
|
||||
XHTTP::XHTTP(SInt32 statusCode) :
|
||||
XBase(),
|
||||
m_status(statusCode),
|
||||
m_reason(getReason(statusCode))
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
XHTTP::XHTTP(SInt32 statusCode, const CString& reasonPhrase) :
|
||||
XBase(),
|
||||
m_status(statusCode),
|
||||
m_reason(reasonPhrase)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
XHTTP::~XHTTP()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
SInt32 XHTTP::getStatus() const
|
||||
{
|
||||
return m_status;
|
||||
}
|
||||
|
||||
CString XHTTP::getReason() const
|
||||
{
|
||||
return m_reason;
|
||||
}
|
||||
|
||||
void XHTTP::addHeaders(CHTTPReply&) const
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
CString XHTTP::getWhat() const throw()
|
||||
{
|
||||
try {
|
||||
char code[20];
|
||||
sprintf(code, "%d ", m_status);
|
||||
CString msg(code);
|
||||
if (!m_reason.empty()) {
|
||||
msg += m_reason;
|
||||
}
|
||||
else {
|
||||
msg += getReason(m_status);
|
||||
}
|
||||
return msg;
|
||||
}
|
||||
catch (...) {
|
||||
return CString();
|
||||
}
|
||||
}
|
||||
|
||||
const char* XHTTP::getReason(SInt32 status)
|
||||
{
|
||||
switch (status) {
|
||||
case 300: return "Multiple Choices";
|
||||
case 301: return "Moved Permanently";
|
||||
case 302: return "Moved Temporarily";
|
||||
case 303: return "See Other";
|
||||
case 304: return "Not Modified";
|
||||
case 305: return "Use Proxy";
|
||||
case 400: return "Bad Request";
|
||||
case 401: return "Unauthorized";
|
||||
case 402: return "Payment Required";
|
||||
case 403: return "Forbidden";
|
||||
case 404: return "Not Found";
|
||||
case 405: return "Method Not Allowed";
|
||||
case 406: return "Not Acceptable";
|
||||
case 407: return "Proxy Authentication Required";
|
||||
case 408: return "Request Time-out";
|
||||
case 409: return "Conflict";
|
||||
case 410: return "Gone";
|
||||
case 411: return "Length Required";
|
||||
case 412: return "Precondition Failed";
|
||||
case 413: return "Request Entity Too Large";
|
||||
case 414: return "Request-URI Too Large";
|
||||
case 415: return "Unsupported Media Type";
|
||||
case 500: return "Internal Server Error";
|
||||
case 501: return "Not Implemented";
|
||||
case 502: return "Bad Gateway";
|
||||
case 503: return "Service Unavailable";
|
||||
case 504: return "Gateway Time-out";
|
||||
case 505: return "HTTP Version not supported";
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//
|
||||
// XHTTPAllow
|
||||
//
|
||||
|
||||
XHTTPAllow::XHTTPAllow(const CString& allowed) :
|
||||
XHTTP(405),
|
||||
m_allowed(allowed)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
XHTTPAllow::~XHTTPAllow()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void XHTTPAllow::addHeaders(CHTTPReply& reply) const
|
||||
{
|
||||
reply.m_headers.push_back(std::make_pair(CString("Allow"), m_allowed));
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef XHTTP_H
|
||||
#define XHTTP_H
|
||||
|
||||
#include "BasicTypes.h"
|
||||
#include "CString.h"
|
||||
#include "XBase.h"
|
||||
|
||||
class CHTTPReply;
|
||||
|
||||
class XHTTP : public XBase {
|
||||
public:
|
||||
XHTTP(SInt32 statusCode);
|
||||
XHTTP(SInt32 statusCode, const CString& reasonPhrase);
|
||||
~XHTTP();
|
||||
|
||||
SInt32 getStatus() const;
|
||||
CString getReason() const;
|
||||
|
||||
virtual void addHeaders(CHTTPReply&) const;
|
||||
|
||||
protected:
|
||||
virtual CString getWhat() const throw();
|
||||
|
||||
private:
|
||||
static const char* getReason(SInt32 status);
|
||||
|
||||
private:
|
||||
SInt32 m_status;
|
||||
CString m_reason;
|
||||
};
|
||||
|
||||
class XHTTPAllow : public XHTTP {
|
||||
public:
|
||||
XHTTPAllow(const CString& allowedMethods);
|
||||
~XHTTPAllow();
|
||||
|
||||
// XHTTP overrides
|
||||
virtual void addHeaders(CHTTPReply&) const;
|
||||
|
||||
private:
|
||||
CString m_allowed;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,823 @@
|
|||
#include "CHTTPServer.h"
|
||||
#include "CHTTPProtocol.h"
|
||||
#include "XHTTP.h"
|
||||
#include "CServer.h"
|
||||
#include "CScreenMap.h"
|
||||
#include "CLog.h"
|
||||
#include "XThread.h"
|
||||
#include "ISocket.h"
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
|
||||
//
|
||||
// CHTTPServer
|
||||
//
|
||||
|
||||
CHTTPServer::CHTTPServer(CServer* server) : m_server(server)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
CHTTPServer::~CHTTPServer()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void CHTTPServer::processRequest(ISocket* socket)
|
||||
{
|
||||
assert(socket != NULL);
|
||||
|
||||
CHTTPRequest* request = NULL;
|
||||
try {
|
||||
// parse request
|
||||
request = CHTTPProtocol::readRequest(socket->getInputStream());
|
||||
if (request == NULL) {
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
// if absolute uri then strip off scheme and host
|
||||
if (request->m_uri[0] != '/') {
|
||||
CString::size_type n = request->m_uri.find('/');
|
||||
if (n == CString::npos) {
|
||||
throw XHTTP(404);
|
||||
}
|
||||
request->m_uri = request->m_uri.substr(n);
|
||||
}
|
||||
|
||||
// process
|
||||
CHTTPReply reply;
|
||||
doProcessRequest(*request, reply);
|
||||
|
||||
// send reply
|
||||
CHTTPProtocol::reply(socket->getOutputStream(), reply);
|
||||
log((CLOG_INFO "HTTP reply %d for %s %s", reply.m_status, request->m_method.c_str(), request->m_uri.c_str()));
|
||||
|
||||
// clean up
|
||||
delete request;
|
||||
}
|
||||
catch (XHTTP& e) {
|
||||
log((CLOG_WARN "returning HTTP error %d %s for %s", e.getStatus(), e.getReason().c_str(), (request != NULL) ? request->m_uri.c_str() : "<unknown>"));
|
||||
|
||||
// clean up
|
||||
delete request;
|
||||
|
||||
// return error
|
||||
CHTTPReply reply;
|
||||
reply.m_majorVersion = 1;
|
||||
reply.m_minorVersion = 0;
|
||||
reply.m_status = e.getStatus();
|
||||
reply.m_reason = e.getReason();
|
||||
reply.m_method = "GET";
|
||||
// FIXME -- use a nicer error page
|
||||
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
|
||||
CString("text/plain")));
|
||||
reply.m_body = e.getReason();
|
||||
e.addHeaders(reply);
|
||||
CHTTPProtocol::reply(socket->getOutputStream(), reply);
|
||||
}
|
||||
catch (...) {
|
||||
// ignore other exceptions
|
||||
RETHROW_XTHREAD
|
||||
}
|
||||
}
|
||||
|
||||
void CHTTPServer::doProcessRequest(
|
||||
CHTTPRequest& request,
|
||||
CHTTPReply& reply)
|
||||
{
|
||||
reply.m_majorVersion = request.m_majorVersion;
|
||||
reply.m_minorVersion = request.m_minorVersion;
|
||||
reply.m_status = 200;
|
||||
reply.m_reason = "OK";
|
||||
reply.m_method = request.m_method;
|
||||
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
|
||||
CString("text/html")));
|
||||
|
||||
// switch based on uri
|
||||
if (request.m_uri == "/editmap") {
|
||||
if (request.m_method == "GET" || request.m_method == "HEAD") {
|
||||
doProcessGetEditMap(request, reply);
|
||||
}
|
||||
else if (request.m_method == "POST") {
|
||||
doProcessPostEditMap(request, reply);
|
||||
}
|
||||
else {
|
||||
throw XHTTPAllow("GET, HEAD, POST");
|
||||
}
|
||||
}
|
||||
else {
|
||||
// unknown page
|
||||
throw XHTTP(404);
|
||||
}
|
||||
}
|
||||
|
||||
void CHTTPServer::doProcessGetEditMap(
|
||||
CHTTPRequest& /*request*/,
|
||||
CHTTPReply& reply)
|
||||
{
|
||||
static const char* s_editMapProlog1 =
|
||||
"<html>\r\n"
|
||||
"<head>\r\n"
|
||||
" <title>Synergy -- Edit Screens</title>\r\n"
|
||||
"</head>\r\n"
|
||||
"<body>\r\n"
|
||||
" <form method=\"POST\" action=\"editmap\" "
|
||||
"enctype=\"multipart/form-data\">\r\n"
|
||||
" <input type=hidden name=\"size\" value=\"";
|
||||
static const char* s_editMapProlog2 =
|
||||
"\">\r\n";
|
||||
static const char* s_editMapEpilog =
|
||||
" <input type=submit name=\"submit\">\r\n"
|
||||
" <input type=reset>\r\n"
|
||||
" <br>\r\n"
|
||||
" </form>\r\n"
|
||||
"</body>\r\n"
|
||||
"</html>\r\n";
|
||||
static const char* s_editMapTableProlog =
|
||||
"<table border=\"1\" cellspacing=\"0\" cellpadding=\"0\"><tr><td>"
|
||||
" <table border=\"0\" cellspacing=\"2\" cellpadding=\"6\">\r\n";
|
||||
static const char* s_editMapTableEpilog =
|
||||
" </table>"
|
||||
"</td></tr></table>\r\n";
|
||||
static const char* s_editMapRowProlog =
|
||||
"<tr align=\"center\" valign=\"top\">\r\n";
|
||||
static const char* s_editMapRowEpilog =
|
||||
"</tr>\r\n";
|
||||
static const char* s_editMapScreenDummy =
|
||||
"<td>";
|
||||
static const char* s_editMapScreenPrimary =
|
||||
"<td bgcolor=\"#2222288\"><input type=\"text\" readonly value=\"";
|
||||
static const char* s_editMapScreenLive =
|
||||
"<td bgcolor=\"#cccccc\"><input type=\"text\" value=\"";
|
||||
static const char* s_editMapScreenDead =
|
||||
"<td><input type=\"text\" value=\"";
|
||||
static const char* s_editMapScreenLiveDead1 =
|
||||
"\" size=\"16\" maxlength=\"64\" name=\"";
|
||||
static const char* s_editMapScreenLiveDead2 =
|
||||
"\">";
|
||||
static const char* s_editMapScreenEnd =
|
||||
"</td>\r\n";
|
||||
|
||||
ostringstream s;
|
||||
|
||||
// convert screen map into a temporary screen map
|
||||
CScreenArray screens;
|
||||
{
|
||||
CScreenMap currentMap;
|
||||
m_server->getScreenMap(¤tMap);
|
||||
screens.convertFrom(currentMap);
|
||||
// FIXME -- note to user if currentMap couldn't be exactly represented
|
||||
}
|
||||
|
||||
// insert blank columns and rows around array (to allow the user
|
||||
// to insert new screens)
|
||||
screens.insertColumn(0);
|
||||
screens.insertColumn(screens.getWidth());
|
||||
screens.insertRow(0);
|
||||
screens.insertRow(screens.getHeight());
|
||||
|
||||
// get array size
|
||||
const SInt32 w = screens.getWidth();
|
||||
const SInt32 h = screens.getHeight();
|
||||
|
||||
// construct reply
|
||||
reply.m_body += s_editMapProlog1;
|
||||
s << w << "x" << h;
|
||||
reply.m_body += s.str();
|
||||
reply.m_body += s_editMapProlog2;
|
||||
|
||||
// add screen map for editing
|
||||
const CString primaryName = m_server->getPrimaryScreenName();
|
||||
reply.m_body += s_editMapTableProlog;
|
||||
for (SInt32 y = 0; y < h; ++y) {
|
||||
reply.m_body += s_editMapRowProlog;
|
||||
for (SInt32 x = 0; x < w; ++x) {
|
||||
s.str("");
|
||||
if (!screens.isAllowed(x, y) && screens.get(x, y) != primaryName) {
|
||||
s << s_editMapScreenDummy;
|
||||
}
|
||||
else {
|
||||
if (!screens.isSet(x, y)) {
|
||||
s << s_editMapScreenDead;
|
||||
}
|
||||
else if (screens.get(x, y) == primaryName) {
|
||||
s << s_editMapScreenPrimary;
|
||||
}
|
||||
else {
|
||||
s << s_editMapScreenLive;
|
||||
}
|
||||
s << screens.get(x, y) <<
|
||||
s_editMapScreenLiveDead1 <<
|
||||
"n" << x << "x" << y <<
|
||||
s_editMapScreenLiveDead2;
|
||||
}
|
||||
s << s_editMapScreenEnd;
|
||||
reply.m_body += s.str();
|
||||
}
|
||||
reply.m_body += s_editMapRowEpilog;
|
||||
}
|
||||
reply.m_body += s_editMapTableEpilog;
|
||||
|
||||
reply.m_body += s_editMapEpilog;
|
||||
}
|
||||
|
||||
void CHTTPServer::doProcessPostEditMap(
|
||||
CHTTPRequest& request,
|
||||
CHTTPReply& reply)
|
||||
{
|
||||
typedef std::vector<CString> ScreenArray;
|
||||
typedef std::set<CString> ScreenSet;
|
||||
|
||||
// parse the result
|
||||
CHTTPProtocol::CFormParts parts;
|
||||
if (!CHTTPProtocol::parseFormData(request, parts)) {
|
||||
log((CLOG_WARN "editmap: cannot parse form data"));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
try {
|
||||
ostringstream s;
|
||||
|
||||
// convert post data into a temporary screen map. also check
|
||||
// that no screen name is invalid or used more than once.
|
||||
SInt32 w, h;
|
||||
CHTTPProtocol::CFormParts::iterator index = parts.find("size");
|
||||
if (index == parts.end() ||
|
||||
!parseXY(index->second, w, h) ||
|
||||
w <= 0 || h <= 0) {
|
||||
log((CLOG_WARN "editmap: cannot parse size or size is invalid"));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
ScreenSet screenNames;
|
||||
CScreenArray screens;
|
||||
screens.resize(w, h);
|
||||
for (SInt32 y = 0; y < h; ++y) {
|
||||
for (SInt32 x = 0; x < w; ++x) {
|
||||
// find part
|
||||
s.str("");
|
||||
s << "n" << x << "x" << y;
|
||||
index = parts.find(s.str());
|
||||
if (index == parts.end()) {
|
||||
// FIXME -- screen is missing. error?
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip blank names
|
||||
const CString& name = index->second;
|
||||
if (name.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// check name. name must be legal and must not have
|
||||
// already been seen.
|
||||
if (screenNames.count(name)) {
|
||||
// FIXME -- better error message
|
||||
log((CLOG_WARN "editmap: duplicate name %s", name.c_str()));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
// FIXME -- check that name is legal
|
||||
|
||||
// save name. if we've already seen the name then
|
||||
// report an error.
|
||||
screens.set(x, y, name);
|
||||
screenNames.insert(name);
|
||||
}
|
||||
}
|
||||
|
||||
// if new map is invalid then return error. map is invalid if:
|
||||
// there are no screens, or
|
||||
// the screens are not 4-connected.
|
||||
if (screenNames.empty()) {
|
||||
// no screens
|
||||
// FIXME -- need better no screens
|
||||
log((CLOG_WARN "editmap: no screens"));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
if (!screens.isValid()) {
|
||||
// FIXME -- need better unconnected screens error
|
||||
log((CLOG_WARN "editmap: unconnected screens"));
|
||||
throw XHTTP(400);
|
||||
}
|
||||
|
||||
// convert temporary screen map into a regular map
|
||||
CScreenMap newMap;
|
||||
screens.convertTo(newMap);
|
||||
|
||||
// set new screen map on server
|
||||
m_server->setScreenMap(newMap);
|
||||
|
||||
// now reply with current map
|
||||
doProcessGetEditMap(request, reply);
|
||||
}
|
||||
catch (XHTTP& e) {
|
||||
// FIXME -- construct a more meaningful error?
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
bool CHTTPServer::parseXY(
|
||||
const CString& xy, SInt32& x, SInt32& y)
|
||||
{
|
||||
istringstream s(xy);
|
||||
char delimiter;
|
||||
s >> x;
|
||||
s.get(delimiter);
|
||||
s >> y;
|
||||
return (!!s && delimiter == 'x');
|
||||
}
|
||||
|
||||
/*
|
||||
#include <iostream> // FIXME
|
||||
// FIXME
|
||||
cout << "method: " << request.m_method << endl;
|
||||
cout << "uri: " << request.m_uri << endl;
|
||||
cout << "version: " << request.m_majorVersion << "." <<
|
||||
request.m_majorVersion << endl;
|
||||
|
||||
cout << "headers:" << endl;
|
||||
for (CHTTPRequest::CHeaderMap::const_iterator
|
||||
index = request.m_headerIndexByName.begin();
|
||||
index != request.m_headerIndexByName.end();
|
||||
++index) {
|
||||
assert(index->second < request.m_headers.size());
|
||||
cout << " " << index->first << ": " <<
|
||||
request.m_headers[index->second] << endl;
|
||||
}
|
||||
cout << endl;
|
||||
|
||||
cout << request.m_body << endl;
|
||||
|
||||
// FIXME
|
||||
reply.m_majorVersion = request.m_majorVersion;
|
||||
reply.m_minorVersion = request.m_minorVersion;
|
||||
reply.m_status = 200;
|
||||
reply.m_reason = "OK";
|
||||
reply.m_method = request.m_method;
|
||||
reply.m_headers.push_back(std::make_pair(CString("Content-Type"),
|
||||
CString("text/html")));
|
||||
if (request.m_uri != "/bar") {
|
||||
reply.m_body =
|
||||
"<html>\r\n"
|
||||
" <head>\r\n"
|
||||
" <title>test</title>\r\n"
|
||||
" </head>\r\n"
|
||||
" <body>\r\n"
|
||||
" <h2>test</h2>\r\n"
|
||||
" <form method=POST action=\"bar\" enctype=\"multipart/form-data\">\r\n"
|
||||
" <input type=text name=a size=8 maxlength=40 value=\"aValue\">\r\n"
|
||||
" <input type=submit name=b value=\"blah\">\r\n"
|
||||
" </form>\r\n"
|
||||
" </body>\r\n"
|
||||
"</html>\r\n"
|
||||
;
|
||||
}
|
||||
else {
|
||||
reply.m_body =
|
||||
"<html>\r\n"
|
||||
" <head>\r\n"
|
||||
" <title>test reply</title>\r\n"
|
||||
" </head>\r\n"
|
||||
" <body>\r\n"
|
||||
" <h2>test reply</h2>\r\n"
|
||||
" </body>\r\n"
|
||||
"</html>\r\n"
|
||||
;
|
||||
}
|
||||
// FIXME
|
||||
*/
|
||||
|
||||
CHTTPServer::CScreenArray::CScreenArray() : m_w(0), m_h(0)
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
CHTTPServer::CScreenArray::~CScreenArray()
|
||||
{
|
||||
// do nothing
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::resize(SInt32 w, SInt32 h)
|
||||
{
|
||||
m_screens.clear();
|
||||
m_screens.resize(w * h);
|
||||
m_w = w;
|
||||
m_h = h;
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::insertRow(SInt32 i)
|
||||
{
|
||||
assert(i >= 0 && i <= m_h);
|
||||
|
||||
CNames newScreens;
|
||||
newScreens.resize(m_w * (m_h + 1));
|
||||
|
||||
for (SInt32 y = 0; y < i; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
newScreens[x + y * m_w] = m_screens[x + y * m_w];
|
||||
}
|
||||
}
|
||||
for (SInt32 y = i; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
newScreens[x + (y + 1) * m_w] = m_screens[x + y * m_w];
|
||||
}
|
||||
}
|
||||
|
||||
m_screens.swap(newScreens);
|
||||
++m_h;
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::insertColumn(SInt32 i)
|
||||
{
|
||||
assert(i >= 0 && i <= m_w);
|
||||
|
||||
CNames newScreens;
|
||||
newScreens.resize((m_w + 1) * m_h);
|
||||
|
||||
for (SInt32 y = 0; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < i; ++x) {
|
||||
newScreens[x + y * (m_w + 1)] = m_screens[x + y * m_w];
|
||||
}
|
||||
for (SInt32 x = i; x < m_w; ++x) {
|
||||
newScreens[(x + 1) + y * (m_w + 1)] = m_screens[x + y * m_w];
|
||||
}
|
||||
}
|
||||
|
||||
m_screens.swap(newScreens);
|
||||
++m_w;
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::eraseRow(SInt32 i)
|
||||
{
|
||||
assert(i >= 0 && i < m_h);
|
||||
|
||||
CNames newScreens;
|
||||
newScreens.resize(m_w * (m_h - 1));
|
||||
|
||||
for (SInt32 y = 0; y < i; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
newScreens[x + y * m_w] = m_screens[x + y * m_w];
|
||||
}
|
||||
}
|
||||
for (SInt32 y = i + 1; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
newScreens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
|
||||
}
|
||||
}
|
||||
|
||||
m_screens.swap(newScreens);
|
||||
--m_h;
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::eraseColumn(SInt32 i)
|
||||
{
|
||||
assert(i >= 0 && i < m_w);
|
||||
|
||||
CNames newScreens;
|
||||
newScreens.resize((m_w - 1) * m_h);
|
||||
|
||||
for (SInt32 y = 0; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
newScreens[x + y * (m_w - 1)] = m_screens[x + y * m_w];
|
||||
}
|
||||
for (SInt32 x = i + 1; x < m_w; ++x) {
|
||||
newScreens[(x - 1) + y * (m_w - 1)] = m_screens[x + y * m_w];
|
||||
}
|
||||
}
|
||||
|
||||
m_screens.swap(newScreens);
|
||||
--m_w;
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::rotateRows(SInt32 i)
|
||||
{
|
||||
// nothing to do if no rows
|
||||
if (m_h == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// convert to canonical form
|
||||
if (i < 0) {
|
||||
i = m_h - ((-i) % m_h);
|
||||
}
|
||||
else {
|
||||
i %= m_h;
|
||||
}
|
||||
if (i == 0 || i == m_h) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (i > 0) {
|
||||
// rotate one row
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
CString tmp = m_screens[x];
|
||||
for (SInt32 y = 1; y < m_h; ++y) {
|
||||
m_screens[x + (y - 1) * m_w] = m_screens[x + y * m_w];
|
||||
}
|
||||
m_screens[x + (m_h - 1) * m_w] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::rotateColumns(SInt32 i)
|
||||
{
|
||||
// nothing to do if no columns
|
||||
if (m_h == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// convert to canonical form
|
||||
if (i < 0) {
|
||||
i = m_w - ((-i) % m_w);
|
||||
}
|
||||
else {
|
||||
i %= m_w;
|
||||
}
|
||||
if (i == 0 || i == m_w) {
|
||||
return;
|
||||
}
|
||||
|
||||
while (i > 0) {
|
||||
// rotate one column
|
||||
for (SInt32 y = 0; y < m_h; ++y) {
|
||||
CString tmp = m_screens[0 + y * m_w];
|
||||
for (SInt32 x = 1; x < m_w; ++x) {
|
||||
m_screens[x - 1 + y * m_w] = m_screens[x + y * m_w];
|
||||
}
|
||||
m_screens[m_w - 1 + y * m_w] = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::remove(SInt32 x, SInt32 y)
|
||||
{
|
||||
set(x, y, CString());
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::set(
|
||||
SInt32 x, SInt32 y, const CString& name)
|
||||
{
|
||||
assert(x >= 0 && x < m_w);
|
||||
assert(y >= 0 && y < m_h);
|
||||
|
||||
m_screens[x + y * m_w] = name;
|
||||
}
|
||||
|
||||
bool CHTTPServer::CScreenArray::isAllowed(
|
||||
SInt32 x, SInt32 y) const
|
||||
{
|
||||
assert(x >= 0 && x < m_w);
|
||||
assert(y >= 0 && y < m_h);
|
||||
|
||||
if (x > 0 && !m_screens[(x - 1) + y * m_w].empty()) {
|
||||
return true;
|
||||
}
|
||||
if (x < m_w - 1 && !m_screens[(x + 1) + y * m_w].empty()) {
|
||||
return true;
|
||||
}
|
||||
if (y > 0 && !m_screens[x + (y - 1) * m_w].empty()) {
|
||||
return true;
|
||||
}
|
||||
if (y < m_h - 1 && !m_screens[x + (y + 1) * m_w].empty()) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHTTPServer::CScreenArray::isSet(
|
||||
SInt32 x, SInt32 y) const
|
||||
{
|
||||
assert(x >= 0 && x < m_w);
|
||||
assert(y >= 0 && y < m_h);
|
||||
|
||||
return !m_screens[x + y * m_w].empty();
|
||||
}
|
||||
|
||||
CString CHTTPServer::CScreenArray::get(
|
||||
SInt32 x, SInt32 y) const
|
||||
{
|
||||
assert(x >= 0 && x < m_w);
|
||||
assert(y >= 0 && y < m_h);
|
||||
|
||||
return m_screens[x + y * m_w];
|
||||
}
|
||||
|
||||
bool CHTTPServer::CScreenArray::find(
|
||||
const CString& name,
|
||||
SInt32& xOut, SInt32& yOut) const
|
||||
{
|
||||
for (SInt32 y = 0; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
if (m_screens[x + y * m_w] == name) {
|
||||
xOut = x;
|
||||
yOut = y;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CHTTPServer::CScreenArray::isValid() const
|
||||
{
|
||||
SInt32 count = 0, isolated = 0;
|
||||
for (SInt32 y = 0; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
if (isSet(x, y)) {
|
||||
++count;
|
||||
if (!isAllowed(x, y)) {
|
||||
++isolated;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (count <= 1 || isolated == 0);
|
||||
}
|
||||
|
||||
bool CHTTPServer::CScreenArray::convertFrom(
|
||||
const CScreenMap& screenMap)
|
||||
{
|
||||
typedef std::set<CString> ScreenSet;
|
||||
|
||||
// insert the first screen
|
||||
CScreenMap::const_iterator index = screenMap.begin();
|
||||
if (index == screenMap.end()) {
|
||||
// no screens
|
||||
resize(0, 0);
|
||||
return true;
|
||||
}
|
||||
CString name = *index;
|
||||
resize(1, 1);
|
||||
set(0, 0, name);
|
||||
|
||||
// flood fill state
|
||||
CNames screenStack;
|
||||
ScreenSet doneSet;
|
||||
|
||||
// put all but the first screen on the stack
|
||||
// note -- if all screens are 4-connected then we can skip this
|
||||
while (++index != screenMap.end()) {
|
||||
screenStack.push_back(*index);
|
||||
}
|
||||
|
||||
// put the first screen on the stack last so we process it first
|
||||
screenStack.push_back(name);
|
||||
|
||||
// perform a flood fill using the stack as the seeds
|
||||
while (!screenStack.empty()) {
|
||||
// get next screen from stack
|
||||
CString name = screenStack.back();
|
||||
screenStack.pop_back();
|
||||
|
||||
// skip screen if we've seen it before
|
||||
if (doneSet.count(name) > 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// add this screen to doneSet so we don't process it again
|
||||
doneSet.insert(name);
|
||||
|
||||
// find the screen. if it's not found then not all of the
|
||||
// screens are 4-connected. discard disconnected screens.
|
||||
SInt32 x, y;
|
||||
if (!find(name, x, y)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// insert the screen's neighbors
|
||||
// FIXME -- handle edge wrapping
|
||||
CString neighbor;
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kLeft);
|
||||
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
||||
// insert left neighbor, adding a column if necessary
|
||||
if (x == 0 || get(x - 1, y) != neighbor) {
|
||||
++x;
|
||||
insertColumn(x - 1);
|
||||
set(x - 1, y, neighbor);
|
||||
}
|
||||
screenStack.push_back(neighbor);
|
||||
}
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kRight);
|
||||
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
||||
// insert right neighbor, adding a column if necessary
|
||||
if (x == m_w - 1 || get(x + 1, y) != neighbor) {
|
||||
insertColumn(x + 1);
|
||||
set(x + 1, y, neighbor);
|
||||
}
|
||||
screenStack.push_back(neighbor);
|
||||
}
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kTop);
|
||||
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
||||
// insert top neighbor, adding a row if necessary
|
||||
if (y == 0 || get(x, y - 1) != neighbor) {
|
||||
++y;
|
||||
insertRow(y - 1);
|
||||
set(x, y - 1, neighbor);
|
||||
}
|
||||
screenStack.push_back(neighbor);
|
||||
}
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kBottom);
|
||||
if (!neighbor.empty() && doneSet.count(neighbor) == 0) {
|
||||
// insert bottom neighbor, adding a row if necessary
|
||||
if (y == m_h - 1 || get(x, y + 1) != neighbor) {
|
||||
insertRow(y + 1);
|
||||
set(x, y + 1, neighbor);
|
||||
}
|
||||
screenStack.push_back(neighbor);
|
||||
}
|
||||
}
|
||||
|
||||
// check symmetry
|
||||
// FIXME -- handle edge wrapping
|
||||
for (index = screenMap.begin(); index != screenMap.end(); ++index) {
|
||||
const CString& name = *index;
|
||||
SInt32 x, y;
|
||||
if (!find(name, x, y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
CString neighbor;
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kLeft);
|
||||
if ((x == 0 && !neighbor.empty()) ||
|
||||
(x > 0 && get(x - 1, y) != neighbor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kRight);
|
||||
if ((x == m_w - 1 && !neighbor.empty()) ||
|
||||
(x < m_w - 1 && get(x + 1, y) != neighbor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kTop);
|
||||
if ((y == 0 && !neighbor.empty()) ||
|
||||
(y > 0 && get(x, y - 1) != neighbor)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
neighbor = screenMap.getNeighbor(name, CScreenMap::kBottom);
|
||||
if ((y == m_h - 1 && !neighbor.empty()) ||
|
||||
(y < m_h - 1 && get(x, y + 1) != neighbor)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CHTTPServer::CScreenArray::convertTo(
|
||||
CScreenMap& screenMap) const
|
||||
{
|
||||
// add screens and find smallest box containing all screens
|
||||
SInt32 x0 = m_w, x1 = 0, y0 = m_h, y1 = 0;
|
||||
for (SInt32 y = 0; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
if (isSet(x, y)) {
|
||||
screenMap.addScreen(get(x, y));
|
||||
if (x < x0) {
|
||||
x0 = x;
|
||||
}
|
||||
if (x > x1) {
|
||||
x1 = x;
|
||||
}
|
||||
if (y < y0) {
|
||||
y0 = y;
|
||||
}
|
||||
if (y > y1) {
|
||||
y1 = y;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// make connections between screens
|
||||
// FIXME -- add support for wrapping
|
||||
// FIXME -- mark topmost and leftmost screens
|
||||
for (SInt32 y = 0; y < m_h; ++y) {
|
||||
for (SInt32 x = 0; x < m_w; ++x) {
|
||||
if (!isSet(x, y)) {
|
||||
continue;
|
||||
}
|
||||
if (x > x0 && isSet(x - 1, y)) {
|
||||
screenMap.connect(get(x, y),
|
||||
CScreenMap::kLeft,
|
||||
get(x - 1, y));
|
||||
}
|
||||
if (x < x1 && isSet(x + 1, y)) {
|
||||
screenMap.connect(get(x, y),
|
||||
CScreenMap::kRight,
|
||||
get(x + 1, y));
|
||||
}
|
||||
if (y > y0 && isSet(x, y - 1)) {
|
||||
screenMap.connect(get(x, y),
|
||||
CScreenMap::kTop,
|
||||
get(x, y - 1));
|
||||
}
|
||||
if (y < y1 && isSet(x, y + 1)) {
|
||||
screenMap.connect(get(x, y),
|
||||
CScreenMap::kBottom,
|
||||
get(x, y + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,101 @@
|
|||
#ifndef CHTTPSERVER_H
|
||||
#define CHTTPSERVER_H
|
||||
|
||||
#include "BasicTypes.h"
|
||||
#include "CString.h"
|
||||
#include <vector>
|
||||
|
||||
class CServer;
|
||||
class CScreenMap;
|
||||
class CHTTPRequest;
|
||||
class CHTTPReply;
|
||||
class ISocket;
|
||||
|
||||
class CHTTPServer {
|
||||
public:
|
||||
CHTTPServer(CServer*);
|
||||
virtual ~CHTTPServer();
|
||||
|
||||
// manipulators
|
||||
|
||||
// synchronously process an HTTP request on the given socket
|
||||
void processRequest(ISocket*);
|
||||
|
||||
// accessors
|
||||
|
||||
protected:
|
||||
virtual void doProcessRequest(CHTTPRequest&, CHTTPReply&);
|
||||
|
||||
virtual void doProcessGetEditMap(CHTTPRequest&, CHTTPReply&);
|
||||
virtual void doProcessPostEditMap(CHTTPRequest&, CHTTPReply&);
|
||||
|
||||
static bool parseXY(const CString&, SInt32& x, SInt32& y);
|
||||
|
||||
class CScreenArray {
|
||||
public:
|
||||
CScreenArray();
|
||||
~CScreenArray();
|
||||
|
||||
// resize the array. this also clears all the elements.
|
||||
void resize(SInt32 w, SInt32 h);
|
||||
|
||||
// insert/remove a row/column. all elements in a new row/column
|
||||
// are unset.
|
||||
void insertRow(SInt32 insertedBeforeRow);
|
||||
void insertColumn(SInt32 insertedBeforeColumn);
|
||||
void eraseRow(SInt32 row);
|
||||
void eraseColumn(SInt32 column);
|
||||
|
||||
// rotate rows or columns
|
||||
void rotateRows(SInt32 rowsDown);
|
||||
void rotateColumns(SInt32 columnsDown);
|
||||
|
||||
// remove/set a screen name. setting an empty name is the
|
||||
// same as removing a name. names are not checked for
|
||||
// validity.
|
||||
void remove(SInt32 x, SInt32 y);
|
||||
void set(SInt32 x, SInt32 y, const CString&);
|
||||
|
||||
// convert a CScreenMap to a CScreenArray. returns true iff
|
||||
// all connections are symmetric and therefore exactly
|
||||
// representable by a CScreenArray.
|
||||
bool convertFrom(const CScreenMap&);
|
||||
|
||||
// accessors
|
||||
|
||||
// get the array size
|
||||
SInt32 getWidth() const { return m_w; }
|
||||
SInt32 getHeight() const { return m_h; }
|
||||
|
||||
// returns true iff the cell has a 4-connected neighbor
|
||||
bool isAllowed(SInt32 x, SInt32 y) const;
|
||||
|
||||
// returns true iff the cell has a (non-empty) name
|
||||
bool isSet(SInt32 x, SInt32 y) const;
|
||||
|
||||
// get a screen name
|
||||
CString get(SInt32 x, SInt32 y) const;
|
||||
|
||||
// find a screen by name. returns true iff found.
|
||||
bool find(const CString&, SInt32& x, SInt32& y) const;
|
||||
|
||||
// return true iff the overall array is valid. that means
|
||||
// just zero or one screen or all screens are 4-connected
|
||||
// to other screens.
|
||||
bool isValid() const;
|
||||
|
||||
// convert this to a CScreenMap
|
||||
void convertTo(CScreenMap&) const;
|
||||
|
||||
private:
|
||||
typedef std::vector<CString> CNames;
|
||||
|
||||
SInt32 m_w, m_h;
|
||||
CNames m_screens;
|
||||
};
|
||||
|
||||
private:
|
||||
CServer* m_server;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -75,6 +75,18 @@ void CScreenMap::disconnect(const CString& srcName,
|
|||
index->second.m_neighbor[srcSide - kFirstDirection].erase();
|
||||
}
|
||||
|
||||
CScreenMap::const_iterator
|
||||
CScreenMap::begin() const
|
||||
{
|
||||
return const_iterator(m_map.begin());
|
||||
}
|
||||
|
||||
CScreenMap::const_iterator
|
||||
CScreenMap::end() const
|
||||
{
|
||||
return const_iterator(m_map.end());
|
||||
}
|
||||
|
||||
CString CScreenMap::getNeighbor(const CString& srcName,
|
||||
EDirection srcSide) const
|
||||
{
|
||||
|
|
|
@ -11,6 +11,42 @@ public:
|
|||
kFirstDirection = kLeft, kLastDirection = kBottom };
|
||||
enum EDirectionMask { kLeftMask = 1, kRightMask = 2,
|
||||
kTopMask = 4, kBottomMask = 8 };
|
||||
private:
|
||||
class CCell {
|
||||
public:
|
||||
CString m_neighbor[kLastDirection - kFirstDirection + 1];
|
||||
};
|
||||
typedef std::map<CString, CCell> CCellMap;
|
||||
|
||||
public:
|
||||
typedef CCellMap::const_iterator internal_const_iterator;
|
||||
class const_iterator : public std::iterator<
|
||||
std::bidirectional_iterator_tag,
|
||||
CString, ptrdiff_t, CString*, CString&> {
|
||||
public:
|
||||
explicit const_iterator() : m_i() { }
|
||||
explicit const_iterator(const internal_const_iterator& i) : m_i(i) { }
|
||||
|
||||
const_iterator& operator=(const const_iterator& i) {
|
||||
m_i = i.m_i;
|
||||
return *this;
|
||||
}
|
||||
CString operator*() { return m_i->first; }
|
||||
const CString* operator->() { return &(m_i->first); }
|
||||
const_iterator& operator++() { ++m_i; return *this; }
|
||||
const_iterator operator++(int) { return const_iterator(m_i++); }
|
||||
const_iterator& operator--() { --m_i; return *this; }
|
||||
const_iterator operator--(int) { return const_iterator(m_i--); }
|
||||
bool operator==(const const_iterator& i) const {
|
||||
return (m_i == i.m_i);
|
||||
}
|
||||
bool operator!=(const const_iterator& i) const {
|
||||
return (m_i != i.m_i);
|
||||
}
|
||||
|
||||
private:
|
||||
CScreenMap::internal_const_iterator m_i;
|
||||
};
|
||||
|
||||
CScreenMap();
|
||||
virtual ~CScreenMap();
|
||||
|
@ -31,6 +67,10 @@ public:
|
|||
|
||||
// accessors
|
||||
|
||||
// iterators over screen names
|
||||
const_iterator begin() const;
|
||||
const_iterator end() const;
|
||||
|
||||
// get the neighbor in the given direction. returns the empty string
|
||||
// if there is no neighbor in that direction.
|
||||
CString getNeighbor(const CString&, EDirection) const;
|
||||
|
@ -39,12 +79,6 @@ public:
|
|||
static const char* dirName(EDirection);
|
||||
|
||||
private:
|
||||
class CCell {
|
||||
public:
|
||||
CString m_neighbor[kLastDirection - kFirstDirection + 1];
|
||||
};
|
||||
typedef std::map<CString, CCell> CCellMap;
|
||||
|
||||
CCellMap m_map;
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
#include "CServer.h"
|
||||
#include "CHTTPServer.h"
|
||||
#include "CInputPacketStream.h"
|
||||
#include "COutputPacketStream.h"
|
||||
#include "CServerProtocol.h"
|
||||
|
@ -15,6 +16,7 @@
|
|||
#include "CThread.h"
|
||||
#include "CTimerThread.h"
|
||||
#include "CStopwatch.h"
|
||||
#include "CFunctionJob.h"
|
||||
#include "TMethodJob.h"
|
||||
#include "CLog.h"
|
||||
#include <assert.h>
|
||||
|
@ -44,7 +46,8 @@ else { wait(0); exit(1); }
|
|||
CServer::CServer() : m_primary(NULL),
|
||||
m_active(NULL),
|
||||
m_primaryInfo(NULL),
|
||||
m_seqNum(0)
|
||||
m_seqNum(0),
|
||||
m_httpServer(NULL)
|
||||
{
|
||||
m_socketFactory = NULL;
|
||||
m_securityFactory = NULL;
|
||||
|
@ -63,18 +66,21 @@ void CServer::run()
|
|||
// connect to primary screen
|
||||
openPrimaryScreen();
|
||||
|
||||
// start listening for HTTP requests
|
||||
m_httpServer = new CHTTPServer(this);
|
||||
CThread(new TMethodJob<CServer>(this, &CServer::acceptHTTPClients));
|
||||
|
||||
// start listening for new clients
|
||||
CThread(new TMethodJob<CServer>(this, &CServer::acceptClients));
|
||||
|
||||
// start listening for configuration connections
|
||||
// FIXME
|
||||
|
||||
// handle events
|
||||
log((CLOG_DEBUG "starting event handling"));
|
||||
m_primary->run();
|
||||
|
||||
// clean up
|
||||
log((CLOG_NOTE "stopping server"));
|
||||
delete m_httpServer;
|
||||
m_httpServer = NULL;
|
||||
cleanupThreads();
|
||||
closePrimaryScreen();
|
||||
}
|
||||
|
@ -82,6 +88,8 @@ void CServer::run()
|
|||
log((CLOG_ERR "server error: %s", e.what()));
|
||||
|
||||
// clean up
|
||||
delete m_httpServer;
|
||||
m_httpServer = NULL;
|
||||
cleanupThreads();
|
||||
closePrimaryScreen();
|
||||
}
|
||||
|
@ -89,6 +97,8 @@ void CServer::run()
|
|||
log((CLOG_DEBUG "unknown server error"));
|
||||
|
||||
// clean up
|
||||
delete m_httpServer;
|
||||
m_httpServer = NULL;
|
||||
cleanupThreads();
|
||||
closePrimaryScreen();
|
||||
throw;
|
||||
|
@ -109,6 +119,12 @@ void CServer::setScreenMap(const CScreenMap& screenMap)
|
|||
m_screenMap = screenMap;
|
||||
}
|
||||
|
||||
CString CServer::getPrimaryScreenName() const
|
||||
{
|
||||
CLock lock(&m_mutex);
|
||||
return (m_primaryInfo == NULL) ? "" : m_primaryInfo->m_name;
|
||||
}
|
||||
|
||||
void CServer::getScreenMap(CScreenMap* screenMap) const
|
||||
{
|
||||
assert(screenMap != NULL);
|
||||
|
@ -956,6 +972,88 @@ void CServer::handshakeClient(void* vsocket)
|
|||
}
|
||||
}
|
||||
|
||||
void CServer::acceptHTTPClients(void*)
|
||||
{
|
||||
log((CLOG_DEBUG1 "starting to wait for HTTP clients"));
|
||||
|
||||
// add this thread to the list of threads to cancel. remove from
|
||||
// list in d'tor.
|
||||
CCleanupNote cleanupNote(this);
|
||||
|
||||
std::auto_ptr<IListenSocket> listen;
|
||||
try {
|
||||
// create socket listener
|
||||
// listen = std::auto_ptr<IListenSocket>(m_socketFactory->createListen());
|
||||
assign(listen, new CTCPListenSocket, IListenSocket); // FIXME
|
||||
|
||||
// bind to the desired port. keep retrying if we can't bind
|
||||
// the address immediately.
|
||||
CStopwatch timer;
|
||||
CNetworkAddress addr(50002 /* FIXME -- m_httpPort */);
|
||||
for (;;) {
|
||||
try {
|
||||
log((CLOG_DEBUG1 "binding listen socket"));
|
||||
listen->bind(addr);
|
||||
break;
|
||||
}
|
||||
catch (XSocketAddressInUse&) {
|
||||
// give up if we've waited too long
|
||||
if (timer.getTime() >= m_bindTimeout) {
|
||||
log((CLOG_DEBUG1 "waited too long to bind HTTP, giving up"));
|
||||
throw;
|
||||
}
|
||||
|
||||
// wait a bit before retrying
|
||||
log((CLOG_DEBUG1 "bind HTTP failed; waiting to retry"));
|
||||
CThread::sleep(5.0);
|
||||
}
|
||||
}
|
||||
|
||||
// accept connections and begin processing them
|
||||
log((CLOG_DEBUG1 "waiting for HTTP connections"));
|
||||
for (;;) {
|
||||
// accept connection
|
||||
CThread::testCancel();
|
||||
ISocket* socket = listen->accept();
|
||||
log((CLOG_NOTE "accepted HTTP connection"));
|
||||
CThread::testCancel();
|
||||
|
||||
// handle HTTP request
|
||||
CThread(new TMethodJob<CServer>(
|
||||
this, &CServer::processHTTPRequest, socket));
|
||||
}
|
||||
}
|
||||
catch (XBase& e) {
|
||||
log((CLOG_ERR "cannot listen for HTTP clients: %s", e.what()));
|
||||
quit();
|
||||
}
|
||||
}
|
||||
|
||||
void CServer::processHTTPRequest(void* vsocket)
|
||||
{
|
||||
// add this thread to the list of threads to cancel. remove from
|
||||
// list in d'tor.
|
||||
CCleanupNote cleanupNote(this);
|
||||
|
||||
ISocket* socket = reinterpret_cast<ISocket*>(vsocket);
|
||||
try {
|
||||
// process the request and force delivery
|
||||
m_httpServer->processRequest(socket);
|
||||
socket->getOutputStream()->flush();
|
||||
|
||||
// wait a moment to give the client a chance to hangup first
|
||||
CThread::sleep(3.0);
|
||||
|
||||
// clean up
|
||||
socket->close();
|
||||
delete socket;
|
||||
}
|
||||
catch (...) {
|
||||
delete socket;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void CServer::clearGotClipboard(ClipboardID id)
|
||||
{
|
||||
for (CScreenList::const_iterator index = m_screens.begin();
|
||||
|
|
|
@ -17,6 +17,7 @@ class IServerProtocol;
|
|||
class ISocketFactory;
|
||||
class ISecurityFactory;
|
||||
class IPrimaryScreen;
|
||||
class CHTTPServer;
|
||||
|
||||
class CServer {
|
||||
public:
|
||||
|
@ -68,6 +69,9 @@ public:
|
|||
// get the current screen map
|
||||
void getScreenMap(CScreenMap*) const;
|
||||
|
||||
// get the primary screen's name
|
||||
CString getPrimaryScreenName() const;
|
||||
|
||||
// get the sides of the primary screen that have neighbors
|
||||
UInt32 getActivePrimarySides() const;
|
||||
|
||||
|
@ -152,6 +156,12 @@ private:
|
|||
// thread method to do startup handshake with client
|
||||
void handshakeClient(void*);
|
||||
|
||||
// thread method to accept incoming HTTP connections
|
||||
void acceptHTTPClients(void*);
|
||||
|
||||
// thread method to process HTTP requests
|
||||
void processHTTPRequest(void*);
|
||||
|
||||
// thread cleanup list maintenance
|
||||
friend class CCleanupNote;
|
||||
void addCleanupThread(const CThread& thread);
|
||||
|
@ -200,6 +210,9 @@ private:
|
|||
CScreenMap m_screenMap;
|
||||
|
||||
CClipboardInfo m_clipboards[kClipboardEnd];
|
||||
|
||||
// server for processing HTTP requests
|
||||
CHTTPServer* m_httpServer;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -13,6 +13,7 @@ LCXXINCS = \
|
|||
-I$(DEPTH)/base \
|
||||
-I$(DEPTH)/mt \
|
||||
-I$(DEPTH)/io \
|
||||
-I$(DEPTH)/http \
|
||||
-I$(DEPTH)/net \
|
||||
-I$(DEPTH)/synergy \
|
||||
$(NULL)
|
||||
|
@ -22,6 +23,7 @@ CXXFILES = \
|
|||
CServerProtocol1_0.cpp \
|
||||
CXWindowsPrimaryScreen.cpp \
|
||||
CServer.cpp \
|
||||
CHTTPServer.cpp \
|
||||
server.cpp \
|
||||
$(NULL)
|
||||
|
||||
|
@ -31,6 +33,7 @@ CXXFILES = \
|
|||
DEPLIBS = \
|
||||
$(LIBDIR)/libsynergy.a \
|
||||
$(LIBDIR)/libnet.a \
|
||||
$(LIBDIR)/libhttp.a \
|
||||
$(LIBDIR)/libio.a \
|
||||
$(LIBDIR)/libmt.a \
|
||||
$(LIBDIR)/libbase.a \
|
||||
|
|
Loading…
Reference in New Issue