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,ðaddr,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サーバを基にしています。
- setup関数は、定義済み変数からファイルセットを定義し、スレッドとソケットのペアを作ります。(このとき、スレッドは停止状態です。)そして、TCP/IPサービス(cUipService)を初期化します。
- loop関数では、TCP/IPサービスの各種パラメタを設定し、サービスを開始させます。次に、スレッドとソケットのペアから、未使用のものを探索し、そのソケットへの接続を一定時間待ちます。もし接続があれば、スレッドを起動して、接続処理とHttpリクエストの処理を別スレッドで処理します。
- スレッドのメイン関数は、server関数です。ここでは、まず接続応答段階のTCPソケットをacceptして、接続を確立します。次に、Httpヘッダ解析、レスポンス内容の決定、レスポンスヘッダの出力、レスポンスBodyの出力を、順に実行します。ヘッダ解析は、parseReqHeader関数、レスポンスの出力は、writeFile, writeError, writeJson で実行しています。
ポイント
- ヘッダ解析とレスポンス出力を分ける理由は、消費スタックの節約のためです。これらは、構文解析のために、32~64バイトのメモリを消費します。関数を分けることで、スタックの消費を抑えることが出来ます。
- 書式文字列の出力には、NyLPC_cHttpBodyWriter_format関数が便利です。この関数はprintf関数のサブセットです。メモリ消費を抑えながら、書式文字列をTCPストリームへ送信できます。
A few years ago I’d have to pay seomone for this information.