The JavaScript Parts ・々 ' ve created our classes in Python and coded our templates in HTML, so now it's time for a bit ofJavaScript. The template file that we created included a JavaScript file called ール〃れ that was supposed tO be served from the static directory. Let's create that file now. Create a new directory called static and create a file called ル″れ inside it. Set up the basics of that file with the following code: google. load("jquery' , google ・ set0nLoadCa11back( function() { $(document) ・ ready(function() { T. init(); T. p 。 11 ( ) ; }); var T = { cursor: false, types: [ ・ hashtags ・ , ー retweets ー T. init = function() { t 、 ・ links ・ ] $(' input[type=checkbox] つ . c1ick(). click); T. click = function(ev) { if(ev. target. id = ・ a11 ' ) { for(i in T. types) $('#' + T. types[i]) . attr( ・ checked ・ , $('#all ・ ). attr( ・ checked')); else $('#all ・ ). attr( ・ checked ・ , false); = function ( ) { T. P011 var args if(). cursor) args. cursor = T. cursor; $ ・ ajax({ "/updates" url: "POST" type : dataType: json" data : $. param(args), success: T. new tweets 96 ー Chapter 5 : Taming the Firehose with Tornad0 an object called T. alization functions. Once that callback arrives, we run the init and P011 methods on the jQuery library for us and wait for a callback from GoogIe before we run any initi- This JavaScript sets up our application. Once again, we ask Google's Ajax APIs to load
Standard Requests Message sent t0 Peter Standard HTTP Servers Andrew Sometime later Check fo 「 new messages Standard HTTP Servers Peter ん g Ⅲ℃ 4-7. S 〃ぬ HTTP 川い ge delivery ( 0 m etd Se rver handles message routing Casey Message sent t0 Pete r lmmediately " delivered Andrew Peter Clients stay ( 0 n nected t0 serve 「 日 g Ⅲ℃ 4-2. Cometd HTTP 川覊 ge delivery Aside from the handshakes and housekeeping involved, the protocol describes a system that actually is quite simple for day-to-day uses. A client subscribes t0 a channel by name, which tends t0 be something like / f00 , /foo/bar, or /chat. Channel globbing is also supported, SO a user can subscribe t0 / f00 / * * , which would include channels such as / f00 , /foo/bar, and /foo/zab. Messages are then sent [ 0 the different named channels. For example, a server will send a message t0 /foo/bar and any client that has subscribed t0 that channel will receive the message. clients can alSO send messages tO specific channels and, assuming the A Crash Course in Server Push ー 59
the basic CSS 千 or the site - く link rel="stylesheet" type="text/css" href="/static/main. css" / > く /head> く body onun10ad="GUn10ad()"> く div id="header"> く h1>iPandemic く /hl> く ul id="options"> く / ul > く /diV> く diV id="messageWindow"> く form id="msg-form"> く input id="msg" placeholder="l challenge you t0 a duel! " / > く input type="submit" id="msg-send" value="Send" / 〉 send a message (whatever you want) t0 challenge the user. く /P> く /form> く /diV> く div id="map"> く /diV> く div id="fullscreen"> く /div> く div id="footer"> { % if a110W sms % } SMS is enabled tO { { mobile number } } { % else % } To enable SMS send " く strong>{{ mobile keyword } } { { mobile_key }} く /strong>" t0 { { mobile short code } }. { % endif % } く a href="{{ logout url }}">Log 0ut く /a> く /div> く /b0dY> く /html> We don't have much to d0 for a user who isn't logged in, but once she does 10g in, we're going t0 want t0 load up the JavaScript files that we'll be using from Google. After the standard includes, this HTML file is just the shellthat we'll end up populating with JavaScript. Feel free t0 change the CSS styles, but the id attributes should be retained because we'll be referring t0 them directly in the JavaScript. Starting from the top of the BODY, we've added a header and an unordered list that will contain any number Of options for the user at certain points during the game. BelOW that is a DIV, hidden by default, that will allow a user t0 send a message t0 another user during a challenge. Below that we have the map DIV, in which we'll draw the actual 1 れ ap. The fullscreen DIV is used tO overlay content across the entire screen when the user takes certain actlons. 238 ー Chapter10: Putting lt 則 Together
We're going tO build the entire client-side functionality Ofour Twitter application inside this T Object. ln constructing the Object, we initialize tWO variables that we'll need in T'S methods, cursor and types. You probably remember that the logic that we built into the 川〃〃げ . file relies on receiving a cursor parameter sent from the client. ThiS variable iS used tO determine which tweets have already been seen by the client when it requests new updates. This variable iS stored in the object's cursor field. we alSO create an array with the names of the possible types of tweets we'll be filtering, which will allow us [ 0 loop through them in various places in the COde. The first ofT's methods thatwe define is the init method. This is used [ 0 set up anything before we actually get to requesting data from the server. lt uses jQuery [ 0 bind a method to the onC1ick event of every checkbox on the page [ 0 the T. click method. The T. click method is defined next. This method is called only as a callback from a JavaScript onClick event on one Ofour checkboxes. T. click is just t0 update the check- boxes in the user interface tO behave as a user would expect. We're dOing this because one of our checkboxes works a bit differently than the rest. A checkbox called "AII" will enable the user tO see all types Of tweets as they come in. If users click on that, we wantto set all of the other checkboxes to match it. So if a user checks the "AII ” check- box, we want all of the other types to be checked as well. If the user unchecks the box, we want all checkboxes [ 0 become unchecked. We d0 that bylooping through the array of T. types that we created earlier and checking or unchecking all of the matching checkboxes. If the user isn't clicking on the 。、 AII" checkbox, we mark it as unchecked. We're also creating the P011 method here. This is the method that actually makes the long polling request tO our Tornado server. If our Object has a cursor set, we'll pass that along as a parameter. 嶬 also specify T. new tweets as a callback method for when the request is finished. The request is being made t0 the URL /updates, which has been mapped ⅲ Tornad0 t0 the UpdateHand1er method in 川〃〃げ . 〃 ). Now that we're polling for updates, let's create the method that will catch them as they are sent by the server. Append the following code t0 your [ ル襯 e s file: d. s1ideDown(); $('#content' ) . prepend(d); d. css('display', none• ) ; var d = $(response. tweets[i] . html); if(). should show tweet(response. tweets[i])) { = 0 ; i く response. tweets. length; + + i ) { for(var T. cursor response. tweets[response. tweets. length - 1 ] . id; $('#waiting ・ ) . remove(); if(!T. cursor) T. new tweets = function(response) { From the Firehose to the Web B 「 owse 「
/ / clean up the DOM var limit = 100 ; var messages for(var x = messages. length-l; x > limit; $(messages[x]) ・ remove(); T. p 。 11 ( ) ; If we ran this COde now, it would give us some JavaScript errors, but we've got every- thing in place [ 0 actually show the tweets on the page. The first thing we d0 is check [ 0 see whether this is our first time through this function.\Ve determine this by checking for the existence Of a cursor variable; if there isn't one, it's our first time through and we remove the "Waiting for content... ' message. After that, we save the id Of the last tweet t0 our cursor field. This is the field that is sent back in the P011 method. Next, we IOOP through all Of the tweets we've Just received, check tO see whether we should display them, and actually add them to the page. We've already built the HTML string in 〃〃ビ r. , so all we need [ 0 d0 is add i い 0 the DOM. We use jQuery t0 create the DOM element, prepend it to our content DIV, and show it with the s1ideDown effect. We'II be appending quite a few tweets to the page every second, so the DOM is going t0 get quite large. TO counter this, we re going t0 keep only the last IOO tweets on the page. We use jQuery's CSS selector methods [ 0 grab all of the messages and remove all but the latest IOO of them. Once we've cleaned up the DOM a bit, we call the P011 method and start the process over agalll. You've probably noticed that we checked [ 0 see whether we should display the tweets using the should show tweet method, but we haven't defined it yet. SO let's create that method now. Add the following code t0 your 加襯げ file: T. should show tweet = function(tweet) { $('#all-count ・ ). text(parselnt($('#all-count ・ ) . text()) + 1); = false; var ShOW tweet for(x in T. types) { var type = T. types[x]; / / does the tweet have the specified type? if(tweet. stats[type] . length) { / / does the user want tO see it? if($("#" + type). attr('checked ・ )) show tweet = true; var count div = $ ( ・ # ・ + type + '-count'); count div. text(parselnt(count div. text()) + 1); return show tweet; 98 ー Chapter 5 : Taming the Firehose with Tornad0
message ・ reply(text) message ・ reply()l don't understand ・ %s ・ " % cmd) This gives the user several different options for interacting with the application. First off, by typing echo He110 world, this would reply t0 the client with the string He110 Wor1d. If the user sends the て 0t13 command, we'll respond by replacing each character in the body with the character thirteen places away in alphabetical order. The Python standard library provides this functionality in the same library that can encode strings in base64 and compress them with zlib. 嶬℃ receive the sum C01 れ 1 れ and , we'll assume the rest Of the string iS a series Of number separated by spaces. We'll take each 0f those numbers and add them together, returning the final sum. We'll also take the each 0f the numbers and join them with a plus character t0 display the entire equation. If we have trouble adding them, we've most likely received something that isn't a number, SO we just complain back [ 0 the user in thiS case. If the user either sends a command that we dO not understand or doesn't send one at all, we Just respond and tell him that we don't understand. Figure 7 ー 13 shows a chat session using all Of these commands. 合 0 0 Chat with instant-messaging Jabber 贈 w 1 11 : PM echO everyone! else: S Ot. ( om 0 ッ「 0 ッ ~ 0 ッ 印 everyone' 「 0t13 リ「躔 b Ntnva HeIIO Again sum2 5 809 42 2 + 5 + 809 + 42 = 858 日 g 尾 7-73. Testing 厖 co 襯川 4 〃ホ 1 ー Chapter 7:Instant Messaging
WhatIs ReaItime? Since the explosion 0f the Web, developers have been inclined t0 think in terms 0f building websites. Even in this b00k, l've spent a good deal 0f time writing about building websites. But make no mistake about it, a realtime user experience does not exist entirely inside a web browser. The original web browsers were designed t0 load and display web pages. The idea 0f a web page iS quite similar tO the printed page. lt iS a static document' stored in a computer, but a static document nonetheless. The interface Of web browsers evolved to work within this paradigm. There is a Next button and a Back button designed t0 take you from the current page t0 the page that you viewed previously. That makes perfect sense when you re working with documents. However, the Web is quite quickly shifting away from a document-based paradigm t0 a web-based form 0f commumcatlon. lt used t0 be that a web page was more or less published t0 the Web. A page was created and given a specific URI, and when the user wentto that page, it was pretty clear what t0 expect. Now we have sites like Faceb00k ( 中 : 〃ルルル扣記わ 00 たェ 0 川 ) where the same URL is not only different for each of the hundreds of millions of different users, but it changes moments after a user loads the page. ChangingInteractions ln the past, the interaction between a user and a web application was very simple. When a user wanted content, she would load up her browser, point it at a URL, and get the content (see Figure 1 ー 1 ). If she wanted t0 write a blog post, she'd load up her browser, fill out a form, and press submit. When she wanted [ 0 see comments, it was much the same. This has changed. NO longer can a website wait for users t0 navigate t0 the right URL; the website must contact the user wherever that user may be. The paradigm has shifted from a website-centric 1 れ Odel , where the website was at the center Of the interaction, tO a user-centric model. NOW all interactions start and end at the user (see Fig- ure 1-2 ) , whether she is visiting the website or sending in Short Message Service (SMS) updates. A truly realtime experience exiStS anywhere the user iS at a given moment. If the user is interacting with the web browser, then that's the place tO contact her. If she's got her instant messenger program open, she'd better be able tO interact with your app from that window. ・ when she's Offline and your application has an important message for her, send it via SMS. Naturally, you'll need t0 ask the user's permission before you d0 some Of these things, but your application needs tO Offer them. 2 ー Chapter 1 : lntroduction
After signing up for the service and registering a keyword, you'll still need t0 create an API key to use the API functions. This is a simple process initiated from the developer section on the TextMarks website ( 印 : 〃ルルル . x la s. co 川 / ツ 4 / 尾 g 庄 You'll be greeted with a form resembling Figure 8-6. After filling outthis form, you should receive an email message from the service almost immediately with your API key. Save this key in a safe place; it'll be used when we start writing the messaging functionality. Registration Form FiII に form t*low for each a 叩ⅱⅱ intend 協 integrate with けに TextMarls V2 API. Your Web Si : http://therealtimebook.com URL ⅸ y 似 web 、い , ℃ 01 半田 IY. E-mail: tedroden@gmail.com A 朝 1 朝 1 い m , for 登 p 「 0 ℃ 0 & 5 朝 app め化 lnte 社 Use: My u “ ge. ・・ D 合 sc 「の 0 t め物 you ⅲ t 田 tO 0 0 API, ex 0 にづ traffc, 飜 c. Ylread & ag 「 to Tex ひぬⅸ sT msof rvi ( 色 REGISTER 日 g Ⅲ℃ 8-6. A 叫″ⅲれ g の 1 ハ円た 0 〃 T こ M の・ The Python class Having built the SMSService interface class, we can start building the actual service classes by expanding on the class we have already defined. ln this case, we'll create a class called Textmark. TO handle sending messages from the TextMarks service, we only need t0 override two methods. lnside your s 川 s. file, add the following code: class Textmark(SMSService) : def init_(self) : # The authentication parameters supplied by textmarks self. auth user = yo リて -0 リ t わ - user ' self. auth pass = yo - 側 t わ - P055 ・ # ( a11 the constructor Of the parent class init_(self, SMSService. 141r # textmarks. (0mな short code ノ 0 - 々 ey Ⅳ 0 て d ・ , api key= ・ Y0礪-0廖-keノ 168 ー Ch 叩 ter 8 : SMS
# if the password is wrong, don ・ t send any instant message else: self. response. out. write(json. dumps({ ・ status' 'fail' 'Wrong secret code. ' } ) ) mesg This API expects three HTTP parameters when it's called. lt expects to receive a to parameter, which is the account Of the user on the receiving end Of this message. lt alSO expects body, which will be used as the content 0f the message. Finally, because this API call will respond when anyone on the lnternet hits the URL, we're forcing every request tO know a secret COde. ThiS acts like a password, and if this is not correct, we don't send anything at all. The first thing we dO is check tO see whether the user is authenticated, because we don't want tO send instant messages via the API that we wouldn't send through the web interface. If the user iS not authenticated, respond tO the request and return frOI れ this method. ln a production environment, it would probably be wise tO respond with a different message that does not identify the exact problem. AS it stands, spammers could easily hitthis API looking for valid email addresses. Next, 、 1 れ ove on and check the pass 、 vord, or secret. Assuming it iS correct, we use the xmpp module t0 send the message and print out a JSON-formatted objectthat can be easily parsed by the caller 0f the API. If the secret is incorrect, we don't bother sending outthe message, but we d0 inform whoever contacted the API that they used the wrong secret COde. Redeploy this application, and let's test it out. From the command line, you can simply use curl or any other command-line-based HTTP client. Using curl, the command would be similar [ 0 the following: $ curl -d "secret=s3cret-cOde&b0dy=Hi +You&t0=you@gmai1 ・ ( om " \ http://application-id.appspot.com/api/send "Sent. "} {"status" "ok" mesg lmmediately after calling this method, you should receive an instant message from the application. If you do, you now have a fully functioning API that can send instant messages t0 your users. If not, check the logs in the App Engine dashboard; chances are the fix iS easy. According [ 0 the XMPP specification, a user can send messages only tO someone whO has added them tO their contact list. If you're going tO launch an application with this API, ensure that the user first sends a message t0 your application-id@appspot.com account. Once they send you a message, you'll be able t0 send them messages whenever you like. This is XMPP's way of allowing an opt-in mechanism. 150 ー Chapter 7 ロ ns ね nt Messaging
# The Send URL "http://devl.api2.textmarks.com/GroupLeader/send one message/" url # setup the http arguments args args['to ・ ] number args[ ・ msg ・ ] = b0dY self. keyword args['tm'] args[ 'api_key' ] self. api key args['auth user'] self. auth user args['auth pass'] self. auth pass # make the HTTP API ( a11 tO send the message result = urlfetch. fetch(url=url, payload=urllib. urlencode(args), method=urlfetch. POST) logging ・ info(result. content) This code should look very similar t0 the code from Chapter 8. lt's a fair bit simpler because we don't need [ 0 support more than one service' but the base logic is the same. We set up the basic API information as the member variables 0f the class and then provide a single member function. That single member function is the send method, which is used t0 actually send the message. AS parameters, it takes in the mobile number Of the recipient and the bOdy Of the message. We assemble the simple API parameters into a dictionary and postthem to the TextMarks service using the App Engine urlfetch class. ln case there are issues' we 10g the response, but other than that, we're done with the SMS class. Messenger we've already added a class to send SMS messages, but we're only part 0f the way t0 our message-sending goals. we wantto be able t0 send both SMS and XMPP-based instant messages from the same interface. TO dO that, we'll wrap the functionality up intO a class called Messenger and use that class tO send a message. TO add this class' add the following code to your 川 0 ls. file: class Messenger(0bject) : # tO send an message via SMS, simply pass this on tO the Textmark class def send via sms(self, mobile number, body) : = Textmark() sms sms. send(mobile number, b0dy) # simply call the xmpp send message method t0 send xmpp messages def send via xmpp(self, t0, b0dy) : xmpp_response = xmpp ・ send message(t0' b0dY) logging ・ info(xmpp_response) 230 ー Ch 叩 ter 10 : Putting lt 則 Together