因为做项目的原因,需要对swift的源代码进行修改,所以从半个月前开始swift源码的阅读,在阅读过程中,有些认识以防日后忘记,特此留恋- -

   前几篇已经大致分析了swift的整个处理过程,但是对于从proxy到后端的*-server的过程只是草草带过,没有细致分析,今天特别做出分析。

   首先比较清晰地就是在swift-init main all过程中,启动了所有的服务器,包括proxy,account,container和object服务器,然后客户端总是通过对proxy服务器进行请求来获得应答的。当proxy接收到了用户请求之后,就分析请求的地址,分析出是对哪一种服务器的请求,其实就是根据路径长度,基本的应该是/version/ac/con/ob,少一个/就向上一层。

 

controller, path_parts = self.get_controller(req.path)

  def get_controller(self, path):
        """
        Get the controller to handle a request.

        :param path: path from request
        :returns: tuple of (controller class, path dictionary)

        :raises: ValueError (thrown by split_path) if given invalid path
        """
        version, account, container, obj = split_path(path, 1, 4, True)
        d = dict(version=version,
                 account_name=account,
                 container_name=container,
                 object_name=obj)
        if obj and container and account:
            return ObjectController, d
        elif container and account:
            return ContainerController, d
        elif account and not container and not obj:
            return AccountController, d
        return None, d

  当得出是对哪一种服务器进行请求之后,调用该服务器的方法,即*Controller,在此以对account的get请求为例,get和head差不多,put和post无非就是多了一个写文件,同步的过程。

  调用AccountController的GetorHead方法,首先通过account的名称,计算出对应的分区和节点,这个地方应该都是一个元组,因为有3个备份。然后调用父类的GetorHead_Base的方法进行处理。

def GETorHEAD(self, req):
        """Handler for HTTP GET/HEAD requests."""
        partition, nodes = self.app.account_ring.get_nodes(self.account_name)
        nodes = self.app.sort_nodes(nodes)
        resp = self.GETorHEAD_base(
            req, _('Account'), partition, nodes, req.path_info.rstrip('/'),
            len(nodes))

  在父类的这个方法中,首先根据计算得到的ip,端口,分区和设备名,与对应的服务器进行HTTP连接,然后接受来自远端服务器的应答信息

with ConnectionTimeout(self.app.conn_timeout):
                    headers = dict(req.headers)
                    headers['Connection'] = 'close'
                    conn = http_connect(
                        node['ip'], node['port'], node['device'], partition,
                        req.method, path, headers=headers,
                        query_string=req.query_string)   
                self.app.set_node_timing(node, time.time() - start_node_timing)
                with Timeout(self.app.node_timeout):
                    possible_source = conn.getresponse()

  这个粘贴复制过程中格式有点乱,但是主要代码就是这个,possible_source就是接受的远端服务器的请求。因为有三个节点保存有请求的信息,所以需要将循环进行三次,分别获得应答信息,然后保存在sources[]这个列表中,如果sources不为空,就表示至少有一个取得了成功,根据返回的时间,对得到的应答进行排序,取出最后一个应答的内容,这里为什么取最后一个我也不太想得通。然后就是对相应的内容进行分析,设置Response的各种信息,然后直接返回。如果sources为空,就表示请求全部失败了,那么就根据三个状态的代码,和返回的信息,来找出一个最好的应答返回给客户端。

1  if sources:
 2             sources.sort(key=source_key)       
 3             source = sources.pop()              
 4             for src in sources:
 5                 self.close_swift_conn(src)      
 6             res = Response(request=req, conditional_response=True)
 7             if req.method == 'GET' and \
 8                     source.status in (HTTP_OK, HTTP_PARTIAL_CONTENT):
 9                 res.app_iter = self._make_app_iter(node, source)
10                 # See NOTE: swift_conn at top of file about this.
11                 res.swift_conn = source.swift_conn
12             res.status = source.status
13             update_headers(res, source.getheaders())
14             if not res.environ:
15                 res.environ = {}
16             res.environ['swift_x_timestamp'] = \
17                 source.getheader('x-timestamp')
18             res.accept_ranges = 'bytes'
19             res.content_length = source.getheader('Content-Length')
20             if source.getheader('Content-Type'):
21                 res.charset = None
22                 res.content_type = source.getheader('Content-Type')
23             return res
24         return self.best_response(req, statuses, reasons, bodies,      
25                                   '%s %s' % (server_type, req.method))

  这个地方就把前面的流程解释完了。对于后端的accountserver则是按照下面的方式工作

  首先作为一个服务器,那么一定是在初始化的时候就进行了启动,有专门的socket对这个端口的HTTP请求进行监听,当有请求来时,这个请求一定是从proxy发过来的,就调用__call__方法,进行相应的处理,这个跟proxy的处理流程是一样的。

