我如何POST
多个二进制文件到Content-Type: "form-data"
的服务器,只使用 http
模块?
例如,我的键看起来像这样:
{
Image1: <binary-data>,
Image2: <binary-data>
}
答案 0 :(得分:1)
TL:DR;请参阅本答案底部的完整示例代码。
我试图在仅使用核心NodeJs模块(不使用POST
之类的东西等)的情况下,想法如何npm install request
多个二进制图像文件到NodeJS中的服务器。大约3个小时后,在DuckDuckGo中键入了错误的搜索条件,并且没有在线查找示例,我即将转换职业。但是在将我的头撞到桌子上将近半天后,我的紧迫感变得迟钝了,我设法将一个有效的解决方案蹒跚而行。如果没有写好this Stackoverflow post和this Github Gist答案的优秀人才,这是不可能的。我使用PostMan和Charles Proxy来分析成功的HTTP POSTS,正如我在NodeJS文档中挖掘的那样。
POST
两个二进制图像和文本字段multipart/form-data
有几点需要理解,仅依赖于核心NodeJS模块:
What is the boundary in multipart/form-data?
解决方案的第一部分是创建一个&#34;边界标识符&#34;,它是一串短划线-
,附加一个随机数。您可以使用任何您想要的内容,foorbar
等。
------------0123456789
然后在每个数据块之间放置边界;无论是二进制数据还是文本数据。列出所有数据后,在末尾添加边界标识符,并附加两个破折号:
------------0123456789--
您还需要将边界添加到headers
,以便接收帖子的服务器了解您的帖子数据的哪一行构成字段之间的边界。
const headers = {
// Inform the server what the boundary identifier looks like
'Content-Type': `multipart/form-data; boundary=${partBoundary}`,
'Content-Length': binaryPostData.length
}
(这可能不是他们所谓的)
您还需要一种方法来为您发送的每个表单字段编写元数据,无论该表单字段是包含二进制文件还是文本对象。以下是图像文件的描述符,其中包含mime类型:
Content-Disposition: form-data; name="Image1"; filename="image-1.jpg"
Content-Type: image/jpeg
这是文本字段的描述符:
Content-Disposition: form-data; name="comment"
因此,发送到服务器的整个发布数据将如下所示:
----------------------------9110957588266537
Content-Disposition: form-data; name="Image1"; filename="image-1.jpg"
Content-Type: image/jpeg
ÿØÿàJFIFHHÿáLExifMMi
ÿí8Photoshop 3.08BIM8BIM%ÔÙ²é ìøB~ÿÀ... <<<truncated for sanity>>>
----------------------------9110957588266537
Content-Disposition: form-data; name="Image2"; filename="image-2.jpg"
Content-Type: image/jpeg
ÿØÿàJFIFHHÿáLExifMMi
ÿí8Photoshop 3.08BIM8BIM%ÔÙ²é ìøB~ÿÀ... <<<truncated for sanity>>>
----------------------------9110957588266537
Content-Disposition: form-data; name="comment"
This is a comment.
----------------------------9110957588266537--
生成此帖子数据后,可以将其转换为二进制文件并写入HTTP POST请求:request.write(binaryPostData)
。
以下是完整的示例代码,允许您发布二进制文件和文本数据,而无需在代码中包含其他NodeJS库和包。
// This form data lists 2 binary image fields and text field
const form = [
{
name: 'Image1',
type: 'file',
value: 'image-1.jpg'
},
{
name: 'Image2',
type: 'file',
value: 'image-2.jpg'
},
{
name: 'comment',
type: 'text',
value: 'This is a comment.'
}
]
// Types of binary files I may want to upload
const types = {
'.json': 'application/json',
'.jpg': 'image/jpeg'
}
const config = {
host: 'ec2-192.168.0.1.compute-1.amazonaws.com',
port: '80',
path: '/api/route'
}
// Create an identifier to show where the boundary is between each
// part of the form-data in the POST
const makePartBoundary = () => {
const randomNumbers = (Math.random() + '').split('.')[1]
return '--------------------------' + randomNumbers
}
// Create meta for file part
const encodeFilePart = (boundary, type, name, filename) => {
let returnPart = `--${boundary}\r\n`
returnPart += `Content-Disposition: form-data; name="${name}"; filename="${filename}"\r\n`
returnPart += `Content-Type: ${type}\r\n\r\n`
return returnPart
}
// Create meta for field part
const encodeFieldPart = (boundary, name, value) => {
let returnPart = `--${boundary}\r\n`
returnPart += `Content-Disposition: form-data; name="${name}"\r\n\r\n`
returnPart += value + '\r\n'
return returnPart
}
const makePostData = {
// Generate the post data for a file
file: (item, partBoundary) => {
let filePostData = ''
// Generate the meta
const filepath = path.join(__dirname, item.value)
const extention = path.parse(filepath).ext
const mimetype = types[extention]
filePostData += encodeFilePart(partBoundary, mimetype, item.name, item.value)
// Add the binary file data
const fileData = fs.readFileSync(filepath, 'binary')
filePostData += fileData
filePostData += '\r\n'
return filePostData
},
// Generate post data for the text field part of the form
text: (item, partBoundary) => {
let textPostData = ''
textPostData += encodeFieldPart(partBoundary, item.name, item.value)
return textPostData
}
}
const post = () => new Promise((resolve, reject) => {
let allPostData = ''
// Create a boundary identifier (a random string w/ `--...` prefixed)
const partBoundary = makePartBoundary()
// Loop through form object generating post data according to type
form.forEach(item => {
if (Reflect.has(makePostData, item.type)) {
const nextPostData = makePostData[item.type](item, partBoundary)
allPostData += nextPostData
}
})
// Create the `final-boundary` (the normal boundary + the `--`)
allPostData += `--${partBoundary}--`
// Convert the post data to binary (latin1)
const binaryPostData = Buffer.from(allPostData, 'binary')
// Generate the http request options
const options = {
host: config.host,
port: config.port,
path: config.path,
method: 'POST',
headers: {
// Inform the server what the boundary identifier looks like
'Content-Type': `multipart/form-data; boundary=${partBoundary}`,
'Content-Length': binaryPostData.length
}
}
// Initiate the HTTP request
const req = http.request(options, res => {
res.setEncoding('utf8')
let body = ''
// Accumulate the response data
res.on('data', chunk => {
body += chunk
})
// Resolve when done
res.on('end', () => {
resolve(body)
})
res.on('close', () => {
resolve(body)
})
res.on('error', err => {
reject(err)
})
})
// Send the binary post data to the server
req.write(binaryPostData)
// Close the HTTP request object
req.end()
})
// Post and log response
post().then(data => {
console.log(data)
})
.catch(err => {
console.error(err)
})