首页 > 解决方案 > 从 Firebase 云存储中检索文件时出错

问题描述

我上传了一个包含图像文件和其他一些字段的表单数据。我将文件图像上传到我的 firebase 云存储,并将其他表单数据添加到我的 firebase firestore 集合中,其中包含我上传的文件图像的构造 URL,类似于 ( https://firebasestorage.googleapis.com/v0/b/${storageBucket key})/o/${filename}?alt=media)。我的上传成功,但是当我尝试使用 URL 在浏览器上查看文件图像时出现错误

{
  "error": {
    "code": 404,
    "message": "Not Found.  Could not get object",
    "status": "GET_OBJECT"
  }
}

下面是我的 RestApi 代码:

exports.addType = (req, res) => {

    const BusBoy = require('busboy');
    const path = require('path');
    const os = require('os');
    const fs = require('fs');

    const busboy = new BusBoy({headers: req.headers});
    let fileName;
    let fileToBeUploaded = {};
    let fields = {};
    busboy.on('field', (fieldname, data)=> {
        fields[fieldname] = data;
    })
    busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
        const imageExtension = filename.split('.')[filename.split('.').length - 1];
        fileName = `${Math.round(Math.random() * 10000000000000)}.${imageExtension}`;
        const filepath = path.join(os.tmpdir(), fileName);
        fileToBeUploaded = { filepath, mimetype };
        file.pipe(fs.createWriteStream(filepath));
    })
    busboy.on('finish', ()=> {
        let imageUrl;
        
        admin.storage().bucket().upload(fileToBeUploaded.filepath, {
            destination:`Type/${fileName}`,
            resumable: false,
            metadata: {
                metadata: {
                    contentType: fileToBeUploaded.mimetype
                }
            }
        })
        .then(() => {
            imageUrl =  `https://firebasestorage.googleapis.com/v0/b/${config.storageBucket}/o/${fileName}?alt=media`;
            return db.collection('Type').doc(fields.type);
        })
        .then(data => {
            if(data.exists){
                return res.status(400).json({error: `${fields.type} already exist`})
            }
            db.collection('Type').doc(fields.type).set({
                type: fields.type,
                category: fields.category,
                details: fields.details,
                imageUrl
            })
        })
        .then(()=> {
            return res.status(200).json({message: `${fields.type} has been added`});
        })
        .catch(err => {
            return res.status(500).json({error: err.code});
        })
    })
    busboy.end(req.rawBody);
}

标签: javascriptfirebase-storagerestbusboy

解决方案


是的。这是 Firebase 存储中的一个错误。访问(在安全规则下)需要“授权”标头 - HTML 和浏览器支持为<img>标签添加标头。可以通过以下方式访问此类文件:

  • 使用绕过安全规则的“长期”(即永久)URL ref.getDownloadURL()(对于需要成为 SEO 结果的一部分的图像有用 - 完全公开)
  • 或者
  • 在添加适当的标头时使用异步获取(获取或 axios 或 XMLHttpRequest 或其他)authorization:。标头的正确值是多少?在 Google 的 Github 中进行了一些研究,但事实证明它就像`Firebase ${JWTToken}`(注意空格)一样简单。
  • 这个gitHub 问题详细讨论了它,但目前的解决方案是:

提议的浏览器/节点端解决方案

由于浏览器的限制,这不是最直接的。无法将自定义标头直接添加到<img>标签,但可以设置onError处理程序以进行独立提取。

JSX

              <img
                src={image}
                //crossOrigin="use-credentials"
                style={whatever}
                alt={whatever}
                onError={errorHandler}
              />

其中:{image} 是 Firebase 存储 URL,可以完全以编程方式生成。注意:URL必须包含alt=media标签(或适合文件类型的任何内容)。如果缺少此参数,Firebase 存储将返回元数据 JSON 对象。{errorHandler} 见下文

Javascript

//built in React
 const [authorization, setAuthorization] = useState(null);

//Asynchronously fetches users JWT.  Note closure, as useEffect itself must be synchronous
  useEffect(() => {
    if (user) {
      (async () => {
        const authToken = await fetchJWT();
        setAuthorization(authToken);
      })();
    }
  }, [user]);

//Technically, this is in my wrapper library - uses the Firebase Auth service to fetch a user's JWT
 export const fetchJWT = async (user) => {
  const thisUser = user || FirebaseAuth.currentUser;
  //the "true" below forces a reset
  const JWT = await thisUser.getIdToken(true);
  return JWT;
 }

//More-or-less copied from firebase-js-sdk/storage/src/implementation/requests.ts
  const addAuthHeader_ = async (headers) => {
    if (authorization !== null && authorization.length > 0) {
      headers["Authorization"] = "Firebase " + authorization;
    }
  };

// handles the actual error event 
  const errorHandler = async (e) => {
    e.stopPropagation();
    const target = e.target;
    const src = $(target).attr("src");
    let headers = {};
    addAuthHeader_(headers);
    const options = {
      headers: headers
    };
    await fetch(src, options)
      .then((res) => {
        return res.status === 200 && res.blob();
      })
      .then((blob) => {
        if (!blob) {
          return;
        }
        $(target).prop("src", URL.createObjectURL(blob));
      });
  };

解释

<img>标签遇到错误时,errorHandler

  • src从事件目标中获取属性。
  • 然后它为调用创建一个options对象fetch(),包括标题。标authorization头添加了一个标头,该标头由一个字符串组成`Firebase ${authortization}`
  • 获取的结果作为 Blob 保存到本地存储,并使用创建的 URL URL.createObjectURL(blob),然后将其设置为src目标的<img>

结果

遵循其中设置的所有安全规则,从 Firebase 存储中获取文件(在本例中为图像) 。不需要永久令牌。只有经过身份验证的用户才能访问。(我会注意到我对新用户使用匿名用户帐户,并对其访问设置了适当的限制)

往前走

我可能会向<img>旨在显示 Firebase 存储中的图像或可能检查 URL 的元素添加一个类。这样,可以指定一个 errorHandler 来处理所有这些。


推荐阅读