MiMicHttpdのサンプルスケッチ

httpdの例です。Webページの表示と、クロスドメインXmlHttpreqestを確認することが出来ます。
このコードは、projects/sample.net/sample.simplehttpdにあるものと同じです。

特徴

  • HTTP/1.1の持続性接続
  • Chunkedエンコーディング
  • Access-Control-Allow-OriginによるクロスドメインRESTリクエスト(0.3秒間隔でサーバステータスの表示を更新)
  • セッション数による負荷制御
  • ROMFSからのファイル読み込み
  • マルチスレッドhttpd処理

Screenshot

※ブラウザには、IE8以外のHtml5に対応したものをお使いください。Safari,Firefox,Chromeは使えます。

Source code

/**
 * httpdサーバのサンプルです。
 * ROMにある定義済みファイルの入出力と、クロスドメインRESTが確認できます。
 */
#include "boot/sketch.h"
#include "NyLPC_uipService.h"
#include "NyLPC_httpService.h"
#include "NyLPC_utils.h"
#include <stdio.h>

//イーサネット用の初期化情報
const struct NyLPC_TEthAddr ethaddr=NyLPC_TEthAddr_pack(0x00,0x01,0x02,0x03,0x04,0x05);
const struct NyLPC_TIPv4Addr ipaddr=NyLPC_TIPv4Addr_pack(192,168,128,201);
const struct NyLPC_TIPv4Addr netmask=NyLPC_TIPv4Addr_pack(255,255,255,0);
const struct NyLPC_TIPv4Addr gateway=NyLPC_TIPv4Addr_pack(192,168,128,254);

//TCP処理スレッドの定義
#define SIZE_OF_RX 256
#define NUM_OF_TH 7
struct TProc{
	NyLPC_TcThread_t th;
	NyLPC_TcTcpSocket_t socket;
	char rbuf[SIZE_OF_RX];
}proc[NUM_OF_TH];

//ROMFSの定義
extern struct NyLPC_TRomFileData file_cat_jpg;
extern struct NyLPC_TRomFileData file_index_html;
NyLPC_TcRomFileSet_t rfs;
const struct NyLPC_TRomFileData* rfsd[]={
		&file_index_html,
		&file_cat_jpg};

//private 関数

static NyLPC_TUInt16 parseReqHeader(NyLPC_TcHttpStream_t* i_st,struct NyLPC_THttpShortRequestHeader* o_reqh);
static NyLPC_TBool writeError(NyLPC_TcHttpStream_t* i_st,const struct NyLPC_THttpBasicHeader* i_rqh,NyLPC_TUInt16 i_status);
static NyLPC_TBool writeFile(NyLPC_TcHttpStream_t* i_st,const struct NyLPC_THttpBasicHeader* i_rqh,const struct NyLPC_TRomFileData* i_file);
static NyLPC_TBool writeJson(NyLPC_TcHttpStream_t* i_st,const struct NyLPC_THttpBasicHeader* i_rqh);

//スケッチ

void setup(void)
{
	int i;
	//uipサービス初期化。いろいろ利用可能に。
	NyLPC_cUipService_initialize();
	for(i=0;i<NUM_OF_TH;i++){
		NyLPC_cThread_initialize(&(proc[i].th),200);
		NyLPC_cTcpSocket_initialize(&(proc[i].socket),proc[i].rbuf,SIZE_OF_RX);
	}
	//ROMのファイルシステムを初期化
	NyLPC_cRomFileSet_initialize(&rfs,rfsd,2);

}

//ステータス用。
static int num_of_reqest=0;
static int num_of_error=0;
static int num_of_connect=0;
static int num_of_tx=0;

