NodeJS请求如何发送多部分/表单数据POST请求

ijnw1ujt  于 2022-12-18  发布在  Node.js
关注(0)|答案(4)|浏览(281)

我试图发送一个POST请求到一个API,请求中有一个图像。我正在使用请求模块进行此操作,但我尝试的所有操作都不起作用。我当前的代码:

const options = {
    method: "POST",
    url: "https://api.LINK.com/file",
    port: 443,
    headers: {
        "Authorization": "Basic " + auth,
        "Content-Type": "multipart/form-data"
    },
    form : {
        "image" : fs.readFileSync("./images/scr1.png")
    }
};

request(options, function (err, res, body) {
    if(err) console.log(err);
    console.log(body);
});

但是request由于某种原因使用了Content-Type: application/x-www-form-urlencoded ...我该如何修复这个问题呢?

rsl1atfo

rsl1atfo1#

如文档表单中所述,multipart/form-data请求正在使用form-data库。因此,您需要提供formData选项,而不是form选项。

const options = {
    method: "POST",
    url: "https://api.LINK.com/file",
    port: 443,
    headers: {
        "Authorization": "Basic " + auth,
        "Content-Type": "multipart/form-data"
    },
    formData : {
        "image" : fs.createReadStream("./images/scr1.png")
    }
};

request(options, function (err, res, body) {
    if(err) console.log(err);
    console.log(body);
});
e3bfsja2

e3bfsja22#

烦恼的是几乎所有解决这个问题的示例都包含第三方模块。我知道简单地包含一个模块并复制/粘贴一个代码示例通常更容易,但对于像这样的基本HTTP概念来说,真的不需要这样的模块。添加模块会快速增加您在AWS lambda这样的云环境中的占用空间,在这种环境中,总的解决方案文件大小会严重影响运行时性能。我在这里找到了这个有用的示例,
https://tanaikech.github.io/2017/07/27/multipart-post-request-using-node.js/

var fs = require('fs');
var request = require('request');
var upfile = 'sample.zip';
fs.readFile(upfile, function(err, content){
    if(err){
        console.error(err);
    }
    var metadata = {
        token: "### access token ###",
        channels: "sample",
        filename: "samplefilename",
        title: "sampletitle",
    };
    var url = "https://slack.com/api/files.upload";
    var boundary = "xxxxxxxxxx";
    var data = "";
    for(var i in metadata) {
        if ({}.hasOwnProperty.call(metadata, i)) {
            data += "--" + boundary + "\r\n";
            data += "Content-Disposition: form-data; name=\"" + i + "\"; \r\n\r\n" + metadata[i] + "\r\n";
        }
    };
    data += "--" + boundary + "\r\n";
    data += "Content-Disposition: form-data; name=\"file\"; filename=\"" + upfile + "\"\r\n";
    data += "Content-Type:application/octet-stream\r\n\r\n";
    var payload = Buffer.concat([
            Buffer.from(data, "utf8"),
            new Buffer(content, 'binary'),
            Buffer.from("\r\n--" + boundary + "--\r\n", "utf8"),
    ]);
    var options = {
        method: 'post',
        url: url,
        headers: {"Content-Type": "multipart/form-data; boundary=" + boundary},
        body: payload,
    };
    request(options, function(error, response, body) {
        console.log(body);
    });
});

希望对其他人有帮助!

lsmepo6l

lsmepo6l3#

由于request模块为deprecated,考虑使用form-data(唯一依赖;处理丑陋低级细节(如计算唯一边界字符串)的小封装)和核心http(s)模块。
这有点冗长,但您可以得到较小的生产构建,这总是好的,在无服务器环境中更是如此,因为它可以减少冷启动时间。

// abstract and promisify actual network request
async function makeRequest(formData, options) {
  return new Promise((resolve, reject) => {
    const req = formData.submit(options, (err, res) => {
      if (err) {
        return reject(new Error(err.message))
      }

      if (res.statusCode < 200 || res.statusCode > 299) {
        return reject(new Error(`HTTP status code ${res.statusCode}`))
      }

      const body = []
      res.on('data', (chunk) => body.push(chunk))
      res.on('end', () => {
        const resString = Buffer.concat(body).toString()
        resolve(resString)
      })
    })
  })
}

