1.. _tutorials: 2 3Tutorials 4--------- 5 6 7This tutorial will walk you through basic but complete CherryPy applications 8that will show you common concepts as well as slightly more advanced ones. 9 10.. contents:: 11 :depth: 4 12 13Tutorial 1: A basic web application 14################################### 15 16The following example demonstrates the most basic application 17you could write with CherryPy. It starts a server and hosts 18an application that will be served at request reaching 19http://127.0.0.1:8080/ 20 21.. code-block:: python 22 :linenos: 23 24 import cherrypy 25 26 27 class HelloWorld(object): 28 @cherrypy.expose 29 def index(self): 30 return "Hello world!" 31 32 33 if __name__ == '__main__': 34 cherrypy.quickstart(HelloWorld()) 35 36Store this code snippet into a file named `tut01.py` and 37execute it as follows: 38 39.. code-block:: bash 40 41 $ python tut01.py 42 43This will display something along the following: 44 45.. code-block:: text 46 :linenos: 47 48 [24/Feb/2014:21:01:46] ENGINE Listening for SIGHUP. 49 [24/Feb/2014:21:01:46] ENGINE Listening for SIGTERM. 50 [24/Feb/2014:21:01:46] ENGINE Listening for SIGUSR1. 51 [24/Feb/2014:21:01:46] ENGINE Bus STARTING 52 CherryPy Checker: 53 The Application mounted at '' has an empty config. 54 55 [24/Feb/2014:21:01:46] ENGINE Started monitor thread 'Autoreloader'. 56 [24/Feb/2014:21:01:46] ENGINE Serving on http://127.0.0.1:8080 57 [24/Feb/2014:21:01:46] ENGINE Bus STARTED 58 59This tells you several things. The first three lines indicate 60the server will handle :mod:`signal` for you. The next line tells you 61the current state of the server, as that 62point it is in `STARTING` stage. Then, you are notified your 63application has no specific configuration set to it. 64Next, the server starts a couple of internal utilities that 65we will explain later. Finally, the server indicates it is now 66ready to accept incoming communications as it listens on 67the address `127.0.0.1:8080`. In other words, at that stage your 68application is ready to be used. 69 70Before moving on, let's discuss the message 71regarding the lack of configuration. By default, CherryPy has 72a feature which will review the syntax correctness of settings 73you could provide to configure the application. When none are 74provided, a warning message is thus displayed in the logs. That 75log is harmless and will not prevent CherryPy from working. You 76can refer to :ref:`the documentation above <perappconf>` to 77understand how to set the configuration. 78 79Tutorial 2: Different URLs lead to different functions 80###################################################### 81 82Your applications will obviously handle more than a single URL. 83Let's imagine you have an application that generates a random 84string each time it is called: 85 86.. code-block:: python 87 :linenos: 88 89 import random 90 import string 91 92 import cherrypy 93 94 95 class StringGenerator(object): 96 @cherrypy.expose 97 def index(self): 98 return "Hello world!" 99 100 @cherrypy.expose 101 def generate(self): 102 return ''.join(random.sample(string.hexdigits, 8)) 103 104 105 if __name__ == '__main__': 106 cherrypy.quickstart(StringGenerator()) 107 108Save this into a file named `tut02.py` and run it as follows: 109 110.. code-block:: bash 111 112 $ python tut02.py 113 114Go now to http://localhost:8080/generate and your browser 115will display a random string. 116 117Let's take a minute to decompose what's happening here. This is the 118URL that you have typed into your browser: http://localhost:8080/generate 119 120This URL contains various parts: 121 122- `http://` which roughly indicates it's a URL using the HTTP protocol (see :rfc:`2616`). 123- `localhost:8080` is the server's address. It's made of a hostname and a port. 124- `/generate` which is the path segment of the URL. This is what CherryPy uses to 125 locate an :term:`exposed` function or method to respond. 126 127Here CherryPy uses the `index()` method to handle `/` and the 128`generate()` method to handle `/generate` 129 130.. _tut03: 131 132Tutorial 3: My URLs have parameters 133################################### 134 135In the previous tutorial, we have seen how to create an application 136that could generate a random string. Let's now assume you wish 137to indicate the length of that string dynamically. 138 139.. code-block:: python 140 :linenos: 141 142 import random 143 import string 144 145 import cherrypy 146 147 148 class StringGenerator(object): 149 @cherrypy.expose 150 def index(self): 151 return "Hello world!" 152 153 @cherrypy.expose 154 def generate(self, length=8): 155 return ''.join(random.sample(string.hexdigits, int(length))) 156 157 158 if __name__ == '__main__': 159 cherrypy.quickstart(StringGenerator()) 160 161Save this into a file named `tut03.py` and run it as follows: 162 163.. code-block:: bash 164 165 $ python tut03.py 166 167Go now to http://localhost:8080/generate?length=16 and your browser 168will display a generated string of length 16. Notice how 169we benefit from Python's default arguments' values to support 170URLs such as http://localhost:8080/generate still. 171 172In a URL such as this one, the section after `?` is called a 173query-string. Traditionally, the query-string is used to 174contextualize the URL by passing a set of (key, value) pairs. The 175format for those pairs is `key=value`. Each pair being 176separated by a `&` character. 177 178Notice how we have to convert the given `length` value to 179an integer. Indeed, values are sent out from the client 180to our server as strings. 181 182Much like CherryPy maps URL path segments to exposed functions, 183query-string keys are mapped to those exposed function parameters. 184 185.. _tut04: 186 187Tutorial 4: Submit this form 188############################ 189 190CherryPy is a web framework upon which you build web applications. 191The most traditional shape taken by applications is through 192an HTML user-interface speaking to your CherryPy server. 193 194Let's see how to handle HTML forms via the following 195example. 196 197.. code-block:: python 198 :linenos: 199 200 import random 201 import string 202 203 import cherrypy 204 205 206 class StringGenerator(object): 207 @cherrypy.expose 208 def index(self): 209 return """<html> 210 <head></head> 211 <body> 212 <form method="get" action="generate"> 213 <input type="text" value="8" name="length" /> 214 <button type="submit">Give it now!</button> 215 </form> 216 </body> 217 </html>""" 218 219 @cherrypy.expose 220 def generate(self, length=8): 221 return ''.join(random.sample(string.hexdigits, int(length))) 222 223 224 if __name__ == '__main__': 225 cherrypy.quickstart(StringGenerator()) 226 227Save this into a file named `tut04.py` and run it as follows: 228 229.. code-block:: bash 230 231 $ python tut04.py 232 233Go now to http://localhost:8080/ and your browser and this will 234display a simple input field to indicate the length of the string 235you want to generate. 236 237Notice that in this example, the form uses the `GET` method and 238when you pressed the `Give it now!` button, the form is sent using the 239same URL as in the :ref:`previous <tut03>` tutorial. HTML forms also support the 240`POST` method, in that case the query-string is not appended to the 241URL but it sent as the body of the client's request to the server. 242However, this would not change your application's exposed method because 243CherryPy handles both the same way and uses the exposed's handler 244parameters to deal with the query-string (key, value) pairs. 245 246.. _tut05: 247 248Tutorial 5: Track my end-user's activity 249######################################## 250 251It's not uncommon that an application needs to follow the 252user's activity for a while. The usual mechanism is to use 253a `session identifier <http://en.wikipedia.org/wiki/Session_(computer_science)#HTTP_session_token>`_ 254that is carried during the conversation between the user and 255your application. 256 257.. code-block:: python 258 :linenos: 259 260 import random 261 import string 262 263 import cherrypy 264 265 266 class StringGenerator(object): 267 @cherrypy.expose 268 def index(self): 269 return """<html> 270 <head></head> 271 <body> 272 <form method="get" action="generate"> 273 <input type="text" value="8" name="length" /> 274 <button type="submit">Give it now!</button> 275 </form> 276 </body> 277 </html>""" 278 279 @cherrypy.expose 280 def generate(self, length=8): 281 some_string = ''.join(random.sample(string.hexdigits, int(length))) 282 cherrypy.session['mystring'] = some_string 283 return some_string 284 285 @cherrypy.expose 286 def display(self): 287 return cherrypy.session['mystring'] 288 289 290 if __name__ == '__main__': 291 conf = { 292 '/': { 293 'tools.sessions.on': True 294 } 295 } 296 cherrypy.quickstart(StringGenerator(), '/', conf) 297 298Save this into a file named `tut05.py` and run it as follows: 299 300.. code-block:: bash 301 302 $ python tut05.py 303 304In this example, we generate the string as in the 305:ref:`previous <tut04>` tutorial but also store it in the current 306session. If you go to http://localhost:8080/, generate a 307random string, then go to http://localhost:8080/display, you 308will see the string you just generated. 309 310The lines 30-34 show you how to enable the session support 311in your CherryPy application. By default, CherryPy will save 312sessions in the process's memory. It supports more persistent 313:ref:`backends <basicsession>` as well. 314 315Tutorial 6: What about my javascripts, CSS and images? 316###################################################### 317 318Web applications are usually also made of static content such 319as javascript, CSS files or images. CherryPy provides support 320to serve static content to end-users. 321 322Let's assume, you want to associate a stylesheet with your 323application to display a blue background color (why not?). 324 325First, save the following stylesheet into a file named `style.css` 326and stored into a local directory `public/css`. 327 328.. code-block:: css 329 :linenos: 330 331 body { 332 background-color: blue; 333 } 334 335Now let's update the HTML code so that we link to the stylesheet 336using the http://localhost:8080/static/css/style.css URL. 337 338.. code-block:: python 339 :linenos: 340 341 import os, os.path 342 import random 343 import string 344 345 import cherrypy 346 347 348 class StringGenerator(object): 349 @cherrypy.expose 350 def index(self): 351 return """<html> 352 <head> 353 <link href="/static/css/style.css" rel="stylesheet"> 354 </head> 355 <body> 356 <form method="get" action="generate"> 357 <input type="text" value="8" name="length" /> 358 <button type="submit">Give it now!</button> 359 </form> 360 </body> 361 </html>""" 362 363 @cherrypy.expose 364 def generate(self, length=8): 365 some_string = ''.join(random.sample(string.hexdigits, int(length))) 366 cherrypy.session['mystring'] = some_string 367 return some_string 368 369 @cherrypy.expose 370 def display(self): 371 return cherrypy.session['mystring'] 372 373 374 if __name__ == '__main__': 375 conf = { 376 '/': { 377 'tools.sessions.on': True, 378 'tools.staticdir.root': os.path.abspath(os.getcwd()) 379 }, 380 '/static': { 381 'tools.staticdir.on': True, 382 'tools.staticdir.dir': './public' 383 } 384 } 385 cherrypy.quickstart(StringGenerator(), '/', conf) 386 387 388Save this into a file named `tut06.py` and run it as follows: 389 390.. code-block:: bash 391 392 $ python tut06.py 393 394Going to http://localhost:8080/, you should be greeted by a flashy blue color. 395 396CherryPy provides support to serve a single file or a complete 397directory structure. Most of the time, this is what you'll end 398up doing so this is what the code above demonstrates. First, we 399indicate the `root` directory of all of our static content. This 400must be an absolute path for security reason. CherryPy will 401complain if you provide only relative paths when looking for a 402match to your URLs. 403 404Then we indicate that all URLs which path segment starts with `/static` 405will be served as static content. We map that URL to the `public` 406directory, a direct child of the `root` directory. The entire 407sub-tree of the `public` directory will be served as static content. 408CherryPy will map URLs to path within that directory. This is why 409`/static/css/style.css` is found in `public/css/style.css`. 410 411Tutorial 7: Give us a REST 412########################## 413 414It's not unusual nowadays that web applications expose some sort 415of datamodel or computation functions. Without going into 416its details, one strategy is to follow the `REST principles 417edicted by Roy T. Fielding 418<http://www.ibm.com/developerworks/library/ws-restful/index.html>`_. 419 420Roughly speaking, it assumes that you can identify a resource 421and that you can address that resource through that identifier. 422 423"What for?" you may ask. Well, mostly, these principles are there 424to ensure that you decouple, as best as you can, the entities 425your application expose from the way they are manipulated or 426consumed. To embrace this point of view, developers will 427usually design a web API that expose pairs of `(URL, HTTP method, data, constraints)`. 428 429.. note:: 430 431 You will often hear REST and web API together. The former is 432 one strategy to provide the latter. This tutorial will not go 433 deeper in that whole web API concept as it's a much more 434 engaging subject, but you ought to read more about it online. 435 436 437Lets go through a small example of a very basic web API 438mildly following REST principles. 439 440.. code-block:: python 441 :linenos: 442 443 import random 444 import string 445 446 import cherrypy 447 448 449 @cherrypy.expose 450 class StringGeneratorWebService(object): 451 452 @cherrypy.tools.accept(media='text/plain') 453 def GET(self): 454 return cherrypy.session['mystring'] 455 456 def POST(self, length=8): 457 some_string = ''.join(random.sample(string.hexdigits, int(length))) 458 cherrypy.session['mystring'] = some_string 459 return some_string 460 461 def PUT(self, another_string): 462 cherrypy.session['mystring'] = another_string 463 464 def DELETE(self): 465 cherrypy.session.pop('mystring', None) 466 467 468 if __name__ == '__main__': 469 conf = { 470 '/': { 471 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 472 'tools.sessions.on': True, 473 'tools.response_headers.on': True, 474 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 475 } 476 } 477 cherrypy.quickstart(StringGeneratorWebService(), '/', conf) 478 479 480Save this into a file named `tut07.py` and run it as follows: 481 482.. code-block:: bash 483 484 $ python tut07.py 485 486Before we see it in action, let's explain a few things. Until now, 487CherryPy was creating a tree of exposed methods that were used to 488match URLs. In the case of our web API, we want to stress the role 489played by the actual requests' HTTP methods. So we created 490methods that are named after them and they are all exposed at once 491by decorating the class itself with `cherrypy.expose`. 492 493However, we must then switch from the default mechanism of matching 494URLs to method for one that is aware of the whole HTTP method 495shenanigan. This is what goes on line 27 where we create 496a :class:`~cherrypy.dispatch.MethodDispatcher` instance. 497 498Then we force the responses `content-type` to be `text/plain` and 499we finally ensure that `GET` requests will only be responded to clients 500that accept that `content-type` by having a `Accept: text/plain` 501header set in their request. However, we do this only for that 502HTTP method as it wouldn't have much meaning on the other methods. 503 504 505For the purpose of this tutorial, we will be using a Python client 506rather than your browser as we wouldn't be able to actually try 507our web API otherwise. 508 509Please install `requests <http://www.python-requests.org/en/latest/>`_ 510through the following command: 511 512.. code-block:: bash 513 514 $ pip install requests 515 516Then fire up a Python terminal and try the following commands: 517 518.. code-block:: pycon 519 :linenos: 520 521 >>> import requests 522 >>> s = requests.Session() 523 >>> r = s.get('http://127.0.0.1:8080/') 524 >>> r.status_code 525 500 526 >>> r = s.post('http://127.0.0.1:8080/') 527 >>> r.status_code, r.text 528 (200, u'04A92138') 529 >>> r = s.get('http://127.0.0.1:8080/') 530 >>> r.status_code, r.text 531 (200, u'04A92138') 532 >>> r = s.get('http://127.0.0.1:8080/', headers={'Accept': 'application/json'}) 533 >>> r.status_code 534 406 535 >>> r = s.put('http://127.0.0.1:8080/', params={'another_string': 'hello'}) 536 >>> r = s.get('http://127.0.0.1:8080/') 537 >>> r.status_code, r.text 538 (200, u'hello') 539 >>> r = s.delete('http://127.0.0.1:8080/') 540 >>> r = s.get('http://127.0.0.1:8080/') 541 >>> r.status_code 542 500 543 544The first and last `500` responses stem from the fact that, in 545the first case, we haven't yet generated a string through `POST` and, 546on the latter case, that it doesn't exist after we've deleted it. 547 548Lines 12-14 show you how the application reacted when our client requested 549the generated string as a JSON format. Since we configured the 550web API to only support plain text, it returns the appropriate 551`HTTP error code <http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.4.7>`_. 552 553 554.. note:: 555 556 We use the `Session <http://www.python-requests.org/en/latest/user/advanced/#session-objects>`_ 557 interface of `requests` so that it takes care of carrying the 558 session id stored in the request cookie in each subsequent 559 request. That is handy. 560 561.. important:: 562 563 It's all about RESTful URLs these days, isn't it? 564 565 It is likely your URL will be made of dynamic parts that you 566 will not be able to match to page handlers. For example, 567 ``/library/12/book/15`` cannot be directly handled by the 568 default CherryPy dispatcher since the segments ``12`` and 569 ``15`` will not be matched to any Python callable. 570 571 This can be easily workaround with two handy CherryPy features 572 explained in the :ref:`advanced section <restful>`. 573 574 575 576.. _tut08: 577 578 579Tutorial 8: Make it smoother with Ajax 580###################################### 581 582In the recent years, web applications have moved away from the 583simple pattern of "HTML forms + refresh the whole page". This 584traditional scheme still works very well but users have become used 585to web applications that don't refresh the entire page. 586Broadly speaking, web applications carry code performed 587client-side that can speak with the backend without having to 588refresh the whole page. 589 590This tutorial will involve a little more code this time around. First, 591let's see our CSS stylesheet located in `public/css/style.css`. 592 593.. code-block:: css 594 :linenos: 595 596 body { 597 background-color: blue; 598 } 599 600 #the-string { 601 display: none; 602 } 603 604We're adding a simple rule about the element that will display 605the generated string. By default, let's not show it up. 606Save the following HTML code into a file named `index.html`. 607 608.. code-block:: html 609 :linenos: 610 611 <!DOCTYPE html> 612 <html> 613 <head> 614 <link href="/static/css/style.css" rel="stylesheet"> 615 <script src="http://code.jquery.com/jquery-2.0.3.min.js"></script> 616 <script type="text/javascript"> 617 $(document).ready(function() { 618 619 $("#generate-string").click(function(e) { 620 $.post("/generator", {"length": $("input[name='length']").val()}) 621 .done(function(string) { 622 $("#the-string").show(); 623 $("#the-string input").val(string); 624 }); 625 e.preventDefault(); 626 }); 627 628 $("#replace-string").click(function(e) { 629 $.ajax({ 630 type: "PUT", 631 url: "/generator", 632 data: {"another_string": $("#the-string input").val()} 633 }) 634 .done(function() { 635 alert("Replaced!"); 636 }); 637 e.preventDefault(); 638 }); 639 640 $("#delete-string").click(function(e) { 641 $.ajax({ 642 type: "DELETE", 643 url: "/generator" 644 }) 645 .done(function() { 646 $("#the-string").hide(); 647 }); 648 e.preventDefault(); 649 }); 650 651 }); 652 </script> 653 </head> 654 <body> 655 <input type="text" value="8" name="length"/> 656 <button id="generate-string">Give it now!</button> 657 <div id="the-string"> 658 <input type="text" /> 659 <button id="replace-string">Replace</button> 660 <button id="delete-string">Delete it</button> 661 </div> 662 </body> 663 </html> 664 665We'll be using the `jQuery framework <http://jquery.com/>`_ 666out of simplicity but feel free to replace it with your 667favourite tool. The page is composed of simple HTML elements 668to get user input and display the generated string. It also 669contains client-side code to talk to the backend API that 670actually performs the hard work. 671 672Finally, here's the application's code that serves the 673HTML page above and responds to requests to generate strings. 674Both are hosted by the same application server. 675 676.. code-block:: python 677 :linenos: 678 679 import os, os.path 680 import random 681 import string 682 683 import cherrypy 684 685 686 class StringGenerator(object): 687 @cherrypy.expose 688 def index(self): 689 return open('index.html') 690 691 692 @cherrypy.expose 693 class StringGeneratorWebService(object): 694 695 @cherrypy.tools.accept(media='text/plain') 696 def GET(self): 697 return cherrypy.session['mystring'] 698 699 def POST(self, length=8): 700 some_string = ''.join(random.sample(string.hexdigits, int(length))) 701 cherrypy.session['mystring'] = some_string 702 return some_string 703 704 def PUT(self, another_string): 705 cherrypy.session['mystring'] = another_string 706 707 def DELETE(self): 708 cherrypy.session.pop('mystring', None) 709 710 711 if __name__ == '__main__': 712 conf = { 713 '/': { 714 'tools.sessions.on': True, 715 'tools.staticdir.root': os.path.abspath(os.getcwd()) 716 }, 717 '/generator': { 718 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 719 'tools.response_headers.on': True, 720 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 721 }, 722 '/static': { 723 'tools.staticdir.on': True, 724 'tools.staticdir.dir': './public' 725 } 726 } 727 webapp = StringGenerator() 728 webapp.generator = StringGeneratorWebService() 729 cherrypy.quickstart(webapp, '/', conf) 730 731Save this into a file named `tut08.py` and run it as follows: 732 733.. code-block:: bash 734 735 $ python tut08.py 736 737Go to http://127.0.0.1:8080/ and play with the input and buttons 738to generate, replace or delete the strings. Notice how the page 739isn't refreshed, simply part of its content. 740 741Notice as well how your frontend converses with the backend using 742a straightfoward, yet clean, web service API. That same API 743could easily be used by non-HTML clients. 744 745.. _tut09: 746 747Tutorial 9: Data is all my life 748############################### 749 750Until now, all the generated strings were saved in the 751session, which by default is stored in the process memory. Though, 752you can persist sessions on disk or in a distributed memory store, 753this is not the right way of keeping your data on the long run. 754Sessions are there to identify your user and carry as little 755amount of data as necessary for the operation carried by the user. 756 757To store, persist and query data you need a proper database server. 758There exist many to choose from with various paradigm support: 759 760- relational: PostgreSQL, SQLite, MariaDB, Firebird 761- column-oriented: HBase, Cassandra 762- key-store: redis, memcached 763- document oriented: Couchdb, MongoDB 764- graph-oriented: neo4j 765 766Let's focus on the relational ones since they are the most common 767and probably what you will want to learn first. 768 769For the sake of reducing the number of dependencies for these 770tutorials, we will go for the :mod:`sqlite` database which 771is directly supported by Python. 772 773Our application will replace the storage of the generated 774string from the session to a SQLite database. The application 775will have the same HTML code as :ref:`tutorial 08 <tut08>`. 776So let's simply focus on the application code itself: 777 778.. code-block:: python 779 :linenos: 780 781 import os, os.path 782 import random 783 import sqlite3 784 import string 785 import time 786 787 import cherrypy 788 789 DB_STRING = "my.db" 790 791 792 class StringGenerator(object): 793 @cherrypy.expose 794 def index(self): 795 return open('index.html') 796 797 798 @cherrypy.expose 799 class StringGeneratorWebService(object): 800 801 @cherrypy.tools.accept(media='text/plain') 802 def GET(self): 803 with sqlite3.connect(DB_STRING) as c: 804 cherrypy.session['ts'] = time.time() 805 r = c.execute("SELECT value FROM user_string WHERE session_id=?", 806 [cherrypy.session.id]) 807 return r.fetchone() 808 809 def POST(self, length=8): 810 some_string = ''.join(random.sample(string.hexdigits, int(length))) 811 with sqlite3.connect(DB_STRING) as c: 812 cherrypy.session['ts'] = time.time() 813 c.execute("INSERT INTO user_string VALUES (?, ?)", 814 [cherrypy.session.id, some_string]) 815 return some_string 816 817 def PUT(self, another_string): 818 with sqlite3.connect(DB_STRING) as c: 819 cherrypy.session['ts'] = time.time() 820 c.execute("UPDATE user_string SET value=? WHERE session_id=?", 821 [another_string, cherrypy.session.id]) 822 823 def DELETE(self): 824 cherrypy.session.pop('ts', None) 825 with sqlite3.connect(DB_STRING) as c: 826 c.execute("DELETE FROM user_string WHERE session_id=?", 827 [cherrypy.session.id]) 828 829 830 def setup_database(): 831 """ 832 Create the `user_string` table in the database 833 on server startup 834 """ 835 with sqlite3.connect(DB_STRING) as con: 836 con.execute("CREATE TABLE user_string (session_id, value)") 837 838 839 def cleanup_database(): 840 """ 841 Destroy the `user_string` table from the database 842 on server shutdown. 843 """ 844 with sqlite3.connect(DB_STRING) as con: 845 con.execute("DROP TABLE user_string") 846 847 848 if __name__ == '__main__': 849 conf = { 850 '/': { 851 'tools.sessions.on': True, 852 'tools.staticdir.root': os.path.abspath(os.getcwd()) 853 }, 854 '/generator': { 855 'request.dispatch': cherrypy.dispatch.MethodDispatcher(), 856 'tools.response_headers.on': True, 857 'tools.response_headers.headers': [('Content-Type', 'text/plain')], 858 }, 859 '/static': { 860 'tools.staticdir.on': True, 861 'tools.staticdir.dir': './public' 862 } 863 } 864 865 cherrypy.engine.subscribe('start', setup_database) 866 cherrypy.engine.subscribe('stop', cleanup_database) 867 868 webapp = StringGenerator() 869 webapp.generator = StringGeneratorWebService() 870 cherrypy.quickstart(webapp, '/', conf) 871 872Save this into a file named `tut09.py` and run it as follows: 873 874.. code-block:: bash 875 876 $ python tut09.py 877 878Let's first see how we create two functions that create 879and destroy the table within our database. These functions 880are registered to the CherryPy's server on lines 85-86, 881so that they are called when the server starts and stops. 882 883Next, notice how we replaced all the session code with calls 884to the database. We use the session id to identify the 885user's string within our database. Since the session will go 886away after a while, it's probably not the right approach. 887A better idea would be to associate the user's login or 888more resilient unique identifier. For the sake of our 889demo, this should do. 890 891.. important:: 892 893 In this example, we must still set the session to a dummy value 894 so that the session is not `discarded <https://cherrypy.readthedocs.org/en/latest/pkg/cherrypy.lib.html?highlight=fixation#session-fixation-protection>`_ 895 on each request by CherryPy. Since we now use the database 896 to store the generated string, we simply store a dummy 897 timestamp inside the session. 898 899.. note:: 900 901 Unfortunately, sqlite in Python forbids us 902 to share a connection between threads. Since CherryPy is a 903 multi-threaded server, this would be an issue. This is the 904 reason why we open and close a connection to the database 905 on each call. This is clearly not really production friendly, 906 and it is probably advisable to either use a more capable 907 database engine or a higher level library, such as 908 `SQLAlchemy <http://sqlalchemy.readthedocs.org>`_, to better 909 support your application's needs. 910 911.. _tut10: 912 913Tutorial 10: Make it a modern single-page application with React.js 914################################################################### 915 916In the recent years, client-side single-page applications (SPA) have 917gradually eaten server-side generated content web applications's lunch. 918 919This tutorial demonstrates how to integrate with 920`React.js <https://facebook.github.io/react/>`_, a Javascript library 921for SPA released by Facebook in 2013. Please refer to React.js 922documentation to learn more about it. 923 924To demonstrate it, let's use the code from :ref:`tutorial 09 <tut09>`. 925However, we will be replacing the HTML and Javascript code. 926 927First, let's see how our HTML code has changed: 928 929.. code-block:: html 930 :linenos: 931 932 <!DOCTYPE html> 933 <html> 934 <head> 935 <link href="/static/css/style.css" rel="stylesheet"> 936 <script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.13.3/react.js"></script> 937 <script src="http://code.jquery.com/jquery-2.1.1.min.js"></script> 938 <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script> 939 </head> 940 <body> 941 <div id="generator"></div> 942 <script type="text/babel" src="static/js/gen.js"></script> 943 </body> 944 </html> 945 946Basically, we have removed the entire Javascript code that was using jQuery. 947Instead, we load the React.js library as well as a new, local, 948Javascript module, named ``gen.js`` and located in the ``public/js`` 949directory: 950 951.. code-block:: javascript 952 :linenos: 953 954 var StringGeneratorBox = React.createClass({ 955 handleGenerate: function() { 956 var length = this.state.length; 957 this.setState(function() { 958 $.ajax({ 959 url: this.props.url, 960 dataType: 'text', 961 type: 'POST', 962 data: { 963 "length": length 964 }, 965 success: function(data) { 966 this.setState({ 967 length: length, 968 string: data, 969 mode: "edit" 970 }); 971 }.bind(this), 972 error: function(xhr, status, err) { 973 console.error(this.props.url, 974 status, err.toString() 975 ); 976 }.bind(this) 977 }); 978 }); 979 }, 980 handleEdit: function() { 981 var new_string = this.state.string; 982 this.setState(function() { 983 $.ajax({ 984 url: this.props.url, 985 type: 'PUT', 986 data: { 987 "another_string": new_string 988 }, 989 success: function() { 990 this.setState({ 991 length: new_string.length, 992 string: new_string, 993 mode: "edit" 994 }); 995 }.bind(this), 996 error: function(xhr, status, err) { 997 console.error(this.props.url, 998 status, err.toString() 999 ); 1000 }.bind(this) 1001 }); 1002 }); 1003 }, 1004 handleDelete: function() { 1005 this.setState(function() { 1006 $.ajax({ 1007 url: this.props.url, 1008 type: 'DELETE', 1009 success: function() { 1010 this.setState({ 1011 length: "8", 1012 string: "", 1013 mode: "create" 1014 }); 1015 }.bind(this), 1016 error: function(xhr, status, err) { 1017 console.error(this.props.url, 1018 status, err.toString() 1019 ); 1020 }.bind(this) 1021 }); 1022 }); 1023 }, 1024 handleLengthChange: function(length) { 1025 this.setState({ 1026 length: length, 1027 string: "", 1028 mode: "create" 1029 }); 1030 }, 1031 handleStringChange: function(new_string) { 1032 this.setState({ 1033 length: new_string.length, 1034 string: new_string, 1035 mode: "edit" 1036 }); 1037 }, 1038 getInitialState: function() { 1039 return { 1040 length: "8", 1041 string: "", 1042 mode: "create" 1043 }; 1044 }, 1045 render: function() { 1046 return ( 1047 <div className="stringGenBox"> 1048 <StringGeneratorForm onCreateString={this.handleGenerate} 1049 onReplaceString={this.handleEdit} 1050 onDeleteString={this.handleDelete} 1051 onLengthChange={this.handleLengthChange} 1052 onStringChange={this.handleStringChange} 1053 mode={this.state.mode} 1054 length={this.state.length} 1055 string={this.state.string}/> 1056 </div> 1057 ); 1058 } 1059 }); 1060 1061 var StringGeneratorForm = React.createClass({ 1062 handleCreate: function(e) { 1063 e.preventDefault(); 1064 this.props.onCreateString(); 1065 }, 1066 handleReplace: function(e) { 1067 e.preventDefault(); 1068 this.props.onReplaceString(); 1069 }, 1070 handleDelete: function(e) { 1071 e.preventDefault(); 1072 this.props.onDeleteString(); 1073 }, 1074 handleLengthChange: function(e) { 1075 e.preventDefault(); 1076 var length = React.findDOMNode(this.refs.length).value.trim(); 1077 this.props.onLengthChange(length); 1078 }, 1079 handleStringChange: function(e) { 1080 e.preventDefault(); 1081 var string = React.findDOMNode(this.refs.string).value.trim(); 1082 this.props.onStringChange(string); 1083 }, 1084 render: function() { 1085 if (this.props.mode == "create") { 1086 return ( 1087 <div> 1088 <input type="text" ref="length" defaultValue="8" value={this.props.length} onChange={this.handleLengthChange} /> 1089 <button onClick={this.handleCreate}>Give it now!</button> 1090 </div> 1091 ); 1092 } else if (this.props.mode == "edit") { 1093 return ( 1094 <div> 1095 <input type="text" ref="string" value={this.props.string} onChange={this.handleStringChange} /> 1096 <button onClick={this.handleReplace}>Replace</button> 1097 <button onClick={this.handleDelete}>Delete it</button> 1098 </div> 1099 ); 1100 } 1101 1102 return null; 1103 } 1104 }); 1105 1106 React.render( 1107 <StringGeneratorBox url="/generator" />, 1108 document.getElementById('generator') 1109 ); 1110 1111Wow! What a lot of code for something so simple, isn't it? 1112The entry point is the last few lines where we indicate that we 1113want to render the HTML code of the ``StringGeneratorBox`` React.js 1114class inside the ``generator`` div. 1115 1116When the page is rendered, so is that component. Notice how it 1117is also made of another component that renders the form itself. 1118 1119This might be a little over the top for such a simple example 1120but hopefully will get you started with React.js in the process. 1121 1122There is not much to say and, hopefully, the meaning of that code 1123is rather clear. The component has an internal `state <https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html>`_ 1124in which we store the current string as generated/modified by the user. 1125 1126When the user `changes the content of the input boxes <https://facebook.github.io/react/docs/forms.html>`_, 1127the state is updated on the client side. Then, when a button is clicked, 1128that state is sent out to the backend server using the API endpoint 1129and the appropriate action takes places. Then, the state is updated and so is the view. 1130 1131 1132Tutorial 11: Organize my code 1133############################# 1134 1135CherryPy comes with a powerful architecture 1136that helps you organizing your code in a way that should make 1137it easier to maintain and more flexible. 1138 1139Several mechanisms are at your disposal, this tutorial will focus 1140on the three main ones: 1141 1142- :ref:`dispatchers <dispatchers>` 1143- :ref:`tools <tools>` 1144- :ref:`plugins <busplugins>` 1145 1146In order to understand them, let's imagine you are at a superstore: 1147 1148- You have several tills and people queuing for each of them (those are your requests) 1149- You have various sections with food and other stuff (these are your data) 1150- Finally you have the superstore people and their daily tasks 1151 to make sure sections are always in order (this is your backend) 1152 1153In spite of being really simplistic, this is not far from how your 1154application behaves. CherryPy helps you structure your application 1155in a way that mirrors these high-level ideas. 1156 1157Dispatchers 1158^^^^^^^^^^^ 1159 1160Coming back to the superstore example, it is likely that you will 1161want to perform operations based on the till: 1162 1163- Have a till for baskets with less than ten items 1164- Have a till for disabled people 1165- Have a till for pregnant women 1166- Have a till where you can only using the store card 1167 1168To support these use-cases, CherryPy provides a mechanism called 1169a :ref:`dispatcher <dispatchers>`. A dispatcher is executed early 1170during the request processing in order to determine which piece of 1171code of your application will handle the incoming request. Or, to 1172continue on the store analogy, a dispatcher will decide which 1173till to lead a customer to. 1174 1175Tools 1176^^^^^ 1177 1178Let's assume your store has decided to operate a discount spree but, 1179only for a specific category of customers. CherryPy will deal 1180with such use case via a mechanism called a :ref:`tool <tools>`. 1181 1182A tool is a piece of code that runs on a per-request 1183basis in order to perform additional work. Usually a tool is a 1184simple Python function that is executed at a given point during 1185the process of the request by CherryPy. 1186 1187Plugins 1188^^^^^^^ 1189 1190As we have seen, the store has a crew of people dedicated to manage 1191the stock and deal with any customers' expectation. 1192 1193In the CherryPy world, this translates into having functions 1194that run outside of any request life-cycle. These functions should 1195take care of background tasks, long lived connections (such as 1196those to a database for instance), etc. 1197 1198:ref:`Plugins <busplugins>` are called that way because 1199they work along with the CherryPy :ref:`engine <cpengine>` 1200and extend it with your operations. 1201