You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Node.js上传文件至Crowdin Storage失败,请求返回400状态码

问题:Node.js上传文件到Crowdin Storage返回400错误(streamIsEmpty)

问题描述

上传文件至Crowdin Storage时,收到400状态码错误,提示流为空。

错误信息

{
  "errors": [
    {
      "error": {
        "key": "fileStream",
        "errors": [
          {
            "code": "streamIsEmpty",
            "message": "Stream size is null. Not empty content expected"
          }
        ]
      }
    }
  ]
}

已尝试的操作

  • 使用Axios发起HTTP请求
  • 上传前验证文件存在且非空
  • 按Crowdin API文档设置必要请求头(Authorization、Crowdin-API-FileName、Content-Type)
  • 使用正确的key将文件添加至FormData

代码片段

const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const path = require('path');

// Replace with your actual Crowdin API key and project ID
const CROWDIN_API_KEY = 'crowdin_api_key';
const CROWDIN_PROJECT_ID = 123467890;

// Function to upload file to Crowdin storage
const uploadFileToStorage = async (filePath) => {
    try {
        const fileName = path.basename(filePath);

        // Check if file exists and is not empty
        if (!fs.existsSync(filePath)) {
            throw new Error(`File does not exist: ${filePath}`);
        }
        const stats = fs.statSync(filePath);
        if (stats.size === 0) {
            throw new Error(`File is empty: ${filePath}`);
        }

        const form = new FormData();
        const fileStream = fs.createReadStream(filePath);

        form.append('file', fileStream);

        console.log('Uploading file to Crowdin:', fileName);
        const response = await axios.post('https://api.crowdin.com/api/v2/storages', form, {
            headers: {
                'Authorization': `Bearer ${CROWDIN_API_KEY}`,
                'Crowdin-API-FileName': fileName,
                ...form.getHeaders()
            }
        });

        return response.data.data.id;
    } catch (error) {
        console.error('Error uploading file to storage:', error.response ? error.response.data : error.message);
        if (error.response) {
            console.error('Response status:', error.response.status);
            console.error('Response data:', JSON.stringify(error.response.data, null, 2));
        }
        throw error;
    }
};

// Function to add file to Crowdin project
const addFileToProject = async (storageId, fileName) => {
    try {
        const response = await axios.post(`https://api.crowdin.com/api/v2/projects/${CROWDIN_PROJECT_ID}/files`, {
            storageId: storageId,
            name: fileName
        }, {
            headers: {
                'Authorization': `Bearer ${CROWDIN_API_KEY}`,
                'Content-Type': 'application/json'
            }
        });

        console.log('File added to project successfully:', response.data);
    } catch (error) {
        console.error('Error adding file to project:', error.response ? error.response.data : error.message);
        if (error.response) {
            console.error('Response status:', error.response.status);
            console.error('Response data:', JSON.stringify(error.response.data, null, 2));
        }
        throw error;
    }
};

// Main function to upload file to Crowdin
const uploadFileToCrowdin = async (filePath) => {
    try {
        const storageId = await uploadFileToStorage(filePath);
        const fileName = path.basename(filePath);
        await addFileToProject(storageId, fileName);
    } catch (error) {
        console.error('Error processing the file:', error.message);
    }
};

// Get the file path from command line arguments and call the upload function
const filePath = process.argv[2];
if (!filePath) {
    console.error('Please provide the file path as an argument.');
    process.exit(1);
}

uploadFileToCrowdin(filePath);

补充信息

  • API key可正常通过Crowdin API认证
  • Postman测试Crowdin API端点功能正常

解决思路与建议

1. 显式传递文件大小到FormData

fs.createReadStream生成的流不会自动携带文件大小信息,Crowdin API可能需要此信息验证流的有效性。修改form.append代码,传入已知的文件大小:

form.append('file', fileStream, {
  filename: fileName,
  knownLength: stats.size
});

2. 确保请求头正确合并

避免axios headers合并时的潜在冲突,先单独获取FormData的headers,再合并自定义头:

const formHeaders = form.getHeaders();
const headers = {
  'Authorization': `Bearer ${CROWDIN_API_KEY}`,
  'Crowdin-API-FileName': fileName,
  ...formHeaders
};

const response = await axios.post('https://api.crowdin.com/api/v2/storages', form, { headers });

3. 尝试读取文件内容而非使用流(适合小文件)

如果文件体积不大,直接读取文件内容传入FormData,避免流处理的问题:

const fileContent = fs.readFileSync(filePath);
form.append('file', fileContent, fileName);

4. 检查流的编码与状态

创建流时指定编码,确保流处于可读状态:

const fileStream = fs.createReadStream(filePath, { encoding: 'binary' });

内容的提问来源于stack exchange,提问作者Santhosh Kumar O

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:51:06