まだプログラマーですが何か?

プログラマーネタとアスリートネタ中心。たまに作成したウェブサービス関連の話も http://twitter.com/dotnsf

タグ:upload

Swagger を使うことで、インタラクティブに実行可能な REST API のドキュメントを作ることができます。比較的有名な所では Swagger がライブデモとして用意している「ペットストア・サービスの REST API」があります:
https://petstore.swagger.io/

2020012201



↑架空の「ペットストア・ウェブサービス」が存在している前提で、ペットやその画像を登録/更新したり、検索したり、、加えてペットストア自体の登録/管理をしたり、利用者となるユーザーの管理といったペットストア運営に必要と思われる最低限の機能の REST API が実行可能な状態で公開されています。


アプリケーションを REST API ベースで作る際に、このような Swagger による API ドキュメントを用意しておくと、REST API の仕様が確認できるだけでなく、実際にパラメータを指定した上で実行したり、実行した時のデータのやりとりやその結果までを確認することができて便利です。REST API を作成した時にセットで作っておくと、開発後の運用時や機能追加、引き継ぎといった段階においても非常に役立つインタラクティブドキュメントだと思っています。


この Swagger ドキュメントは REST API 開発後に swagger.yaml (または swagger.json)というファイルを用意することで自動作成が可能になります。特定のルールに基づいて REST API の内容(ホスト、パス、メソッド、パラメータ、返り値、・・)を記述していくと、ドキュメントの UI は自動的に作成され、指定した通りに実行が可能になります(そして実際に実行された結果を確認することもできるようになります)。

なお上述のペットストア・サービス REST API の定義内容はこちらの Swagger Editor で確認できます:
https://editor.swagger.io/

2020012205


swagger.yaml の記述方法そのものについては本ブログエントリの範囲ではないので、興味ある方は別途調べていただきたいのですが、そこまで難しい内容ではないと感じています。例えば GET /users?limit=xx&offset=yy といった具合に2つの URL パラメータ(limit, offset)を指定可能(必須ではない)な GET の REST API が存在していて、これを Swagger ドキュメントに記述する場合は swagger.yaml ファイルに以下のように指定します:
  :
/users
  get
    parameters:
      - name: limit
        type: number
        in: query
        description: 取得するデータ数
      - name: offset
        type: number
        in: query
        description: 取得するデータのオフセット
  :

実際に Swagger ドキュメントとして生成すると、この部分は以下のような UI に変換されて指定可能になる、といった具合です:
2020012202



で、ここからが本エントリの本題です。この Swagger ドキュメント(の yaml ファイル)を記述する際の、特に「ファイルアップロード」の API を記述する場合のパラメータ指定方法が特殊でちとわかりにくいものでした。試行錯誤した上で実際に動く例を見つけたので、自分の備忘録も兼ねて以下に紹介します。

まず API そのものは単純なファイルアップロード(POST /file)と仮定します。受け取るパラメータはアップロードするファイル本体だけ、他のパラメータは(Swagger ドキュメント的には特別な部分ではないので)いったん無視します。Node.js + Express 環境であれば、以下のような内容で作られているようなものです:
//. multer モジュールを利用してアップロードファイルを受け取る
var express = require( 'express' ),
    multer = require( 'multer' ),
    router = express.Router(),

    :

//. multer モジュールの設定
router.use( multer( { dest: './tmp/' } ).single( 'file' ) );

    :

