アドベントカレンダー企画
みんな大好きアドベントカレンダーの季節になりましたね(12月21日公開)。
この記事はOthloTechのアドベントカレンダー企画とも紐づけて書いています!
他の記事も結構面白いものが多かったので、見てみてください。
C言語でもサーバサイドしたい
と思っている方多いのではないでしょうか?
いや、多いですよね。
PHPに限らず、RubyやPython他、様々な言語で盛り上がっているサーバサイド界隈ですが、個人的に一番好きなC言語でWebのサーバサイドを行いたいなと思いました。
で、やってみる
今回はひとまずできるのかなー?って確認なので、実用面とかはあまり考えないでWebアプリケーションフレームワークライクな振舞いをC言語で実装してみたいと思います。
今回実装する機能はざっくりとこんな感じです。
- HTTPサーバ
- HTMLの出力
- ルーティング
ひとまず、時間の関係上こんな感じの機能もどきを形だけ再現してみます。
環境
以下の環境でひとまずテストは行います。
- OS:Windows 10
- IDE:Visual Studio 2015
尚、今後はLinuxのCentOS当たりで開発をしようかなと思っています。
HTTPサーバをC言語で建ててみる
ってのに関しては過去にソケット通信をしていたのでそのコードを流用します。
ソケット通信の記事はこちら
⇒【Socket通信】WindowsPC同士でサーバとクライアントで通信!
ところどころ改変はしているのですが、ひとまず重要なのはHTTPヘッダを返すってところです。クライアント側の接続に対して「HTTPで通信するよ!」ってのを送って教えてあげよう!ってノリです。多分だいたいそう。
1 2 3 4 5 6 7 |
// HTTP unsigned char *header = "HTTP/1.0 200 OK\n" "Content-type: text/html\n" "\n"; send(sockw, header, strlen(header), 0); |
場所としてはここです。
気を付けて欲しいのは送る文字列はunsigned char型で送ってください。
charってご存知の通りデフォルトではsigned charなので文字化けしてしまいます。最初気にせずchar型で送信したら文字化けしました。
この1文で「今からHTTP通信でHTMLを送るぜ!」ってのを言ってます。多分だいたいそう。
HTMLを表示してみる
みんな大好き、HelloWorldしてみましょう。
HTMLファイルを作って1行ずつ送っても良いのですが、今回は面倒なので簡単に直接文章を送りたいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
strcpy(html, "<!DOCTYPE html>\n" "<html lang = \"ja\">\n" "<head>\n" "<meta charset = \"utf-8\">\n" "</head>\n" "<body>\n" "<h1>HelloWorld</h1>\n" "</body>" "</html>"); // 応答(HTMLを返答) if (send(sockw, html, strlen(html), 0) < 1) { printf("send : %d\n", WSAGetLastError()); break; } |
予め作っておいたhtml配列にhtmlを格納して、そのまま送信したらブラウザにHTMLが出力されます。
ルーティングしてみる。
さて、これではサーバのどこにアクセスしても同じHTMLが出力されてしまいます。そこで、ルーティングします。
ルーティングとは、特定のアドレスにアクセスした際にその目的のアドレスの結果を出力するって感じのやつです。知らない人はちゃんと調べた方がいいです。僕は知らないので後でちゃんと調べます…
実際に書いたコードはこんな感じ
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//接続 recv_len = recvfrom(sockw, buf,1024, 0, (struct sockaddr *)&client, &sockaddr_in_size); buf[recv_len - 1] = 0; if (buf[0] == '\0') strcpy(buf, NULL); // 通信表示 printf("%s \n",buf); // method for (int i = 0; i < strlen(buf); i++) { printf("%d\n",i); if (buf[i] == 'G' && buf[i + 1] == 'E' && buf[i + 2] == 'T' && buf[i + 3] == ' ') { for (int j = 0; buf[i + 4 + j] != ' '; j++) { path[j] = buf[i + 4 + j]; } break; }else if (buf[i] == 'P' && buf[i + 1] == 'O' && buf[i + 2] == 'S' && buf[i + 3] == 'T' && buf[i + 4] == ' ') { for (int j = 0; buf[i + 4 + j] != ' '; j++) { path[j] = buf[i + 4 + j]; } break; } } |
このコードでGET/POSTからの値をpath変数に格納します。path変数って言うとなんだか紛らわしいので命名はまた改めて変えておきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
// ルーティング if (strcmp(path, "/page1") == 0) { strcpy(html, "<!DOCTYPE html>\n" "<html lang = \"ja\">\n" "<head>\n" "<meta charset = \"utf-8\">\n" "</head>\n" "<body>\n" "<h1>Page1</h1>\n" "<a href=\"/page2\">->page2</a>\n" "</body>" "</html>"); }else if (strcmp(path, "/page2") == 0) { strcpy(html, "<!DOCTYPE html>\n" "<html lang = \"ja\">\n" "<head>\n" "<meta charset = \"utf-8\">\n" "</head>\n" "<body>\n" "<h1>Page2</h1>\n" "<a href=\"/page1\">->page1</a>\n" "</body>" "</html>"); }else { strcpy(html, "<!DOCTYPE html>\n" "<html lang = \"ja\">\n" "<head>\n" "<meta charset = \"utf-8\">\n" "</head>\n" "<body>\n" "<h1>Not Found</h1>\n" "</body>" "</html>"); } // 応答(HTMLを返答) if (send(sockw, html, strlen(html), 0) < 1) { printf("send : %d\n", WSAGetLastError()); break; } |
こっちでルーティング。よくあるフレームワークみたいに綺麗じゃなく、単なるif文で申し訳ないですが、こんな感じ。もはやswitchでいいような気もするけど、ひとまずいいや。
これを実行すると、http://localhost:8000/page1に接続するとページが表示され、アンカーからhttp://localhost:8000/page2と行き来できます。
また、他のページを開こうとすると「Not Found」と言われます。
ひとまずはこんな感じ。
一応他にもところどころできた機能もあるものの、今回の記事はここまでにしておきます。元々、C言語でサーバサイドフレームワークを作ろうと思って始めましたが、本当にできそうなので、本格的に開発を行っていきたいと思います(勿論上の適当なコードは書き直します…)
尚、今回のコードの全貌はこちら
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 |
#include <winsock2.h> #include <windows.h> #include <stdio.h> #include <conio.h> #include <tchar.h> WSADATA wsaData; SOCKET sock0; SOCKET sockw; struct sockaddr_in addr; struct sockaddr_in client; #define PORT_NUM 8000 // IPアドレス取得 int getAddrHost(void) { int i; HOSTENT *lpHost; // ホスト情報を格納する構造体 IN_ADDR inaddr; // IPアドレスを格納する構造体 char szBuf[256], szIP[16]; // ホスト名/IPアドレスを格納する配列 // エラー処理 if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { printf("WSAStartup Error\n"); return -1; } // ローカルマシンのホスト名を取得する gethostname(szBuf, (int)sizeof(szBuf)); // ホスト情報を取得 lpHost = gethostbyname(szBuf); // IPアドレスを取得 for (i = 0; lpHost->h_addr_list[i]; i++) { memcpy(&inaddr, lpHost->h_addr_list[i], 4); } strcpy(szIP, inet_ntoa(inaddr)); printf("build server: http://%s:%d\n", szIP,PORT_NUM); WSACleanup(); return 0; } char *get_name(char *name) { } int main() { int len; int n; int sockaddr_in_size = sizeof(struct sockaddr_in); int recv_len = 0; unsigned char buf[1024]; unsigned char path[1024]; unsigned char html[1024]; //IPアドレスの表示 if (getAddrHost() != 0) { printf("get IP address failed"); getch(); return -1; } // winsock2の初期化 if (WSAStartup(MAKEWORD(2, 0), &wsaData)) { puts("reset winsock failed"); getch(); return -1; } // ソケットの作成 sock0 = socket(AF_INET, SOCK_STREAM, 0); if (sock0 == INVALID_SOCKET) { printf("socket : %d\n", WSAGetLastError()); getch(); return -1; } // ソケットの設定 addr.sin_family = AF_INET; addr.sin_port = htons(PORT_NUM); addr.sin_addr.S_un.S_addr = INADDR_ANY; // ソケットをバインド if (bind(sock0, (struct sockaddr *)&addr, sizeof(addr)) != 0) { printf("bind : %d\n", WSAGetLastError()); getch(); return -1; } // TCPクライアントからの接続要求を待てる状態にする if (listen(sock0, 5) != 0) { printf("listen : %d\n", WSAGetLastError()); getch(); return -1; } // サーバ起動 while (1) { len = sizeof(client); sockw = accept(sock0, (struct sockaddr *)&client, &len); if (sockw == INVALID_SOCKET) { printf("accept : %d\n", WSAGetLastError()); break; } //バッファ初期化 memset(path, 0, 1024); memset(html, 0, 1024); //接続 recv_len = recvfrom(sockw, buf,1024, 0, (struct sockaddr *)&client, &sockaddr_in_size); buf[recv_len - 1] = 0; if (buf[0] == '\0') strcpy(buf, NULL); // 通信表示 printf("%s \n",buf); // method for (int i = 0; i < strlen(buf); i++) { printf("%d\n",i); if (buf[i] == 'G' && buf[i + 1] == 'E' && buf[i + 2] == 'T' && buf[i + 3] == ' ') { for (int j = 0; buf[i + 4 + j] != ' '; j++) { path[j] = buf[i + 4 + j]; } break; }else if (buf[i] == 'P' && buf[i + 1] == 'O' && buf[i + 2] == 'S' && buf[i + 3] == 'T' && buf[i + 4] == ' ') { for (int j = 0; buf[i + 4 + j] != ' '; j++) { path[j] = buf[i + 4 + j]; } break; } } printf("request: %s \n",path); // HTTP unsigned char *header = "HTTP/1.0 200 OK\n" "Content-type: text/html\n" "\n"; send(sockw, header, strlen(header), 0); // ルーティング if (strcmp(path, "/page1") == 0) { strcpy(html, "<!DOCTYPE html>\n" "<html lang = \"ja\">\n" "<head>\n" "<meta charset = \"utf-8\">\n" "</head>\n" "<body>\n" "<h1>Page1</h1>\n" "<a href=\"/page2\">->page2</a>\n" "</body>" "</html>"); }else if (strcmp(path, "/page2") == 0) { strcpy(html, "<!DOCTYPE html>\n" "<html lang = \"ja\">\n" "<head>\n" "<meta charset = \"utf-8\">\n" "</head>\n" "<body>\n" "<h1>Page2</h1>\n" "<a href=\"/page1\">->page1</a>\n" "</body>" "</html>"); }else { strcpy(html, "<!DOCTYPE html>\n" "<html lang = \"ja\">\n" "<head>\n" "<meta charset = \"utf-8\">\n" "</head>\n" "<body>\n" "<h1>404- Not Found</h1>\n" "</body>" "</html>"); } // 応答(HTMLを返答) if (send(sockw, html, strlen(html), 0) < 1) { printf("send : %d\n", WSAGetLastError()); break; } // ソケットのクローズ closesocket(sockw); } // winsock2 の終了処理 closesocket(sock0); WSACleanup(); return 0; } |
何か、気になる箇所等あれば、是非ご連絡いただければと思います1
今後はLinuxでしっかりとC言語のWebアプリケーションフレームワーク開発を進めていくつもりなので、また進展があればブログやTwitterで公開したいと思います。
今回のブログ曲
今回投稿中に聴いていた曲はこちら