//Httpのセッション関数
static int server(void* p)
{
	struct TProc* proc=(struct TProc*)p;
	NyLPC_TUInt16 ret;
	const struct NyLPC_TRomFileData* rf;
	NyLPC_TcHttpStream_t st;
	struct NyLPC_THttpShortRequestHeader reqheader;
	if(!NyLPC_cTcpSocket_accept(&(proc->socket),3000)){
		return -1;
	}
	num_of_connect++;

	//TCPのオープン
	if(NyLPC_cHttpStream_initialize(&st,&(proc->socket))){
		for(;;){
			ret=parseReqHeader(&st,&reqheader);
			num_of_reqest++;
			//コネクションが増えすぎたら持続性接続を停止するために上書き。
			if(num_of_connect>5){
				reqheader.super.connection=NyLPC_THttpMessgeHeader_Connection_CLOSE;
			}
			if(ret!=200){
				//エラーならエラーレスポンスを生成。持続性接続しない。
				writeError(&st,&(reqheader.super),ret);
				num_of_error++;
				break;
			}
			//URLから判定。
			if(strncmp("/rf.api?",reqheader.url,8)==0){
				//ファイル検索
				rf=NyLPC_cRomFileSet_getFilaData(&rfs,reqheader.url+8);
				if(rf==NULL){
					num_of_error++;
					if(!writeError(&st,&(reqheader.super),404)){
						break;
					}
				}else{
					if(!writeFile(&st,&(reqheader.super),rf)){
						num_of_error++;
						break;
					}
				}
			}else if(strncmp("/status.json",reqheader.url,8)==0){
				if(!writeJson(&st,&(reqheader.super))){
					break;
				}
				//httpdの状態を返す。
			}else{
				if(!writeFile(&st,&(reqheader.super),NyLPC_cRomFileSet_getFilaData(&rfs,"index.html"))){
					num_of_error++;
					break;
				}
			}

		}
		NyLPC_cHttpStream_finalize(&st);
	}
	//5秒以内に切断
	NyLPC_cTcpSocket_close(&(proc->socket),5000);
	num_of_connect--;
	return 0;
}

void loop(void)
{
	NyLPC_TcIPv4Config_t config;
	NyLPC_TcTcpListener_t listener;
	int i;

	NyLPC_cIPv4Config_initialzeForEthernet(&config,&ethaddr,1480);
	NyLPC_cIPv4Config_setDefaultRoute(&config,&gateway);
	NyLPC_cIPv4Config_setIp(&config,&ipaddr,&netmask);

	NyLPC_cTcpListener_initialize(&listener,80);
	NyLPC_cUipService_start(&config);
	for(;;){
		//ターミネイト状態のタスクを検索
		for(i=0;i<NUM_OF_TH;i++){
			if(NyLPC_cThread_isTerminated(&(proc[i].th))){
				//リスニング
				if(!NyLPC_cTcpListener_listen(&listener,&(proc[i].socket),5000)){
					continue;
				}
				//タスク起動
				NyLPC_cThread_start(&(proc[i].th),server,&(proc[i]));
			}
		}
	}
	for(;;){}
}

/**
 * リクエストヘッダのパーサ
 */
static NyLPC_TUInt16 parseReqHeader(NyLPC_TcHttpStream_t* i_st,struct NyLPC_THttpShortRequestHeader* o_reqh)
{
	NyLPC_TUInt16 ret=200;
	NyLPC_TcHttpShortRequestHeaderParser_t hp;
	NyLPC_cHttpShortRequestHeaderParser_initialize(&hp);

	//ヘッダ解析
	if(!NyLPC_cHttpShortRequestHeaderParser_parse(&hp,i_st,o_reqh)){
		ret=NyLPC_cHttpBasicHeaderParser_getStatusCode(&hp.super);
	}
	//ヘッダの内容確認
	if(o_reqh->super.type!=NyLPC_THttpHeaderType_REQUEST){
		ret=400;
	}
	//GETだけネ
	if(o_reqh->super.startline.req.method!=NyLPC_THttpMethodType_GET){
		ret=405;
	}
	NyLPC_cHttpBasicHeaderParser_finalize(&hp);
	return ret;
}
/**
 * エラーレスポンスのライタ。
 * 戻り値はpersistentConnectionが有効かどうか。
 */
