STM32でのHTTPプロトコルの詳細と実装
HTTPプロトコルとは何か?
HTTP(HyperText Transfer Protocol、ハイパーテキスト転送プロトコル)は、World Wide Web上のサーバーからクライアントブラウザへデータを転送するためのプロトコルです。TCP/IPプロトコルに基づいて動作し、クライアント-サーバーモデルを採用しています。アプリケーション層のプロトコルであり、テキスト、画像、オーディオなど様々なリソースの転送に使用できます。
HTTPプロトコルの特徴:
- シンプルさ:クライアントがサーバーにリクエストを送信する際には、リクエストメソッドとパスを送信するだけでリソースを取得できます。GET、HEAD、POSTなどのメソッドがあり、それぞれクライアントとサーバー間の通信タイプが異なります。
- 高速性:HTTPプロトコルのシンプルさにより、HTTPサーバーのプログラムサイズが小さく、通信速度が高速です。
- 柔軟性:HTTPは任意のデータオブジェクトの転送を許可しており、転送タイプはContent-Typeでマークされます。
- 接続管理:HTTP 1.1以前は、各リクエストごとにTCP接続を切断する「非接続」方式でした。HTTP 1.1以降では、持続的接続が導入され、不要なTCP接続の確立と切断によるオーバーヘッドが削減されています。
- 状態保持の欠如:HTTPはステートレスプロトコルであり、前の状態を記憶しません。これにより、後続処理で前の情報が必要な場合はデータを再送する必要があります。この問題を解決するため、Cookie技術が導入されました。
URLとリソース
URL(Uniform Resource Locator、統一資源位置指定子)は、インターネット上の特定のリソースを一意に識別するためのアドレスです。URI(統一資源識別子)はURLとURN(統一資源名)の2つのサブセットから構成されます。
URLの一般的な形式:
<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
| コンポーネント | 説明 |
|---|---|
| スキーム | サーバーにアクセスする際に使用するプロトコル(HTTP、HTTPS、FTPなど) |
| ユーザー | リソースアクセスに必要なユーザー名 |
| パスワード | ユーザー名に続くパスワード認証 |
| ホスト | リソースが存在するサーバーのホスト名またはIPアドレス |
| ポート | サーバーがリッスンしているポート番号(HTTPは80、HTTPSは443がデフォルト) |
| パス | サーバー上のリソースのパス |
| パラメータ | 特定のスキームで使用されるパラメータ |
| クエリ | アプリケーションをアクティブ化するためのパラメータ |
| フラグメント | リソースの一部を指す名前(サーバーには送信されない) |
HTTPメッセージ
HTTPメッセージは3つの部分で構成されます:
- メッセージを記述する「開始行」
- 属性を含む「ヘッダー」
- オプションの「データ本体」
リクエストメッセージとレスポンスメッセージの開始行の形式が異なります:
HTTPリクエストメッセージ:
<method> <request-URL> <version> // 開始行 <headers> // ヘッダー <entity-body> // データ本体
HTTPレスポンスメッセージ:
<version> <status> <reason-phrase> // 開始行 <headers> // ヘッダー <entity-body> // データ本体
HTTPステータスコード
| 範囲 | 定義済み範囲 | 説明 |
|---|---|---|
| 100-199 | 100-101 | 情報 |
| 200-299 | 200-206 | 成功 |
| 300-399 | 300-305 | リダイレクト |
| 400-499 | 400-415 | クライアントエラー |
| 500-599 | 500-505 | サーバーエラー |
STM32でのHTTP通信の実装
HTTP通信の基本的なフロー:
- 接続構造体の作成
- ドメイン名のサーバーへの接続(DNS推奨)、デフォルトポート番号を使用
- HTTPリクエストメッセージの送信
- サーバーの応答を待ち、データを受信
ネットワークデータは非常に大量になる可能性があるため、十分な大きさのバッファを使用してデータを受信する必要があります。
HTTPリクエストの例
GET / HTTP/1.1 Host: www.example.com
STM32での実装例
以下は、FreeRTOS環境でのTCPクライアント実装の例です:
// タスク:HTTP通信処理
void HttpCommunicationTask(void const * argument)
{
// 変数宣言
uint8_t txBuffer[128];
uint16_t dataLength;
BaseType_t result;
// 無限ループ
for(;;)
{
// HTTPリクエストの準備
dataLength = PrepareHttpRequest(txBuffer, sizeof(txBuffer));
// TCP接続の確立
if(TcpConnectToServer("www.example.com", 80) == SUCCESS)
{
// データの送信
result = TcpSendData(txBuffer, dataLength);
if(result == SUCCESS)
{
// レスポンスの受信処理
ProcessHttpResponse();
}
// 接続のクローズ
TcpCloseConnection();
}
// 次の通信までの待機
osDelay(5000);
}
}
// HTTPリクエストの準備関数
uint16_t PrepareHttpRequest(uint8_t *buffer, uint16_t bufferSize)
{
const char *requestTemplate = "GET / HTTP/1.1\r\nHost: www.example.com\r\n\r\n";
uint16_t length = strlen(requestTemplate);
if(length > bufferSize)
length = bufferSize;
memcpy(buffer, requestTemplate, length);
return length;
}
この実装では、HTTPリクエストを準備し、TCP接続を確立してデータを送信し、サーバーからの応答を処理します。接続が不要になったらクローズし、一定時間待機して次の通信を準備します。
データ受信処理の最適化
大量のデータを受信する場合、効率的な処理が重要です。以下はデータ受信処理の最適化例です:
// レスポンス受信処理関数
void ProcessHttpResponse(void)
{
uint8_t rxBuffer[1024];
uint16_t totalReceived = 0;
uint16_t chunkSize;
while(totalReceived < MAX_RESPONSE_SIZE)
{
// データチャンクの受信
chunkSize = TcpReceiveData(rxBuffer + totalReceived, sizeof(rxBuffer) - totalReceived);
if(chunkSize == 0)
break; // 受信データがなくなった
totalReceived += chunkSize;
// 必要に応じて処理
ProcessDataChunk(rxBuffer + totalReceived - chunkSize, chunkSize);
}
// 完全なデータの処理
ProcessCompleteResponse(rxBuffer, totalReceived);
}
このアプローチでは、データチャンクを効率的に受信し、必要に応じて処理します。大きなデータでもメモリ効率良く扱うことができます。