• Twisted网络编程必备(3)


    转自:http://www.yybug.com/read-htm-tid-15324.html

    3.1WEB客户端

    大部分上网活动都是通过WEB浏览器来访问WEB的。所以通过HTTP协议制作客户端来访问WEB是很有意义的。这一章讲解如何使用twisted.web.client模块来操作互联网资源,包括下载页面,使用HTTP认证,上传文件,使用HTTP字段等。

    3.1 下载网页

    最简单和常用的任务莫过于通过WEB客户端来下载网页了。客户端连接服务器,发送HTTP的GET请求,接收包含网页的HTTP响应。

    3.1.1 下面如何做?

    可以依靠Twisted内置的协议来快速进入工作。twisted.web包包含了完整的HTTP实现,免去自行开发Protocol和ClientFactory的工作。在未来,它还将包括建立HTTP请求的工具函数。获取一个网页,使用twisted.web.client.getPage。例子3-1是webcat.py脚本,用于通过URL获得网页。

    代码
    from twisted.web import client
    from twisted.internet import reactor
    import sys
    def printPage(data):
    print data
    reactor.stop()
    def printError(failure):
    print >> sys.stderr, "Error: ", failure.getErrorMessage()
    reactor.stop()
    if len(sys.argv)==2:
    url
    =sys.argv[1]
    client.getPage(url).addCallback(printPage).addErrback(printError)
    reactor.run()
    else:
    print "Usage: webcat.py <URL>"

    给webcat.py脚本一个URL,将会显示网页代码:

    $ python webcat.py http://www.oreilly.com/

    <!DOCTYPE HTML PUBLIC "-//....

    3.1.2 它们如何工作?

    函数printPage和printError是简单的事件处理器,用于打印下载的网页和错误信息。最重要的一行是client.getPage(url)。这个函数返回了Deferred对象,用于非同步状态下通知下载完成事件。

    注意如何在一行中添加Deferred的回调函数。这是可能的,因为addCallback和addErrback都返回Deferred对象的引用。因此语句:

    d=deferredFunction()

    d.addCallback(resultHandler)

    d.addCallback(errorHandler)

    等同于:

    deferredFunction().addCallback(resultHandler).addErrback(errorHandler)

    这两种方式都可以用,下面那种方式在Twisted代码中更加常见。

    3.1.3 关于

    是否有需要将网页写入磁盘上呢?这个高级功能在使用3-1例子脚本下载巨大文件时可能会出问题。一个更好的解决方法是在下载时将数据写入临时文件,而在下载完成时在从临时文件中全部读出。

    twisted.web.client包含了downloadPage函数,类似于getPage,但是将文件写入文件。调用downloadPage并传入URL做首参数,文件名或文件对象做第二个参数。如下例3-2:

    代码
    from twisted.web import client
    import tempfile
    def downloadToTempFile(url):
    """
    传递一个URL,并返回一个Deferred对象用于下载完成时的回调
    """
    tmpfd,tempfilename
    =tempfile.mkstemp()
    os.close(tmpfd)
    return client.downloadPage(url,tempfilename).addCallback(returnFilename,tempfilename)
    def returnFilename(result,filename):
    return filename
    if __name__=='__main__':
    import sys,os
    from twisted.internet import reactor
    def printFile(filename):
    for line in file(filename,'r+b'):
    sys.stdout.write(line)
    os.unlink(filename)
    #删除文件
    reactor.stop()
    def printError(failure):
    print >> sys.stderr, "Error: ",failure.getErrorMessage()
    reactor.stop()
    if len(sys.argv)==2:
    url
    =sys.argv[1]
    downloadToTempFile(url).addCallback(printFile).addErrback(printError)
    reactor.run()
    else:
    print "Usage: %s <URL>"%sys.argv[0]

    函数downloadToTempFile在调用twisted.web.client.downloadPage时返回Deferred对象。downloadToTempFile还添加了returnFilename作为Deferred的回调,将临时文件名作为附加参数。这意味着当downloadToTempFile返回时,reactor将会调用returnFileName作为downloadToTempFile的首个参数,而文件名作为第二个参数。

    例子3-2注册了另外两个回调函数到downloadToTempFile。记住downloadToTempFile返回的Deferred已经包含了returnFilename作为回调处理器。因此,在结果返回时,returnFilename将会首先被调用。这个函数的结果被用于调用printFile。

    3.2 存取受到密码保护的页面

    有些网页需要认证。如果你正在开发HTTP客户端应用,那么最好准备好处理这些,并在需要时给出用户名和密码。

    3.2.1 下面如何做?

    如果一个HTTP请求以401的状态码执行失败,则是需要认证的。这时传递用户提供的登录用户名和密码到Authorization字段,有如例子3-3中那样:

    代码
    from twisted.web import client,error as weberror
    from twisted.internet import reactor
    import sys,getpass,base64
    def printPage(data):
    print data
    reactor.stop()
    def checkHTTPError(failure,url):
    failure.trap(weberror.Error)
    if failure.value.status=='401':
    print >> sys.stderr,failure.getErrorMessage()
    #要求用户名和密码
    username=raw_input("User name: ")
    password
    =getpass.getpass("Password: ")
    basicAuth
    =base64.encodestring("%s:%s"%(username,password))
    authHeader
    ="Basic "+basicAuth.strip()
    #尝试再次获取页面,已经加入验证信息
    return client.getPage(url,headers={"Authorization":authHeader})
    else:
    return failure
    def printError(failure):
    print >> sys.stderr, 'Error: ',failure.getErrorMessage()
    reactor.stop()
    if len(sys.argv)==2:
    url
    =sys.argv[1]
    client.getPage(url).addErrback(
    checkHTTPError,url).addCallback(
    printPage).addErrback(
    printError)
    reactor.run()
    else:
    print "Usage: %s <URL>"%sys.argv[0]

    运行webcat3.py并传递URL作为附加参数,将会尝试下载页面。如果收到了401错误,则会询问用户名和密码之后自动重试:

    $ python webcat3.py http://example.com/protected/page

    401 Authorization Required

    User name: User

    Password: <输入密码>

    <html>

    ... ...

    3.2.2 它们如何工作?

    这个例子使用了扩展的错误处理器。它首先给client.getPage添加了Deferred,在添加printPage和printError之前。这样做给了checkHTTPError以机会在其他处理器之前来处理client.getPage的错误。

    作为一个errback的错误处理器,checkHTTPError将被twisted.python.failure.Failure对象调用。Failure对象封装了跑出的异常,记录了异常时的traceback,并添加了几个有用的方法。checkHTTPError开始使用了Failure.trap方法来证实异常是twisted.web.error.Error类型的。如果不是,trap将会重新抛出异常,退出当前函数并允许错误传递到下一个errback处理器,printError。

    然后,checkHTTPError检查HTTP响应状态码.failure.value是一个Exception对象,并且是twisted.web.error.Error的对象,已知的status属性包含了HTTP响应状态码。如果状态码不是401,则返回原始错误,通过把错误传递给printError。

    如果状态码是401,checkHTTPError会执行。它会提示输入用户名和密码,并编码成HTTP的Authorization头字段。然后调用client.getPage,返回Deferred对象。这将导致一些很cool的事情发生,反应器等待第二次调用结果,然后调用printPage或者printError来处理结果。实际上,checkHTTPError将会提示"通过另外一个Deferred来处理错误,等待Deferred的结果,并且在另外一行中进行事件处理"。这个技术是很强大的,并且可以在Twisted程序中多次使用。

    最终的结果有如前面的例子:使用printPage或者printError来输出结果。当然,如果初始化请求成功了,则不需要认证,checkHTTPError将不会被调用,返回的结果也将直接调用printPage。

    3.3 上传文件

    以用户的观点来说,没有什么简单的方法使得上传文件到网页。使用HTML表单选择文件并按下提交按钮才可以上传。正因为如此很多站点提供了难用的上传方式。有时当你需要上传文件而又不能使用浏览器时。可能需要自己开发程序来上传照片、基于WEB的文件管理系统等等。下面的例子展示了使用Twisted开发HTTP客户端来上传文件的方法。

    3.3.1 下面如何做?

    首先,对键值对编码,并将上传文件编入multipart/form-data的MIME文件。Python和Twisted都无法提供简便的方法来实现,但你可以也可以不需要太大努力的自己实现一个。然后传递已编码的表单数据到formdata键,并传递给client.getPage或client.downloadPage,并使用HTTP的POST方法。然后就可以执行getPage或downloadPage来获得HTTP响应了。例子3-4(validate.py)展示了如何按照W3C标准上传文件,并保存响应到本地文件,然后显示在用户浏览器中。

    代码
    from twisted.web import client
    import os,tempfile,webbrowser,random
    def encodeForm(inputs):
    """
    传递一个字典参数inputs并返回multipart/form-data字符串,包含了utf-8编码的
    数据。键名必须为字符串,值可以是字符串或文件对象。
    """
    getRandomChar
    =lambda:chr(random.choice(range(97,123)))
    randomChars
    =[getRandomChar() for x in range(20)]
    boundary
    ="---%s---"%''.join(randomChars)
    lines
    =[boundary]
    for key,val in inputs.items():
    header
    ='Content-Disposition: form-data; name="%s"'%key
    if hasattr(val,'name'):
    header
    +=';filename="%s"'%os.path.split(val.name)[1]
    lines.append(header)
    if hasattr(val,'read'):
    lines.append(val.read())
    else:
    lines.append(val.encode(
    'utf-8'))
    lines.append(
    '')
    lines.append(boundary)
    return "\r\n".join(lines)
    def showPage(pageData):
    #将数据写入临时文件并在浏览器中显示
    tmpfd,tmp=tempfile.mkstemp('.html')
    os.close(tmpfd)
    file(tmp,
    'w+b').write(pageData)
    webbrowser.open(
    'file://'+tmp)
    reactor.stop()
    def handleError(failure):
    print "Error: ",failure.getErrorMessage()
    reactor.stop()
    if __name__=='__main__':
    import sys
    from twisted.internet import reactor
    filename
    =sys.argv[1]
    fileToCheck
    =file(filename)
    form
    =encodeForm({'uploaded_file':fileToCheck})
    postRequest
    =client.getPage(
    'http://validator.w3.org/check',
    method
    ="POST",
    headers
    ={'Content-Type':'multipart/form-data; charset=utf-8',
    'Content-Length':str(len(form))},
    postdata
    =form)
    postRequest.addCallback(showPage).addErrback(handleError)
    reactor.run()

    运行validate.py脚本并在第一个参数给出HTML文件名:

    $ python validate.py test.html

    一旦发生了错误,必须获得validation的在浏览器中的错误报告。

    3.3.2 它们是如何做的?

    按照W3C标准页面,例如http://validator.w3.org包含表单如下:

    代码
    <form method="POST" enctype="multipart/form-data" action="check">
    <label title="Choose a Local file to Upload and Validate"
    for
    ="uploaded_file">Local File:
    <input type="file" name="uploaded_file" size="30" /></label>
    <label title="Submit file for validation">
    <input type="submit" value="Check" /></label>
    </form>

    这样可以建立一个HTTP客户端发送与浏览器相同的数据。函数encodeForm输入一个字典包含了以键值对方式引入的上传文件对象,并返回已经按照multipart/form-data的MIME编码文档的字符串。当validate.py运行时,打开特定文件作为第一个参数并传递encodeForm作为'uploaded_file'的值。这将会返回待提交的有效数据。

    validate.py然后使用client.getPage来提交表单数据,传递头部字段包括Content-Length和Content-Type。showPage回调处理器得到返回的数据并写入临时文件。然后使用Python的webbrowser模块调用默认浏览器来打开这个文件。

    3.4 检测网页更新

    当使用流行的HTTP应用打开一个RSS收集时,将会自动下载最新的RSS(或者是Atom)的blog更新。RSS收集定期下载最新的更新,典型值是一个小时。这个机制可以减少浪费大量的带宽。因为内容很少更新,所以客户端常常重复下载相同数据。

    为了减少浪费网络资源,RSS收集(或者其他请求页面的方式)推荐使用条件HTTP GET请求。通过在HTTP请求的头部字段添加条件,客户端可以告知服务器仅仅返回已经看过的时间之后的更新。当然,一种条件是在上次下载后是否修改过。

    3.4.1 下面如何做?

    首次下载网页时保持跟踪头部字段。查找ETag头部字段,这定义了网页的修正版本,或者是Last-Modified字段,给出了网页修改时间。下次请求网页时,发送头部If-None-Match加上ETag值,或者If-Modified-Since加上Last-Modified值。如果服务器支持条件GET请求,如果网页没有修改过则返回304 Unchanged响应。

    getPage和downloadPage函数是很方便的,不过对于控制条件请求是不可用的。可以使用稍微低层次的HTTPClientFactory接口来实现。例子3-5演示了HTTPClientFactory测试网页是否更新。

    代码
    from twisted.web import client
    class HTTPStatusChecker(client.HTTPClientFactory):
    def __init__(self,url,headers=None):
    client.HTTPClientFactory.
    __init__(self,url,headers=headers)
    self.status
    =None
    self.deferred.addCallback(
    lambda data: (data,self.status,self.response_headers))
    def noPage(self,reason): #当返回非200响应时
    if self.status=='304' #页面未曾改变
    client.HTTPClientFactory.page(self,'')
    else:
    client.HTTPClientFactory.noPage(self,reason)
    def checkStatus(url,contextFactory=None,*args,**kwargs):
    scheme,host,port,path
    =client._parse(url)
    factory
    =HTTPStatusChecker(url,*args,**kwargs)
    if scheme=='https':
    from twisted.internet import ssl
    if contextFactory is None:
    contextFactory
    =ssl.ClientContextFactory()
    reactor.connectSSL(host,port,factory,contextFactory)
    else:
    reactor.connectTCP(host,port,factory)
    return factory.deferred
    def handleFirstResult(result,url):
    data,status,headers
    =result
    nextRequestHeaders
    ={}
    eTag
    =headers.get('etag')
    if eTag:
    nextRequestHeaders[
    'If-None-Match']=eTag[0]
    modified
    =headers.get('last-modified')
    if modified:
    nextRequestHeaders[
    'If-Modified-Since']=modified[0]
    return checkStatus(url,headers=nextRequestHeaders).addCallback(
    handleSecondResult)
    def handleSecondResult(result):
    data,status,headers
    =result
    print 'Second request returned status %s:'%status,
    if status=='200':
    print 'Page changed (or server does not support conditional request).'
    elif status=='304':
    print 'Page is unchanged.'
    else:
    print 'Unexcepted Response.'
    reactor.stop()
    def handleError(failure):
    print 'Error',failure.getErrorMessage()
    reactor.stop()
    if __name__=='__main__':
    import sys
    from twisted.internet import reactor
    url
    =sys.argv[1]
    checkStatus(url).addCallback(
    handleFirstResult,url).addErrback(
    handleError)
    reactor.run()

    运行updatecheck.py脚本并传入URL作为首参数。它首先尝试下载WEB页面,使用条件GET。如果返回了304错误,则表示服务器未更新网页。常见的条件GET适用于静态文件,如RSS种子,但不可以是动态生成页面,如主页。

    $ python updatecheck.py http://slashdot.org/slashdot.rss

    Second request returned status 304: Page is unchanged

    $ python updatecheck.py http://slashdot.org/

    Second request returned status 200: Page changed

    (or server does not support conditional requests).

    3.4.2 它们如何做?

    HTTPStatusChecker类是client.HTTPClientFactory的子类。它负责两件重要的事情。在初始化时使用lambda添加回调函数到self.deferred。这个匿名函数将会在结果传递到其他处理器之前截获self.deferred。这将会替换结果(已下载数据)为包含更多信息的元组:数据、HTTP状态码、self.response_headers,这是一个响应字段的字典。

    HTTPStatusChecker也重载了noPage方法,就是由HTTPClientFactory调用的成功的响应码。如果响应码是304(未改变状态码),则noPage方法调用HTTPClientFactory.page替换原来的noPage方法,表示成功的响应。如果执行成功了,HTTPStatusChecker的noPage同样返回重载的HTTPClientFactory中的noPage方法。在这个时候,它提供了304响应来替换错误。

    checkStatus函数需要一个被twisted.web.client._parse转换过的URL参数。它看起来像是URL的一部分,可以从中获取主机地址和使用HTTP/HTTPS协议(TCP/SSL)。下一步,checkStatus创建一个HTTPStatusChecker工厂对象,并打开连接。所有这些代码对twisted.web.client.getPage都是很简单的,并推荐使用修改过的HTTPStatusChecker工厂来代替HTTPClientFactory。

    当updatecheck.py运行时,会调用checkStatus,设置handleFirstResult作为回调事件处理器,按照循序,第二次请求会使用If-None-Match或If-Modified-Since条件头字段,设置handleSecondResult作为回调函数处理器。handleSecondResult函数报告服务器是否返回304错误,然后停止反应器。

    handleFirstResult实际上返回的是handleSecondResult的deferred结果。这允许printError,就是被指定给checkStatus的错误事件处理器,用于所有在第二次请求中调用checkStatus的其他错误。

    3.5 监视下载进度

    以上的例子暂时还没有办法监视下载进度。当然,Deferred可以在下载完成时返回结果,但有时你需要监视下载进度。

    3.5.1 下面如何做?

    再次,twisted.web.client没能提供足够的实用函数来控制进度。所以定义一个client.HTTPDownloader的子类,这个工厂类常用于下载网页到文件。通过重载一对方法,你可以保持对下载进度的跟踪。webdownload.py脚本是3-6例子,展示了如何使用:

    代码
    from twisted.web import client
    class HTTPProgressDownloader(client.HTTPDownloader):
    def gotHeaders(self,headers):
    if self.status=='200':
    if headers.has_key('content-length'):
    self.totalLength
    =int(headers['content-length'][0])
    else:
    self.totalLength
    =0
    self.currentLength
    =0.0
    print ''
    return client.HTTPDownloader.gotHeaders(self,headers)
    def pagePart(self,data):
    if self.status=='200':
    self.currentLength
    +=len(data)
    if self.totalLength:
    percent
    ="%i%%"%(
    (self.currentLength
    /self.totalLength)*100)
    else:
    percent
    ="%dK"%(self.currentLength/1000)
    print "\033[1FProgress: "+percent
    return client.HTTPDownloader.pagePart(self,data)
    def downloadWithProgress(url,file,contextFactory=None,*args,**kwargs):
    scheme,host,port,path
    =client._parse(url)
    factory
    =HTTPProgressDownloader(url,file,*args,**kwargs)
    if scheme=='https':
    from twisted.internet import ssl
    if contextFactory is None:
    contextFactory
    =ssl.ClientContextFactory()
    reactor.connectSSL(host,port,factory,contextFactory)
    else:
    reactor.connectTCP(host,port,factory)
    return factory.deferred
    if __name__=='__main__':
    import sys
    from twisted.internet import reactor
    def downloadComplete(result):
    print "Download Complete."
    reactor.stop()
    def downloadError(failure):
    print "Error: ",failure.getErrorMessage()
    reactor.stop()
    url,outputFile
    =sys.argv[1:]
    downloadWithProgress(url,outputFile).addCallback(
    downloadComplete).addErrback(
    downloadError)
    reactor.run()

    运行webdownload.py脚本并加上URL和存储文件名这两个参数。作为命令工作会打印出下载进度的更新。

    $ python webdownload.py http://www.oreilly.com/ oreilly.html

    Progress: 100% <- 下载过程中的更新

    Download Complete.

    如果WEB服务器并不返回Content-Length头字段,则无法计算下载进度。在这种情况下,webdownload.py打印已经下载的KB数量。

    $ python webdownload.py http://www.slashdot.org/ slashdot.html

    Progress: 60K <- 下载过程中更新

    Download Complete.

    3.5.2 它们如何工作?

    HTTPProgressDownloader是client.HTTPDownloader的子类。重载了gotHeaders方法并检查Content-Length头字段用于计算下载总量。它还同时提供重载了pagePart方法,在每次接收到一块数据时发生,用来保持对下载量的跟踪。

    每次有数据到达时,HTTPProgressDownloader打印进度报告。字符串\033[1F是一个终端控制序列,可以确保每次替换掉当前行。这个效果看起来可以在原位置更新。

    downloadWithProgress函数包含了例子3-5中相同的代码用来转换URL,创建HTTPProgressDownloader工厂对象,并初始化连接。downloadComplete和downloadError是用于打印消息和停止反应器的简单回调函数。

  • 相关阅读:
    MySQL数据库表的设计和优化(上)
    MySQL性能优化最佳实践20条
    MySQL高性能优化指导思路
    MySQL 5.6 my.cnf优化后的标准配置(4核 16G Centos6.5 x64)
    MySQL优化之索引优化
    MySQL优化之SQL语句优化
    MySQL优化之配置参数调优
    Apache的ab测试
    FastCGI模式下安装Xcache
    除了用作缓存数据,Redis还可以做这些
  • 原文地址:https://www.cnblogs.com/sevenyuan/p/1884492.html
Copyright © 2020-2023  润新知