January Star
  • Home
  • Categories
  • Tags
  • Archives

DocXMLRPCServer标准库源码学习

Contents

  • 简介
  • ServerHTMLDoc
  • XMLRPCDocGenerator
  • DocXMLRPCRequestHandler
    • self.server.generate_html_documentation
  • DocXMLRPCServer
  • DocCGIXMLRPCRequestHandler

简介

DocXMLRPCServer 比 SimpleXMLRPCServer 多提供了以下功能:

显示 Server 本身提供的 RPC 方法的文档说明。

用户可以直接通过 GET 请求(浏览器直接访问)即可了解这些 RPC 方法的文档说明。

ServerHTMLDoc

ServerHTMLDoc 主要是通过 内省 来获取传入对象的相关属性并组装成 pydoc 风格的 HTML 文档。

具体的代码基本是各个拼接字符串,最终组装成 HTML 文档,所以就不再详细注释。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
class ServerHTMLDoc(pydoc.HTMLDoc):
    """Class used to generate pydoc HTML document for a server"""

    def markup(self, text, escape=None, funcs={}, classes={}, methods={}):
        """Mark up some plain text, given a context of symbols to look for.
        Each context dictionary maps object names to anchor names."""
        escape = escape or self.escape
        results = []
        here = 0

        # XXX Note that this regular expression does not allow for the
        # hyperlinking of arbitrary strings being used as method
        # names. Only methods with names consisting of word characters
        # and '.'s are hyperlinked.
        pattern = re.compile(r'\b((http|ftp)://\S+[\w/]|'
                                r'RFC[- ]?(\d+)|'
                                r'PEP[- ]?(\d+)|'
                                r'(self\.)?((?:\w|\.)+))\b')
        while 1:
            match = pattern.search(text, here)
            if not match: break
            start, end = match.span()
            results.append(escape(text[here:start]))

            all, scheme, rfc, pep, selfdot, name = match.groups()
            if scheme:
                url = escape(all).replace('"', '"')
                results.append('<a href="%s">%s</a>' % (url, url))
            elif rfc:
                url = 'http://www.rfc-editor.org/rfc/rfc%d.txt' % int(rfc)
                results.append('<a href="%s">%s</a>' % (url, escape(all)))
            elif pep:
                url = 'http://www.python.org/dev/peps/pep-%04d/' % int(pep)
                results.append('<a href="%s">%s</a>' % (url, escape(all)))
            elif text[end:end+1] == '(':
                results.append(self.namelink(name, methods, funcs, classes))
            elif selfdot:
                results.append('self.<strong>%s</strong>' % name)
            else:
                results.append(self.namelink(name, classes))
            here = end
        results.append(escape(text[here:]))
        return ''.join(results)

    def docroutine(self, object, name, mod=None,
                   funcs={}, classes={}, methods={}, cl=None):
        """Produce HTML documentation for a function or method object."""

        anchor = (cl and cl.__name__ or '') + '-' + name
        note = ''

        title = '<a name="%s"><strong>%s</strong></a>' % (
            self.escape(anchor), self.escape(name))

        if inspect.ismethod(object):
            args, varargs, varkw, defaults = inspect.getargspec(object.im_func)
            # exclude the argument bound to the instance, it will be
            # confusing to the non-Python user
            argspec = inspect.formatargspec (
                    args[1:],
                    varargs,
                    varkw,
                    defaults,
                    formatvalue=self.formatvalue
                )
        elif inspect.isfunction(object):
            args, varargs, varkw, defaults = inspect.getargspec(object)
            argspec = inspect.formatargspec(
                args, varargs, varkw, defaults, formatvalue=self.formatvalue)
        else:
            argspec = '(...)'

        if isinstance(object, tuple):
            argspec = object[0] or argspec
            docstring = object[1] or ""
        else:
            docstring = pydoc.getdoc(object)

        decl = title + argspec + (note and self.grey(
               '<font face="helvetica, arial">%s</font>' % note))

        doc = self.markup(
            docstring, self.preformat, funcs, classes, methods)
        doc = doc and '<dd><tt>%s</tt></dd>' % doc
        return '<dl><dt>%s</dt>%s</dl>\n' % (decl, doc)

    def docserver(self, server_name, package_documentation, methods):
        """Produce HTML documentation for an XML-RPC server."""

        fdict = {}
        for key, value in methods.items():
            fdict[key] = '#-' + key
            fdict[value] = fdict[key]

        server_name = self.escape(server_name)
        head = '<big><big><strong>%s</strong></big></big>' % server_name
        result = self.heading(head, '#ffffff', '#7799ee')

        doc = self.markup(package_documentation, self.preformat, fdict)
        doc = doc and '<tt>%s</tt>' % doc
        result = result + '<p>%s</p>\n' % doc

        contents = []
        method_items = sorted(methods.items())
        for key, value in method_items:
            contents.append(self.docroutine(value, key, funcs=fdict))
        result = result + self.bigsection(
            'Methods', '#ffffff', '#eeaa77', pydoc.join(contents))

        return result

