はじめに
FileStationAPIには、ファイルアップロードする機能(SYNO.FileStation.Upload)も提供されているのですが、自作のFlutterアプリから画像ファイルのアップロードを試みたところ、少し苦戦したのでその記録です。
httpパッケージでの試み
FlutterでREST APIにアクセスするとなると、一番有名なのがhttpパッケージかと思われます。
該当のAPIはPOSTメソッドなので、httpのpostを使ってアクセスしてみます。
リクエストボディのパラメーターについては以下のようになります。
パラメーター名 | 型 | 説明 |
---|---|---|
path | String | アップロード先のFileStationのフォルダのパス |
create_parents | Boolean | pathで指定したフォルダの親フォルダが存在しない時に作成するか |
overwrite | String("overwrite"か"skip") | 同名のファイルがアップロード済みの場合、置き換えるかスキップするか |
filepart | バイナリ(何型?) | ファイルデータをバイナリ化したもの |
ではリクエストボディのデータをそれぞれ設定してアクセスしてみましょう。
final file = File('target_image.JPG');
// ファイルデータをバイナリとして読み込み
final fileData = file.readAsBytesSync();
final response = await http.post(
Uri(
scheme: 'http',
host: '192.xxx.yyy.zzz',
port: 5000,
path: 'webapi/entry.cgi',
queryParameters: <String, dynamic>{
"api": "SYNO.FileStation.Upload",
"version": "3",
"method": "upload",
"_sid": sid, // ログインAPIで得られるsid
},
),
body: <String, dynamic>{
"path": "/home",
"create_parents": false,
"overwrite": "overwrite",
"filename": fileData,
},
);
で、結果がこちら
200
{"error":{"code":401},"success":false}
上がHttp通信でのステータスコード、下が返却されたレスポンスボディです。
通信自体は成功してますが、内部で失敗してますね...
で、FileStationAPIにおけるエラーコード401は何を示すのかというと
(API公式リファレンスより)
「ファイル操作において不明なエラー」
原因分かんないじゃん笑
まあ、十中八九設定したファイルデータに問題がありそうだなと思い、他のバイナリ形式に変更してみました。FileのreadAsBytesSyncメソッドでは「Uint8List」という型になります。
Dartではこれ以外にも「Base64」や「ByteData」など様々なバイナリ形式が提供されており、それぞれ相互変換できるようになっているため、色んなデータに変換して試してみたのですが、いずれの形式でも上記エラーから解消しませんでした。
困ったなぁと...
アップロードの成功例を探してみる
アップロードAPIが動作せず、途方に暮れていたのですが、このAPIがちゃんと動作している成功例を探すことに。
そこで見つけたのがFileStationAPIのPythonのラッパーパッケージでした。
github上に上げられており、pipでインストール可能なのでこれを使用してみることにしました。
アップロード処理はfilestationクラスのupload_fileメソッドから実行できるようです。
from synology_api import filestation
# FileStationのオブジェクト生成
fileStationApi = filestation.FileStation("192.xxx.yyy.zzz", "5000", "[ユーザー名]", "[パスワード]")
# アップロード処理
fileStationApi.upload_file(
dest_path = "/home",
file_path = "target_image.JPG",
create_parents = False
)
上記コードを実行したところ...
Upload Progress: 100%|████████████████████████████| 7.99M/7.99M [00:03<00:00, 2.77MB/s]
プログレスバーがコンソール上に表示され、成功した模様ですね。
ではFileStationで確認すると
アップロードされてますね!
というわけで、アップロードAPIでアップロード処理はちゃんとできるみたい。
では次に、アップロード処理のソースを見ていきます。
encoder = MultipartEncoder({
'path': dest_path,
'create_parents': str(create_parents).lower(),
'overwrite': str(overwrite).lower(),
'files': (filename, payload, 'application/octet-stream')
})
何やらリクエストボディ設定部分で「MultipartEncoder」なるものを使用している模様。
調べていると、どうやら画像ファイルをアップロードする際は今回のFileStationAPIに関係なく、基本的にContent-Typeを「multipart/form-data」というものにしなければならないそう。
先ほどの「MultipartEncoder」はリクエストボディの辞書型データをMultipartの形式にしてくれるもの(超ざっくり)だったようです。
Dioの使用
ではDartでMultipartを使うにはどうすれば良いのかなと調べていると、dioパッケージで実現できるとのこと。
こちらもhttpと同じくらい通信処理で使用されることが多いパッケージですね。
dioにはMultipartFileという、ファイルデータをMultipartにしてくれる機能が提供されているため、これを使えば実装できるはずです。
というわけで以下のように実装しました。
final dio = Dio();
// MultipartFileでファイルデータ生成
final fileData = await MultipartFile.fromFile('target_image.JPG');
final response = await dio.post(
'http://192.xxx.yyy.zzz/webapi/entry.cgi',
queryParameters: <String, dynamic>{
"api": "SYNO.FileStation.Upload",
"version": "3",
"method": "upload",
"_sid": sid,
},
// FormDataがリクエストボディに該当するためデータを設定
data: FormData.fromMap(<String, dynamic>{
"path": "/home",
"create_parents": false,
"overwrite": "overwrite",
"filepart": fileData,
}),
);
得られた結果がこちら
200
{"data":{"blSkip":false,"file":"target_image.JPG","pid":30875,"progress":1},"success":true}
httpの時と同様に上がHttp通信でのステータスコード、下が返却されたレスポンスボディです。
今回は通信と内部共に成功しているっぽいですね!
ではFileStationで確かめると
ちゃんとアップロードされてますね!
というわけで、アップロードのAPIを使う場合はdioとMultipartFileを使用しないといけないというお話でした。