9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

REST API 内で別のサービスからファイルをダウンロードし、APIのクライアントに返す

Posted at

要件

  • 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();
    }
    });
  }

9
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?