static NyLPC_TBool writeError(NyLPC_TcHttpStream_t* i_st,const struct NyLPC_THttpBasicHeader* i_rqh,NyLPC_TUInt16 i_status)
{
	static const char* HTML_FORMAT="<html><h1>STATUS %d</h1><hr/>"NyLPC_cHttpdConfig_SERVER"</html>";
	NyLPC_TcHttpHeaderWriter_t hw;
	NyLPC_TcHttpBodyWriter_t bw;
	//ヘッダライタの生成
	if(!NyLPC_cHttpHeaderWriter_initialize(&hw,i_st,i_rqh)){
		return NyLPC_TBool_FALSE;
	}
	//ヘッダ書込み
	if(!NyLPC_THttpBasicHeader_isPersistent(i_rqh)){
		NyLPC_cHttpHeaderWriter_setClose(&hw);
	}
	//@bug HTTP/1.1未満のクライアントを考慮していない。
	NyLPC_cHttpHeaderWriter_setChunked(&hw);
	//ヘッダの基本情報出力
	NyLPC_cHttpHeaderWriter_writeHeader(&hw,i_status);
	//拡張メッセージヘッダの出力
	NyLPC_cHttpHeaderWriter_writeMessage(&hw,"Content-type","text/html");

	//ヘッダ書込み終わり。(最後だけチェック)
	if(!NyLPC_cHttpHeaderWriter_close(&hw)){
		NyLPC_cHttpHeaderWriter_finalize(&hw);
		return NyLPC_TBool_FALSE;
	}
	NyLPC_cHttpHeaderWriter_finalize(&hw);

	//Bodyの書込み
	NyLPC_cHttpBodyWriter_initialize(&bw,i_st);
	//チャンク転送設定
	NyLPC_cHttpBodyWriter_setChunked(&bw);
	NyLPC_cHttpBodyWriter_format(&bw,HTML_FORMAT,(NyLPC_TInt32)i_status);
	//エラーチェック
	if(!NyLPC_cHttpBodyWriter_close(&bw)){
		NyLPC_cHttpBodyWriter_finalize(&hw);
		return NyLPC_TBool_FALSE;
	}
	NyLPC_cHttpBodyWriter_finalize(&hw);
	return NyLPC_THttpBasicHeader_isPersistent(i_rqh);
}

/**
 * エラーレスポンスのライタ。
 * 戻り値はpersistentConnectionが有効かどうか。
 */
static NyLPC_TBool writeJson(NyLPC_TcHttpStream_t* i_st,const struct NyLPC_THttpBasicHeader* i_rqh)
{
	static const char* JSON_FORMAT="{nr:%d,ne:%d,ac:%d,er:\"%d/%d/%d\",tx:%d}";
	NyLPC_TcHttpHeaderWriter_t hw;
	NyLPC_TcHttpBodyWriter_t bw;
	//ヘッダライタの生成
	if(!NyLPC_cHttpHeaderWriter_initialize(&hw,i_st,i_rqh)){
		return NyLPC_TBool_FALSE;
	}
	//ヘッダ書込み
	if(!NyLPC_THttpBasicHeader_isPersistent(i_rqh)){
		NyLPC_cHttpHeaderWriter_setClose(&hw);
	}
	//@bug HTTP/1.1未満のクライアントを考慮していない。
	NyLPC_cHttpHeaderWriter_setChunked(&hw);
	//ヘッダの基本情報出力
	NyLPC_cHttpHeaderWriter_writeHeader(&hw,200);
	//拡張メッセージヘッダの出力
	NyLPC_cHttpHeaderWriter_writeMessage(&hw,"Content-type","application/json");
	NyLPC_cHttpHeaderWriter_writeMessage(&hw,"Access-Control-Allow-Origin","*");

	//ヘッダ書込み終わり。(最後だけチェック)
	if(!NyLPC_cHttpHeaderWriter_close(&hw)){
		NyLPC_cHttpHeaderWriter_finalize(&hw);
		return NyLPC_TBool_FALSE;
	}
	NyLPC_cHttpHeaderWriter_finalize(&hw);

	//Bodyの書込み
	NyLPC_cHttpBodyWriter_initialize(&bw,i_st);
	//チャンク転送設定
	NyLPC_cHttpBodyWriter_setChunked(&bw);
	NyLPC_cHttpBodyWriter_format(&bw,JSON_FORMAT,
		(NyLPC_TInt32)num_of_reqest,(NyLPC_TInt32)num_of_error,(NyLPC_TInt32)num_of_connect,
		NyLPC_assert_counter,NyLPC_abort_counter,NyLPC_debug_counter,dbg_getNumofUsedTx());
	//エラーチェック
	if(!NyLPC_cHttpBodyWriter_close(&bw)){
		NyLPC_cHttpBodyWriter_finalize(&hw);
		return NyLPC_TBool_FALSE;
	}
	NyLPC_cHttpBodyWriter_finalize(&hw);
	return NyLPC_THttpBasicHeader_isPersistent(i_rqh);
}
/**
 * ROMファイルのレスポンスライタ。
 * 戻り値はpersistentConnectionが有効かどうか。
 */
