First commit
[anna.git] / example / http / wims20XRServer / main.cpp
1 // ANNA - Anna is Not 'N' Anymore
2 //
3 // (c) Copyright 2005-2014 Eduardo Ramos Testillano & Francisco Ruiz Rayo
4 //
5 // https://bitbucket.org/testillano/anna
6 //
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10 //
11 //     * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13 //     * Redistributions in binary form must reproduce the above
14 // copyright notice, this list of conditions and the following disclaimer
15 // in the documentation and/or other materials provided with the
16 // distribution.
17 //     * Neither the name of Google Inc. nor the names of its
18 // contributors may be used to endorse or promote products derived from
19 // this software without specific prior written permission.
20 //
21 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
24 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
25 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
26 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
27 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
28 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
29 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
30 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 //
33 // Authors: eduardo.ramos.testillano@gmail.com
34 //          cisco.tierra@gmail.com
35
36
37 /*
38   Ejemplo de programa servidor. Atiende peticiones aritmeticas, el protocolo de transporte sera HTTP
39   ver http::Transport y el contenido del mensaje sera el resutaldo de un comm::Codec con los
40   (x, y, op) -> El resultado sera estos tres componente mas result.
41
42   Para poder probar el sistema de congestion se puede indicar un numero de milisegundos de
43   retardo aplicados a cada contestacion.
44   
45   Los clientes pueden ser: http_kclient.p http_client.p
46 */
47 #include <iostream>
48
49 #include <anna/core/core.hpp>
50
51 #include <anna/xml/Node.hpp>
52 #include <anna/xml/Attribute.hpp>
53
54 #include <anna/comm/comm.hpp>
55
56 #include <anna/http/Request.hpp>
57 #include <anna/http/Response.hpp>
58 #include <anna/http/Handler.hpp>
59 #include <anna/http/Transport.hpp>
60 #include <anna/http/functions.hpp>
61
62 #include <anna/http/wims20/ServerSide.hpp>
63
64 #include <anna/test/Communicator.hpp>
65
66 using namespace std;
67
68 class MyHandler : public http::Handler {
69 public:
70    MyHandler () : http::Handler ("http_rserver::MyHandler"),
71       a_xmlResponse (NULL),
72       a_request (NULL)
73    {
74       anna_memset (a_xmlAttributes, 0, sizeof (a_xmlAttributes));
75    }
76    ~MyHandler () {
77       delete a_xmlResponse;
78       delete a_request;
79    }
80
81    static const char* className () throw () { return "http_rserver::ReceiverFactory"; }
82
83 private:
84    struct Attribute { enum _v { ValueOne, ValueTwo, Operator, Result, Time, Max }; };
85    
86    test::Communicator* a_communicator;
87    http::wims20::ServerSide* a_request;
88    xml::Node* a_xmlResponse;
89    xml::Attribute* a_xmlAttributes [Attribute::Max];
90    std::string a_serviceID;
91    std::string a_guid;
92    
93    void initialize () throw (RuntimeException);
94    void evRequest (ClientSocket&, const http::Request& request) throw (RuntimeException);
95    void evResponse (ClientSocket&, const http::Response&) throw (RuntimeException) {;}
96 };
97
98 class WIMS20ArithmeticServer : public comm::Application {
99 public:
100    WIMS20ArithmeticServer ();
101       
102 private:
103    test::Communicator a_communicator;
104    ReceiverFactoryImpl <MyHandler> a_receiverFactory;
105    comm::ServerSocket* a_serverSocket;
106
107    void initialize () throw (RuntimeException);
108    void run () throw (RuntimeException);
109    xml::Node* asXML (xml::Node* app) const throw ();
110 };
111
112 using namespace std;
113 using namespace anna::comm;
114
115 int main (int argc, const char** argv)
116 {
117    CommandLine& commandLine (CommandLine::instantiate ());
118    WIMS20ArithmeticServer app;
119
120    http::functions::initialize ();
121
122    srand (time (NULL));
123
124    try {
125       commandLine.initialize (argv, argc);
126       commandLine.verify ();
127
128       Logger::setLevel (Logger::Debug); 
129       Logger::initialize ("http_server", new anna::TraceWriter ("file.trace", 4048000));
130  
131       app.start ();
132    }
133    catch (Exception& ex) {
134       cout << ex.asString () << endl;
135    }
136    
137    return 0;
138 }
139
140 WIMS20ArithmeticServer::WIMS20ArithmeticServer () : 
141    Application ("http_rserver", "Servidor WIMS20 de operaciones aritmeticas (iRS)", "1.0") 
142 {
143    CommandLine& commandLine (CommandLine::instantiate ());
144       
145    commandLine.add ("p", CommandLine::Argument::Mandatory, "Puerto en el que atender peticiones");
146    commandLine.add ("a", CommandLine::Argument::Mandatory, "Direccin IP en la que atender");
147    commandLine.add ("d", CommandLine::Argument::Mandatory, "Retardo aplicado a la contestacion");
148    commandLine.add ("r", CommandLine::Argument::Optional, "Indicador de reuso de direccion", false);
149    commandLine.add ("limit", CommandLine::Argument::Mandatory, "% de ocupacion que permitimos");
150    commandLine.add ("n", CommandLine::Argument::Optional, "Numero de mensajes a servir", true);
151    commandLine.add ("trace", CommandLine::Argument::Optional, "Nivel de trazas (debug,warning, error,...)");
152    commandLine.add ("timeout", CommandLine::Argument::Optional, "Timeout (ms) del cliente sin enviar peticiones");
153    commandLine.add ("quota", CommandLine::Argument::Optional, "Numero de bytes aceptados antes de cerrar el socket");
154    commandLine.add ("domain", CommandLine::Argument::Mandatory, "http://domain-openapis/path-openapis/serviceID/guid/other_possible_levels?query_parameters");
155    commandLine.add ("service", CommandLine::Argument::Mandatory, "http://domain-openapis/path-openapis/serviceID/guid/other_possible_levels?query_parameters");
156    commandLine.add ("guid", CommandLine::Argument::Mandatory, "http://domain-openapis/path-openapis/serviceID/guid/other_possible_levels?query_parameters");
157 }
158
159 void WIMS20ArithmeticServer::initialize () 
160    throw (RuntimeException)
161 {
162    CommandLine& cl (CommandLine::instantiate ());
163
164    int port = cl.getIntegerValue ("p");
165    const comm::Device* device = Network::instantiate ().find (Device::asAddress (cl.getValue ("a")));
166
167    comm::TransportFactory* ttff = &http::Transport::getFactory ();
168
169    if (cl.exists ("quota") == true) 
170       ttff->setOverQuotaSize (cl.getIntegerValue ("quota"));
171
172    a_serverSocket = new ServerSocket (INetAddress (device, port), cl.exists ("r"), ttff);
173    a_serverSocket->setReceiverFactory (a_receiverFactory);
174 }
175
176 void WIMS20ArithmeticServer::run ()
177    throw (RuntimeException)
178 {
179    CommandLine& cl (CommandLine::instantiate ());
180
181    a_communicator.attach (a_serverSocket);
182    a_communicator.setDelay ((Millisecond)cl.getIntegerValue ("d"));
183
184    if (cl.exists ("n") == true)
185       a_communicator.setMaxMessage (cl.getIntegerValue ("n"));
186
187    if (cl.exists ("trace"))
188       Logger::setLevel (Logger::asLevel (cl.getValue ("trace")));
189
190    CongestionController::instantiate ().setLimit (cl.getIntegerValue ("limit"));
191
192    if (cl.exists ("timeout") == true)
193       a_communicator.setTimeout ((Millisecond)cl.getIntegerValue ("timeout"));
194
195    a_communicator.accept ();
196 }
197
198 xml::Node* WIMS20ArithmeticServer::asXML (xml::Node* app) const 
199    throw ()
200 {
201    xml::Node* node = app::Application::asXML (app);
202    
203    node->createAttribute ("MaxMessage", a_communicator.getMaxMessage ());
204    node->createAttribute ("Message", a_communicator.getMessage ());
205    
206    return node;
207 }
208
209 void MyHandler::initialize ()
210    throw (RuntimeException)
211 {
212    CommandLine& cl (CommandLine::instantiate ());
213    
214    a_communicator = app::functions::component <test::Communicator> (ANNA_FILE_LOCATION);
215    
216    /**
217     * Crea el documento XML que usaremos para codificar la respuesta y 
218     * pre-localiza los objetos sobre los que tendrá que actuar
219     */
220    a_xmlResponse = new xml::Node ("Response");
221    a_xmlAttributes [Attribute::ValueOne] = a_xmlResponse->createAttribute ("ValueOne", 0);
222    a_xmlAttributes [Attribute::ValueTwo] = a_xmlResponse->createAttribute ("ValueTwo", 0);
223    a_xmlAttributes [Attribute::Operator] = a_xmlResponse->createAttribute ("Operator", 0);
224    a_xmlAttributes [Attribute::Result] = a_xmlResponse->createAttribute ("Result", 0);
225    a_xmlAttributes [Attribute::Time] = a_xmlResponse->createAttribute ("Time", 0);
226    
227    /**
228     * Crea el analizador de la petición REST, que viene formateada según la especificación WIMS2.0
229     */
230    a_request = new http::wims20::ServerSide (cl.getValue ("domain"));
231    a_serviceID = cl.getValue ("service");
232    a_guid = cl.getValue ("guid");
233 }
234
235 /*
236  *  Recibe una URI que puede ser similar a:
237  *  URI: http://www.localhost.es/math/user@tid.es?Operator=*&ValueOne=20&ValueTwo=2&Time=10000 
238  */
239 void MyHandler::evRequest (ClientSocket& clientSocket, const http::Request& request)
240    throw (RuntimeException)
241 {
242    LOGMETHOD (TraceMethod tm ("MyReceiver", "apply", ANNA_FILE_LOCATION));
243
244    if (a_communicator->canContinue (clientSocket) == false)
245       return;      
246       
247    // Extrae los parámetros de la petición REST de la URI de la petición HTTP
248    a_request->decode (request);
249    
250    http::Response* response = allocateResponse ();
251
252    /*
253     * Si la petición no tiene los parámetros esperados devuelve error.
254     */
255    if (a_request->getServiceID () != a_serviceID || a_request->getGUID () != a_guid) {
256       response->setStatusCode (401);
257       clientSocket.send (*response);
258       return;
259    }
260
261    // Antes de contestar espera el tiempo indicado en la configuración
262    a_communicator->delay ();
263    
264    /**
265     * Obtiene los valores de la URI correspondiente a la petición REST
266     */
267    const char* op = a_request->getCStringValue ("Operator");
268    int v1 = a_request->getIntegerValue ("ValueOne");
269    int v2 = a_request->getIntegerValue ("ValueTwo");
270       
271    /**
272     * Establece el valor de los objetos XML pre-localizados.
273     */
274    a_xmlAttributes [Attribute::ValueOne]->setValue (v1);
275    a_xmlAttributes [Attribute::ValueTwo]->setValue (v2);
276    a_xmlAttributes [Attribute::Operator]->setValue (op);
277    
278    // Este dato sirve para calcular el tiempo de respuesta del servidor 
279    a_xmlAttributes [Attribute::Time]->setValue (a_request->getIntegerValue ("Time"));
280    
281    response->setStatusCode (200);
282
283    switch (*op) {
284       case 'a': a_xmlAttributes [Attribute::Result]->setValue (v1 + v2); break;
285       case 's': a_xmlAttributes [Attribute::Result]->setValue (v1 - v2); break;
286       case 'm': a_xmlAttributes [Attribute::Result]->setValue (v1 * v2); break;
287       case 'd':
288          if (v2 == 0) {
289             response->setStatusCode (400);
290             response->setReasonPhrase ("Division por cero");
291          }
292          else
293             a_xmlAttributes [Attribute::Result]->setValue (v1 / v2);
294          break;
295       default:
296          response->setStatusCode (501);
297          break;
298    }
299
300    /* Observar que establece directamente el documento XML como cuerpo
301     * de la respuesta HTTP.
302     */    
303    if (response->getStatusCode () == 200)
304       response->setBody (a_xmlResponse);
305    else
306       response->clearBody ();      
307    
308    clientSocket.send (*response);
309 }