使用NodeJ发布多个二进制文件

时间:2017-06-30 16:01:46

标签: node.js forms http post binary

我如何POST多个二进制文件到Content-Type: "form-data"的服务器,只使用 http模块?

例如,我的键看起来像这样:

{
    Image1: <binary-data>,
    Image2: <binary-data>
}

1 个答案:

答案 0 :(得分:1)

TL:DR;请参阅本答案底部的完整示例代码。

我试图在仅使用核心NodeJs模块(不使用POST之类的东西等)的情况下,想法如何npm install request多个二进制图像文件到NodeJS中的服务器。大约3个小时后,在DuckDuckGo中键入了错误的搜索条件,并且没有在线查找示例,我即将转换职业。但是在将我的头撞到桌子上将近半天后,我的紧迫感变得迟钝了,我设法将一个有效的解决方案蹒跚而行。如果没有写好this Stackoverflow postthis Github Gist答案的优秀人才,这是不可能的。我使用PostManCharles Proxy来分析成功的HTTP POSTS,正如我在NodeJS文档中挖掘的那样。

POST两个二进制图像和文本字段multipart/form-data有几点需要理解,仅依赖于核心NodeJS模块:

1)边界标识符

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
}

2)表单字段元描述符

(这可能不是他们所谓的)

您还需要一种方法来为您发送的每个表单字段编写元数据,无论该表单字段是包含二进制文件还是文本对象。以下是图像文件的描述符,其中包含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)
})