web框架之cgi.FieldStorage()与数据提交

来源:互联网 发布:淘宝会员等级怎么看 编辑:程序博客网 时间:2024/05/29 05:55

 POST 一般用来向服务端提交数据,HTTP 协议是以 ASCII 码传输,建立在 TCP/IP 协议之上的应用层规范。规范把 HTTP 请求分为三个部分:状态行、请求头、消息主体。

<method> <request-URL> <version><headers><entity-body>
协议规定 POST 提交的数据必须放在消息主体(entity-body)中,但协议并没有规定数据必须使用什么编码方式。实际上,开发者完全可以自己决定消息主体的格式,只要最后发送的 HTTP 请求满足上面的格式就可以。

multipart/form-data:这是一个常见的 POST 数据提交的方式。我们使用表单上传文件时,必须让 form 的 enctype 等于这个值。

请求示例:

POST http://www.example.com HTTP/1.1Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA------WebKitFormBoundaryrGKCBY7qhFd3TrwAContent-Disposition: form-data; name="text"title------WebKitFormBoundaryrGKCBY7qhFd3TrwAContent-Disposition: form-data; name="file"; filename="chrome.png"Content-Type: image/pngPNG ... content of chrome.png ...------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

这种方式一般用来上传文件(Content-Disposition后可接form-data表示表单数据,attachment表示附件)。RFC2183
消息主体<entity-body>里按照字段个数又分为多个结构类似的部分:
每部分都是以 --boundary 开始,紧接着内容描述信息,然后是回车,最后是字段具体内容(文本或二进制)。如 text 的值就是 title

如果传输的是文件,还要包含文件名和文件类型(Content-Type)信息。如文件名为chrome.png的文件类型是image/png,具体内容(值)就是‘ PNG ... content of chrome.png ... ’ (二进制数据)

消息主体最后以 --boundary-- 标示结束。

参考资料:https://www.imququ.com/post/four-ways-to-post-data-in-http.html(挺好的资料)


下面分析web框架中分析post请求实体<entity-post>的代码:

# coding=utf-8import cgidef _to_unicode(s, encoding='utf-8'):    return s.decode('utf-8')class MultipartFile(object):    def __init__(self, storage):        self.filename = _to_unicode(storage.filename)        self.file = storage.fileclass Request(object):    def __init__(self, environ):        self._environ = environ    def _parse_input(self):        def _convert(item):            if isinstance(item, list):                return [_to_unicode(i.value) for i in item]            if item.filename:                return MultipartFile(item)            return _to_unicode(item.value)        fs = cgi.FieldStorage(fp=self._environ['wsgi.input'], environ=self._environ, keep_blank_values=True)        inputs = {}        for key in fs:            inputs[key] = _convert(fs[key])   #key的value可以是一个list        return inputs    def _get_raw_input(self):        if not hasattr(self, '_raw_input'):            self._raw_input = self._parse_input()        return self._raw_input    def __getitem__(self, key):        r = self._get_raw_input()[key]        if isinstance(r, list):            return r[0]        return r    def get(self, key, default=None):        r = self._get_raw_input().get(key, default)        if isinstance(r, list):            return r[0]        return r    def gets(self, key):        r = self._get_raw_input()[key]        if isinstance(r, list):            return r[:]        return [r]from StringIO import StringIOr = Request({'REQUEST_METHOD':'POST', 'wsgi.input':StringIO('a=1&b=M%20M&c=ABC&c=XYZ&e=')})print r['a']print r['c']b = '----WebKitFormBoundaryQQ3J8kPsjFpTmqNz'   #这是分割线,用于分割不同的字段,为了避免与正文内容重复,boundary 很长很复杂pl = ['--%s' % b, 'Content-Disposition: form-data; name=\"name\"\n', 'Scofield', '--%s' % b, 'Content-Disposition: form-data; name=\\"name\\"\\n', 'Lincoln', '--%s' % b, 'Content-Disposition: form-data; name=\\"file\\"; filename=\\"test.txt\\"', 'Content-Type: text/plain\\n', 'just a test', '--%s' % b, 'Content-Disposition: form-data; name=\\"id\\"\\n', '4008009001', '--%s--' % b, '']payload = '\n'.join(pl)r = Request({'REQUEST_METHOD':'POST', 'CONTENT_LENGTH':str(len(payload)), 'CONTENT_TYPE':'multipart/form-data; boundary=%s' % b, 'wsgi.input':StringIO(payload)})r.get('name')r.gets('name')f = r.get('file')print f.filename#print f.file.read()