static NyLPC_TBool writeFile(NyLPC_TcHttpStream_t* i_st,const struct NyLPC_THttpBasicHeader* i_rqh,const struct NyLPC_TRomFileData* i_file)
{
	NyLPC_TcHttpHeaderWriter_t hw;
	NyLPC_TcHttpBodyWriter_t bw;
	//ヘッダライタの生成
	if(!NyLPC_cHttpHeaderWriter_initialize(&hw,i_st,i_rqh)){
		return NyLPC_TBool_FALSE;
	}
	//ヘッダ書込み
	if(!NyLPC_THttpBasicHeader_isPersistent(i_rqh)){
		NyLPC_cHttpHeaderWriter_setClose(&hw);
	}
	NyLPC_cHttpHeaderWriter_setContentLength(&hw,i_file->size);
	//ヘッダの基本情報出力
	NyLPC_cHttpHeaderWriter_writeHeader(&hw,200);
	//拡張メッセージヘッダの出力
	NyLPC_cHttpHeaderWriter_writeMessage(&hw,"Content-type",i_file->content_type);

	//ヘッダ書込み終わり。(最後だけチェック)
	if(!NyLPC_cHttpHeaderWriter_close(&hw)){
		NyLPC_cHttpHeaderWriter_finalize(&hw);
		return NyLPC_TBool_FALSE;
	}
	NyLPC_cHttpHeaderWriter_finalize(&hw);

	//Bodyの書込み
	NyLPC_cHttpBodyWriter_initialize(&bw,i_st);
	NyLPC_cHttpBodyWriter_write(&bw,i_file->data,i_file->size);
	//エラーチェック
	if(!NyLPC_cHttpBodyWriter_close(&bw)){
		NyLPC_cHttpBodyWriter_finalize(&hw);
		return NyLPC_TBool_FALSE;
	}
	NyLPC_cHttpBodyWriter_finalize(&hw);
	return NyLPC_THttpBasicHeader_isPersistent(i_rqh);
}

解説

このプログラムは、マルチスレッドのTCPサーバを基にしています。

  1. setup関数は、定義済み変数からファイルセットを定義し、スレッドとソケットのペアを作ります。(このとき、スレッドは停止状態です。)そして、TCP/IPサービス(cUipService)を初期化します。
  2. loop関数では、TCP/IPサービスの各種パラメタを設定し、サービスを開始させます。次に、スレッドとソケットのペアから、未使用のものを探索し、そのソケットへの接続を一定時間待ちます。もし接続があれば、スレッドを起動して、接続処理とHttpリクエストの処理を別スレッドで処理します。
  3. スレッドのメイン関数は、server関数です。ここでは、まず接続応答段階のTCPソケットをacceptして、接続を確立します。次に、Httpヘッダ解析、レスポンス内容の決定、レスポンスヘッダの出力、レスポンスBodyの出力を、順に実行します。ヘッダ解析は、parseReqHeader関数、レスポンスの出力は、writeFile, writeError, writeJson で実行しています。

ポイント

  • ヘッダ解析とレスポンス出力を分ける理由は、消費スタックの節約のためです。これらは、構文解析のために、32~64バイトのメモリを消費します。関数を分けることで、スタックの消費を抑えることが出来ます。
  • 書式文字列の出力には、NyLPC_cHttpBodyWriter_format関数が便利です。この関数はprintf関数のサブセットです。メモリ消費を抑えながら、書式文字列をTCPストリームへ送信できます。

 

One Response

  1. Bobbe
    Bobbe at |

    A few years ago I’d have to pay seomone for this information.

Comments are closed, but trackbacks and pingbacks are open.