root/branches/v2.1/example/servlets.d

Revision 886, 21.9 kB (checked in by kris, 2 years ago)

added missing import

Line 
1 /******************************************************************************
2
3         @file servlets.d
4
5         Here's some contrived servlets to give you an idea what Mango does.
6
7         Point your Browser at http://127.0.0.1 and try the following paths:
8
9         1) /example/echo should return a bunch of diagnostic stuff back to
10            the browser
11
12         2) /example/ping maintains a count of how many times a particular
13            ip-address has made a request, and check to see if Google News
14            page has been updated since the last visit (per address). This
15            is a hideously contrived example of VirtualCache and HttpClient.
16            That is, it illustrates how to maintain lightweight server-side
17            state (when necessary), and how to make client-side requests to
18            a remote server. One might handle state management using cookies
19            or url-rewrites instead.
20
21         3) /admin/logger allows you to modify current Loggers and Levels,
22            as well as the ability to create new Logger/Level combinations.
23
24         4) all other paths are mapped to a file-request handler. Requesting
25            /index.html should return the doxygen page for Mango; all other
26            links should operate correctly. Be sure to start the executable
27            from the mango/obj directory, otherwise you'll probably run into
28            404-Not-Found errors.
29
30         
31         Kris, May 2nd 2004
32         Scott Sanders, June 1, 2004
33
34 *******************************************************************************/
35
36         // for a variety of servlet IO
37 import  mango.io.Uri,
38         mango.io.Socket,
39         mango.io.Exception,
40         mango.io.FileBucket,
41         mango.io.DisplayWriter,
42         mango.io.PickleRegistry;
43
44         // for numeric conversion
45 import  mango.convert.Integer;
46
47         // for threads
48 import  mango.sys.System;
49
50         //for logging
51 import  mango.log.Admin,
52         mango.log.Logger,
53         mango.log.Configurator;
54
55         // for testing the http server
56 import  mango.http.server.HttpServer;
57
58         // for testing the http client
59 import  mango.http.client.HttpClient;
60
61         // for testing the servlet-engine
62 import  mango.servlet.Servlet,
63         mango.servlet.ServletContext,
64         mango.servlet.ServletProvider;
65
66         // for working with cache entries
67 import  mango.cache.Payload,
68         mango.cache.VirtualCache;
69
70         // setup a logger for module scope
71 private Logger mainLogger;
72
73
74 /*******************************************************************************
75
76         Directive to include the winsock2 library
77
78 *******************************************************************************/
79
80 version (Win32)
81          pragma (lib, "wsock32");
82
83
84 /*******************************************************************************
85
86         an HTML wrapper built upon a DisplayWriter
87
88 *******************************************************************************/
89
90 class HtmlWriter : DisplayWriter
91 {      
92         /***********************************************************************
93         
94         ***********************************************************************/
95
96         this (IWriter writer)
97         {
98                 super (writer.getBuffer);
99         }
100
101         /***********************************************************************
102         
103         ***********************************************************************/
104
105         override IWriter newline()
106         {
107                 return super.put ("<br>\r\n"c);
108         }
109 }
110
111
112 /*******************************************************************************
113
114         Servlet to return a file.
115
116 *******************************************************************************/
117
118 class FileServlet : MethodServlet
119 {
120         private static Logger logger;
121
122         /***********************************************************************
123         
124                 get a Logger for this class
125
126         ***********************************************************************/
127
128         static this ()
129         {
130                 logger = Logger.getLogger ("mango.servlets.File");
131         }
132
133         /***********************************************************************
134         
135                 support GET requests only! All other method requests will
136                 return an error to the user-agent
137
138         ***********************************************************************/
139
140         void doGet (IServletRequest request, IServletResponse response)
141         {   
142                 logger.info ("request for file: " ~ request.getUri.getPath);
143
144                 response.copyFile (request.getContext, request.getPathInfo);
145         }
146 }
147
148
149 /*******************************************************************************
150
151         Servlet to return a page echoing request-details sent to the server
152
153 *******************************************************************************/
154
155 class Echo : Servlet
156 {
157         private static Logger logger;
158
159         /***********************************************************************
160         
161                 get a Logger for this class
162
163         ***********************************************************************/
164
165         static this ()
166         {
167                 logger = Logger.getLogger ("mango.servlets.Echo");
168         }
169
170         /***********************************************************************
171
172                 Handle all the different request methods ...
173         
174         ***********************************************************************/
175
176         void service (IServletRequest request, IServletResponse response)
177         {   
178                 Uri uri = request.getUri;
179
180                 logger.info ("request for echo");
181
182                 // say we're writing html
183                 response.setContentType ("text/html");
184
185                 // wrap an HtmlWriter around the response output ...
186                 HtmlWriter output = new HtmlWriter(response.getWriter);
187
188                 // write HTML preamble ...
189                 output ("<HTML><HEAD><TITLE>Echo</TITLE></HEAD><BODY>"c);
190
191                 // log everything to the output
192                 output ("------------------------"c).cr()
193                        ("Uri: "c) (uri).cr()
194                        ("------------------------"c).cr()
195                        ("Headers:"c).cr()
196                        (request.getHeaders)
197                        ("------------------------"c).cr()
198                        ("Cookies:"c).cr()
199                        (request.getCookies)
200                        ("------------------------"c).cr()
201                        ("Parameters:"c).cr()
202                        (request.getParameters)
203                        ("------------------------"c).cr();
204
205                 // display the Servlet environment
206                 output ("encoding: "c) (request.getCharacterEncoding).cr()
207                        ("content length: "c) (request.getContentLength).cr()
208                        ("content type: "c) (request.getContentType).cr()                   
209                        ("protocol: "c) (request.getProtocol).cr()                   
210                        ("scheme: "c) (uri.getScheme).cr()                   
211                        ("method: "c) (request.getMethod).cr()                   
212                        ("host name: "c) (request.getServerName).cr()                   
213                        ("host port: "c) (request.getServerPort).cr()                   
214                        ("remote address: "c) (request.getRemoteAddress).cr()                   
215                        ("remote host: "c) (request.getRemoteHost).cr()                   
216                        ("path info: "c) (request.getPathInfo).cr()                   
217                        ("query: "c) (uri.getQuery).cr()                   
218                        ("path: "c) (uri.getPath).cr()                   
219                        ("context path: "c) (request.getContextPath).cr().cr().cr(); 
220
221                 // write HTML closure
222                 output("</BODY></HTML>"c);
223         }
224 }
225
226
227 /*******************************************************************************
228
229         A truly contrived example of server-side state management, and
230         client-side http requests.
231
232 *******************************************************************************/
233
234 class Ping : Servlet
235 {
236         private static VirtualCache cache;
237         private static FileBucket   bucket;
238         private static PingThread   thread;
239         private static int          ping_id;
240
241         /***********************************************************************
242
243                 A Thread subclass to monitor external web-pages
244
245         ***********************************************************************/
246
247         static class PingThread
248         {       
249                 bool                    halt;
250                 ulong                   time;
251                 int                     delta;
252                 int                     pause,
253                                         content;
254
255                 HttpClient              client;
256                 Logger                  logger;
257
258
259                 /**************************************************************
260
261                 **************************************************************/
262
263                 this (MutableUri uri, int pause)
264                 {
265                         // get a Logger for this class
266                         logger = Logger.getLogger ("mango.servlets.PingThread");
267
268                         this.pause = pause;
269                         client = new HttpClient (HttpClient.Head, uri);
270                 }
271
272                 /**************************************************************
273
274                         Check the provided URL now and then to see if it
275                         has changed ...
276         
277                 **************************************************************/
278
279                 version (Ares)
280                          alias void ThreadReturn;
281                       else
282                          alias int ThreadReturn;
283
284                 ThreadReturn run()
285                 {
286                         while (true)
287                                try {
288
289                                    // should we bail out?
290                                    if (halt)
291                                        return 0;
292
293                                    // reset, and set up a Host header
294                                    client.reset ();
295                                    client.getRequestHeaders.add (HttpHeader.Host, client.getUri.getHost);
296
297                                    // make request
298                                    client.open ();
299
300                                    // close connection
301                                    client.close ();
302
303                                    // check return status for validity
304                                    if (client.isResponseOK)
305                                       {
306                                       // extract modifed date (be aware of -1 return, for no header)
307                                       ulong time = client.getResponseHeaders.getDate (HttpHeader.LastModified);
308                                       if (time != -1)
309                                          {
310                                          if (time > this.time)
311                                             {
312                                             this.time = time;
313                                             ++delta;
314                                             }
315                                          }
316                                       else
317                                          {
318                                          int content = client.getResponseHeaders.getInt (HttpHeader.ContentLength);
319                                          if (content != this.content)
320                                             {
321                                             this.content = content;
322                                             ++delta;
323                                             }
324                                          }
325                                       }
326
327                                    // see if tracing is enabled before doing a bunch of work
328                                    if (logger.isEnabled (logger.Level.Trace))
329                                       {
330                                       char[16] tmp;
331                                       logger.trace (Integer.format (tmp, delta));
332
333                                       foreach (HeaderElement header; client.getResponseHeaders())
334                                               {
335                                               logger.trace (header.name.value ~ header.value);
336                                               }
337                                       }
338
339                                    // sleep for a few seconds
340                                    System.sleep (pause);
341
342                                    } catch (IOException x)
343                                             logger.error ("IOException: " ~ x.toString);
344
345                                      catch (Object x)
346                                             logger.fatal ("Fatal: " ~ x.toString);
347                         return 0;
348                 }
349         }
350
351
352         /***********************************************************************
353
354                 Each unique requesting IP address has one of these
355                 maintained on the server in a cache. When the cache
356                 fills up, LRU entries are spooled out to disk. The
357                 next request for an 'old' entry will cause it to be
358                 resurrected from disk storage, with state intact.
359
360         ***********************************************************************/
361
362         private static class PingEntry : Payload
363         {
364                 // these are serialized
365                 long            delta;
366                 int             count;
367
368                 /***************************************************************
369
370                         Reset our state via the provided reader
371
372                 ***************************************************************/
373
374                 override void read (IReader input)
375                 {
376                         input (count) (delta);
377                 }
378
379                 /***************************************************************
380
381                         Save our state via the provided writer
382
383                 ***************************************************************/
384
385                 override void write (IWriter output)
386                 {
387                         output (count) (delta);
388                 }
389
390                 /***************************************************************
391
392                         ISerializable factory; used for creating new
393                         class instances, which are then primed with
394                         previously saved state.
395
396                 ***************************************************************/
397
398                 override Object create (IReader reader)
399                 {
400                         PingEntry p = new PingEntry;
401                         p.read (reader);
402                         return p;
403                 }
404
405                 /***************************************************************
406
407                         Return a network identifier for serializing this
408                         class.
409
410                 ***************************************************************/
411
412                 override char[] getGuid()
413                 {
414                         return this.classinfo.name;
415                 }
416         }
417
418
419         /***********************************************************************
420
421                 Initialize the Ping environment
422
423         ***********************************************************************/
424
425         static this()
426         {
427                 // create a file bucket for serialized PingEntry instances
428                 bucket = new FileBucket (new FilePath("bucket.bin"), FileBucket.HalfK);
429
430                 // create a VirtualCache to host popular PingEntry instances.
431                 // When the cache fills, LRU entries get flushed out to disk,
432                 // and then retrieved and resurrected as necessary.
433                 cache = new VirtualCache (bucket, 101);
434
435                 // enroll the PingEntry for serialization
436                 PickleRegistry.enroll (new PingEntry);
437
438                 // create a thread to poll Google News for changes ...
439                 thread = new PingThread (new MutableUri ("http", "news.google.com", "/", null),
440                                                           System.Interval.Second * 30);
441                 System.createThread (&thread.run, true);
442         }
443
444         /***********************************************************************
445
446                 clean up when we're done
447
448         ***********************************************************************/
449
450         static ~this()
451         {
452                 thread.halt = true;
453                 bucket.close ();
454         }
455
456         /***********************************************************************
457
458                 handle all service requests
459
460         ***********************************************************************/
461
462         void service (IServletRequest request, IServletResponse response)
463         {   
464                 PingEntry ping;
465
466                 // log an info message
467                 Logger.getLogger ("mango.servlets.Ping").info ("request for ping");
468
469                 // get the remote ip-address
470                 char[] ua = request.getRemoteAddress;
471
472                 // protect against thread collisions ...
473                 synchronized (cache)
474                              {
475                              // seen this address before?
476                              ping = cast(PingEntry) cache.get (ua);
477                              if (ping is null)
478                                  // nope; create new one
479                                  cache.put (ua, ping = new PingEntry);
480                              }
481
482                 // bump ping count
483                 ++ping.count;
484
485                 // has Google news been updated?
486                 long changes = thread.delta - ping.delta;
487                 ping.delta = thread.delta;
488
489                 // say we're writing html
490                 response.setContentType ("text/html");
491
492                 // grab the response writer ...
493                 IWriter output = response.getWriter;
494
495                 // write HTML page ...
496                 output ("<HTML><HEAD><TITLE>Ping</TITLE></HEAD><BODY>"c)
497                        ("You've visited this page "c)
498                        (ping.count)
499                        (" times. Google News has had "c)
500                        (changes)
501                        (" update(s) since your last visit."c)
502                        ("</BODY></HTML>"c);
503         }
504 }
505          
506
507
508 /*******************************************************************************
509
510         Create an http server with the given IProvider. Wait for console
511         input, then quit.
512
513 *******************************************************************************/
514
515 void testServer (IProvider provider)
516 {      
517         mainLogger.info ("starting server");
518
519         // bind to port 80 on a local address
520         InternetAddress addr = new InternetAddress (80);
521
522         // create a (1 thread) server using the IProvider to service requests
523         HttpServer server = new HttpServer (provider, addr, 1, mainLogger);
524        
525         // start listening for requests (but this thread does not listen)
526         server.start ();
527
528         // send this thread to sleep for ever ...
529         System.sleep ();
530
531         // should never get here
532         mainLogger.info ("halting server");
533 }
534
535
536 /*******************************************************************************
537
538         Test the servlet wrapper. We have three servlets that we map to
539         various request paths. We take advantage of a 'default' context
540         to serve up pages from the Mango help files.
541
542 *******************************************************************************/
543
544 void testServletEngine ()
545 {      
546         mainLogger.info ("registering servlets");
547
548         // construct a servlet-provider
549         ServletProvider sp = new ServletProvider();
550
551         // create a context for example servlets
552         ServletContext example = sp.addContext (new ServletContext ("/example"));
553
554         // create a context for admin servlets
555         ServletContext admin = sp.addContext (new AdminContext (sp, "/admin"));
556
557         // map echo requests to our echo servlet
558         sp.addMapping ("/echo", sp.addServlet (new Echo, "echo", example));
559
560         // map ping requests to our ping servlet
561         sp.addMapping ("/ping", sp.addServlet (new Ping, "ping", example));
562
563         // point the default context to the mango help files
564         sp.addContext (new ServletContext ("", "../doc/html"));
565
566         // map all other requests to our file servlet
567         sp.addMapping ("/", sp.addServlet (new FileServlet, "files"));
568        
569         // fire up a server
570         testServer (sp);
571 }
572
573 /*******************************************************************************
574
575
576 *******************************************************************************/
577
578 int main ()
579 {  
580         BasicConfigurator.configure ();
581         mainLogger = Logger.getLogger ("mango.servlets");
582         mainLogger.setLevel (mainLogger.Level.Info);
583
584         try {
585             testServletEngine();
586
587             mainLogger.info ("Done");
588             } catch (Exception x)
589                     {
590                     mainLogger.error (x.msg);
591                     }
592         return 0;
593 }
Note: See TracBrowser for help on using the browser.