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:
crs 2002-05-30 16:13:16 +00:00
parent 2cc63e31aa
commit 70f5f9491d
14 changed files with 1986 additions and 10 deletions

View File

@ -9,6 +9,7 @@ SUBDIRS = \
base \
mt \
io \
http \
net \
synergy \
client \

View File

@ -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

611
http/CHTTPProtocol.cpp Normal file
View File

@ -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);
}

88
http/CHTTPProtocol.h Normal file
View File

@ -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

26
http/Makefile Normal file
View File

@ -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)

119
http/XHTTP.cpp Normal file
View File

@ -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));
}

44
http/XHTTP.h Normal file
View File

@ -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

823
server/CHTTPServer.cpp Normal file
View File

@ -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(&currentMap);
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));
}
}
}
}

101
server/CHTTPServer.h Normal file
View File

@ -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

View File

@ -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
{

View File

@ -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;
};

View File

@ -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();

View File

@ -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

View File

@ -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 \