CでWebサーバーを作る
June 26, 2019
C始めました
Raspberry Pi でベアメタルプログラミングする前にちょろっとCを勉強しようと、少し触り始めた。 ついでなので、これまで実装したことがなかったWebサーバーをCで実装してみた次第。
Webサーバーを作る
GETだけに絞って静的ファイルを返すだけの単純なWebサーバーを実装してみた。
とりあえず汚いコードを全部載せておく。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <time.h>
#include <fcntl.h>
#define DOCUMENT_ROOT "."
#define BUF_SIZE 1024
int generate_date(char *time_str) {
time_t timer;
struct tm *gmt;
char wday_name[][4] = {"Sun", "Mon", "Tue", "Wed", "Thr", "Fri", "Sat"},
mon_name[][4] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
timer = time(NULL);
gmt = gmtime(&timer);
sprintf(
time_str,
"%s, %d %s %d %d:%d:%d GMT\r\n",
wday_name[gmt->tm_wday],
gmt->tm_mday,
mon_name[gmt->tm_mon],
gmt->tm_year + 1900,
gmt->tm_hour,
gmt->tm_min,
gmt->tm_sec);
return 0;
}
int handle_tcp_client(int serverSock, int clientSock) {
int clientLen, i, filefd;
struct sockaddr_in client;
char request_buf[BUF_SIZE],
response_header[BUF_SIZE],
response_body[BUF_SIZE],
request_http_method[BUF_SIZE],
request_url[BUF_SIZE],
request_protocol[BUF_SIZE],
file_path[BUF_SIZE] = "",
time_str[BUF_SIZE];
// 接続を受け付ける
clientLen = sizeof(client);
clientSock = accept(serverSock, (struct sockaddr *) &client, (socklen_t* ) &clientLen);
printf("Client Address: %s\n", inet_ntoa(client.sin_addr));
if (clientSock < 0) {
perror("Error: accept");
return 1;
}
memset(request_buf, 0, sizeof(request_buf));
// 接続情報をrequest_bufに格納
recv(clientSock, request_buf, sizeof(request_buf), 0);
sscanf(request_buf, "%s %s %s", request_http_method, request_url, request_protocol);
sprintf(file_path, DOCUMENT_ROOT);
strcat(file_path, request_url);
if (file_path[strlen(file_path) - 1] == '/') {
strcat(file_path, "index.html");
}
// 確認用
printf("%s\n", "****************************");
printf("Method: %s\n", request_http_method);
printf("Request Url: %s\n", request_url);
printf("Protocol: %s\n", request_protocol);
printf("%s\n", "****************************");
generate_date(time_str);
// response_header 送信
memset(response_header, 0, sizeof(response_header));
i = snprintf(response_header, sizeof(response_header), "HTTP/1.1 200 OK\r\n");
i += snprintf(response_header + i, sizeof(response_header), "Date: %s", time_str);
i += snprintf(response_header + i, sizeof(response_header), "Server: Urache 0.0.1\r\n");
i += snprintf(response_header + i, sizeof(response_header), "Accept-Ranges: bytes\r\n");
i += snprintf(response_header + i, sizeof(response_header), "Content-Type: text/html; charset=UTF-8\r\n");
i += snprintf(response_header + i, sizeof(response_header), "\r\n");
if (send(clientSock, response_header, (int) strlen(response_header), 0) < 0) {
perror("Error: Sending Header");
}
// response_body 送信
memset(response_body, 0, sizeof(response_body));
if ((filefd = open(file_path, O_RDONLY)) < 0) {
perror("open");
fprintf(stderr, "file: %s\n", file_path);
} else {
while ((read(filefd, response_body, sizeof(response_body))) > 0) {
if (send(clientSock, response_body, (int) strlen(response_body), 0) < 0) {
perror("Error: Sending Body");
}
}
}
// 接続を閉じる
close(clientSock);
return 0;
}
int main() {
int serverSock, clientSock, v = 1;
struct sockaddr_in addr;
serverSock = socket(AF_INET, SOCK_STREAM, 0);
if (serverSock < 0) {
perror("Error: socket");
return 1;
}
addr.sin_family = AF_INET;
addr.sin_port = htons(9000);
addr.sin_addr.s_addr = INADDR_ANY;
setsockopt(serverSock,
SOL_SOCKET, SO_REUSEADDR, (const char *) &v, sizeof(v));
if (bind(serverSock, (struct sockaddr *) &addr, sizeof(addr)) != 0) {
perror("Error: bind");
return 1;
}
// 接続を待機する
if (listen(serverSock, 5) != 0) {
perror("Error: listen");
return 1;
}
for (;;) {
// 接続に対して返信する
if (handle_tcp_client(serverSock, clientSock) > 0) {
break;
}
}
close(serverSock);
return 0;
}
めちゃめちゃ最低限の関数化をしてるけど、お世辞にも綺麗とは言えないネ。 メモリの扱い方わかっていないし、BUF_SIZEとかめっちゃ適当。
メモリとかはこれからベアメタルプログラミングでちゃんと学ぶ気持ちでいる(気持ち止まり)
動かしてみた
下記のようなディレクトリ構成で動かしてみた。
src
├ content/index.html
├ index.html
└ urache ( 実行ファイル )
いい感じにHeaderとBodyが返ってきてますね。
おわりに
真面目にWebサーバー作ってみようかななんて思ったりもしたけど、本来の目的であるベアメタルプログラミングから遠ざかってしまう感も否めないので、一旦はこれで終わりかな。
真剣に作るとなると、マルチスレッド(これはそんなに大変ではなさそう)やセキュリティ(これは大変そう)を実装するかなーって感じ。 あとは、別にCにこだわる必要はないからWebサーバーを本当に作りたくなったら慣れてる言語にするかなという感じ。
Written by Ryo @neer_chan