1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ファイル交換用のウェブストレージをgoogle cloudで組み立てる時のメモ

Posted at

目的と前提

ファイル交換を目的としたウェブストレージをGoogle CloudのストレージサービスとVMインスタンスを使って組み立てるための自分用のメモです。
VMインスタンスとバケット間のファイルのやり取りにはcgiとしてpythonを使います。
次のものはすでに準備しているものとして詳細を省略します。

  • ストレージのバケット
  • インターネットアクセスが可能なVMインスタンス(ubuntu server)
  • Apacheのインストール
  • phpのインストール
  • pythonライブラリ google-cloud-storage のインストール

手順

  1. サービスアカウントの作成
  2. jsonキーの設定
  3. apacheでcgiにpython追加
  4. ファイルサイズに関するphpの設定変更
  5. pythonコード

1. サービスアカウントの作成

VMインスタンスからバケットにアクセスするために、サービスアカウントを使う。認証手段としてjsonキーを使う。

  • 使うコンソールメニューは、APIとサービス>認証情報。
  • ロールは、オブジェクトユーザ。
  • 鍵は、jsonキーを作成。

VMインスタンスからバケットにアクセスするためには、VMインスタンスにIAM権限を設定する方法の方が良いと思うが、なぜかうまくいかなかったので、jsonキーを使う。

2. jsonキーの設定

jsonキーをVMインスタンスに保存。
cgiでjsonキーを使うには、cgiコードに直接キーのパスを記入。jsonキーの権限設定に注意(www-dataがアクセスできるか)。

os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/jsonkey.json"

VMインスタンスのコンソールからコードを実行する際には、環境変数に設定してしまえばコード内にキーのパスを記入する必要がないが、今回はcgiでの実行なので、環境変数に設定してもwww-dataユーザには引き継がれない。
なお、環境変数への設定は、次のとおり。

  • 永続させる
echo 'export GOOGLE_APPLICATION_CREDENTIALS="/path/to/jsonkey.json"' >> ~/.bashrc
source ~/.bashrc
  • 永続させない
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/jsonkey.json"

3. apacheでcgiにpython追加

いつもこの設定をどのconfファイルに設定するのか忘れてしまうので、メモ。

  • CGIの許可
sudo a2enmod cgi
sudo systemctl restart apache2
  • confファイルの編集
    /etc/apache2/sites-available/000-default.confの末尾に次の内容を追加
<Directory "/usr/lib/cgi-bin">
    AllowOverride None
    Options +ExecCGI
    AddHandler cgi-script .py
    Require all granted
</Directory>

4. ファイルサイズに関するphpの設定変更

これもいつもどこをいじったのか忘れるので、メモ。
変更する設定ファイルは、/etc/php/8.1/apache2/php.ini。設定変更するのは次の項目。確認はphpinfo()で。

  • memory_limit
  • post_max_size
  • upload_max_filesize

5. pythonコード(アップロード、リスト表示、ダウンロード)

アップロードの枠組み

  1. HTMLフォームから、保存ファイルに設定する接頭辞(name="upidx")とファイル(name="upfiles[]")が送られるので、それを処理する。
  2. 処理状況をHTMLとして返す。
  3. 返されたHTMLはフォーム側でalertとして表示する。
#!/usr/bin/python3
# -*- coding: utf-8 -*-

import cgi,sys,os,io
from google.cloud import storage as gcs

sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')

bucket_name = 'bucketname'
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/jsonkey.json"
client = gcs.Client()
bucket = client.bucket(bucket_name)
tmpdir = '/file/tmpsave/dir/path'

#ファイルをVMに一時保存
def single_upload_file(tmpdir, idx, item):
    filename = idx+'__'+item.filename
    try:
        path = os.path.join(tmpdir, os.path.basename(filename))
        chunk = item.file.read()
        if chunk:
            fout = open(path,mode='wb')
            fout.write(chunk)
            fout.close()
    except Exception as ee:
        print(ee)

#ストレージにアップロード
def upload_gcs(tmpdir, idx, filename):
    try:
        upfilename = idx+'__'+filename
        tmppath = os.path.join(tmpdir, upfilename)
        blob = bucket.blob(upfilename)
        blob.upload_from_filename(tmppath)
    except Exception as ee:
        print(ee)

