MultiPartParserError: - 边界无效

时间:2015-12-17 03:31:20

标签: python django django-rest-framework python-requests

我试图使用Python请求模块将一些数据和文件发送到我的django rest应用程序,但得到以下错误。

    raise MultiPartParserError('Invalid boundary in multipart: %s' % boundary)
MultiPartParserError: Invalid boundary in multipart: None

代码: -

import requests
payload={'admins':[
                    {'first_name':'john'
                    ,'last_name':'white'
                    ,'job_title':'CEO'
                    ,'email':'test1@gmail.com'
                    },
                    {'first_name':'lisa'
                    ,'last_name':'markel'
                    ,'job_title':'CEO'
                    ,'email':'test2@gmail.com'
                    }
                    ],
        'company-detail':{'description':'We are a renowned engineering company'
                    ,'size':'1-10'
                    ,'industry':'Engineering'
                    ,'url':'http://try.com'
                    ,'logo':''
                    ,'addr1':'1280 wick ter'
                    ,'addr2':'1600'
                    ,'city':'rkville'
                    ,'state':'md'
                    ,'zip_cd':'12000'
                    ,'phone_number_1':'408-393-254'
                    ,'phone_number_2':'408-393-221'
                    ,'company_name':'GOOGLE'}
        }
files = {'upload_file':open('./test.py','rb')}
import json
headers = {'content-type' : 'application/json'}      
headers = {'content-type' : 'multipart/form-data'}      

#r = requests.post('http://127.0.0.1:8080/api/create-company-profile/',data=json.dumps(payload),headers=headers,files=files)
r = requests.post('http://127.0.0.1:8080/api/create-company-profile/',data=payload,headers=headers,files=files)
print r.status_code
print r.text

Django代码: -

class CompanyCreateApiView(CreateAPIView):
    parser_classes = (MultiPartParser, FormParser,)
    def post(self, request, *args, **kwargs):
        print 'request ==', request.data

5 个答案:

答案 0 :(得分:7)

好的,我忘记了你的标题。根据{{​​3}}:

Content-Type   = "Content-Type" ":" media-type
     

MIME提供了许多" multipart"类型 - 封装   单个邮件正文中的一个或多个实体。   所有多部分类型共享一个通用语法,...并且必须包含边界参数作为媒体类型的一部分   值。

以下是包含multipart / form-data的请求:

