カ e Ne 肥 - みる会図書館


検索対象: Building the Realtime User Experience
314件見つかりました。

1. Building the Realtime User Experience

/ / start pinging ・ Ana1ytics ・ ping(); This code doesn't do anything too complicated. We build the start of an object called Ana1ytics, which will contain all 0f the client-side tracking functionality. First, we de- fine a few variables that we'll be using throughoutthe object. The variable uid will be used [ 0 contain the unique identifier for each user WhO loads this page. The variables start time and last activity are used tO contain the time ofwhen the script first gets started and tO monitor the time Of the last user interaction, respectively. The final var- iable defined is called ping_url. This is used t0 inform the script which base URL to use when pinging the server. ln this case, we'll be se ハ ring this COde from our Tornado server, and the ping_url be determined by the server and populated into this field. This iS a small convenience that will allOW us tO serve thiS up and copy the file tO different servers. You could get some performance gains by se ハ ring this file from a content de- 1 ⅳ e network (CDN) and hard-coding this value in place. The next bit of code here defines the start method, which is called from each web page tO get the analytics process started. lnside this methOd we populate the timing variables start time and last activity. Next, we call the method setupUID, which will be de- fined next. Once we have the basic variables set up and the UID has been either generated or loaded, the next job is t0 attach callback methods [ 0 any 0f the events that signal the user has been active. If the user moves a mouse, presses a key, CliCkS, or scrolls, Ⅵ℃ want t0 know and update the last activity field. The only eventthat we want t0 track differently is the onbeforeunload event, which is the event that is called when a user navigates away from the page. When this happens, we want t0 not only 10g the time, but alSO ensure we get in one last ping tO the server. The ping iS defined next, but before we get t0 that, let's set up the uid field by writing the setupUID method. ln your 尾記行襯 e - ana s file, add the following method: Ana1ytics. setupUID = function() { / / check fo て a cookie "realtime analytics" var cookie name = document. cookie. split(/\; ?/g); var cookies for(var i = 0 ; i く cookies. length; + + i ) { var name = cookies[i] . split(" = COOkie name) { if (name = Ana1ytics. uid = cookies[i]. split("= return; / / if we're here, we need to generate and store a UID / / generate the UID AnaIytics. uid = new Date(). getTime(). toString(); ' + Math. f100r(Math. random() * 10000 の ; Ana1ytics. uid + = 190 ー Chapter 9 : Measuring U 史「 Engagement: Analytics on the Realtime Web

2. Building the Realtime User Experience

The App1ication class also defines and loads a membervariable called self. chat, which iS an instance Of the chat class that was defined earlier. Creating this Object here, and only here, will ensure that we have only one server-side chat Object for the entire lifetime Of the server. At the tail end Of the script, we run the basic COde tO start the server. lt parses the command-line options in case you want tO override the default port or any Other setting. Then, it instructs the Tornado's http server tO listen on that port. Finally, it starts up the 100P that runs untilthe server is killed. The JavaScript Base Now that we have a runnable Python script and a serviceable HTML page, wejust need to add some JavaScript t0 finish building the shell of our application. ln the static directory, create a file called 訪酊 . This is the file that is included via the static url function inside our 訪 - 〃 la ⅲ . / 山司 template. ln 訪 , add the following code: / / Load the YUI JavaScript from Yah00. new YAHOO. util. YUILoader({ require: ["connection" "container", "fonts" "dragdrop"], yahoo-dom-event" combine: true, / / combine the files / / minimize them filter: "MIN" / / once the includes are loaded, initialize the chat code onSuccess: function() { chat. init(); } / / our callback function / / the basic object that we' 11 be using }) ・ insert(); var chat login_panel: false, / / user id: false, user name: false, users: [ ] , user count : 0 / / the / / a11 / / the / / the chat. init = function() { current user (me) current user name (me) connected users count Of those users . / / Setup the onCIick method YAHOO. util. Event. addListener("10gin-button" "click" chat. login); / / turn the login div intO a YUI panel chat. login_panel = new YAHOO. widget. panel("login", { width: " 230PX " , chat. login_panel. render(); Setting Up the Basic C0de ー 105 constraintoviewport:true close:false, visible:true,

3. Building the Realtime User Experience

This change tells App Engine to send any requeststarting with /sms/ to the s 川 s. file. If the URL does not match that pattern, it will be picked up by the much more lib- regular expression below and sent on t0 〃ⅲ . 〃ツ . The SMS functionality is not eral . * provided by App Engine, SO we d0 not need t0 add any new inbound services [ 0 get the server [ 0 respond. Everything we d0 in this will actually be initiated via a standard HTTP request. From Google's point ofview, this is a much more standard requestthan the XMPP request we used in the previous example. TO getthis script started, let's add a 10 [ 0f the housekeeping code that is needed justto getthis thing set up ・ Create a file called s 川 s. and add the following code: import wsgiref. handlers import logging import base64 import hmac import sha import time import urllib from google ・ appengine ・ api import urlfetch, users from google ・ appengine. ext import webapp, db from google. appengine. ext. webapp. util import login_required from django ・ utils import simplejson as json # the SMSUser data Mode1 class SMSUser(db. M0de1) : the google user account and the mobile phone number # tWO properties. return Fa1se eIse: return True if sms user: sms user = SMSUser. gqI('WHERE mobile number = def is authenticated(self, mobile number) : # is a specific mobile number already authenticated? u ・ put() u SMSUser(mobi1e number=mobile number) def authenticate(self, mobile number) : # add a record for this mobile number mobile number = db. PhoneNumberProperty() account = db. UserProperty() : 1 ・ , mobile number) . get() Building the Basic Application ー 159 she interacts with this application. defines a simple db. M0de1 module that will be used t0 store data aboutthe SMS user as miliar. lt imports all of the different Python modules that this script will use. Then, it Assuming you worked through the previous example, this code should look very fa-

4. Building the Realtime User Experience

the callback URL and how to handle advertising on your keyword. Amongst the packages provided is one called GroupLeader, which contains a function called send one message that can be used tO send one message tO one user. TO use this API function, we simply need [ 0 send an HTTP POST request t0 印 : 〃 de ⅵ . a 2. x ト 〃黻 s. co 川 / Gro 〃〃 1 ヱ ade 夜ー 0 〃 e ー川 es 覊 ge /. The bulk of the code in this method sets up the arguments that get sent as the payload of that HTTP API call. After building a simple dictionary that contains all of the parameters, we run App Engine's urlfetch. fetch method, which actually makes the request. whereas 1 れ OSt Of the parameters for thiS method are contained in variables that are handled by the SMSService class, the parameters required by this API call are outlined here: to The mobile number that iS the recipient Of this message. The body of the message itself, which should be no more than 120 characters when using the free account. ThiS iS the key 、 vord ()r text mark) associated with your message. api key The TextMarks API key. auth user The username used [ 0 log in tO the Text Marks website. auth pass The password used to log in [ 0 the Text Marks website. Testing it out At this point, our code may not d0 much, but it has the ability to respond to SMS messages. ThiS iS a good time tO test ⅱ out tO ensure that we're on the right track. Just like ⅲ the previous chapter, we can use the Google App Engine Launcher to deploy this application. Press the Deploy button to redeploy the instant messaging application. Once this code has been pushed, there is nothing new [ 0 see by simply visiting the website. From your cell phone, send a text message tO 41411 using your shortcode. You can send any message tO the service that you want as long as it's preceded by the key- word that you registered with TextMarks. ln my case, I would send a message along the lines ofrealtime Hi There ! tO 41411 and wait for a response. The transaction should look like Figure 8-7. lfyou received an error message or no response at all, go [ 0 the App Engine dashboard and take a look atthe logs. Theselogs generally provide a backtrace that is helpful in determining what went wrong. msg tm 170 ー Ch 叩 ter 8 : 5M5

5. Building the Realtime User Experience

Luckily, we're already monitoring keystrokes on the client side while waiting for the user t0 press the Enter key. This code can easily go in that method as well. However, we don't want tO send an update tO the server with every single keystroke. we just want [ 0 determine whether a user is typing and ensure we send the notification once every couple 0f seconds. SO first we want t0 keep track 0f every keystroke. TO d0 that, let's add another variable to the main chat object. ln the 訪 file, modify it to include the following variable: var chat / / initialize variables login_panel: false, user id: false, previous typing_ping: 0 , timeouts: { } ; / / the current user (me) we're going tO ping the server every couple Of seconds [ 0 let it know that the current user is typing a message t0 another user, and previous typing_ping will hold the last time that we actually sent that message. When the user types a key, we'll be able t0 check whether enough time has elapsed between "now" and previous typing_ping [ 0 warrant a new ping. NOW that we have a place tO store the amount Of elapsed time, we need to actually monitor the typing and make the HTTP request. TO do that, add the following code [ 0 chat. keypress: / / setup the Ajax params "from user id=" 十 chat. user id; var params params 十 = "&tO user id=" 十 tO user id; if(ev. keyC0de & & (ev. keyC0de = else { / / the current time, in mi1115e ( onds since 1970 var now = new Date(). getTime( ) ; / / ping every 1.5 seconds ( 1500 milliseconds) 竏 ( (now - chat ・ previous_typing_ping) 〉 150 の { / / update the previous" time chat. previous typing_ping = now; / / notification the server YAHOO. util. Connect. asyncRequest(' POST ・ ・ /typing' , 十 alse , params); ThiS code monitors every keystroke that a user makes inside a textarea, except for the Enter key, which is caught by the if statement. When we get any other keystroke, we check tO see hOW long ago we last submitted a request tO the server. The first time a user starts typing, the previous typing_ping variable is zero, which means the time difference between now and then is easily greater than one and a half seconds. so the firsttime a user types a key, we'll immediately send an HTTP requestto /send. If the 124 ー Ch 叩 ter 6 : Chat

6. Building the Realtime User Experience

Tornado Offers a couple Of neat features when serving StatiC content. First Of all, it aggressively tries tO get the client tO cache any C011tent that is served れ the StatiC directory, going as far [ 0 suggest that the content won't expire for at least 10 years. TO ensure that browsers dO see the newest content, it alSO appends a unique version string [ 0 each URL served using the static url function. SO in our template shown earlier, the full path to a JavaScript file may be /users/tedroden/realtime/static/twitter . js, but it will be translated t0 the following by static url: http://10ca1host:8888/static/twitter.js?v=aa3ce The parameter v is calculated by generating a hash of the content of the static file. So it will remain the same until the actual content Of the file is changed. AII this means that Tornado will enable unchanged files to remain cached on the client for a very long time. This type Of performance gain is a definite part Of providing a realtime expenence tO the user. The template itself specifies some basic markup that will enable us to do some basic filtering and ShOW some statistics on the data we're receiving from Twitter. By checking the boxes on the page, the users will be able tO specify whether they want [ 0 see tweets that are retweets, contain hashtags or links, or even if they just reference a specific Twitter user.\'Ve've also specified a place in the template tO show the number of each type Of tweet that we've received. Let's startthe server [ 0 see whatthat page looks like when it's served by Tornado. This isn't going t0 show any tweets ()e need t0 set up the JavaScript for that t0 happen), but let's start it up and take a lOOk at our work SO far. ln your terminal window, run the following command: ~ $ python runner ・ py Now point your browser [ 0 http : 〃 10C01 わ 05t : 8888. lt should look something like Figure 5-6. Ⅳ 4 g ル「 co れれれ .. AII 0 ) 団 #hashtags ( 0 ) @巫 ( 0 ) 四 ( の links ( 0 一十 6 h れ p : / ハ“ ho 8888 / Loading-.. X Q• Goog 厄 Twitter / Tornado From the Firehose t0 the Web B 「 ow 史「一 95 日 gu 尾 5-6. The ″〃 d , 立卍ⅲⅳ e , 尾記朝れ 6 T ル 6 ⅱ厩げ角 ce

7. Building the Realtime User Experience

The Two-Connection Limit There iS big a reason you need tO open these files up in separate browsers and not Just different tabs in the same browser when testing. 、åOSt modern browsers limit the amount Of concurrent connections tO Jt1St tWO connections per server. ThiS means that if you have tWO connections open tO the server and try [ 0 open another connection in the same browser, even if it's in a new tab, the browser will wait until one Of the original connections disconnects. When we're dOing long polling, that means we'll be waiting a long time between connections. Cometd actually helps with this issue by using the advice part of the protocolto instruct the clientto fall back to regular polling atstandard intervals. Although this helps keep connections alive, it means we re not getting truly realtime content because Of the length Of time between requests. ln practice for normal users, this isn't as much Of an issue, but when building sites, it poses a bit Of a problem. The solution is simple: use totally different browsers. You can easily check to see if the cometd libraries have fallen back to standard polling at long intervals by examining the transfers with the Firefox extension Firebug ( : 〃 ge 尾わ g. co 川 ). Firebug has many features that make debugging web applications much easier, such as the ability tO examine the network activity, including connections that are still active. When you load up and enable Firebug, the console tab will show you the different POST requests currently active (see Figure 4-7 ). If one of them is constantly active, long polling is working. If the connection returns immediately, it's fallen back. To fix this, navigate away from the page for a minute; it should go right back [ 0 long polling when you return. POST h p : 〃朝 me 」 0 ( host な om 秋 d ト POST h p : / / 爬 a me 」 0 ( host / ( om d POST http://realtime.localhost.com/td POST h p : / / 爬朝 me 」 0 ( host な om d ト POST h れ p : 〃代 altime 」 0 ( ho 靆な om d ト POST http://realtime.localhost.com/td ト POST h p : 〃爬ⅱ me 」 0 ( host / ( om に td POST h れ p : 〃代朝 me 」 0 ( host な om 秋 d ト POST h p : / なに altime 」 0 ( host な om に td 200 OK 6m5 200 OK 7m5 2000K 2m 05 2000K 2m 05 2000K 2m05 2000K 2m 05 2000K 2m 05 ~ 000K 2m 05 dojoxd.js 0 ⅲ・ 16 ) dojoxd.js 0 ⅲに 16 ) dojo.xd.js 朝 16 ) dojoxd.js 価 n に 16 ) dojo.xd.js ()i 飛 16 ) dojo.xd.js (line 16 ) d 可 0 ょ djs ()i 16 ) dojo.xd.js (line 16 ) d 可 0 ょ d.js 0 ⅲに 16 ) ん g Ⅲ℃ 4-7. 尾わ″ g dut ・ ing 4 long 〃 0 ″ⅲ g s お s れ 72 ー Chapter4: River ofContent incomplete, for all intents and purposes, everything is ready [ 0 go. will keep waiting until it does. Although the status bar may claim the page load is still loading an asset. lt is! lt's waiting on your long polling operation [ 0 finish, and it status bar say something [ 0 the effect Of "completed 5 Of 6 items," as if the browser is WhiIe the browser is looking at a page in a long polling section, you may notice the

8. Building the Realtime User Experience

messenger program for the same result. lt alSO allOWS her [ 0 receive the message on her phone after she's stepped away from her computer. UserThreats AS germs are created and die, they interact with Other germs. If one opens up the application inside an area that has been infected by a user named Jane, Thom is threatened by that germ. Thom is then required [ 0 take some sort Of action because he is 、 'threatened" by the germ. ln this case, he faces a threat simply by being in the area 0f an existing germ. Thom has some options. He can challenge Jane by sending her a message that iS delivered tO her in realtime through a number ofdifferent methOdS. At this point, t 、 VO different UserThreats Objects have been created. One threat was directed at Thom because he turned up in hostile territory. Another threat was created by Thom, who directed a challenge directly at Jane. One way or another, these threats need tO be dealt with by the users, and the class UserThreats stores these threats in the datastore until then. Let's build the basic parts of this class. Add the following t0 川 0 ls. : class UserThreats(db. M0del) : # whO needs tO respond tO this threat? user = db. UserProperty() # what type Of threat is this? threat_type = db. StringProperty() # some threats can be addressed via IM/SMS usign a specific key response key = db. lntegerproperty() # when does this threat expire? expire_time = db. DateTimeProperty() # does this threat involve a specific germ? germ threat = db. ReferenceProperty(Germ) # did a specific user initiate this challenge? challenge user = db. UserProperty() def create(self, user, germ threat, expires in minutes=5, challenge_user=None' threat_type="natural") : # because failing tO respond tO threats a 幵 ects score and strength # we want tO make sure not tO create the same threat multiple times q = UserThreats. a11() user) q. filter(' user q. filter('germ threat = # if this threat doesn't exist if not q. count(): q. filter(' challenge_user = , challenge_user) , germ threat) q. filter('threat type = , threat type) 232 ー ChapterIO: Putting lt AII Together

9. Building the Realtime User Experience

import wsgiref. handlers # App Engine supports standard logging, use it. import 05 , logging from google ・ appengine. ext import webapp' db # import some more app engine module from google ・ appengine ・ api import xmpp' users' urlfetch from google ・ appengine ・ ext. webapp ・ util import login—required class BaseHand1er(webapp. RequestHand1er) : # the jabber id 0f the server server_jid = 05. environ[ ・ APPLICATION lD ・ ] + ・@aPPSPOt.com/ # the server URL "http://%s.appspot.com/ % 05. environ[ ・ APPLICATION ID' ] server ur1 class MainHand1er(BaseHand1er) : # force the user tO 10g in tO see this page ・ @login_required def get(self) : user = users. get_current user() logging. info( "G0t request for MainHandler") self. response. out. write( ・ He110 , % 5 ! ・ % user. nickname()) def main(): application = webapp. WSGIApplication([ ()/ ・ , MainHandler)] , debug=True) wsgiref. hand1ers.CGIHand1er(). run(application) if name main() maIn This code is still essentially a 、 'Hello World" application, but it has been expanded in a couple 0f key ways. We added a couple 0f new import statements t0 pull in modules supplied by google ・ appengine as well as from Python's standard library, all ofwhich is available in the App Engine environment. Another modification is the creation Of a BaseHandler class tO use in place Of App En- gine's webapp. Request module. This class serves exactly the same purpose as the Base Handler class introduced in the chat application created with Tornado, which is [ 0 Offer some convenience functionality during web requests that are specific tO our application. ln this case, we're setting up some variables that can be accessed later. These variables make it easy for our application [ 0 refer t0 b0th its URL address and its XMPP address. We've also added a @login_required decorator to the MainHandler class. This one line Of COde tells Google that any user whO views thiS page must be authenticated using their Google Account credentials. If a user hits this page without first authenticating, Google will automatically take them t0 a speciallogin page, redirecting them here once that is complete. The get method still serves the same purpose, but this time it logs some information and responds with the logged-in user s nickname instead Of saying hello to the entire "WorId. ' 134 ー Chapter 7 : lnstant Messaging

10. Building the Realtime User Experience

合 0 の L095 - 、ね徹 Messag ⅲ 9 ExampIe 4 ト。。①十 hhttps://appengine.google.(0Eハ095?=&aPP」d=ⅲ5ねn←me5、ag所g&いに , C009 厄 ( 惑 e app engine i 「馬ね n い 11e5S ⅱマ 1 ted「。d”@9れ油 l.com ー My A008 は出 Sign 引 AII AppIicatiQ09 Main Quo 第 Det 訓 C 「 Jo い Datastore lndexeq St 則 Administration Fllter Logs ② XMMP S00d0 ! : 亡 ed ご oden 起 9 コ aii. co 重 / れ 0150085BEFC1 フ - body : E0110 from iChat! : ! ・ 11- 05 : 41PM 16.598 ”れ s セ a し一爪 essagi こ g ・は ppspo こ . co 珮” 日 TTP / 1 . 1 ” 200 124 ー 30 / No / 2009 : 1 フ : 4 : : 16 -0800 ー "POST / ah / xmpp / 爪 05S490 / c ト 4 こ / 0 . 1 . 0 . 10 11-8 : 41PM 16.1 ー ah/xmpp/mwage/cha€・ 28 44 給 4 pt 」一 ms Okb 田・ 1 05:43PM 、 1 XMMP sender. 幡d「朝en@gmaiわ 001 汝選 5BEFC17 - body: This is 田・ 11-8 : 43PM 19 、 737 XMMP sender:tedroden@gmail.com/nelson85BEFC17 ー body: Messages f Tip: CIick a ーⅱ「 to )OW 併ん de its details. Minimum S 部 : lnfo , 田旦 日 g ″尾 7-70. The ⅲ s ね厩襯お g い received the 覊げ Sending lnstant Messages Receiving messages that originated fr()l れ an instant messenger client is nice t0 have, and certainly opens up a 10t Of possibilities for collecting data in realtime. But the true realtime power Of this technology comes from the ability tO both send and receive these messages. Let'S expand on our existing application by taking the message that we received and Just printing it back t0 the client. ln the 川ⅲ〃 . file, make the following addition to the XMPPHand1er class: class XMPPHand1er(BaseHand1er) : def post(self) : # Parse the XMPP request = xmpp. Message(se1f. request. POST) message # Log it t0 the ( 0n501e logging ・ inf0("XMMP sender: %s - body: %s" % (message. sender, message. body)) message ・ reply(message. body) That additionalline of code takes the message that was received and calls the reply method to send the body right back [ 0 the sender. If you'd like to testthis out on the server, go ahead and deploy the code as it is now. Figure 7-12 shows a typical chat session with the server after adding this functionality. SendingInstant Messages