1 def __call__(self, env, start_response):
 2         start_time = time.time()
 3         req = Request(env)
 4         self.logger.txn_id = req.headers.get('x-trans-id', None)
 5         if not check_utf8(req.path_info):
 6             res = HTTPPreconditionFailed(body='Invalid UTF8 or contains NULL')
 7         else:
 8             try:
 9                 # disallow methods which are not publicly accessible
10                 try:
11                     method = getattr(self, req.method)
12                     getattr(method, 'publicly_accessible')
13                 except AttributeError:
14                     res = HTTPMethodNotAllowed()
15                 else:
16                     res = method(req)
17             except (Exception, Timeout):
18                 self.logger.exception(_('ERROR __call__ error with %(method)s'
19                                         ' %(path)s '),
20                                       {'method': req.method, 'path': req.path})
21                 res = HTTPInternalServerError(body=traceback.format_exc())
22         trans_time = '%.4f' % (time.time() - start_time)
23         additional_info = ''
24         if res.headers.get('x-container-timestamp') is not None:
25             additional_info += 'x-container-timestamp: %s' % \
26                 res.headers['x-container-timestamp']
27         log_message = '%s - - [%s] "%s %s" %s %s "%s" "%s" "%s" %s "%s"' % (
28             req.remote_addr,
29             time.strftime('%d/%b/%Y:%H:%M:%S +0000', time.gmtime()),
30             req.method, req.path,
31             res.status.split()[0], res.content_length or '-',
32             req.headers.get('x-trans-id', '-'),
33             req.referer or '-', req.user_agent or '-',
34             trans_time,
35             additional_info)
36         if req.method.upper() == 'REPLICATE':
37             self.logger.debug(log_message)
38         else:
39             self.logger.info(log_message)
40         return res(env, start_response)

  看起来挺长的,其实关键就是method(req)这一个方法,其他的都是一些swift的验证措施,不得不说这些中间件确实把整个swift代码弄得比较复杂,对于我这种新手读者造成很大的困扰。

  比如我们假设的是Get方法,那么我们就相当于是Get(req)。

def GET(self, req):
        """Handle HTTP GET request."""
        try:
            drive, part, account = req.split_path(3)

这个就是将req的路径分为三段,需要说的是,在一开始的时候req的路径并不是这样的,这个路径本来就是在proxy那一部分的account的base.py中通过方法合成出来的。。不知道这样表达有木有人能明白。反正我是明白了。。。

broker = self._get_account_broker(drive, part, account)

建立一个与数据库连接的代理,然后后面对数据库的操作都是通过这个代理调用db.py中的方法进行完成。
关键的其实就是列出整个account下包含的container的名称。

account_list = broker.list_containers_iter(limit, marker, end_marker,
                                                   prefix, delimiter)

这里面的一些参数都是在HTTP请求中就会包含有的,比如limit,就是说取出的container个数。最后的部分就是根据提出的格式要求,对这些数据进行排版。

if out_content_type == 'application/json':
            data = []
            for (name, object_count, bytes_used, is_subdir) in account_list:
                if is_subdir:
                    data.append({'subdir': name})
                else:
                    data.append({'name': name, 'count': object_count,
                                'bytes': bytes_used})
            account_list = json.dumps(data)
        elif out_content_type.endswith('/xml'):
            output_list = ['<?xml version="1.0" encoding="UTF-8"?>',
                           '<account name="%s">' % account]
            for (name, object_count, bytes_used, is_subdir) in account_list:
                name = saxutils.escape(name)
                if is_subdir:
                    output_list.append('<subdir name="%s" />' % name)
                else:
                    item = '<container><name>%s</name><count>%s</count>' \
                           '<bytes>%s</bytes></container>' % \
                           (name, object_count, bytes_used)
                    output_list.append(item)
            output_list.append('</account>')
            account_list = '\n'.join(output_list)

  然后将这个内容放在resp中,进行返回,由上面分析过的getresponse()获得。

这样就完成了一次请求及其处理过程