# ファイル転送クライアント開発

このページでは、クライアント視点からのファイル転送プロセスの概要と、EMQXへのファイルアップロード用コマンドの詳細情報を提供します。クライアント側のファイル転送機能の開発に役立ててください。

## ファイル転送プロセス

既存のMQTT接続を再利用しつつ、クライアントは特定のプレフィックスを持つトピックに所定のメッセージをパブリッシュすることでファイル転送セッションを制御できます。

::: warning リスナーマウントポイントとの非互換性
ファイル転送は、[マウントポイント](../../guides/configuration/listener.md#mountpoint)が設定されたリスナー経由で接続されたクライアントでは動作しません。EMQXは`$file/`および`$file-async/`コマンドプレフィックスのマッチング前にマウントポイントを適用するため、ファイル転送コマンドは通常のメッセージとしてマウントされたリテラルトピックにルーティングされます。`$file-response/`応答トピックへのサブスクライブもマウントされるため、クライアントはEMQXからのコマンド結果メッセージを受信できません。クライアントにはエラーは通知されません。
:::

クライアント側のファイル転送プロセスは以下の通りです。

1. **転送準備**：クライアントデバイスはアップロードするファイルを選択し、転送セッションを識別するための一意の`file_id`を生成します。
2. **ファイル転送の初期化**：クライアントは`$file/{file_id}/init`トピックに`init`コマンドをパブリッシュします。メッセージはJSON形式で、ファイル名、サイズ、チェックサムなどのメタデータを含みます。
3. **分割ファイル転送**：クライアントは`$file/{file_id}/{offset}[/{checksum}]`トピックに連続してメッセージをパブリッシュし、ファイルを転送します。メッセージ内容は現在のファイルセグメントのデータブロックで、オプションでデータブロックのチェックサムを含む`checksum`フィールドがあります。
4. **ファイル転送の完了**：クライアントは`$file/{file_id}/fin/{file_size}[/{checksum}]`トピックに`fin`コマンドをパブリッシュし、ファイル転送の完了を示します。メッセージ内容は空で、`file_size`はファイルの総サイズ、`checksum`はファイル全体のチェックサムです。

各転送コマンドの詳細な説明と注意点については、以下のセクションを参照してください。

![EMQX ファイル転送プロセス](./assets/emqx-ft-flow.jpg)

## ファイル転送コマンド

ファイル転送コマンドは、特定のトピック形式とメッセージ内容を持つMQTTメッセージです。

- **トピック**：トピックプレフィックスは`$file/`および`$file-async/`があり、それぞれ同期転送と非同期転送に使用されます。同一ファイル転送セッション内で異なるコマンドに混在して使用可能です。
  - **同期転送**：クライアントはEMQXのコマンド実行結果の確認を待ってから次の処理を行います。
  - **非同期転送**：クライアントはコマンド実行結果の確認を待たずに次のコマンドを送信できるため、処理が高速化されます。
- **QoS**：すべてのコマンドは信頼性確保のためQoSレベル1でパブリッシュされます。
- **メッセージ本文**：JSON形式またはデータブロックを含むメッセージ本文。

各コマンドパブリッシュ後、PUBACKメッセージまたは応答トピックを通じてコマンド実行結果を取得できます。詳細は[コマンド実行結果の取得](#retrieve-command-execution-results)を参照してください。

:::tip

1. すべてのファイル転送コマンドはEMQXブローカーで処理され、他のMQTTクライアントには送信されません。
2. 非同期転送モードはEMQX v5.3.2以降で利用可能です。
3. MQTT v3.1/v3.1.1クライアントはPUBACK Reason Codeが利用できないため、非同期転送モードの使用を推奨します。

:::

### init コマンド

`init`コマンドはファイル転送セッションを初期化するために使用します。

- トピック：`$file/{file_id}/init` または `$file-async/{file_id}/init`
- メッセージ本文：以下のフィールドを持つJSONオブジェクト

  ```json
  {
    "name": "{name}",
    "size": {size},
    "checksum": "{checksum}",
    "expire_at": {expire_at},
    "segments_ttl": {segments_ttl},
    "user_data": {user_data}
  }
  ```

| フィールド        | 説明                                                                                  |
| ----------------- | ------------------------------------------------------------------------------------- |
| `file_id`         | ファイル転送セッションの一意識別子。                                                  |
| `name`            | ファイル名。予約ファイル名（例："."、".."）と競合する場合や特殊文字を含む場合はパーセントエンコードされます。ファイル名のバイナリ長は240バイトを超えないこと。 |
| `size`            | ファイルサイズ。                                                                       |
| `checksum`        | ファイルのSHA256チェックサム（任意）。指定するとEMQXはファイルのチェックサムを検証します。 |
| `expire_at`       | ストレージからファイルを削除可能なタイムスタンプ（エポック秒）。                       |
| `segments_ttl`    | ファイルセグメントの有効期間（秒）。`minimum_segments_ttl`と`maximum_segments_ttl`の範囲内で指定。詳細は[セグメントストレージ](./broker.md#segment-storage)参照。 |
| `user_data`       | ファイルに関する追加情報やメタデータを格納する任意のJSONオブジェクト。                 |

メッセージ本文で必須なのは`name`フィールドのみです。

例：

```json
{
  "name": "ml-logs-data.log",
  "size": 12345,
  "checksum": "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
  "expire_at": 1696659943,
  "segments_ttl": 600
}
```

### segment コマンド

`segment`コマンドはファイルのデータブロックをアップロードするために使用します。

- トピック：`$file/{file_id}/{offset}[/{checksum}]` または `$file-async/{file_id}/{offset}[/{checksum}]`
- メッセージ本文：ファイルブロックのバイナリデータ

| フィールド    | 説明                                                                                  |
| ------------- | ------------------------------------------------------------------------------------- |
| `file_id`     | ファイル転送セッションの一意識別子。                                                  |
| `offset`      | ファイルの先頭からのバイト単位の開始オフセット。                                      |
| `checksum`    | ファイルブロックのSHA256チェックサム（任意）。                                        |

### fin コマンド

`fin`コマンドはファイル転送セッションの完了を示すために使用します。

- トピック：`$file/{file_id}/fin/{file_size}[/{checksum}]` または `$file-async/{file_id}/fin/{file_size}[/{checksum}]`
- メッセージ本文：空のメッセージ本文

| フィールド     | 説明                                                                                  |
| -------------- | ------------------------------------------------------------------------------------- |
| `file_id`      | ファイル転送セッションの一意識別子。                                                  |
| `file_size`    | ファイルの総サイズ（バイト単位）。                                                    |
| `checksum`     | ファイル全体のSHA256チェックサム（任意）。指定すると`init`コマンドの`checksum`より優先されます。 |

`fin`コマンド受信後、EMQXはファイルを組み立てるために必要なすべてのセグメントが受信済みか検証します。ファイルが正常にエクスポートされチェックサムが有効な場合、EMQXは成功のReason Codeで応答します。エラーがある場合は適切なエラー応答を返します。

## コマンド実行結果の取得

コマンド実行結果はMQTTのPUBACK Reason Codeで示されます。

- **同期転送**：PUBACK Reason Codeが操作の最終結果を表します。
- **非同期転送**：非ゼロのPUBACK Reason Codeは即時の失敗を示し、ゼロはコマンドが処理のため受理されたことを示します。結果は完了後に応答メッセージで返されます。

### PUBACK Reason Code

| Reason Code | MQTTでの意味           | ファイル転送での意味                                         |
| ----------- | ---------------------- | ------------------------------------------------------------ |
| None        |                        | 0x00と同じ意味。                                            |
| 0x00        | 成功                   | ファイルブロックが正常にパーシステンスされた。               |
| 0x10        | サブスクライバーなし   | サーバーはクライアントにすべてのファイルブロックの再送を要求。 |
| 0x80        | 不特定エラー           | `segment`コマンドでは現在のファイルブロックの再送を要求。`fin`コマンドではすべてのファイルブロックの再送を要求。 |
| 0x83        | 特定エラー             | サーバーはクライアントに送信のキャンセルを要求。             |
| 0x97        | クォータ超過           | サーバーはクライアントに送信の一時停止を要求。クライアントは再送前に待機すべき。 |

### 応答メッセージ

- トピック：`$file-response/{clientId}`（`clientId`はクライアントID）
- メッセージ：応答結果を含むJSONオブジェクト

例：

```json
{
  "vsn": "0.1",
  "topic": "$file-async/[COMMAND]",
  "packet_id": 1,
  "reason_code": 0,
  "reason_description": "success"
}
```

| フィールド            | 説明                                                                                  |
| --------------------- | ------------------------------------------------------------------------------------- |
| `vsn`                 | 応答メッセージフォーマットのバージョン                                               |
| `topic`               | 応答対象のコマンドトピック（例：`$file-async/somefileid/init`）                      |
| `packet_id`           | 応答対象のコマンドのMQTTメッセージID                                                |
| `reason_code`         | コマンドの実行結果コード。詳細は[Reason Code](#PUBACK-Reason-Code)参照               |
| `reason_description`  | 実行結果の説明                                                                       |

クライアントは同期・非同期に関わらず、`$file-response/{clientId}`トピックを通じてコマンドの実際の操作結果を取得できます。

## 注意事項

1. ファイル転送中にクライアントが切断された場合や、優先度の高いメッセージ送信のため転送を中断する必要がある場合は、未アックのデータブロックやコマンドを再送するだけでよく、ファイル全体の再送は不要です。これにより転送効率が向上します。
2. EMQXは受信したファイルセグメントからファイルを組み立てて設定済みストレージにエクスポートするため、`fin`コマンドの処理に時間がかかる場合があります。この間、クライアントは他のコマンド送信を継続可能です。`fin`コマンド処理中に切断が発生した場合は、コマンドを再送して転送を再開できます。ファイル転送が完了している場合は、EMQXは即座に成功応答を返します。

## クライアントコード例

各種言語およびクライアントライブラリのファイル転送クライアントコード例を参照できます。

- [C - Paho](https://github.com/emqx/MQTT-Client-Examples/blob/master/mqtt-client-C-paho/emqx_file_transfer.c)
- [Python3 - Paho](https://github.com/emqx/MQTT-Client-Examples/blob/master/mqtt-client-Python3/file_transfer.py)
- [Java - Paho](https://github.com/emqx/MQTT-Client-Examples/blob/master/mqtt-client-Java/src/main/java/io/emqx/mqtt/MqttFileTransferSample.java)
- [Golang - Paho](https://github.com/emqx/MQTT-Client-Examples/pull/110/files#diff-ea542153b4dd7109626626beff78b699ed649f9a7c05af362e5d67cce0866a94)
- [Node.js - MQTT.js](https://github.com/emqx/MQTT-Client-Examples/blob/master/mqtt-client-Node.js/emqx-file-transfer.js)
