因为做项目的原因,需要对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()获得。
这样就完成了一次请求及其处理过程