POST /myapp/company/ HTTP/1.1
Host: localhost:8000
Content-Length: 265
Accept-Encoding: gzip, deflate
Accept: */*
User-Agent: python-requests/2.9.0
Connection: keep-alive
Content-Type: multipart/form-data; boundary=63c5979328c44e2c869349443a94200e   

--63c5979328c44e2c869349443a94200e
Content-Disposition: form-data; name="hello"

world
--63c5979328c44e2c869349443a94200e
Content-Disposition: form-data; name="mydata"; filename="data.txt"

line 1
line 2
line 3
line 4

--63c5979328c44e2c869349443a94200e--

查看数据部分是如何被边界分开的:

--63c5979328c44e2c869349443a94200e--

我们的想法是使用某些东西作为不太可能出现在数据中的边界。请注意,边界包含在请求的Content-Type标题中。

该请求是由此代码生成的:

import requests

myfile = {'mydata': open('data.txt','rb')}

r = requests.post(url, 
        #headers = myheaders
        data = {'hello': 'world'}, 
        files = myfile
) 

看起来您正在仔细关注django-rest-framework the spec中的以下注释:

  

注意:开发客户端应用程序时,请务必确保   您在HTTP中发送数据时设置Content-Type标头   请求。

     

如果您未设置内容类型,则大多数客户端将默认使用   ' application / x-www-form-urlencoded',这可能不是你想要的。

但是当您使用requests时,如果您自己指定了Content-Type标题,那么requests会假设您知道自己在做什么,而且它并没有。用您提供的Content-Type标头覆盖Content-Type标头。

根据要求,您未在Content-Type标题中提供边界。你怎么能?您没有组装请求的主体并创建边界来分隔各种数据,因此您无法知道边界是什么。

django-rest-framework注释表明您的请求中应包含Content-Type标题时,其真正含义是:

  

您或您用于创建请求的任何程序都需要包含一个   Content-Type标题。

所以@AChampion在评论中完全正确:在所有requests文档广告之后,让Content-Type header提供requests

  

请求完成了Python HTTP / 1.1

的所有工作

requests的工作方式如下:如果您提供files关键字arg,则请求使用Content-Type multipart/form-data标头,并在标头中指定边界;然后requests使用边界组装请求的主体。如果您提供data关键字参数,则请求使用Content-Type application/x-www-form-urlencoded,它只会将字典中的所有键和值组合成以下格式:

x=10&y=20

无需边界。

并且,如果您同时提供files关键字arg和data关键字arg,则请求使用Content-Type multipart/form-data

答案 1 :(得分:1)

我使用django-rest-framework中的CreateAPIView尝试了您的代码。修复代码生成的所有初步错误后,我无法重现边界错误。

目录结构:

my_site/ 
     myapp/
         views.py
         serializers.py
         urls.py
         models.py
     mysite/
         settings.py
         urls.py

<强>程序my_app / views.py:

from rest_framework.generics import CreateAPIView
from rest_framework.response import Response
from rest_framework.parsers import MultiPartParser, FormParser

from myapp.serializers import CompanySerializer 
from myapp.models import Company

class CompanyCreateApiView(CreateAPIView):
    parser_classes = (MultiPartParser, FormParser,)  #Used to parse the Request.

    queryset = Company.objects.all()  #The contents of the Response.
    serializer_class = CompanySerializer  #Determines how the contents of the Response will be converted to json. 
                                          #Required. Defined in myapp/serializers.py

    def post(self, request, *args, **kwargs):
        print('data ==', request.data)
        print('myjson ==', request.data["myjson"].read())
        print('mydata ==', request.data["mydata"].read())

        queryset = self.get_queryset()
        serializer = CompanySerializer(queryset, many=True)

        return Response(serializer.data)

<强>的myapp / serializers.py:

from rest_framework import serializers
from myapp.models import Company

#Directions for converting a model instance into json:
class CompanySerializer(serializers.ModelSerializer):
    class Meta:
        model = Company  #The model this serializer applies to.

        #By default all fields are converted to json.  
        #To limit which fields should be converted:
        fields = ("name", "email")
        #Or, if its easier you can do this:
        #exclude = ('id',)

<强>的myapp / urls.py:

from django.conf.urls import url

from . import views

urlpatterns = (
    url(r'^company/', views.CompanyCreateApiView.as_view() ),
)

<强> my_site / urls.py:

from django.conf.urls import include, url
from django.contrib import admin


urlpatterns = [
    url(r'^admin/', include(admin.site.urls) ),

    url(r'^myapp/', include("myapp.urls") ),
]

<强>的myapp / models.py:

from django.db import models

# Create your models here.

class Company(models.Model):
    name = models.CharField(max_length=50)
    email = models.CharField(max_length=50)

    def __str__(self):
        return "{} {}".format(self.name, self.email)

<强> my_site / settings.py:

...
...
DEFAULT_APPS = (
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
)

THIRD_PARTY_APPS = (
    'rest_framework',

)

LOCAL_APPS = (
    'myapp',

)

INSTALLED_APPS = DEFAULT_APPS + THIRD_PARTY_APPS + LOCAL_APPS

REST_FRAMEWORK = {
    # Use Django's standard `django.contrib.auth` permissions,
    # or allow read-only access for unauthenticated users.
    #'DEFAULT_PERMISSION_CLASSES': [
    #    'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
    #]
}

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django.middleware.security.SecurityMiddleware',
)
    ...
    ...

请注意,我没有必要禁用csrf令牌。

<强> requests_client.py:

import requests

import json
from io import StringIO 

my_dict = {
    'admins': [
                {'first_name':'john'
                ,'last_name':'white'
                ,'job_title':'CEO'
                ,'email':'test1@gmail.com'
                },

                {'first_name':'lisa'
                ,'last_name':'markel'
                ,'job_title':'CEO'
                ,'email':'test2@gmail.com'
                }

    ],
    'company-detail': {
                'description': 'We are a renowned engineering company'
                ,'size':'1-10'
                ,'industry':'Engineering'
                ,'url':'http://try.com'
                ,'logo':''
                ,'addr1':'1280 wick ter'
                ,'addr2':'1600'
                ,'city':'rkville'
                ,'state':'md'
                ,'zip_cd':'12000'
                ,'phone_number_1':'408-393-254'
                ,'phone_number_2':'408-393-221'
                ,'company_name':'GOOGLE'
    }
}

url = 'http://localhost:8000/myapp/company/'

#StringIO creates a file-like object in memory, rather than on disk:

#python3.4:
#with StringIO(json.dumps(my_dict)) as json_file, open("data.txt", 'rb') as data_file:

#python2.7:
with StringIO(json.dumps(my_dict).decode('utf-8')) as json_file, open("data.txt", 'rb') as data_file:

    myfiles = [
        ("mydata", ("data.txt", data_file, "text/plain")),
        ("myjson", ("json.json", json_file, "application/json")),
    ]

    r = requests.post(url, files=myfiles) 

print(r.status_code)
print(r.text)

request_client.py终端窗口中的输出:

200 
[{"name":"GE","email":"ge@ge.com"},{"name":"APPL","email":"appl@appl.com"}]

django服务器窗口中的输出:

...
...
Quit the server with CONTROL-C.

data == <QueryDict: {'mydata': [<InMemoryUploadedFile: data.txt (text/plain)>], 
'myjson': [<InMemoryUploadedFile: json.json (application/json)>]}>

myjson == b'{"admins": [{"first_name": "john", "last_name": "white", "email": "test1@gmail.com", "job_title": "CEO"}, 
{"first_name": "lisa", "last_name": "markel", "email": "test2@gmail.com", "job_title": "CEO"}], "company-detail": 
{"description": "We are a renowned engineering company", "phone_number_2": "408-393-221", "phone_number_1": "408-393-254",
 "addr2": "1600", "addr1": "1280 wick ter", "logo": "", "size": "1-10", "city": "rkville", "url": "http://try.com", 
"industry": "Engineering", "state": "md", "company_name": "GOOGLE", "zip_cd": "12000"}}'

mydata == b'line 1\nline 2\nline 3\nline 4\n'

[18/Dec/2015 13:41:57] "POST /myapp/company/ HTTP/1.1" 200 75

答案 2 :(得分:1)

顺便说一下,在开发过程中使用requests时阅读django的大量html错误响应是一种不那么痛苦的方法:

$ python requests_client.py > error.html  (send output to the file error.html)

然后在浏览器中执行:

File > Open File

并导航至error.html。阅读浏览器中的错误说明,然后找出django项目中的错误。

然后返回终端窗口并按键盘上的向上箭头键返回:

$ python requests_client.py > error.html 

点击Return,然后刷新显示error.html的浏览器窗口。根据需要重复。 (不要错误地反复刷新浏览器并认为发送新请求 - 您必须再次运行request_client.py以发送新请求。)

答案 3 :(得分:0)

为了使用requests,我必须在我的django项目中禁用csrf tokens,否则我在发送帖子请求时会出错:

  

CSRF验证失败。请求中止。

     

您正在看到此消息   因为在提交表单时此站点需要CSRF cookie。这个   出于安全原因需要cookie,以确保您的浏览器   没有被第三方劫持。

settings.py:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    #'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

接下来,您的payload不是json数据。 json数据是一个字符串,但您的payload是一个python字典。而且,你不能只在你的字典中包装引号,因为json数据不能包含单引号 - 你只使用它。所以,你需要将你的python字典转换为json。

此处是requests客户:

import requests
import json
from io import StringIO 

my_dict = {
    'admins': [
                {'first_name':'john'
                ,'last_name':'white'
                ,'job_title':'CEO'
                ,'email':'test1@gmail.com'
                },

                {'first_name':'lisa'
                ,'last_name':'markel'
                ,'job_title':'CEO'
                ,'email':'test2@gmail.com'
                }

    ],
    'company-detail': {
                'description': 'We are a renowned engineering company'
                ,'size':'1-10'
                ,'industry':'Engineering'
                ,'url':'http://try.com'
                ,'logo':''
                ,'addr1':'1280 wick ter'
                ,'addr2':'1600'
                ,'city':'rkville'
                ,'state':'md'
                ,'zip_cd':'12000'
                ,'phone_number_1':'408-393-254'
                ,'phone_number_2':'408-393-221'
                ,'company_name':'GOOGLE'
    }
}

url = 'http://localhost:8000/myapp/upload/'


#StringIO creates a file-like object in memory, rather than on disk:
with StringIO(json.dumps(my_dict)) as json_file, open("data.txt", 'rb') as data_file:

    myfiles = [
        ("mydata", ("data.txt", data_file, "text/plain")),
        ("myjson", ("json.json", json_file, "application/json")),
    ]

    r = requests.post(url, files=myfiles) 

print(r.text)

请参阅requests文档的Advanced Usage部分。

这是基于django类的视图(尽管不是Django Rest Framework类视图):

from django.shortcuts import render
from django.http import HttpResponse

# Create your views here.

from django.views.generic import View

from django.views.generic import View

class FileUploadView(View):
    def post(self, request):
        file_dict = request.FILES
        print(file_dict)

        for name in file_dict:
            print(name)
            print(file_dict[name].read())
            print('*' * 50)

        return HttpResponse('thanks')

客户端窗口中的输出:

django186p34)~/python_programs$ python django_client.py 
thanks
(django186p34)~/python_programs$ 

django服务器窗口中的输出:

...
...
Quit the server with CONTROL-C.

<MultiValueDict: {'myjson': [<InMemoryUploadedFile: json.json (application/json)>], 
'mydata': [<InMemoryUploadedFile: data.txt (text/plain)>]}>
**************************************************
myjson
b'{"admins": [{"job_title": "CEO", "last_name": "white", "first_name": "john", "email": "test1@gmail.com"}, 
{"job_title": "CEO", "last_name": "markel", "first_name": "lisa", "email": "test2@gmail.com"}], "company-detail": 
{"description": "We are a renowned engineering company", "city": "rkville", "state": "md", "company_name": "GOOGLE", 
"addr1": "1280 wick ter", "url": "http://try.com", "phone_number_2": "408-393-221", "industry": "Engineering", "logo": "", "addr2": "1600",
 "phone_number_1": "408-393-254", "size": "1-10", "zip_cd": "12000"}}'
**************************************************
mydata
b'line 1\nline 2\nline 3\nline 4\n'
**************************************************
[16/Dec/2015 07:34:06] "POST /myapp/upload/ HTTP/1.1" 200 6

显然,requests <--> django mind meld允许您将POST数据与multipart / form-data混合,您可以这样做:

with open('data.txt', 'rb') as f:
    myfile = {'myfile': f}
    r = requests.post(url, data={"hello": "world"}, files=myfile) 

print(r.text)

加上这种观点:

from django.http import HttpResponse
from django.views.generic import View

class FileUploadView(View):
    def post(self, request):
        x = request.POST
        print(x)
        print(x["hello"])

        file_dict = request.FILES
        print(file_dict)
        print('*' * 50)

        for name in file_dict:
            print(name)
            print(file_dict[name].read())
            print('*' * 50)

        return HttpResponse('thanks')

我在服务器窗口中获得以下输出:

...
...
Quit the server with CONTROL-C.

<QueryDict: {'hello': ['world']}>
world
<MultiValueDict: {'myfile': [<InMemoryUploadedFile: data.txt ()>]}>
**************************************************
myfile
b'line 1\nline 2\nline 3\nline 4\n'
**************************************************
[16/Dec/2015 8:04:17] "POST /myapp/upload/ HTTP/1.1" 200 6

要在字典中允许使用UTF-8字符,转换为json时必须执行一些额外步骤。请参阅here

答案 4 :(得分:0)

使用参数上传文件时:

  1. 请勿覆盖标题

  2. 将其他参数与upload_file一起放入文件dict。

    input ={"md5":"xxxx","key":"xxxxx","sn":"xxxx"}
    files = {"pram1":"abc",
             "pram2":json.dumps(input),
             "upload_file": open('/home/gliu/abc', 'rb')}    
    res = requests.post(url, files=files)