Repl.itでFlaskの疑似OSもどき用WEBアプリを作ってみました

## ０．初めに

私はエンジニアではないただのドシロウトです。

[Flask Advent Calendar 2019](https://qiita.com/advent-calendar/2019/flask)が沢山空いていたのでシロウトのショボい記事ですが書く気になりました。

私はJavaScriptが少し触れる程度のスキルしかありません。

その乏しいスキルを使って以前「疑似OSもどき」という物を作り、Qiita記事にしています。

[ドシロウトがOSもどきみたいのをHTMLで作ってみる - Qiita]
(https://qiita.com/basictomonokai/items/5ec68a2aeb01de94c526)

HTML,CSS,JSを使ったOSみたいな画面でJSで作った「疑似アプリもどき」を動かす仕組みです。

この記事では簡単なホームページくらいしかつくれないドシロウトが1個の「疑似アプリもどき」をオンラインIDEのRepl.itを使ってFlask　WEBアプリで作ってみたという話になります。

## １．Repl.itとは

丁寧な照会記事がありましたので以下をごらんください。

[Repl.itの使い方 – TWEI blog](https://twei-blog.com/etc/how-to-replit/)

多言語の対応したオンラインIDEでPython+FlaskでWEBアプリケーションが作れます。

## ２．作ったもの

以下のアニメGIFの通り、Tarファイルをアップロード→解凍→Zipファイルを生成→ダウンロードするFlaskのWebアプリを作りました。

![アニメ](https://i.imgur.com/ljPltSt.gif)

## ３．参考にした記事

まず、TarやZipをPythonで扱っている記事を調べました

[【Python】 tarによるデータアーカイブと圧縮 | Hibiki Programming Notes](https://hibiki-press.tech/learn_prog/python/tarfile/1435)

[【Python】ファイルやフォルダの圧縮と展開  tarfile, zipfile](https://code-graffiti.com/tarfile-and-zipfile-in-python/)

次にFlaskでのファイルのアップロード、ダウンロード記事を調査。

[Pythonのrequestsを利用してmultipart/form-dataのFormにファイルアップロードする方法 - Qiita](https://qiita.com/5zm/items/92cde9e043813e02eb68)

[Flaskでファイルダウンロードを実現する３つの方法 - Qiita](https://qiita.com/5zm/items/760000cf63b176be544c)

更にMIMEタイプやOSのファイル操作などで以下の記事を参考にしています。

[application/x-tar【MIMEタイプ】とは｜「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典](https://wa3.i-3-i.info/word15817.html)

[Python で MIME Type を取得する - Qiita(https://qiita.com/chibi929/items/745193870589d3e968c0)

[Python入門 ディレクトリ操作の基本 (1/3)：Python入門 - ＠IT](https://www.atmarkit.co.jp/ait/articles/1910/25/news021.html)

[ファイルやディレクトリの有無を調べる - Python Tips](https://www.gesource.jp/programming/python/code/0008.html)

PythonはJS以上に初心者なのでググりまくりました…(;^_^A

## ４. 悩んだ所

作っていて困った事が一つありました。

FlaskはWebアプリなので同じTarファイルで何回もテストするとキャッシュの影響でダウンロードできるZIPファイルが古いままダウンロードされる状態になりました。

しかたなくキャッシュが使われないようにダウンロードリンクをZIPファイル名＋'?'+日付時刻情報「datetime.now().strftime("%Y%m%d_%H%M%S")」にして回避しています。

## ４．コードの例

ソースファイルはPythonプログラム1本とHTMLテンプレート1本だけです。

```python:main.py
# -*- coding: utf-8 -*-
from flask import Flask, request, Response,render_template,send_file
import os
import werkzeug
import mimetypes
from datetime import datetime
import glob
import zipfile
import tarfile


# flask
app = Flask(__name__)

# 最大ファイルサイズ100MB
app.config['MAX_CONTENT_LENGTH'] = 100 * 1024 * 1024

# UPLOADディレクトリの存在確認
UPLOAD_DIR = 'flxx'
if os.path.exists(UPLOAD_DIR):
  print('UPLOAD_DIR exist')
else:
  print('UPLOAD_DIR gen')
  os.makedirs(UPLOAD_DIR)

# ルート処理
@app.route('/')
def home():
    return render_template("up.html")

# アップロード処理
@app.route('/data/upload', methods=['POST'])
def upload_multipart():
    if 'uploadFile' not in request.files:
        return Response('<head><meta name="viewport" content="width=device-width, initial-scale=1"></head>\n<body>'+'<h3>uploadFile is required.</h3>\n'+'''
        <a href="/">トップページへ戻る</a><br />
        '''+'</body>')        

    file = request.files['uploadFile']
    fileName = file.filename
    if '' == fileName:
        return Response('<head><meta name="viewport" content="width=device-width, initial-scale=1"></head>\n<body>'+'<h3>filename must not empty.</h3>\n'+'''
        <a href="/">トップページへ戻る</a><br />
        '''+'</body>')        

    saveFileName = werkzeug.utils.secure_filename(fileName)
    file.save(os.path.join(UPLOAD_DIR, saveFileName))
    return Response('<head><meta name="viewport" content="width=device-width, initial-scale=1"></head>\n<body>'+'<h3>upload OK.</h3>\n'+'<a href="/downfl/'+saveFileName+'?'+datetime.now().strftime("%Y%m%d_%H%M%S")+'">ZIP変換してダウンロード</a><br />'+'''
    <a href="/">トップページへ戻る</a><br />
    '''+'</body>')        

# ファイルサイズが大きすぎの場合
@app.errorhandler(werkzeug.exceptions.RequestEntityTooLarge)
def handle_over_max_file_size(error):
    print("werkzeug.exceptions.RequestEntityTooLarge")
    return Response('<head><meta name="viewport" content="width=device-width, initial-scale=1"></head>\n<body>'+'<h3>file size is overed.</h3>\n'+'''
    <a href="/">トップページへ戻る</a><br />
    '''+'</body>') 

# ダウンロード処理
@app.route('/downfl/<string:flname>', methods=['GET'])
def downfl(flname):

    flname2='flxx/'+flname

    dir1 = './extracted'+datetime.now().strftime('%Y%m%d_%H%M%S') 

    with tarfile.open(flname2, 'r:*') as tar:
        tar.extractall(dir1)

    with zipfile.ZipFile(flname2+'.zip', 'w') as z:
        for f in glob.glob(dir1+'/**', recursive=True):
            # print(f)
            z.write(f)

    downloadFileName = flname + '.zip'
    downloadFile = flname2 + '.zip'


    return send_file(downloadFile, as_attachment = True, \
        attachment_filename = downloadFileName, \
        mimetype = mimetypes.guess_type(downloadFile)[0])       

# main　一応waitressを使ってます
if __name__ == "__main__":
  from waitress import serve
  serve(app, host="0.0.0.0", port=3000)
```

```html:templates/up.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<h3>tar ⇒ zip変換</h3>  
<form action="/data/upload" method="post" enctype="multipart/form-data">
  <input type="file" name="uploadFile" accept="application/x-tar"/>
  <input type="submit" value="アップロード"/>
</form>
</body>
</html>
```

私は不慣れなので少し手間取りましたが、Pythonを使われる方には簡単すぎると思います。HTMLの方は普通のファイルのPOST送信画面です。

## ５．まとめ

Repl.it+Flaskは良い感じだと思います。

a. Repl.itはブラウザーで気楽に無料で使えるオンラインIDE
b. Repl.itを使うと割と簡単にPython+FlaskのWEBアプリが作れる
c. Flaskでのアップロード、ダウンロードは簡単
d. TarやZipの操作も簡単
e. 疑似OSもどきのアプリも増やせる

まとめの最後の項目は私だけのメリットですが…(;^_^A

以　上