要件
- Aアプリケーションが、Bアプリケーションに登録されているファイルをダウンロードする機能を作る
- Bアプリには、ファイルに対するリンクが既定されており、Aアプリ内にそのリンクを貼ればダウンロードは可能
- ただし、Bアプリにアクセスするためには、Bアプリの認証情報が必要で、Aアプリのユーザにその情報は公開されていない。また、公開もしない。
- AアプリからBアプリのファイルをダウンロードする際、ユーザにはBアプリの認証情報を意識せずダウンロードさせたい
- AアプリはAngular製のSPAで、バックエンドはDjango製のAPI
実現方法
- Aアプリのバックエンドに、Bアプリの認証情報を持たせる
- AアプリAPIをコールする際、Bアプリのダウンロードリンクを渡す
- サーバ上で持っている認証情報+渡されたダウンロードリンクで、サーバ上でBアプリからファイルをダウンロードする
- ダウンロードしたファイルをbase64エンコードして、Aアプリへ返す
- Aアプリ内でbase64デコードして、バイナリ形式してダウンロードさせる
言語、フレームワークのバージョン
- python 3.6.x
- Django 1.11.x
- typescript 2.4.x
- Angular 5.0.x
ダウンロード用API
views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status
import requests
import base64
from urllib import parse
class DownloadDocment(APIView):
def get(self, request, format=None):
# クエリストリングの取得
params = {}
params.update(self.request.query_params.dict())
try:
# fileuri に 格納されたファイルのURIが設定されている(パラメータ uri にサーバ上の格納場所が入っている)
file_uri = params["file_uri"]
file_list = parse.parse_qs(parse.urlparse(file_uri).query)["uri"]
except KeyError:
return Response('{message:ファイルURIの指定がありません。 指定クエリ: ' +
str(params) + '}',
status=status.HTTP_400_BAD_REQUEST)
# 上記で取得出来るのはリストなので、リストの最初の要素を取り出す
file_name = ""
if file_list:
file_name = file_list[0].split("/")[-1]
# 認証情報設定(HTTPヘッダーに認証情報を埋め込む形, AUTH_KEY は別途定義されている前提)
HEADERS = {'Authorization': AUTH_KEY}
# ファイル取得
r = requests.get(encoded_uri, headers=HEADERS)
# リクエストのステータス判定が要りそう
# base64にエンコードしたファイルの中身とファイル名を返す
return Response({'body': base64.b64encode(r.content),
'filename': file_name})
Angular画面ダウンロード処理
- ダウンロードの処理以外は割愛(テンプレート、サービスなど)
- base64からのデコードが意外と厄介だった
xxx.component.ts
base64ToArrayBuffer(base64) {
/* base64形式の文字列をバイナリに変換する
https://stackoverflow.com/questions/9267899/arraybuffer-to-base64-encoded-string
*/
var binary_string = window.atob(base64);
var len = binary_string.length;
var bytes = new Uint8Array( len );
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes.buffer;
}
fileDownload(uri: string){
let downloadFile: DownloadFile;
//getFile で上記APIを呼び出し、ファイル名とファイルの中身(base64形式)を取得する
this.xxxService.getFile(uri).subscribe(downloadFile=> {
//base64 decode
var b = this.base64ToArrayBuffer(downloadFile.body);
var blob = new Blob([b], {"type" : "application/octet-stream"});
//IEとそれ以外で処理を分ける
if (window.navigator.msSaveBlob) {
window.navigator.msSaveOrOpenBlob(blob, downloadFile.filename);
} else {
var a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.target = '_blank';
a.download = downloadFile.filename;
a.click();
}
});
}