const formData = new FormData()

formData.append('comment', 'Some note attached to the submitted file')
formData.append('image', fs.createReadStream('./images/logo.png'))

const options = {
  host: 'postman-echo.com',
  path: '/post',
  method: 'POST',
  protocol: 'https:', // note : in the end
  headers: {
    Authorization: `Basic some-token-here`,
  },
}

const res = await makeRequest(formData, options)

更新,解决生产/lambda内部版本大小。使用request包的答案添加3.84mb的内存占用。此处使用的form-data库为304kb

j9per5c4

j9per5c44#

我想在没有外部依赖的typescript中构建一个工作示例。我仍然推荐above解决方案,但认为这可能对其他人有帮助。这里唯一的外部依赖是mime-types,用于设置内容类型头,但如果您事先知道要上传的内容类型,则可以删除此依赖,从而使解决方案完全没有依赖性。
实施:

/* eslint-disable prefer-template */
import http from 'http';
import fs from 'fs';
import mime from 'mime-types';

interface FileObject extends Record<string, string> {
  fileName: string,
}

const buildFormInputFields = (fileObj: FileObject) => Object.entries(fileObj).map(
  ([key, value], index) => {
    const pairStr = `name="${key}"; ${key}="${value}"`;
    return index === Object.keys(fileObj).length - 1 ? pairStr : `${pairStr};`;
  },
).join(' ');

const createFileRequest = (
  fileObj: FileObject,
  data: Buffer,
  boundary: string,
) => {
  const inputFields = buildFormInputFields(fileObj);
  const { fileName } = fileObj;
  const mimeType = mime.lookup(fileName);
  if (!mimeType) throw new Error(`invalid mime type for file ${fileName}`);
  const header = (
    `--${boundary}\r\n`
    + `Content-Disposition: form-data; ${inputFields}`
    + '\r\n'
    + `Content-Type: ${mime.lookup(fileObj.fileName)}`
    + '\r\n\r\n'
  );
  return Buffer.concat([
    Buffer.from(header, 'utf-8'),
    data,
    Buffer.from('\r\n', 'utf-8'),
  ]);
};

const buildRequestOptions = (boundary: string, length: number) => (
  {
    // your host here
    host: 'localhost',
    // your port here
    port: 8088,
    // your endpoint here
    path: '/api/v1/files',
    method: 'POST',
    headers: {
      'Content-Type': `multipart/form-data; boundary=${boundary}`,
      'Content-Length': length,
      Accept: '*/*',
    },
  }
);

const sendFileUploadRequest = (files: FileObject[], boundary: string) => new Promise(
  (resolve, reject) => {
    const fileBodies = files.map((fileObj) => {
      const { fileName } = fileObj;
      const data = fs.readFileSync(`${__dirname}/${fileName}`);
      return createFileRequest(fileObj, data, boundary);
    });

    const footer = `--${boundary}--`;

    const body = Buffer.concat([
      ...fileBodies,
      Buffer.from(footer, 'utf-8'),
    ]);

    const options = buildRequestOptions(boundary, body.length);

    const req = http.request(options, (res) => {
      console.log(`status: ${res.statusCode}`);

      res.on('data', (chunk) => {
        console.log(`BODY: ${chunk}`);
      });

      res.on('end', () => {
        console.log('No more data in response.');
        resolve(null);
      });
    });

    req.on('error', (e) => {
      reject(e);
      console.error(`problem with request: ${e.message}`);
    });
    req.write(body);
    req.end();
  },
);

示例脚本使用此:

(async () => {
  const files: FileObject[] = [
    {
      fileName: 'test1.txt',
      foo: 'bar',
    },
    {
      fileName: 'test2.txt',
      foo: 'bar',
    },
  ];
  const boundary = '----FOOBAR';
  await sendFileUploadRequest(files, boundary);
})();

相关问题