Wiley.Mobile.Python.Rapid.prototyping.of.applications.on.the.mobile.platform.Dec.2007 (779889), страница 43
Текст из файла (страница 43)
As in the previous applications,we fill in the request parameters in a dictionary params. However,this time we do not make the call directly but by a special functionflickr signed call() that cryptographically signs the request. Thissounds fancy, but actually it is just a clever, simple trick that is describedin Section 9.4.3.If the code is accepted, Flickr replies with the following type of XMLmessage:<auth><token>433445-76598454353455</token><perms>write</perms><user nsid="11223344556@N01" username="Mopy" fullname="N.N"/></auth>Here the tag token contains the final token that gives access to theuser’s Flickr account.
We can extract this value with the naive xmlparser as described in the MopyMaps! section. We save this string tothe file TOKEN FILE. Next time the user opens InstaFlickr, the token isread from the file by the function load token(). This way, the userhas to authenticate to the system only once; after the first time, uploadingis really instantaneous. The load token() function presents the ‘Newtoken’ dialog automatically if a TOKEN FILE was not found.9.4.3Making Signed Calls to FlickrThe function flickr signed call() in Example 97 makes a signedcall to Flickr using the given parameters params. Why does the requesthave to be signed in the first place? The previous applications workedfine without any signing.220WEB SERVICESExample 97: InstaFlickr (3/6)def flickr_signed_call(params):keys = params.keys()keys.sort()msg = SECRETfor k in keys:if k != "photo":msg += k + params[k]params['api_sig'] = md5.new(msg).hexdigest()if "photo" in params:return flickr_multipart_post(params)else:url = API_URL + "?" + urllib.urlencode(params)return urllib.urlopen(url).read()In contrast to MopyMaps! and EventFu, InstaFlickr deals with someone’s personal data.
It has the power to delete all the photos in the user’sFlickr account. In contrast, the previous applications made read-onlyrequests to public data. Especially if your phone is connected to theInternet by an insecure wireless LAN, it is easy for anyone to eavesdropthe requests that are sent out by the application. If the requests were notsigned, the eavesdropper could then pretend to be you and send modifiedrequests to Flickr with disastrous effects.Signing makes these man-in-the-middle attacks practically impossible.Signing is made effective by the SECRET string that is known only by yourapplication and the recipient, in this case Flickr.
Since SECRET is neversent anywhere, you can securely communicate with the other party whoknows the SECRET, as long as you keep the phone holding the SECRETsafe.Signing works as follows: the parameters of the request, params,except for any image data, are combined into a single string msg thatbegins with the SECRET string.
The order of the parameters is important sothat the recipient can re-construct the same message, thus, the parametersare sorted in alphabetical order. Then we use a well-known cryptographichash function called MD5 to compute a long number based on the newlyformed string msg.MD5 is designed to generate a unique result so that no other input stringproduces exactly the same number or signature.
For brevity, the numberis saved in hexadecimal notation. The signature is assigned to the requestkey 'api sig' in addition to the other parameters, params, as specifiedin the Flickr API. This process is described in the Flickr authenticationspecification at www.flickr.com/services/api/auth.spec.html.The result of this signing procedure is that anyone who knows theSECRET string can easily reproduce msg based on the request parameters.She can then compute the corresponding signature and check that itINSTAFLICKR: SHOOT AND UPLOAD PHOTOS TO FLICKR221matches with 'api sig'.
On the other hand, someone who doesnot know SECRET cannot change the parameters without the recipientnoticing, as she is unable to produce the correct signature.After the signature has been computed, flickr signed call()chooses one of the two ways to make the actual request. If the parameterscontain the key 'photo', a special function flickr multipartpost() is chosen that is suitable for large requests – in this case therequest contains the data of a full-scale photo. Otherwise, the requestis made with the urllib.urlopen() function in a similar way to theprevious example applications.9.4.4Data UploadingExample 98 contains the function flickr multipart post() thatimplements a standard HTTP request method called multipart POST.
Thisis the standard way to upload files to the web. Unfortunately, however,this method is not supported directly by Python’s standard web modules,so we must provide an implementation of our own.Example 98: InstaFlickr (4/6)def flickr_multipart_post(params):BOUNDARY = "----ds9io349sfdfd!%#!dskm"body = []for k, v in params.items():body.append("--" + BOUNDARY)part_head = 'Content-Disposition: form-data; name="%s"' % kif k == "photo":body.append(part_head + ';filename="up.jpg"')body.append('Content-Type: image/jpeg')else:body.append(part_head)body.append('')body.append(v)body.append("--" + BOUNDARY + "--")body_txt = "\r\n".join(body)proto, tmp, host, path = UPLOAD_URL.split('/', 3)h = httplib.HTTP(host)h.putrequest('POST', "/%s" % path)h.putheader('content-type',"multipart/form-data; boundary=%s" % BOUNDARY)h.putheader('content-length', str(len(body_txt)))h.endheaders()try:offs = 0for i in range(0, len(body_txt), BLOCKSIZE):offs += BLOCKSIZEh.send(body_txt[i: offs])222WEB SERVICESprogress_bar(min(1.0, offs / float(len(body_txt))))errcode, errmsg, headers = h.getreply()return h.file.read()except:return NoneThe implementation, which includes the lines from the beginning ofthe function to the try statement, requires some understanding of theHTTP protocol.
There is nothing particularly difficult in it and you donot need to understand it to use PyS60 successfully. Because of this,we omit the explanation here. Anyone interested in the protocol canfind several comprehensive explanations on the web by searching for‘multipart post’.However, this implementation contains one interesting detail: Oncethe message has been constructed in the string body txt, it is not sentto the destination URL in one part.
Since the image data can take 50–100kilobytes, sending the data over any wireless network will take sometime. Because of this, we send the data in parts, block by block. Aftereach block is sent, a progress bar is updated to depict the progress of thetransfer. Figure 9.3(c) shows a progress bar in action.Piecewise sending is implemented by the for loop inside the try–except block at the end of flickr multipart post(). The loopvariable i goes from 0 to the message size, increasing the variableby BLOCKSIZE after each iteration. During each iteration, a blockof data starting at i is sent to the recipient.
After that, the functionprogress bar is called with a single parameter that denotes theprogress in the scale from 0 to 1.0. Implementation of progress bar()is described in Section 9.4.5.After all the data has been sent successfully, flickr multipartpost() reads a reply from the recipient and returns it to the caller. If anyerrors occur during sending, None is returned instead.9.4.5 Taking Photos and the Progress BarExample 99 implements the camera functionality, the progress bar and afunction that draws status messages on screen. Functions related to thecamera, finder cb(), start viewfinder() and take photo()are based on Example 36 in Section 5.4.Example 99: InstaFlickr (5/6)def progress_bar(p):y = canvas.size[1] / 2max_w = canvas.size[0] - 30canvas.rectangle((15, y, p * max_w, y + 10), fill = PROGRESS_COLOR)INSTAFLICKR: SHOOT AND UPLOAD PHOTOS TO FLICKR223def show_text(txt):s = canvas.sizecanvas.text((10, s[1] / 2 - 15), txt, fill=TEXT_COLOR, font="title")def finder_cb(im):canvas.blit(im)def start_viewfinder():if flickr_token:camera.start_finder(finder_cb)canvas.bind(key_codes.EKeySelect, take_photo)else:appuifw.note(u"Give a Flickr token first", "error")def take_photo():canvas.bind(key_codes.EKeySelect, None)camera.stop_finder()show_text(u"Hold still!")image = camera.take_photo(size = (640, 480))s = canvas.sizecanvas.blit(image,target=(0,0,s[0],(s[0]/4*3)), scale=1)show_text(u"Uploading to Flickr...")image.save(IMG_FILE)jpeg = file(IMG_FILE, "r").read()params = {'api_key': API_KEY,'title': 'InstaFlickr','auth_token': flickr_token,'photo': jpeg}ret = flickr_signed_call(params)canvas.clear((255, 255, 255))if ret:show_text(u"Photo sent ok!")else:show_text(u"Network error")After a photo has been shot, the function take photo() loads theimage data from the image file IMG FILE and prepares an upload requestin params to be sent to Flickr.