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

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

  


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

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

请求示例:

POST http://www.example.com HTTP/1.1
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryrGKCBY7qhFd3TrwA

------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="text"

title
------WebKitFormBoundaryrGKCBY7qhFd3TrwA
Content-Disposition: form-data; name="file"; filename="chrome.png"
Content-Type: image/png

PNG ... content of chrome.png ...
------WebKitFormBoundaryrGKCBY7qhFd3TrwA--

这种方式一般用来上传文件(Content-Disposition后可接form-data表示表单数据,attachment表示附件)。RFC2183
消息主体里按照字段个数又分为多个结构类似的部分:
每部分都是以 --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请求实体的代码:

# coding=utf-8

import cgi

def _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.file

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

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

###################################
|||  
###################################
POST / HTTP/1.1

'''
#############
|||
#############

boundary是自定义的一串复杂无规律的字符串
Content-Type:
有附件时类型务必是multipart/form-data;
无附件(仅post表单)默认类型是application/x-www-form-urlencoded.
'''

Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryQQ3J8kPsjFpTmqNz

################
|||
################

------WebKitFormBoundaryQQ3J8kPsjFpTmqNz
Content-Disposition: form-data; name="name"

Scofield
------WebKitFormBoundaryQQ3J8kPsjFpTmqNz
Content-Disposition: form-data; name="name"

Lincoln
------WebKitFormBoundaryQQ3J8kPsjFpTmqNz
Content-Disposition: form-data; name="file"; filename="test.txt"
Content-Type: text/plain

just a test
------WebKitFormBoundaryQQ3J8kPsjFpTmqNz
Content-Disposition: form-data; name="id"

4008009001
------WebKitFormBoundaryQQ3J8kPsjFpTmqNz--
前端对应的HTML表单是这样的( 也就是cgi.FieldStorage()调用的东西)

web框架之cgi.FieldStorage()与数据提交_第1张图片

浏览器显示出来是这样的:
web框架之cgi.FieldStorage()与数据提交_第2张图片

注: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属性的名称:

web框架之cgi.FieldStorage()与数据提交_第3张图片

结果:

web框架之cgi.FieldStorage()与数据提交_第4张图片

如图所示,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 属性进行相应处理!

web框架之cgi.FieldStorage()与数据提交_第5张图片

你可能感兴趣的:(python,Web,HTML)