APIからstreamを取得してblob変換してダウンロードさせようとしたとき、vanillaのfetchならすんなり出来たのだが、Zodiosを利用すると少し躓いたので備忘録。
前提
サーバー側はこんな感じでstreamを返している。
return new Response(stream, {
headers: {
'Content-Disposition': 'attachment; filename="sample.pdf',
'Content-Type': 'application/pdf',
},
});
ダウンロード
フロント側はZodiosでstreamを受ける。
const api = (() => {
const client = createApiClient('/');
client.axiosInstance.interceptors.response.use(
(response) => {
response.data = {
data: response.data,
headers: response.headers,
};
return response;
},
(error) => {
return Promise.reject(error);
}
);
return client;
})();
const blobDownload = (blob: Blob, fileName: string): void => {
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.style.display = 'none';
a.download = fileName;
a.href = url;
document.body.appendChild(a);
a.click();
a.remove();
URL.revokeObjectURL(url);
};
const getFileName = (headers: AxiosHeaders): string | undefined => {
const contentDispositionHeader = headers['content-disposition'];
if (!contentDispositionHeader) {
return;
}
const contentDisposition = contentDispositionHeader
.split(';')
.map((item: string): string => item.trim());
const filenameParameter = contentDisposition.find((item: string): boolean =>
item.toLowerCase().startsWith('filename='),
);
if (!filenameParameter) {
return;
}
const fileName = filenameParameter.split('=')[1];
return decodeURIComponent(fileName.replace(/['"]+/g, ''));
};
const res = await api.getPdf({
params,
queries,
responseType: 'blob'
});
blobDownload(res.data, getFileName(res.headers) ?? 'default.pdf');
client.axiosInstance.interceptors.response.use
Zodios のレスポンスは Axios の response.data の部分のみ返してくる。
今回だと直接 blob が返ってきてしまう。
しかし後続処理で headers の内容を参照したいので、レスポンスを改変する。
注意点としては、Zodiosが生成する api の Axios Instance をいじってしまうと他にも影響があるため、この処理専用の Api Client を生成すること。
なお設定などは変わらないのでAxios Instanceを自作するの面倒だということで、client の axiosInstance に直接ごにょごにょしているが、勿論以下のように Axios Instance を渡す形でもいい。
const axiosInstance = Axios();
axiosInstance.interceptors.response.use(
(response) => {
response.data = {
data: response.data,
headers: response.headers,
};
return response;
},
(error) => {
return Promise.reject(error);
}
);
const client = createApiClient('/', { axiosInstance });
responseType: 'blob'
Zodiosはある程度Axiosのオプションを受け付けているので、忘れずにresponseTypeを指定する。
responseType を指定することで stream を blob に変換までしてくれる。
指定しない場合は json 扱いされ、上手く後続処理に続けることが出来なくなる。
getFileName
ここでファイル名を取得したかったので headers が必要だったのですね。
ファイル名をフロントで決めていいのであれば、axios をいじる必要もなければ、この処理も不要です。
blobDownload
ここはよくある処理なので説明割愛。
感想
今回のはまりどころは2点。
- Axios で Stream を受け取るには
responseType の指定が必要。
- Zodios で Headers を受け取るにはちょっとした改良が必要。