我有一个 GraphQL 服务器,托管在 Express 上。我想通过发送回 NodeJS 缓冲区对象来将图像返回给客户端。我如何配置 graphql 服务器以返回字节而不是 json?我不想通过 base64 来执行此操作,因为图像尺寸很大。
你必须返回JSON,但还有办法。 我们使用 GraphQL 返回存储在旧版 SQL 数据库中的 Blob 字段中的图像。 我们使用sequelize、graphql-sequelize 和graphql-js。 我们已经在 graphql 模式中将 Blob 字段定义为 String 类型,因此它们可以很好地通过 json 回复。 然后我们在传递之前转换为缓冲区,就像
const imgBuffer = new Buffer.from(imgObj.data, 'ascii');
唯一的问题是我们现在无法通过 graphql 接口将图像数据保存回数据库。 当我们的突变函数在字符串中发现某些错误的 unicode 字符时,例如 \U0000 之类的,就会给我们带来语法错误(所以我发现你的问题正在寻找解决方案)。
有一种方法,但它很复杂,而且非常手动,我只会向您概述我在 ApolloServer 中所做的事情,但我认为这应该足够了。
首先,您需要在请求中使用“Accept”标头发送二进制 mime 类型,并在响应中发送匹配的“Content-Type”。正如您将看到的(使用 EJSON),这对于提高效率是必要的,但对于工作却不是必要的。
要序列化和反序列化尊重标头,您可能需要编写一个快速中间件,并且您需要使用 {$data: "..."} 封装对象(如 EJSON 那样)或只是(奇怪的是)处理 base64 编码如果有人使用“application/json”作为“accept”标头发出二进制数据请求,则返回 null。您还需要选择支持的二进制格式。我只使用1:“application/x-msgpack”,但我听说“application/cbor”变得越来越流行。您可以使用 EJSON、MessagePack 和 CBOR 库来进行序列化,因此这并不像听起来那么难。
我强烈建议在任何图像上使用@defer。有关 @defer 的更多信息,请参阅这篇文章:https://www.apollographql.com/blog/introducing-defer-in-apollo-server-f6797c4e9d6e/
我已经做到了。这并不容易,如果 ApolloServer 能够“开箱即用”地工作那就更好了。
The Guild 提供了一个自定义的 Byte scalar 来处理这个问题。
例如,假设您想将 CSV 缓冲区传递给客户端,然后下载它:
在您的 GraphQl 架构中:
scalar Byte
type Query {
myFile: MyFile!
}
type MyFile {
filename: String!
contentType: String!
byteBuffer: Byte!
}
在您的服务器上发送:
// create the csv string by hand or with your favorite library
return {
filename: 'my.csv',
contentType: 'text/csv',
byteBuffer: Buffer.from(csv)
};
在您的客户端上,接收它:
// setup your query then call it
const result = await myFile();
const { filename, contentType, byteBuffer } = result.data.myFile;
const blob = createBlobFromByteBuffer(byteBuffer, contentType);
downloadFile(blob, filename);
在您的客户端上,有这些实用程序:
export const createBlobFromByteBuffer = (byteBuffer: { data: any }, contentType: string): Blob => {
return new Blob([new Uint8Array(byteBuffer.data)], { type: contentType });
};
export const downloadFile = (blob: Blob, filename: string): void => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = filename;
a.click();
a.remove();
URL.revokeObjectURL(url);
};
使用here找到的节点模块可能会回答您的问题。