XMLRPCDocGenerator

XMLRPCDocGenerator 主要提供了 generate_html_documentation :

收集 XML-RPC Server 所提供的所有 RPC 方法,并通过 `ServerHTMLDoc` 来生成 HTML 文档

代码基本上也没啥好注释的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
class XMLRPCDocGenerator:
    """Generates documentation for an XML-RPC server.

    This class is designed as mix-in and should not
    be constructed directly.
    """

    def __init__(self):
        # setup variables used for HTML documentation
        self.server_name = 'XML-RPC Server Documentation'
        self.server_documentation = \
            "This server exports the following methods through the XML-RPC "\
            "protocol."
        self.server_title = 'XML-RPC Server Documentation'

    def set_server_title(self, server_title):
        """Set the HTML title of the generated server documentation"""

        self.server_title = server_title

    def set_server_name(self, server_name):
        """Set the name of the generated HTML server documentation"""

        self.server_name = server_name

    def set_server_documentation(self, server_documentation):
        """Set the documentation string for the entire server."""

        self.server_documentation = server_documentation

    def generate_html_documentation(self):
        """generate_html_documentation() => html documentation for the server

        Generates HTML documentation for the server using introspection for
        installed functions and instances that do not implement the
        _dispatch method. Alternatively, instances can choose to implement
        the _get_method_argstring(method_name) method to provide the
        argument string used in the documentation and the
        _methodHelp(method_name) method to provide the help text used
        in the documentation."""

        methods = {}

        for method_name in self.system_listMethods():
            if method_name in self.funcs:
                method = self.funcs[method_name]
            elif self.instance is not None:
                method_info = [None, None] # argspec, documentation
                if hasattr(self.instance, '_get_method_argstring'):
                    method_info[0] = self.instance._get_method_argstring(method_name)
                if hasattr(self.instance, '_methodHelp'):
                    method_info[1] = self.instance._methodHelp(method_name)

                method_info = tuple(method_info)
                if method_info != (None, None):
                    method = method_info
                elif not hasattr(self.instance, '_dispatch'):
                    try:
                        method = resolve_dotted_attribute(
                                    self.instance,
                                    method_name
                                    )
                    except AttributeError:
                        method = method_info
                else:
                    method = method_info
            else:
                assert 0, "Could not find method in self.functions and no "\
                          "instance installed"

            methods[method_name] = method

        documenter = ServerHTMLDoc()
        documentation = documenter.docserver(
                                self.server_name,
                                self.server_documentation,
                                methods
                            )

        return documenter.page(self.server_title, documentation)

DocXMLRPCRequestHandler