#VMの一時保存ファイルを削除
def deletefile(tmpdir, idx, filename):
    try:
        upfilename = idx+'__'+filename
        deletepath = os.path.join(tmpdir, upfilename)
        os.remove(deletepath)
    except Exception as ee:
        print(ee)

form = cgi.FieldStorage()

print("Content-Type: text/html")
print()
if 'upfiles[]' in form:
    idx = form['upidx'].value
    #ファイルが複数送られたときと1つの時に場合分け
    if (isinstance(form['upfiles[]'],list)):
        for f in form['upfiles[]']:
            single_upload_file(tmpdir, idx, f)
            print(f"{f.filename}を一時保存しました")
            upload_gcs(tmpdir, idx, f.filename)
            print(f"{f.filename}をストレージに保存しました")
            deletefile(tmpdir, idx, f.filename)
            print(f"{f.filename}のアップロードが完了しました\n")
    else:
        if form['upfiles[]'].filename:
            single_upload_file(tmpdir, idx, form['upfiles[]'])
            print(f"{form['upfiles[]'].filename}を一時保存しました")
            upload_gcs(tmpdir, idx, form['upfiles[]'].filename)
            print(f"{form['upfiles[]'].filename}をストレージに保存しました")
            deletefile(tmpdir, idx, form['upfiles[]'].filename)
            print(f"{form['upfiles[]'].filename}のアップロードが完了しました\n")
        else:
            print('ファイルが選択されていません')

リスト表示の枠組み

  1. HTMLフォームから、検索する接頭辞(name="dlidx")が送られるので、それを処理する。
  2. 処理状況をHTMLとして返す。
  3. 返されたHTMLはフォーム側で指定の
    タグ内表示する。
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    import cgi,sys,os,io
    from google.cloud import storage as gcs
    
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
    
    bucket_name = 'bucketname'
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/jsonkey.json"
    client = gcs.Client()
    bucket = client.bucket(bucket_name)
    tmpdir = '/file/tmpsave/dir/path'
    
    form = cgi.FieldStorage()
    
    #DLボタンクリック時の動作(jquery使用)
    #jspostの定義はHTML側(第2引数をpostdataとして第1引数に送る関数)
    jsscript = '''
    <script type="text/javascript">
    $('button[name="dlbtn"]').on('click', function(){
        var senddata = {'filename':$(this).val()};
        jspost('/cgi-bin/cgi_pythonfile.py', senddata);
    });
    </script>
    '''
    
    print("Content-Type: text/html")
    print()
    
    if form['dlidx'].value:
        prefix = form['dlidx'].value + '__'
        blobs = client.list_blobs(bucket_name, prefix=prefix)
        print(f"インデックス{prefix}ファイルリスト",'<br>')
        for blob in blobs:
            print(blob.name)
            print(f'<button type="button" name="dlbtn" value="{blob.name}">ダウンロード</button><br>')
    
    print(jsscript)
    

    ダウンロードの枠組み

    1. HTMLフォームから、ダウンロードするファイル名(name="filename")が送られるので、それを処理する。
    2. ファイルダウンロード処理を返す。
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    import cgi,sys,os,io
    from google.cloud import storage as gcs
    
    sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
    
    bucket_name = 'bucketname'
    os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "/path/to/jsonkey.json"
    client = gcs.Client()
    bucket = client.bucket(bucket_name)
    tmpdir = '/file/tmpsave/dir/path'
    
    form = cgi.FieldStorage()
    
    #gcsからDL
    gcsfilename = form['filename'].value
    dlpath = os.path.join(tmpdir, gcsfilename)
    blob = bucket.blob(gcsfilename)
    blob.download_to_filename(dlpath)
    
    #ファイル名から接頭辞を分離
    dlfilename = form['filename'].value.split('__')[1]
    
    #DLさせるためのヘッダ
    print(f'Content-Disposition: attachment; filename={dlfilename}')
    print('', flush=True)
    
    #DL
    with open(dlpath, 'rb') as f:
        sys.stdout.buffer.write(f.read())
    
    #一時ファイル削除
    os.remove(dlpath)
    
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?