Files
barrier/server/CHTTPServer.cpp
crs 70f5f9491d 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.
2002-05-30 16:13:16 +00:00

824 lines
19 KiB
C++

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