- Se elimina el mutex para el socket (comentado por ahora).
- Se implementan rudimentariamente algunos comandos del servidor:
/server/status, /server/stop, /connection/list.
- Se empieza a implementar el ControlClient, agregando algunas señales que
probablemente no sean las definitivas.
- Se comienza a implementar la obtención del cuerpo del mensaje en HTTPMessage.
- Se actualiza la documentación.
Seguramente no compila.
/// Conexión.
class Connection: public Runnable {
+ // Constantes.
+
protected:
/// Tamaño del buffer usado para enviar y recibir datos.
- static const int BUFFER_SIZE = 4096;
+ //static const int BUFFER_SIZE = 4096;
+
+ // Atributos.
+
+ protected:
/// Socket a usar en la conexión.
iosockinet socket;
+ /// Mutex para el socket.
+ //Glib::Mutex socket_mutex;
+
+ // Métodos.
+
public:
/**
*/
Connection(sockbuf::type type);
+ /**
+ * Finaliza la conexión.
+ *
+ * \param attach Si es true, la función no retorna hasta que no
+ * finalice la tearea (no recomendable).
+ *
+ * \note Para saber cuando la tarea fue finalizada puede utilizar
+ * la señal signal_finished().
+ */
+ virtual void finish(bool attach = false);
+
+ /**
+ * Obtiene el nombre del host local de la conexión.
+ */
+ std::string get_peerhost(void);
+
+ /**
+ * Obtiene el puerto local de la conexión.
+ */
+ unsigned get_peerport(void);
+
};
}
/// Conexión para enviar comandos de control a una planta.
class ControlClient: public Connection {
+ // Tipos.
+
+ public:
+
+ /// Tipo de señal para indicar que se recibió una respuesta OK.
+ typedef SigC::signal0<void> SignalOKReceived;
+
+ /// Tipo de señal para indicar que se recibió un error.
+ typedef SigC::signal0<void> SignalErrorReceived;
+
// Atributos.
- private:
+ protected:
+
+ /// Señal para indicar que se recibió una respuesta OK.
+ SignalOKReceived ok_received;
+
+ /// Señal para indicar que se recibió un error.
+ SignalErrorReceived error_received;
- /// Mutex para el socket.
- Glib::Mutex socket_mutex;
-
// Métodos.
private:
*/
ControlClient(std::string host = "localhost", int port = 7522);
+ /**
+ * Obtiene la señal para indicar que se recibió una respuesta OK.
+ */
+ SignalOKReceived& signal_ok_received(void);
+
+ /**
+ * Obtiene la señal para indicar que se recibió un error.
+ */
+ SignalErrorReceived& signal_error_received(void);
+
+ /**
+ * Envía un comando al servidor.
+ *
+ * \param command Comando a enviar.
+ */
+ void send(const Command& command);
+
};
}
/// Conexión para recibir comandos de control para una planta.
class ControlServer: public Connection {
+
// Tipos.
public:
/// Tipo de señal para indicar que se recibió un comando.
typedef SigC::Signal1<void, const Command&> SignalCommandReceived;
+
// Atributos.
private:
- /// Mutex para el socket.
- Glib::Mutex socket_mutex;
-
/// Señal para indicar que se recibió un comando.
SignalCommandReceived command_received;
/// \todo TODO completar codigos.
static const unsigned OK = 200;
static const unsigned BAD_REQUEST = 401;
+ static const unsigned NOT_FOUND = 404;
static const unsigned LENGTH_REQUIRED = 411;
static const unsigned INTERNAL_SERVER_ERROR = 500;
static const unsigned NOT_IMPLEMENTED = 501;
// Atributos.
- protected:
+ //protected: FIXME - hacer privado con get/set?
+ public:
/// Código de estado.
unsigned status_code;
#include "plaqui/server/connection.h"
#include <socket++/sockinet.h>
#include <list>
+#include <vector>
namespace PlaQui {
/// Lista de conexiones de control.
typedef std::list<Connection*> ConnectionList;
+ public:
+
+ /// Información sobre una conexión de contro.
+ struct ConnectionInfo {
+ /// Host.
+ std::string host;
+ /// Port.
+ unsigned port;
+ };
+
+ /// Lista de información de conexiones de control.
+ typedef std::vector<ConnectionInfo> ConnectionInfoList;
+
// Atributos.
private:
*/
void on_connection_finished(Connection* connection);
+ /**
+ * Obtiene una lista conexiones de control abiertas.
+ */
+ ConnectionInfoList get_connected(void);
+
};
}
objects+=tcpserver.o
tcpserver.o: $(tcpserver_h) tcpserver.cpp
-server_h=$(tcpserver) $(controlserver_h) $(transmitter_h) \
+server_h=$(tcpserver) $(httpresponse_h) $(controlserver_h) $(transmitter_h) \
$(INCLUDE_DIR)/server.h
objects+=server.o
server.o: $(server_h) server.cpp
} else {
command.target = "";
}
- if (command.args.size() > 1) {
+ if (command.args.size() > 0) {
command.command = command.args[0];
command.args.erase(command.args.begin());
} else {
#endif // DEBUG
}
+void Connection::finish(bool attach) {
+ //socket_mutex.lock();
+ socket->shutdown(sockbuf::shut_readwrite);
+ //socket_mutex.unlock();
+ Runnable::finish(attach);
+}
+
+string Connection::get_peerhost(void) {
+ //socket_mutex.lock();
+ string host = socket->peerhost();
+ //socket_mutex.unlock();
+ return host;
+}
+
+unsigned Connection::get_peerport(void) {
+ //socket_mutex.lock();
+ unsigned port = socket->peerport();
+ //socket_mutex.unlock();
+ return port;
+}
+
} // namespace Server
} // namespace PlaQui
cerr << __FILE__ << ": host" << host
<< " | port = " << port << endl;
#endif // DEBUG
- // FIXME - poner en run().
socket->connect(host.c_str(), port);
+ if (!socket->is_open()) {
+ throw ios::failure("Can't connect!");
+ }
}
void ControlClient::real_run(void) {
#ifdef DEBUG
cerr << __FILE__ << ": real_run." << endl;
#endif // DEBUG
+ while (!stop) {
+ HTTPResponse response;
+ try {
+ //Glib::Mutex::Lock lock(socket_mutex);
+ socket >> response;
+ // Si se cerró el socket.
+ } catch (const ios::failure& e) {
+ stop = true;
+ continue;
+ // Si hay un error al parsear la respuesta.
+ } catch (const HTTPResponse::Error& e) {
#ifdef DEBUG
- // FIXME - debería tirar una excepción?
- if (!socket->is_open()) {
- cerr << "No se pudo conectar a " << socket->peerhost() <<
- ":" << socket->peerport() << "." << endl;
- } else {
- cerr << "Conectado a " << socket->peerhost() <<
- ":" << socket->peerport() << "." << endl;
+ cerr << __FILE__ << " : real_run() ERROR nro: " << e << endl;
+#endif // DEBUG
+ // TODO - pasar como parametro codigo de error o algo.
+ error_received();
+ continue;
+ }
+ switch (response.status_code) {
+ case HTTPMessage::OK:
+ ok_received();
+ default:
+ error_received();
+ }
}
+}
+
+ControlClient::SignalOKReceived& ControlClient::signal_ok_received(void) {
+ return ok_received;
+}
+
+ControlClient::SignalErrorReceived& ControlClient::signal_error_received(void) {
+ return error_received;
+}
+
+void ControlClient::send(const Command& command) {
+ socket << command << flush;
+#ifdef DEBUG
+ cerr << __FILE__ << ": send() Enviado!" << endl;
#endif // DEBUG
}
while (!stop) {
Command command;
try {
- Glib::Mutex::Lock lock(socket_mutex);
+ //Glib::Mutex::Lock lock(socket_mutex);
socket >> command;
// Si se cerró el socket.
} catch (const ios::failure& e) {
<< e.code << " | reason = " << HTTPMessage::reason(e.code)
<< " | desc = " << e.what() << endl;
#endif // DEBUG
+ //Glib::Mutex::Lock lock(socket_mutex);
socket << HTTPResponse(e) << flush;
continue;
}
}
void ControlServer::send(const HTTPResponse& response) {
- Glib::Mutex::Lock lock(socket_mutex);
+ //Glib::Mutex::Lock lock(socket_mutex);
socket << response << flush;
+#ifdef DEBUG
+ cerr << __FILE__ << ": send() Enviado!" << endl;
+#endif // DEBUG
}
ControlServer::SignalCommandReceived& ControlServer::signal_command_received(void) {
body = _body;
if (body.length()) {
stringstream ss; // TODO ver forma mas linda de convertir
- ss << (body.length());
+ ss << (body.length()+1); // FIXME No se por que tengo que sumarle 1.
headers["Accept-Ranges"] = "bytes";
headers["Content-Length"] = ss.str();
}
#endif // DEBUG
char buf[BUFSIZ];
bool is_header = true;
- // TODO body
- // Para hacer que reciba bien el body hay que chequear la cabecera
- // Content-length, si queda tiempo lo haré...
- //stringstream body_ss;
while (is.getline(buf, BUFSIZ)) {
String sbuf(buf);
sbuf.trim();
if (sbuf.length()) {
- if (is_header) {
- stringstream ss;
- ss << sbuf;
- ss >> m.headers;
- }// else { TODO body
- // body_ss << sbuf << endl;
- //}
+ stringstream ss;
+ ss << sbuf;
+ ss >> m.headers;
+ // Fin de las cabeceras.
} else {
- if (is_header) {
- // TODO body
- // Ver si tiene un Content-Length para saber si esperamos body.
- // Si no esperamos body, no hay que hacer otro is.getline()
- // porque se queda esperando forever.
- is_header = false;
- break;
- }// else { TODO body
- // body_ss << sbuf << endl;
- //}
+ // Hay Content-Length, entonces hay body (no respeta RFC AFAIK).
+ if (m.headers.find("Content-Length") != m.headers.end()) {
+ // Descarta la línea vacía para separar las cabeceras.
+ is.getline(buf, BUFSIZ);
+ stringstream ss(m.headers["Content-Length"]);
+ streamsize size;
+ ss >> size;
+ char* buf2 = new char[size+1];
+ if (is.readsome(buf2, size)) {
+ m.body = buf2;
+ }
+ delete buf2[];
+ }
+ // Después de una línea vacía, haya obtenido el body o no, sale del
+ // while.
+ break;
}
}
- // TODO body
- //m.body = body_ss.str();
return is;
}
return "OK";
case BAD_REQUEST:
return "Bad Request";
+ case NOT_FOUND:
+ return "Not Found";
case LENGTH_REQUIRED:
return "Length Required";
case INTERNAL_SERVER_ERROR:
case HTTP_VERSION_NOT_SUPPORTED:
return "HTTP Version Not Supported";
default:
- return "";
+ return "No Reason";
}
}
#endif // DEBUG
}
-/*
-HTTPRequest::HTTPRequest(const Serializable& body,
- const string& version):
- HTTPMessage(body, version) {
-#ifdef DEBUG
- cerr << __FILE__ << ": http_version = " << http_version
- << " | body = " << body.serialize() << endl;
-#endif // DEBUG
-}
-
-HTTPRequest::HTTPRequest(const string& uri,
- const HTTPRequest::HTTPMethod& method,
- string& query, string& version):
- HTTPMessage(body, version) {
-#ifdef DEBUG
- cerr << __FILE__ << ": http_version = " << http_version
- << " | body = " << body.serialize() << endl;
-#endif // DEBUG
-}
-*/
-
istream& operator>>(istream& is, HTTPRequest& req)
throw(HTTPError, ios::failure) {
#ifdef DEBUG
<< ", args = [" << String::join(command.get_args(), ", ") << "])"
<< endl;
#endif // DEBUG
- // TODO, seguir aca!
- stringstream response_xml;
- response_xml << "<html>" << endl;
- response_xml << " <head>" << endl;
- response_xml << " <title>PlaQui v0.4</title>" << endl;
- response_xml << " </head>" << endl;
- response_xml << " <body>" << endl;
- response_xml << " <h1>PlaQui</h1>" << endl;
- response_xml << " <p>versión 0.4</p>" << endl;
- response_xml << " <h2>Comando</h2>" << endl;
- response_xml << " <ul>" << endl;
- response_xml << " <li><b>Target:</b> " << command.get_target() << endl;
- response_xml << " <li><b>Command:</b> " << command.get_command() << endl;
- response_xml << " <li><b>Argumentos:</b>" << endl;
- response_xml << " <ol>" << endl;
- for (Command::Arguments::const_iterator i = command.get_args().begin();
- i != command.get_args().end(); i++) {
- response_xml << " <li>" << *i << "</li>" << endl;
+ HTTPResponse response(HTTPMessage::OK);
+ if (command.get_target() == "server") {
+#ifdef DEBUG
+ cerr << __FILE__ << ": server" << endl;
+#endif // DEBUG
+ if (command.get_command() == "status") {
+ // FIXME
+ stringstream response_xml;
+ response_xml << "<html>" << endl;
+ response_xml << " <head>" << endl;
+ response_xml << " <title>PlaQui v0.6</title>" << endl;
+ response_xml << " </head>" << endl;
+ response_xml << " <body>" << endl;
+ response_xml << " <h1>PlaQui</h1>" << endl;
+ response_xml << " <p>versión 0.6</p>" << endl;
+ response_xml << " <h2>Comando</h2>" << endl;
+ response_xml << " <ul>" << endl;
+ response_xml << " <li><b>Target:</b> " << command.get_target() << endl;
+ response_xml << " <li><b>Command:</b> " << command.get_command() << endl;
+ response_xml << " <li><b>Argumentos:</b>" << endl;
+ response_xml << " <ol>" << endl;
+ for (Command::Arguments::const_iterator i = command.get_args().begin();
+ i != command.get_args().end(); i++) {
+ response_xml << " <li>" << *i << "</li>" << endl;
+ }
+ response_xml << " </ol>" << endl;
+ response_xml << " </ul>" << endl;
+ response_xml << " <h2>Desarrollado por</h2>" << endl;
+ response_xml << " <ul>" << endl;
+ response_xml << " <li>Nicolás Dimov.</li>" << endl;
+ response_xml << " <li>Leandro Lucarella.</li>" << endl;
+ response_xml << " <li>Ricardo Markiewicz.</li>" << endl;
+ response_xml << " </ul>" << endl;
+ response_xml << " <address>" << endl;
+ response_xml << " Copyleft 2003 - bajo los " << endl;
+ response_xml << " términos de la licencia GPL" << endl;
+ response_xml << " </address>" << endl;
+ response_xml << " </body>" << endl;
+ response_xml << "</html>" << endl;
+ response.status_code = HTTPMessage::OK;
+ response.set_body(response_xml.str());
+ } else if (command.get_command() == "stop") {
+ stop = true;
+ response.set_body("El server se apagará en instantes...");
+ } else {
+ response.status_code = HTTPMessage::NOT_FOUND;
+ response.set_body("Invalid command for 'server' taget!");
+ }
+ } else if (command.get_target() == "connection") {
+ if (command.get_command() == "list") {
+ // FIXME
+ TCPServer::ConnectionInfoList cil = get_connected();
+ stringstream response_xml;
+ response_xml << "<html>" << endl;
+ response_xml << " <head>" << endl;
+ response_xml << " <title>PlaQui v0.6</title>" << endl;
+ response_xml << " </head>" << endl;
+ response_xml << " <body>" << endl;
+ response_xml << " <h1>PlaQui</h1>" << endl;
+ response_xml << " <p>versión 0.6</p>" << endl;
+ response_xml << " <h2>Lista de conexiones:</h2>" << endl;
+ response_xml << " <ul>" << endl;
+ for (TCPServer::ConnectionInfoList::const_iterator i = cil.begin();
+ i != cil.end(); i++) {
+ response_xml << " <li>" << i->host
+ << ":" << i->port << " [<a href=\"/connection/disconnect/"
+ << i->host << "/" << i->port << "\">deconectar</a>]</li>"
+ << endl;
+ }
+ response_xml << " </ul>" << endl;
+ response_xml << " <address>" << endl;
+ response_xml << " Copyleft 2003 - bajo los " << endl;
+ response_xml << " términos de la licencia GPL" << endl;
+ response_xml << " </address>" << endl;
+ response_xml << " </body>" << endl;
+ response_xml << "</html>" << endl;
+ response.status_code = HTTPMessage::OK;
+ response.set_body(response_xml.str());
+ } else if (command.get_command() == "stop") {
+ // TODO server->finish();
+ response.set_body("La conexión se cerrará en instantes...");
+ } else {
+ response.status_code = HTTPMessage::NOT_FOUND;
+ response.set_body("Invalid command for 'connection' taget!");
+ }
+ } else if (command.get_target() == "transmission") {
+ } else if (command.get_target() == "plant") {
+ } else {
+ response.status_code = HTTPMessage::NOT_FOUND;
+ response.set_body("Invalid Target!");
}
- response_xml << " </ol>" << endl;
- response_xml << " </ul>" << endl;
- response_xml << " <h2>Desarrollado por</h2>" << endl;
- response_xml << " <ul>" << endl;
- response_xml << " <li>Nicolás Dimov.</li>" << endl;
- response_xml << " <li>Leandro Lucarella.</li>" << endl;
- response_xml << " <li>Ricardo Markiewicz.</li>" << endl;
- response_xml << " </ul>" << endl;
- response_xml << " <address>" << endl;
- response_xml << " Copyleft 2003 - bajo los " << endl;
- response_xml << " términos de la licencia GPL" << endl;
- response_xml << " </address>" << endl;
- response_xml << " </body>" << endl;
- response_xml << "</html>" << endl;
- HTTPResponse response(HTTPMessage::OK, response_xml.str());
+ // FIXME
response.headers["Content-Type"] = "text/html; charset=iso-8859-1";
- //response.headers["Connection"] = "close";
- server->send(response);
- }
+ response.headers["Connection"] = "close";
+ server->send(response);
+ server->finish();
+}
} // namespace Server
}
}
+TCPServer::ConnectionInfoList TCPServer::get_connected(void) {
+#ifdef DEBUG
+ cerr << __FILE__ << ": get_connected()" << endl;
+#endif // DEBUG
+ TCPServer::ConnectionInfoList con;
+ Glib::Mutex::Lock lock(connections_mutex);
+ for (ConnectionList::const_iterator i = connections.begin();
+ i != connections.end(); i++) {
+ TCPServer::ConnectionInfo ci =
+ { (*i)->get_peerhost(), (*i)->get_peerport() };
+ con.push_back(ci);
+ }
+ return con;
+}
+
} // namespace Server
} // namespace PlaQui
# $Id$
#
+# Defino compilador y linker ?
+CC=gcc
+
# Ubicación de archivos .h
INCLUDE_BASE_DIR=../include
INCLUDE_DIR=$(INCLUDE_BASE_DIR)
CXXFLAGS+=-g -DDEBUG
#CXXFLAGS+=-g
#CXXFLAGS+=-O3
-LDFLAGS=-lsocket++ -L$(LIB_FILES) `pkg-config --libs glibmm-2.0 gthread-2.0`
+LDFLAGS=-lsocket++ `pkg-config --libs glibmm-2.0 gthread-2.0` #-L$(LIB_FILES)
TARGETS=server_test
+----------------------------+
- | PROPUESTA DE SERVIDOR v0.3 |
+ | PROPUESTA DE SERVIDOR v0.4 |
+----------------------------+
$Id$
status | Obtiene estado general | Cantidad de plantas, conexiones,
| del servidor. | transmisiones, versión, uptime, etc.
---------+------------------------+---------------------------------------------
+stop | Detiene el servidor. | Nada.
Comandos para una Planta:
-------------------------
---------------+-----------------------------+----------------------------------
set/<planta> |Cambia la propiedad <prop> |Nada (a ver si no retorna el valor
/<elem> |del elemento <elem>, |realmente aceptado).
- /<prop> |asignándole el valor <v> a |
- /<v> |planta de nombre <planta>. |
+ /<prop> |asignándole el valor <val> a |
+ /<val> |planta de nombre <planta>. |
NOTA: Los nombres entre "<" y ">" denotan un argumento.
Todos los comandos de transmisiones comienzan con /transmission/ y continúan con
alguna de las siguientes opciones:
-Comando |Descripción |Respuesta
+Comando |Descripción |Respuesta
-------------------+-----------------------------------------+------------------
-list |Obtiene una lista de las las |Lista de transmi-
- |transmisiones activas. |siones activas
+list |Obtiene una lista de las transmisiones |Lista de transmi-
+ |activas. |siones activas
| |(host, puerto,
| |uptime, etc).
-------------------+-----------------------------------------+------------------
-start/<host>/<port>|Comienza la transmisión al <host> en el |Nada.
- |puerto al <host> en el puerto <port>. |
+start/<plant> |Comienza la transmisión de la planta |Nada.
+ /<host>/<port> |<plant> al <host> en elpuerto al <host> |
+ |en el puerto <port>. |
-------------------+-----------------------------------------+------------------
-stop/<host>/<port> |Finaliza la transmisiónal <host> en el |Nada.
- |puerto <port>. Si se omite el <host>, se |
+stop/<host>/<port> |Finaliza la transmisión al <host> en el |Nada.
+ |puerto <port>. Si se omite el <port>, se |
+ |finalizan todas las transmisiones al |
+ |<host>. Si se omite el <host>, se |
|finalizan todas las transmisiones. |
+Comandos para una Conexión de Control:
+--------------------------------------
+Todos los comandos de transmisiones comienzan con /transmission/ y continúan con
+alguna de las siguientes opciones:
+
+Comando |Descripción |Respuesta
+-------------------+-----------------------------------------+------------------
+list |Obtiene una lista de las conexiones de |Lista de conexio-
+ |control activas. |nes activas (host,
+ | |puerto, uptime,
+ | |etc).
+-------------------+-----------------------------------------+------------------
+stop/<host>/<port> |Finaliza la conexión de control del host |Nada.
+ |<host> en el puerto <port>. Si se omite |
+ |el <port> se finalizan todas las |
+ |conexiones al <host>. Si se omite también|
+ |el <host>, se finalizan todas las |
+ |conexiones de control. |
+
NOTA: Los nombres entre "<" y ">" denotan un argumento.