Fixes and improvements
[anna.git] / example / http / wims20XRServer / main.cpp
1 // ANNA - Anna is Not Nothingness Anymore                                                         //
2 //                                                                                                //
3 // (c) Copyright 2005-2015 Eduardo Ramos Testillano & Francisco Ruiz Rayo                         //
4 //                                                                                                //
5 // See project site at http://redmine.teslayout.com/projects/anna-suite                           //
6 // See accompanying file LICENSE or copy at http://www.teslayout.com/projects/public/anna.LICENSE //
7
8
9 /*
10   Ejemplo de programa servidor. Atiende peticiones aritmeticas, el protocolo de transporte sera HTTP
11   ver http::Transport y el contenido del mensaje sera el resutaldo de un comm::Codec con los
12   (x, y, op) -> El resultado sera estos tres componente mas result.
13
14   Para poder probar el sistema de congestion se puede indicar un numero de milisegundos de
15   retardo aplicados a cada contestacion.
16   
17   Los clientes pueden ser: http_kclient.p http_client.p
18 */
19 #include <iostream>
20
21 #include <anna/core/core.hpp>
22
23 #include <anna/xml/Node.hpp>
24 #include <anna/xml/Attribute.hpp>
25
26 #include <anna/comm/comm.hpp>
27
28 #include <anna/http/Request.hpp>
29 #include <anna/http/Response.hpp>
30 #include <anna/http/Handler.hpp>
31 #include <anna/http/Transport.hpp>
32 #include <anna/http/functions.hpp>
33
34 #include <anna/http/wims20/ServerSide.hpp>
35
36 #include <anna/test/Communicator.hpp>
37
38 using namespace std;
39
40 class MyHandler : public http::Handler {
41 public:
42    MyHandler () : http::Handler ("http_rserver::MyHandler"),
43       a_xmlResponse (NULL),
44       a_request (NULL)
45    {
46       anna_memset (a_xmlAttributes, 0, sizeof (a_xmlAttributes));
47    }
48    ~MyHandler () {
49       delete a_xmlResponse;
50       delete a_request;
51    }
52
53    static const char* className () throw () { return "http_rserver::ReceiverFactory"; }
54
55 private:
56    struct Attribute { enum _v { ValueOne, ValueTwo, Operator, Result, Time, Max }; };
57    
58    test::Communicator* a_communicator;
59    http::wims20::ServerSide* a_request;
60    xml::Node* a_xmlResponse;
61    xml::Attribute* a_xmlAttributes [Attribute::Max];
62    std::string a_serviceID;
63    std::string a_guid;
64    
65    void initialize () throw (RuntimeException);
66    void evRequest (ClientSocket&, const http::Request& request) throw (RuntimeException);
67    void evResponse (ClientSocket&, const http::Response&) throw (RuntimeException) {;}
68 };
69
70 class WIMS20ArithmeticServer : public comm::Application {
71 public:
72    WIMS20ArithmeticServer ();
73       
74 private:
75    test::Communicator a_communicator;
76    ReceiverFactoryImpl <MyHandler> a_receiverFactory;
77    comm::ServerSocket* a_serverSocket;
78
79    void initialize () throw (RuntimeException);
80    void run () throw (RuntimeException);
81    xml::Node* asXML (xml::Node* app) const throw ();
82 };
83
84 using namespace std;
85 using namespace anna::comm;
86
87 int main (int argc, const char** argv)
88 {
89    CommandLine& commandLine (CommandLine::instantiate ());
90    WIMS20ArithmeticServer app;
91
92    http::functions::initialize ();
93
94    srand (time (NULL));
95
96    try {
97       commandLine.initialize (argv, argc);
98       commandLine.verify ();
99
100       Logger::setLevel (Logger::Debug); 
101       Logger::initialize ("http_server", new anna::TraceWriter ("file.trace", 4048000));
102  
103       app.start ();
104    }
105    catch (Exception& ex) {
106       cout << ex.asString () << endl;
107    }
108    
109    return 0;
110 }
111
112 WIMS20ArithmeticServer::WIMS20ArithmeticServer () : 
113    Application ("http_rserver", "Servidor WIMS20 de operaciones aritmeticas (iRS)", "1.0") 
114 {
115    CommandLine& commandLine (CommandLine::instantiate ());
116       
117    commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que atender peticiones");
118    commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccin IP en la que atender");
119    commandLine.add ("d", CommandLine::Argument::Mandatory, "Retardo aplicado a la contestacion");
120    commandLine.add ("r", CommandLine::Argument::Optional, "Indicador de reuso de direccion", false);
121    commandLine.add ("limit", CommandLine::Argument::Mandatory, "% de ocupacion que permitimos");
122    commandLine.add ("n", CommandLine::Argument::Optional, "Numero de mensajes a servir", true);
123    commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
124    commandLine.add ("timeout", CommandLine::Argument::Optional, "Timeout (ms) del cliente sin enviar peticiones");
125    commandLine.add ("quota", CommandLine::Argument::Optional, "Numero de bytes aceptados antes de cerrar el socket");
126    commandLine.add ("domain", CommandLine::Argument::Mandatory, "http://domain-openapis/path-openapis/serviceID/guid/other_possible_levels?query_parameters");
127    commandLine.add ("service", CommandLine::Argument::Mandatory, "http://domain-openapis/path-openapis/serviceID/guid/other_possible_levels?query_parameters");
128    commandLine.add ("guid", CommandLine::Argument::Mandatory, "http://domain-openapis/path-openapis/serviceID/guid/other_possible_levels?query_parameters");
129 }
130
131 void WIMS20ArithmeticServer::initialize () 
132    throw (RuntimeException)
133 {
134    CommandLine& cl (CommandLine::instantiate ());
135
136    int port = cl.getIntegerValue ("p");
137    const comm::Device* device = Network::instantiate ().find (Device::asAddress (cl.getValue ("a")));
138
139    comm::TransportFactory* ttff = &http::Transport::getFactory ();
140
141    if (cl.exists ("quota") == true) 
142       ttff->setOverQuotaSize (cl.getIntegerValue ("quota"));
143
144    a_serverSocket = new ServerSocket (INetAddress (device, port), cl.exists ("r"), ttff);
145    a_serverSocket->setReceiverFactory (a_receiverFactory);
146 }
147
148 void WIMS20ArithmeticServer::run ()
149    throw (RuntimeException)
150 {
151    CommandLine& cl (CommandLine::instantiate ());
152
153    a_communicator.attach (a_serverSocket);
154    a_communicator.setDelay ((Millisecond)cl.getIntegerValue ("d"));
155
156    if (cl.exists ("n") == true)
157       a_communicator.setMaxMessage (cl.getIntegerValue ("n"));
158
159    if (cl.exists ("trace"))
160       Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));
161
162    CongestionController::instantiate ().setLimit (cl.getIntegerValue ("limit"));
163
164    if (cl.exists ("timeout") == true)
165       a_communicator.setTimeout ((Millisecond)cl.getIntegerValue ("timeout"));
166
167    a_communicator.accept ();
168 }
169
170 xml::Node* WIMS20ArithmeticServer::asXML (xml::Node* app) const 
171    throw ()
172 {
173    xml::Node* node = app::Application::asXML (app);
174    
175    node->createAttribute ("MaxMessage", a_communicator.getMaxMessage ());
176    node->createAttribute ("Message", a_communicator.getMessage ());
177    
178    return node;
179 }
180
181 void MyHandler::initialize ()
182    throw (RuntimeException)
183 {
184    CommandLine& cl (CommandLine::instantiate ());
185    
186    a_communicator = app::functions::component <test::Communicator> (ANNA_FILE_LOCATION);
187    
188    /**
189     * Crea el documento XML que usaremos para codificar la respuesta y 
190     * pre-localiza los objetos sobre los que tendrá que actuar
191     */
192    a_xmlResponse = new xml::Node ("Response");
193    a_xmlAttributes [Attribute::ValueOne] = a_xmlResponse->createAttribute ("ValueOne", 0);
194    a_xmlAttributes [Attribute::ValueTwo] = a_xmlResponse->createAttribute ("ValueTwo", 0);
195    a_xmlAttributes [Attribute::Operator] = a_xmlResponse->createAttribute ("Operator", 0);
196    a_xmlAttributes [Attribute::Result] = a_xmlResponse->createAttribute ("Result", 0);
197    a_xmlAttributes [Attribute::Time] = a_xmlResponse->createAttribute ("Time", 0);
198    
199    /**
200     * Crea el analizador de la petición REST, que viene formateada según la especificación WIMS2.0
201     */
202    a_request = new http::wims20::ServerSide (cl.getValue ("domain"));
203    a_serviceID = cl.getValue ("service");
204    a_guid = cl.getValue ("guid");
205 }
206
207 /*
208  *  Recibe una URI que puede ser similar a:
209  *  URI: http://www.localhost.es/math/user@tid.es?Operator=*&ValueOne=20&ValueTwo=2&Time=10000 
210  */
211 void MyHandler::evRequest (ClientSocket& clientSocket, const http::Request& request)
212    throw (RuntimeException)
213 {
214    LOGMETHOD (TraceMethod tm ("MyReceiver", "apply", ANNA_FILE_LOCATION));
215
216    if (a_communicator->canContinue (clientSocket) == false)
217       return;      
218       
219    // Extrae los parámetros de la petición REST de la URI de la petición HTTP
220    a_request->decode (request);
221    
222    http::Response* response = allocateResponse ();
223
224    /*
225     * Si la petición no tiene los parámetros esperados devuelve error.
226     */
227    if (a_request->getServiceID () != a_serviceID || a_request->getGUID () != a_guid) {
228       response->setStatusCode (401);
229       clientSocket.send (*response);
230       return;
231    }
232
233    // Antes de contestar espera el tiempo indicado en la configuración
234    a_communicator->delay ();
235    
236    /**
237     * Obtiene los valores de la URI correspondiente a la petición REST
238     */
239    const char* op = a_request->getCStringValue ("Operator");
240    int v1 = a_request->getIntegerValue ("ValueOne");
241    int v2 = a_request->getIntegerValue ("ValueTwo");
242       
243    /**
244     * Establece el valor de los objetos XML pre-localizados.
245     */
246    a_xmlAttributes [Attribute::ValueOne]->setValue (v1);
247    a_xmlAttributes [Attribute::ValueTwo]->setValue (v2);
248    a_xmlAttributes [Attribute::Operator]->setValue (op);
249    
250    // Este dato sirve para calcular el tiempo de respuesta del servidor 
251    a_xmlAttributes [Attribute::Time]->setValue (a_request->getIntegerValue ("Time"));
252    
253    response->setStatusCode (200);
254
255    switch (*op) {
256       case 'a': a_xmlAttributes [Attribute::Result]->setValue (v1 + v2); break;
257       case 's': a_xmlAttributes [Attribute::Result]->setValue (v1 - v2); break;
258       case 'm': a_xmlAttributes [Attribute::Result]->setValue (v1 * v2); break;
259       case 'd':
260          if (v2 == 0) {
261             response->setStatusCode (400);
262             response->setReasonPhrase ("Division por cero");
263          }
264          else
265             a_xmlAttributes [Attribute::Result]->setValue (v1 / v2);
266          break;
267       default:
268          response->setStatusCode (501);
269          break;
270    }
271
272    /* Observar que establece directamente el documento XML como cuerpo
273     * de la respuesta HTTP.
274     */    
275    if (response->getStatusCode () == 200)
276       response->setBody (a_xmlResponse);
277    else
278       response->clearBody ();      
279    
280    clientSocket.send (*response);
281 }