//. POST /file として REST API を用意する
router.post( '/file', function( req, res ){
  //. アップロードされたファイルの情報を取り出す
  var filepath = req.file.path;
  var filetype = req.file.mimetype;

    :
    :


UI 側からこの API を呼ぶ時は、普通に以下の様な enctype="multipart/form-data" を指定した form の HTML を用意すれば実行できるものです:
<form method="POST" action="/file" enctype="multipart/form-data">
<input type="file" name="file"/>
</form>

↓こんな感じの見た目になって実行されるものです:
2020012203

このような挙動を行う API : POST /file を Swagger ドキュメントとして用意する場合の(パラメータ部分の)記述方法をどのようにするか、というのが今回のテーマでした。

ちょっと特殊な指定方法となりますが、結論としては以下のようになります:
  :
/file:
  post:
    consumes:
      - multipart/form-data
    parameters:
      - in: formData
        name: file
        type: file
        description: アップロードするファイル
        required: true
  :

特殊な部分を解説します。まず HTML の form 属性で enctype="multipart/form-data" 指定を行っていた部分ですが、上述のように consumes: で "multipart/form-data" を指定することで実現します。

またパラメータ部分ですが、POST のデータとして送信するので "in: formData" を指定します。またパラメータの種類として "type: file" も付与します。 加えて、このパラメータは API 実行時には必須指定パラメータとなるので "required: true" も付与します。swagger.yaml ではこのように指定することでファイルアップロード系の API を記述できます。

参考程度に、このように指定した swatter.yaml を実際に Swagger ドキュメントに変換して表示すると、以下のような画面になりました:
2020012204


期待通りの結果になりました。

東芝の(いい意味で)変態な SD カード "FlashAir" 。実は以前のブログエントリでは以前のバージョンの FlashAir のこんなハックを紹介したことがありました:


比較的最近のモデルでは lua 言語によるスクリプト処理が可能になったり、「ファイルが FlashAir カードに追加された」などのイベントをハンドリングして処理を実行したり、といったことも可能になり、ますます変態チックな度合いが上がっているようです:



で、せっかく lua スクリプトが使えるようになったのであれば、このカードの中のファイルを(例えば FlashAir カードにファイルが追加されたタイミングなどで)サーバーにアップロードして、サーバー側ではアップロードされた画像ファイルになんらかの(例えば「保存する」などの)処理を実行する、という一連のスクリプトも記述できるのではないか、と考えました。

サーバー側の処理はアップロードされたファイルを取り出して・・・という内容になると思うので、一般的なファイルアップロード処理を行うことになります。

一方、クライアント(FlashAir カード)側の処理は、やることは簡単なのですが、何しろ普段名前を聞くくらいでしか知らなかった lua という軽量言語を触ったことがありませんでした。加えて、ちょっと特殊な言語のようで結局、未だにデバッグ方法を理解していません(苦笑)。 FlashAir も変態でしたが、lua は lua でまたどM向けプログラミング言語というか・・・ 「変態」とか「どM」とか、IT とは異なる世界の話をしている気分になってきます。 (^^;

まあ、でもなんとかファイルアップロードができることを確認したスクリプトが作れました。もしかしたら私以外の変態な皆様の役に立つかもしれないと思って公開することにします(赤字はコメントなので実際には不要、というか残したままだと正しく動きません):
boundary = "1234567890" 適当な文字列
contenttype = "multipart/form-data; boundary=" .. boundary
fname = "sample.png" 転送するファイル名
fpath = "/DCIM/100__TSB/" .. fname 転送するファイルのフルパス
mes = "--" ..  boundary .. "\r\n"
  .."Content-Disposition: form-data; name=\"file\"; filename=\""..fname.."\"\r\n"
  .."Content-Type: image/png\r\n\r\n"
  .."\r\n"
  .."--" .. boundary .. "--\r\n"

blen = lfs.attributes(fpath,"size") + string.len(mes) - 17
b, c, h = fa.request{url = "http://xx.xx.xx.xx/up.php", http://xx.xx.xx.xx/up.php というアップローダーに転送
  method = "POST",
  headers = {["Content-Length"] = tostring(blen),
  ["Content-Type"] = contenttype},
  file = fpath,
  body = mes
}

#だいたい lua のコメントってどう書くのかと??

この例では FlashAir 内の /DCIM/100__TSB/sample.png というファイルを http://xx.xx.xx.xx/up.php というアップローダーに転送することを想定した、マルチパートにすらしていないシンプルなスクリプトです。固定の同じファイルだけを送信するような内容になっていますが、これは実際にはファイル名のパラメータ渡しなどで対応できないかな、と思ってます。

受け取る側の up.php は例えばこんな感じで:
<?php
$name = $_FILES["file"]["name"]; // ファイル名
$mimetype = $_FILES["file"]["type"]; // Content-Type
$filesize = $_FILES["file"]["size"]; // ファイルサイズ
$tmpname = $_FILES["file"]["tmp_name"]; // 一時ファイル名(ここに実体がある)

$filename = "/var/www/html/imgs/" . $name; $result = @move_uploaded_file( $tmpname, $filename ); // 一時ファイルを Document Root 以下に移動するだけ echo( $result ); ?>

こちらはある意味で「一般的な」 PHP アップローダーの中身です。 $_FILES 変数に入った情報を元にファイル名やサイズ、そしてファイルの実体を取り出しています。この例では単純にアップロードされたファイルをドキュメントルート以下に移動するだけの内容です。これが http://xx.xx.xx.xx/up.php という URL でアクセスできる PHP サーバー上に用意されている、という想定です。
2015062401


この PHP では単純にアップロードされたファイルをドキュメントルート以下に置き直しているだけですが、その画像を使って PHP で更に別の処理を行う、なんてことももちろんできます。また PHP である必要もなく、ごく一般的な HTTP ファイルアップローダーの仕組みが用意されていればよい、という認識で大丈夫です。


これら2つのファイルを FlashAir および PHP サーバー側にそれぞれ用意しておくことで FlashAir からのファイルアップロードが実現できました。後はこれを HTTP 経由で呼び出すか、あるいは FlashAir の SD カードにファイルが追加されたタイミングで実行する、などの方法で lua を動かして使うことになります。



参考にしたのはこの辺りです:
- FlashAir Developers : チュートリアル
- FlashAir Developers : API ガイド
おまけ程度のツール置き場 - FlashAirをいじってわかったこと。


#いやあ、しかし lua は難しいというか、、、資料が少ないのも原因なんだろうけど・・・
 

このページのトップヘ