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

使用Google Cloud Storage预签名URL上传图片异常(MERN+React Native)

Fixing Corrupted Image Uploads to Google Cloud Storage via Pre-Signed URLs in React Native (Expo)

I see exactly what's going on here—when you send the image object directly through Axios, React Native can’t automatically convert that asset URI into the raw binary data Google Cloud Storage (GCS) needs. That’s why your uploaded images end up corrupted. Let’s fix this step by step.

Step 1: Tweak Server-Side Pre-Signed URL Settings

Your current server code is almost there, but we can add a small safeguard to ensure GCS handles the upload correctly:

const { Storage } = require("@google-cloud/storage");
const storage = new Storage({keyFilename: "keys.json"});
const bucketName = "bucket";
const bucket = storage.bucket(bucketName);
const { v4 } = require("uuid");

async upload(req, res, next) {
  const options = {
    action: "write",
    expires: Date.now() + 15 * 60 * 1000, // 15 minutes
    contentType: "image/jpeg",
    // Ensure GCS treats the file as a direct upload, not an attachment
    contentDisposition: "inline"
  };
  const fileName = `1234/${v4()}.jpg`;
  const [url] = await bucket.file(fileName).getSignedUrl(options);
  res.send(url);
}

The contentDisposition: "inline" flag isn’t mandatory, but it helps GCS avoid misinterpreting the file as a downloadable attachment.

Step 2: Convert React Native Asset URI to Binary Data

React Native’s asset URIs (like assets-library://...) can’t be sent directly as a request body. We need to convert the URI into a Blob first. If you’re using Expo, the expo-file-system library makes this simple:

First, install the library if you haven’t already:

npx expo install expo-file-system

Then update your client-side code to read the URI, convert it to binary, and send it properly:

import axios from "axios";
import * as FileSystem from 'expo-file-system';

async function uploadImageToGCS(signedUrl, imageUri) {
  // Read the image from the asset URI as base64
  const base64Data = await FileSystem.readAsStringAsync(imageUri, {
    encoding: FileSystem.EncodingType.Base64,
  });

  // Convert base64 to a Blob (raw binary data)
  const blob = new Blob(
    [Uint8Array.from(atob(base64Data), char => char.charCodeAt(0))],
    { type: "image/jpeg" }
  );

  // Send the PUT request with the Blob
  await axios.put(signedUrl, blob, {
    headers: {
      "Content-Type": "image/jpeg",
      "Content-Length": blob.size.toString()
    }
  });
}

// Usage example:
// const image = {uri: "assets-library://...", type: "image/jpeg", name: "IMG_4831.JPG"};
// uploadImageToGCS(yourSignedUrlFromServer, image.uri);

Why This Works:

  • FileSystem.readAsStringAsync pulls the raw image data from the asset URI as a base64 string.
  • We convert that base64 string into a Uint8Array (binary data) and wrap it in a Blob—this is the format GCS expects for PUT uploads.
  • The Content-Length header helps GCS verify it received the full file, preventing partial/corrupted uploads.

Step 3: Verify Critical Details

  • Content Type Consistency: Make sure the contentType in your server’s pre-signed URL options matches exactly what you send in the client’s Content-Type header (both should be image/jpeg). Mismatches will cause GCS to misinterpret the file.
  • Expiry Time: 15 minutes is usually enough, but if you’re uploading large images, extend the expiry to 30 minutes to avoid mid-upload timeouts.
  • Permissions: Confirm your service account (from keys.json) has the storage.objects.create permission on your GCS bucket.

Alternative: Use fetch Instead of Axios

If you still run into issues with Axios, try the native fetch API—it sometimes handles Blobs more reliably in React Native:

async function uploadImageToGCS(signedUrl, imageUri) {
  const base64Data = await FileSystem.readAsStringAsync(imageUri, {
    encoding: FileSystem.EncodingType.Base64,
  });
  const blob = new Blob(
    [Uint8Array.from(atob(base64Data), char => char.charCodeAt(0))],
    { type: "image/jpeg" }
  );

  const response = await fetch(signedUrl, {
    method: "PUT",
    body: blob,
    headers: {
      "Content-Type": "image/jpeg",
      "Content-Length": blob.size.toString()
    }
  });

  if (!response.ok) {
    throw new Error(`Upload failed: ${response.statusText}`);
  }
}

内容的提问来源于stack exchange,提问作者agaveazul

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 11:28:15