在看Dev Guide的時候,有一些關於請求參數的約束內容,但分布的比較零散。今天早上整理了一下,並做了驗證,現在PO出來。
1)account、object、container的自訂中繼資料約束
- 每次請求:自訂的X-Account/Object/Container-Meta-*的條目數量 <= 90 個;
- 每次請求:自訂的X-Account/Object/Container-Meta-*的每個value,經過UTF-8的URL-Encoded之後的位元組 <= 256(一個漢字算3個位元組);
- 每次請求:自訂的X-Account/Object/Container-Meta-*的所有<key + value>,經過UTF-8的URL-Encoded之後的位元組 <= 4096(一個漢字算3個位元組);
- X-Account/Object/Container-Meta-*的總量目前沒發現限制。雖然官方文檔上說,Object和Container的中繼資料最多是90個,但是經實測發現,只是每次請求最多90個,總量並沒有限制。至少,現在還未發現。
2)Container名稱約束
- 不能包含'/'字元:結尾可以添加'/',會被自動忽略,且不計入總長度;
- 經過UTF-8的URL-Encoded之後的位元組 <= 256。雖然官方文檔上說,是經過URL編碼之後的長度不能大於256,但經實測發現,這個長度其實是UTF-8位元組,URL-Encoded之後,一個漢字(%xx%xx%xx)算3個位元組。
3)Object名稱約束
- 不能包含保留字元。官方文檔上如是說,但沒說哪些是保留字元,源碼裡也沒找到 -。-;
- 經過UTF-8的URL-Encoded之後的位元組 <= 1024。雖然官方文檔上說,是經過URL編碼之後的長度不能大於1024,但經實測發現,這個長度其實是UTF-8位元組,URL-Encoded之後,一個漢字(%xx%xx%xx)算3個位元組。
4)參數(marker、endMarker、prefix)約束 URL-Encoded的
5)Request Line Request Line <= 8192位元組。 這個完全就是URL-Encoded之後的長度,一個漢字(%xx%xx%xx)算
9個位元組。 以下為源碼constraints.py。約束值的定義:
#: Max file size allowed for objectsMAX_FILE_SIZE = constraints_conf_int('max_file_size', 5368709122) # 5 * 1024 * 1024 * 1024 + 2#: Max length of the name of a key for metadataMAX_META_NAME_LENGTH = constraints_conf_int('max_meta_name_length', 128)#: Max length of the value of a key for metadataMAX_META_VALUE_LENGTH = constraints_conf_int('max_meta_value_length', 256)#: Max number of metadata itemsMAX_META_COUNT = constraints_conf_int('max_meta_count', 90)#: Max overall size of metadataMAX_META_OVERALL_SIZE = constraints_conf_int('max_meta_overall_size', 4096)#: Max object name lengthMAX_OBJECT_NAME_LENGTH = constraints_conf_int('max_object_name_length', 1024)#: Max object list length of a get request for a containerCONTAINER_LISTING_LIMIT = constraints_conf_int('container_listing_limit', 10000)#: Max container list length of a get request for an accountACCOUNT_LISTING_LIMIT = constraints_conf_int('account_listing_limit', 10000)#: Max account name lengthMAX_ACCOUNT_NAME_LENGTH = constraints_conf_int('max_account_name_length', 256)#: Max container name lengthMAX_CONTAINER_NAME_LENGTH = constraints_conf_int('max_container_name_length', 256)#: Query string format= values to their corresponding content-type valuesFORMAT2CONTENT_TYPE = {'plain': 'text/plain', 'json': 'application/json', 'xml': 'application/xml'}
判斷中繼資料是否合法:
def check_metadata(req, target_type): """ Check metadata sent in the request headers. :param req: request object :param target_type: str: one of: object, container, or account: indicates which type the target storage for the metadata is :raises HTTPBadRequest: bad metadata """ prefix = 'x-%s-meta-' % target_type.lower() meta_count = 0 meta_size = 0 for key, value in req.headers.iteritems(): if not key.lower().startswith(prefix): continue key = key[len(prefix):] if not key: return HTTPBadRequest(body='Metadata name cannot be empty', request=req, content_type='text/plain') meta_count += 1 meta_size += len(key) + len(value) if len(key) > MAX_META_NAME_LENGTH: return HTTPBadRequest( body='Metadata name too long; max %d' % MAX_META_NAME_LENGTH, request=req, content_type='text/plain') elif len(value) > MAX_META_VALUE_LENGTH: return HTTPBadRequest( body='Metadata value too long; max %d' % MAX_META_VALUE_LENGTH, request=req, content_type='text/plain') elif meta_count > MAX_META_COUNT: return HTTPBadRequest( body='Too many metadata items; max %d' % MAX_META_COUNT, request=req, content_type='text/plain') elif meta_size > MAX_META_OVERALL_SIZE: return HTTPBadRequest( body='Total metadata too large; max %d' % MAX_META_OVERALL_SIZE, request=req, content_type='text/plain') return None
判斷建立對象的資料是否合法:
def check_object_creation(req, object_name): """ Check to ensure that everything is alright about an object to be created. :param req: HTTP request object :param object_name: name of object to be created :raises HTTPRequestEntityTooLarge: the object is too large :raises HTTPLengthRequered: missing content-length header and not a chunked request :raises HTTPBadRequest: missing or bad content-type header, or bad metadata """ if req.content_length and req.content_length > MAX_FILE_SIZE: return HTTPRequestEntityTooLarge(body='Your request is too large.', request=req, content_type='text/plain') if req.content_length is None and \ req.headers.get('transfer-encoding') != 'chunked': return HTTPLengthRequired(request=req) if 'X-Copy-From' in req.headers and req.content_length: return HTTPBadRequest(body='Copy requests require a zero byte body', request=req, content_type='text/plain') if len(object_name) > MAX_OBJECT_NAME_LENGTH: return HTTPBadRequest(body='Object name length of %d longer than %d' % (len(object_name), MAX_OBJECT_NAME_LENGTH), request=req, content_type='text/plain') if 'Content-Type' not in req.headers: return HTTPBadRequest(request=req, content_type='text/plain', body='No content type') if not check_utf8(req.headers['Content-Type']): return HTTPBadRequest(request=req, body='Invalid Content-Type', content_type='text/plain') if 'x-object-manifest' in req.headers: value = req.headers['x-object-manifest'] container = prefix = None try: container, prefix = value.split('/', 1) except ValueError: pass if not container or not prefix or '?' in value or '&' in value or \ prefix[0] == '/': return HTTPBadRequest( request=req, body='X-Object-Manifest must in the format container/prefix') return check_metadata(req, 'object')
其實我在測試的過程中發現,swift已經相當健壯了,會對違反以上所描述的所有約束的情況進行錯誤處理:拒絕請求,返迴響應的錯誤碼、錯誤原因說明。因此,我們本打算在SDK層再進行一次約束的想法就沒那麼重要了,反而會增加複雜度、降低SDK效率。因此,只添加業務層約束就OK,例如:
- X-Account/Object/Container-Meta-*中的'*'即key值,ISO-8859-1字元集,即英文字母、-、_、空格等簡單字元;
- 使用者不能直接建立、訪問、刪除尾碼為"_segments"的Container;
- 使用者不能直接建立、訪問、刪除尾碼為"_versions"的Container。