8
5

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.

SLP KBITAdvent Calendar 2018

Day 3

JavaScriptでfileをfetchしてFlaskで処理させる

Last updated at Posted at 2018-12-02

はじめに

これは,SLP KBIT Advent Calendar 2018 3日目の記事です.
前日の記事は こちら

今回やること

JavaScriptで画像ファイルを**/api/picsにfetch(POST)して,
(Flaskで処理した後に)画像を
/pics**で表示させること

環境

Windows10, 1803
WSL, Ubuntu, 16.04.4 LTS
Python, 3.5.2
Firefox, 63.0.3

ディレクトリ構造

app.py
templates/
 ┣ layout.html #ベースとなるHTML
 ┗ pic.html #layout.htmlを継承させている
static/
 ┣ js/
 ┃ ┗ main.js
 ┣ css/
 ┃ ┗ styles.css
 ┗ pic/
   ┗ アップロードされた画像ファイル

送信ボタンを押してファイルを送信する

image.png
ここで,ファイルを選択して送信すると,/api/picsにファイルをPOSTして,
/picsに画像が表示されるようにします.

templates/pic.html
<!--画像投稿フォーム-->
<input type="file" name="img_file" id="img_file" class="col-sm-5">
<input type="submit" id="submit_btn" value="送信">

<div id="alter-album"></div>
<script src="/static/js/main.js"></script>

formからPOSTしてみる

templates/pic.html
<form id="file_post" method="post" action="/api/pics" enctype="multipart/form-data">
  <input type="file" name="img_file" id="img_file" class="col-sm-4">
  <input type="submit" id="submit_btn" value="送信">
</form>

とすると,POSTはできますが,/api/picsに移動します.
actionを使っているので当然ですね.
image.png

fetchで投げてみる

static/js/main.js
let formData = new FormData();

const upload = () => {
  const btn = document.getElementById('submit_btn'); //送信ボタン
  file = document.getElementById('img_file'); //file
  btn.disabled = true; //連打防止
  btn.value="送信中";
  fetch('/api/pics', {
    method: 'POST',
    body: formData ,
  }).then(res => res.json()
  ).then(json => {
    if(json["status"] == "false"){ //Flask側で"false"と判断されたらアラートする
      alert(json["message"])
    }
    afterPost(); //POST後の処理
  })
};

const onSelectFile = () => upload();
document.getElementById('submit_btn').addEventListener('click', onSelectFile, false);

/*--------------------------------------------*/

window.addEventListener("load", () => {
  const input = document.getElementById('img_file');
  input.addEventListener("change", () => {
    formData.append('img_file', input.files[0]);
  });
})

投げられたので,再読み込みします.

static/js/main.js
function afterPost() {
  const btn = document.getElementById('submit_btn');
  btn.disabled = false;
  btn.value="送信";
  renderPictures();
}

/*------画像を読み込む------*/
function renderPictures() {
  const album = document.getElementById('alter-album');
  while (album.firstChild) album.removeChild(album.firstChild);
  getPictures().then(pictures => {
    pictures.forEach(picture => {
      let img = document.createElement('img')
      img.src = picture
      img.addEventListener("click", () => deletePicture(picture))
      album.appendChild(img)
    })
  })
}

function getPictures() {
  return fetch("/api/pics")
    .then(res => res.json())
}

送信後フォームがリセットされるようにする

このままだと,送信後も下のように送信したファイルが残ってしまいます.
image.png

送信後,ファイル名を消しましょう.

input.value = "";

ファイル名はリセットされますが,送信ボタンを押すと,以前送信したファイルが送信されてしまいました.
image.png

FormDataをnewしよう

ファイルを送信したら,FormDataをnewするようにします.

static/js/main.js
function get_func(url) {
  const input = document.getElementById('img_file');
  const btn = document.getElementById('submit_btn');
  btn.value="送信";
  input.value = "";
  formData = new FormData(); //ここでnewさせる
  fetch(url)
    .then(() => renderPictures())
}

これで,ファイルを送信した後にフォームがリセットされました.

ボタンを押せないようにする

エラーを防ぐためにも,そもそもファイル名が無い場合,ボタンを押せないようにすれば良いということに気づきました.const upload内にvalue = "";のときは送信されないように記述します.

static/js/main.js
  if (!file.value){
    return false;
  }

ただ,押せなくなったので,formDataをnewしなくても問題ないというわけではなく,次に述べる問題が発生した際,newしないと正常なファイルが送信できなくなってしまいます.

問題点

ファイルサイズの上限

app.py
#アップロードの上限サイズを1MBにする
app.config['MAX_CONTENT_LENGTH'] = 1*1024*1024

このように,上限を1MBに設定しました.

app.py
try:
    img_file = request.files['img_file']
except RequestEntityTooLarge as err:
    return jsonify({'status': "false",
                    'message': "アップロード可能なファイルサイズは1MBまでです"})

fileサイズが1.3MB程度の時は,app.pyでエラー(413)と判断してjsonをresponseしていたのですが,3MBなど超過が大きくなると,下のようにFlaskからresponseが返ってこなくなりました.
TypeError: "NetworkError when attempting to fetch resource."
試しに他のブラウザでも検証.

ブラウザ 結果
Firefox, 63.* 上限を越えていても,サイズが1MB程度の時はres有り(413)
Edge, 42.* responseが返ってくる(200)
Chrome, 70.* responseが返ってこない

検証してみた結果として,ファイルサイズの上限を500KBにして,投稿するファイルのサイズが1MB程度の時は,413のresponseが返ってきました.上限が1MBのときと同じく,ファイルサイズが3MB程度になると,responseが返ってこなくなりました.このことから,ブラウザ側のセキュリティ関連の問題かと疑ったのですが解決には至りませんでした.原因がわかり次第追記します.

responseがとれないのでcatchする

static/js/main.js
  fetch('/api/pics', {
    mode: 'cors',
    method: 'POST',
    body: formData ,
  }).then(res => res.json()
  ).then(json => {
    if(json["status"] == "false"){
      alert(json["message"])
    }
    afterPost();
  }).catch(err => {
      alert("ファイルサイズが1MBを超えていませんか?")
      afterPost();
  })

image.png

とりあえず,アラートを出すことはできました.
応答が無いのは問題ですが…….

感想

fetchを用いたファイルの送信を理解することができました.しかし,上で述べましたエラーの原因については解明できなかったので,助言を頂ければ幸いです.

GitHub

Amakuchisan/pictures-warehouse

参考

How do I upload a file with the JS fetch API?
FlaskでAPI作って素のJavaScriptでAPI実行する

8
5
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
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?