我正确解析这个HTTP POST请求了吗?

3 投票
3 回答
5427 浏览
提问于 2025-04-16 01:28

首先,我想说的是,我正在使用 twisted.web 框架。Twisted.web 的文件上传功能没有按照我想要的方式工作(它只包含文件数据,而没有其他信息),cgi.parse_multipart 也没有达到我的期望(同样的情况,twisted.web 使用了这个函数),cgi.FieldStorage 也不行(因为我通过 twisted 获取 POST 数据,而不是通过 CGI 接口——据我所知,FieldStorage 是通过标准输入来获取请求的),而且 twisted.web2 也不适合我,因为使用 Deferred 让我感到困惑和恼火(对我来说太复杂了)。

话虽如此,我决定尝试自己解析 HTTP 请求。

在 Chrome 中,HTTP 请求是这样形成的:

------WebKitFormBoundary7fouZ8mEjlCe92pq
Content-Disposition: form-data; name="upload_file_nonce"

11b03b61-9252-11df-a357-00266c608adb
------WebKitFormBoundary7fouZ8mEjlCe92pq
Content-Disposition: form-data; name="file"; filename="login.html"
Content-Type: text/html

<!DOCTYPE html>
<html>
  <head> 

...

------WebKitFormBoundary7fouZ8mEjlCe92pq
Content-Disposition: form-data; name="file"; filename=""


------WebKitFormBoundary7fouZ8mEjlCe92pq--

这总是这样形成的吗?我用正则表达式来解析它,像这样(请原谅我代码的冗长):

(注意,我剪掉了大部分代码,只显示我认为相关的部分(正则表达式(是的,嵌套的括号),这是我构建的 Uploads 类中的一个 __init__ 方法(目前为止唯一的方法)。完整代码可以在修订历史中查看(希望我没有搞错任何括号))

if line == "--{0}--".format(boundary):
    finished = True

if in_header == True and not line:
    in_header = False
    if 'type' not in current_file:
        ignore_current_file = True

if in_header == True:
    m = re.match(
        "Content-Disposition: form-data; name=\"(.*?)\"; filename=\"(.*?)\"$", line)
    if m:
        input_name, current_file['filename'] = m.group(1), m.group(2)

    m = re.match("Content-Type: (.*)$", line)
    if m:
        current_file['type'] = m.group(1)

    else:
        if 'data' not in current_file:
            current_file['data'] = line
        else:
            current_file['data'] += line

你可以看到,每当达到一个边界时,我就会开始一个新的“文件”字典。我把 in_header 设置为 True,表示我正在解析头部。当我遇到一个空行时,我把它改为 False——但在此之前我会检查是否为该表单值设置了 Content-Type——如果没有,我就把 ignore_current_file 设置为真,因为我只关注文件上传。

我知道我应该使用一个库,但我已经厌倦了阅读文档,尝试让不同的解决方案在我的项目中工作,同时还要让代码看起来合理。我只想尽快通过这一部分——如果解析带文件上传的 HTTP POST 这么简单,那我就继续这样做。

注意:这段代码现在运行得很好,我只是想知道它是否会在某些浏览器的请求上出错。

3 个回答

1

内容处置头(content-disposition header)里的字段没有固定的顺序,而且它可能包含的不仅仅是文件名。所以你用来匹配文件名的方式可能会失败——甚至可能根本就没有文件名!

你可以查看一下 rfc2183这个是关于邮件的,关于http的可以看看 rfc1806rfc2616,还有可能更多的文档)

另外,我建议在这种正则表达式中,把每个空格替换成 \s*,而不是依赖字母的大小写。

7

我解决这个问题的方法是使用 cgi.FieldStorage 来解析内容,像这样:

class Root(Resource):

def render_POST(self, request):

    self.headers = request.getAllHeaders()
    # For the parsing part look at [PyMOTW by Doug Hellmann][1]
    img = cgi.FieldStorage(
        fp = request.content,
        headers = self.headers,
        environ = {'REQUEST_METHOD':'POST',
                 'CONTENT_TYPE': self.headers['content-type'],
                 }
    )

    print img["upl_file"].name, img["upl_file"].filename,
    print img["upl_file"].type, img["upl_file"].type
    out = open(img["upl_file"].filename, 'wb')
    out.write(img["upl_file"].value)
    out.close()
    request.redirect('/tests')
    return ''
1

你可能不太想去看文档,但我觉得最好的建议还是去看看:

这样可以确保你不会遗漏任何情况。更简单的方法可能是使用poster这个库。

撰写回答