payload(pl)就是post请求实体<entity-post>部分(这里是手动给的用来测试,正常情况下都是由浏览器客户端发来的),类似于我们上面的“ 请求示例”,存储在wsgi server的environ. wsgi.input中( 'wsgi.input':StringIO(payload) ),利用cgi.FieldStorage( )可以分解出其中的变量。

相应的,如果是浏览器客户端发来上面的请求,应该是这样的:

###################################|||<method> <request-URL> <version>###################################POST / HTTP/1.1'''#############|||<headers>#############boundary是自定义的一串复杂无规律的字符串Content-Type:有附件时类型务必是multipart/form-data;无附件(仅post表单)默认类型是application/x-www-form-urlencoded.'''Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryQQ3J8kPsjFpTmqNz################|||<entity-body>################------WebKitFormBoundaryQQ3J8kPsjFpTmqNzContent-Disposition: form-data; name="name"Scofield------WebKitFormBoundaryQQ3J8kPsjFpTmqNzContent-Disposition: form-data; name="name"Lincoln------WebKitFormBoundaryQQ3J8kPsjFpTmqNzContent-Disposition: form-data; name="file"; filename="test.txt"Content-Type: text/plainjust a test------WebKitFormBoundaryQQ3J8kPsjFpTmqNzContent-Disposition: form-data; name="id"4008009001------WebKitFormBoundaryQQ3J8kPsjFpTmqNz--
前端对应的HTML表单是这样的(也就是cgi.FieldStorage()调用的东西)


浏览器显示出来是这样的:

注:test.txt文本文档里面的内容是 " just a test " .


可从Web框架中得到启发:

form = cgi.FieldStorage()  # 获取文件名  fileitem = form['file']  # 检测文件是否上传  if fileitem.filename: # 设置文件路径   fn = os.path.basename(fileitem.filename) open('/tmp/' + fn, 'wb').write(fileitem.file.read())   

其中 ' file '是HTML表单name属性的名称:


结果:

如图所示,file文件类型的name属性名称是“ file ”,text文本框的name属性名称是“ trial ”,cgi.FieldStorage()就是通过“ file ”和“ trial ”来获取相关表单信息的。form['file']或form[ ' trial ' ]表示一个input标签整体,而标签有属性,如可以通过form['file'].filename或form['file'].file等来获取文件的相关信息,通过form[ ' trial ' ].value来获取text文本框里面的值。


回到web框架的源代码就容易理解了。

fs [key] 的fs是cgi.FieldStorage()实例,代表整个表单,key就是表单中各个标签的“name”属性的名称——"name","name","file","id",而 fs [key] 则表示一个input标签整体!可用标签属性来获取标签的具体内容。
                                                                                    name---- " name "-----Scofield(fs [name].value,标签的value属性可获取text的内容)
                                                                                    name---- " name "-----Lincoln fs [name].value,标签的value属性可获取text的内容
                                                                                    name----- " file "--------" test.txt "fs [file].filename,标签的filename属性可获取文件的名称
                                                                                    name----- " id "----------" 4008009001 "fs [id]
.value,标签的value属性可获取text的内容

出现了两个fs [name],可遍历后 return一个list(' return [_to_unicode(i.value) for i in item] ');对于文件类型的,通过判断其 filename 属性进行相应处理!


0 0
原创粉丝点击