重载 SimpleXMLRPCRequestHandler ,添加 GET 请求方法的处理逻辑。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class DocXMLRPCRequestHandler(SimpleXMLRPCRequestHandler):

    def do_GET(self):
        if not self.is_rpc_path_valid():
            self.report_404()
            return

        # self.server 指代 `DocXMLRPCServer`
        response = self.server.generate_html_documentation()
        self.send_response(200)
        self.send_header("Content-type", "text/html")
        self.send_header("Content-length", str(len(response)))
        self.end_headers()
        self.wfile.write(response)

self.server.generate_html_documentation

self.server 指代 DocXMLRPCServer ,由于 DocXMLRPCServer 桥接了 XMLRPCDocGenerator ,所以它有 generate_html_documentation 方法。

self.server 是在哪里定义的?

这个要从 DocXMLRPCRequestHandler 和 DocXMLRPCServer 的继承体系说起。

  1. DocXMLRPCRequestHandler 继承自 SimpleXMLRPCServer.SimpleXMLRPCRequestHandler
  2. SimpleXMLRPCServer.SimpleXMLRPCRequestHandler 继承自 BaseHTTPServer.BaseHTTPRequestHandler
  3. BaseHTTPServer.BaseHTTPRequestHandler 继承自 SocketServer.StreamRequestHandler
  4. SocketServer.StreamRequestHandler 继承自 SocketServer.BaseRequestHandler

DocXMLRPCServer 的继承体系也大体如此, 最终指定的是 SocketServer.BaseServer

最后 , 我们再看一下 SocketServer.BaseRequestHandler 和 SocketServer.BaseServer 两者的源码就一清二楚了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class BaseRequestHandler:

    def __init__(self, request, client_address, server):
        self.request = request
        self.client_address = client_address
        self.server = server //  远在天边,近在眼前 
        self.setup()
        try:
            self.handle()
        finally:
            self.finish()

    ... ...
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
class BaseServer:
    timeout = None

    def __init__(self, server_address, RequestHandlerClass):
        self.server_address = server_address # 服务绑定的地址 
        self.RequestHandlerClass = RequestHandlerClass # 处理请求的类 
        # 下面这两个属性主要用来给 self.shutdown 方法使用 
        self.__is_shut_down = threading.Event() # 服务结束通知 
        self.__shutdown_request = False # 服务已关闭的标志位 

    ... ...

    def finish_request(self, request, client_address):
        # 参数中的 self 表示本将服务实例传入到 RequestHandler 中 
        self.RequestHandlerClass(request, client_address, self)

    ... ...

DocXMLRPCServer

DocXMLRPCServer 使用了 桥接模式 将 SimpleXMLRPCServer 和 XMLRPCDocGenerator 组合起来。

所以它本质不做实质的事情,看下面的实现代码就清楚这一点了。

1
2
3
4
5
6
7
8
class DocXMLRPCServer(  SimpleXMLRPCServer,
                        XMLRPCDocGenerator):
    def __init__(self, addr, requestHandler=DocXMLRPCRequestHandler,
                 logRequests=1, allow_none=False, encoding=None,
                 bind_and_activate=True):
        SimpleXMLRPCServer.__init__(self, addr, requestHandler, logRequests,
                                    allow_none, encoding, bind_and_activate)
        XMLRPCDocGenerator.__init__(self)

DocCGIXMLRPCRequestHandler

DocCGIXMLRPCRequestHandler 和 CGIXMLRPCRequestHandler 的功能差不多。

它也额外提供了 GET 方式(浏览器访问)获取 PRC 方法的文档说明。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class DocCGIXMLRPCRequestHandler(   CGIXMLRPCRequestHandler,
                                    XMLRPCDocGenerator):
    def handle_get(self):
        response = self.generate_html_documentation()

        print 'Content-Type: text/html'
        print 'Content-Length: %d' % len(response)
        print
        sys.stdout.write(response)

    def __init__(self):
        CGIXMLRPCRequestHandler.__init__(self)
        XMLRPCDocGenerator.__init__(self)
0 Comments

Published

Dec 8, 2014

Category

Python

Tags

  • python 23
  • stdlibs 15

Contact

  • Powered by Pelican. Theme: Elegant by Talha Mansoor