diff --git a/C/webserver.c b/C/webserver.c index 7e74b61..77796f6 100644 --- a/C/webserver.c +++ b/C/webserver.c @@ -16,37 +16,62 @@ #include // inet_ntop() #include // close() -/* - * GET要求を処理する - */ -void handle_get(FILE *fp, const char *path) +// プロトタイプ宣言 +void handle_get(FILE *fp, const char *path); +void handle_client(int client_fd, struct sockaddr_in *client_addr); + +int main(int argc, char **argv) { - if (strcmp(path, "/") == 0) { // http://localhost:8000/ へのアクセスの場合 - // C言語では連続した文字列は自動的に連結される - fprintf(fp, - "HTTP/1.0 200 OK\r\n" // ステータス行 - "Content-Type: text/html\r\n" // 本体がHTMLであることを伝える - "\r\n" // ヘッダの終了を表す空行 - "\r\n" // 以下はHTML (HTML5) - "\r\n" - "Sample\r\n" - "This server is implemented with C!\r\n" - "\r\n" - ); - } else { // 他のパス (http://localhost:8000/abc.html) などへのアクセスの場合,404エラー - fprintf(fp, - "HTTP/1.0 404 Not Found\r\n" // 404はNot Foundを表す - "Content-Type: text/html\r\n" - "\r\n" - "\r\n" - "\r\n" - "404 Not Found\r\n" - "%s is not found\r\n" - "\r\n", - path - ); + // socket()はソケットを生成し,ファイル記述子(ソケットに割り当てられた整数値)を返す + // - AF_INETはIPv4 + // - SOCK_STREAMは信頼性の高い全2重バイトストリームの指定 + // - IPPROTO_TCPはTCPの指定 + int server_fd; + if ((server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { + // socket()はエラーのとき負の値を返す + perror("socket"); + exit(1); + } + + // プログラム再実行時に,サーバのポートをすぐに再利用できるようにする + int yes = 1; + setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); + + // bind()で自ノード側のアドレスを設定 + struct sockaddr_in server_addr; // sockaddr_inはIPアドレスとポート番号を保持する構造体 + memset(&server_addr, 0, sizeof(server_addr)); // 構造体をいったんゼロクリア + server_addr.sin_family = AF_INET; // IPv4アドレス + // サーバ側のIPアドレスはANY(指定しない).htonlは4バイトをネットワークバイトオーダに変換 + server_addr.sin_addr.s_addr = htonl(INADDR_ANY); + server_addr.sin_port = htons(8000); // サーバ側のポート番号の指定.htonsは2バイトをネットワークバイトオーダに変換 + if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { + perror("bind"); + exit(1); + } + + // ソケットをサーバ用に設定.5はコネクション要求を貯めておくキューの長さ + // - コネクション要求はaccept()で受け取る必要があるが,それをどのくらい溜めておけるか + if (listen(server_fd, 5) < 0) { + perror("listen"); + exit(1); + } + + printf("open http://localhost:8000/ with your browser!\n"); + + // クライアントからのコネクションを処理するためのループ + while (1) { + // accept()はクライアントとのコネクションが確立するのを待ってブロックする(待つ). + // 確立すると当該コネクションで通信するためのファイル記述子を返す. + // - client_addrはクライアントのIPアドレスとポート番号を受け取るための構造体 + int client_fd; + struct sockaddr_in client_addr; + socklen_t addr_length = sizeof client_addr; + if ((client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_length)) < 0) { + perror("accept"); + exit(1); + } + handle_client(client_fd, &client_addr); // クライアントとのやりとりを行う } - fflush(fp); // 溜まっているバッファをフラッシュ(送信) } /* @@ -59,9 +84,9 @@ void handle_client(int client_fd, struct sockaddr_in *client_addr) char ipstr[128]; if (inet_ntop(AF_INET, &client_addr->sin_addr, ipstr, sizeof ipstr) == NULL) { perror("inet_ntop"); - exit(1); + return; } - printf("connected: %s\n", ipstr); + printf("Connection from %s has been established!\n", ipstr); // ファイル記述子を直接扱うとバイト列で読み書きすることしかできない // 行単位で読み書きするために,fdopen()を使ってfgetsやfprintfを使えるようにする @@ -77,17 +102,17 @@ void handle_client(int client_fd, struct sockaddr_in *client_addr) const int MAX_HEADER_LINES = 256; char *headers[MAX_HEADER_LINES]; int header_index = 0; - // クライアントからの要求を読み出すループ + // HTTPヘッダを読むための繰り返し while (1) { if (header_index >= MAX_HEADER_LINES) { - printf("too many HTTP headers!"); - break; + puts("too many HTTP headers!"); + goto bailout; } // 1行読み込み char line[1024]; if (fgets(line, sizeof line, fp) == NULL) { - printf("connection closed!"); - break; + puts("connection closed!"); + goto bailout; } // fgetsは改行を削除しないので,行中の最初の\rか\nを\0に書き換えて切り詰める char *eol = strpbrk(line, "\r\n"); @@ -98,96 +123,77 @@ void handle_client(int client_fd, struct sockaddr_in *client_addr) // headersに代入.strdup()は文字列をmallocした領域にコピーする関数 headers[header_index++] = strdup(line); - // 空行かどうかの判定 + // 空行判定 if (strcmp(line, "") == 0) { - // 最初の行は "GET / HTTP/1.0" のような形式になっている.これを取り出す. - char *request_line = headers[0]; - char *req[] = { NULL, NULL, NULL }; // method, path, versionの3つの要素を入れる配列 - // strtok()で空白で分割する - for (int i = 0; i < 3; i++) { - char *word = strtok(request_line, " "); // " " で区切られた最初のトークンを返す - request_line = NULL; // strtok()の2回目以降の呼び出しでは第1引数をNULLにする約束 - req[i] = word; - if (word == NULL) { - break; - } - } - // 念のため,3つめの要素があるかを確認 - if (req[2] == NULL) { - printf("wrong format\n"); - break; - } - const char *method = req[0]; - const char *path = req[1]; - const char *http_version = req[2]; - printf("method=%s, path=%s, http_version=%s\n", method, path, http_version); - // 要求を処理 - if (strcmp(method, "GET") == 0) { - handle_get(fp, path); - } else { - // GET要求以外の場合,クライアントに 501 Not Implemented エラーを返す - printf("unsupported method: %s\n", method); - fprintf(fp, "HTTP/1.0 501 Not Implemented\r\n"); - } break; } } + + // 最初の行は "GET / HTTP/1.0" のような形式になっている.これを取り出す. + char *req_line = headers[0]; + char *req[] = { NULL, NULL, NULL }; // method, path, versionの3つの要素を入れる配列 + // strtok()で空白で分割する + for (int i = 0; i < 3; i++) { + char *word = strtok(req_line, " "); // " " で区切られた最初のトークンを返す + req_line = NULL; // strtok()の2回目以降の呼び出しでは第1引数をNULLにする約束 + req[i] = word; + if (word == NULL) { + break; + } + } + // 念のため,3つめの要素があるかを確認 + if (req[2] == NULL) { + printf("wrong format: %s\n", headers[0]); + goto bailout; + } + const char *method = req[0]; + const char *path = req[1]; + const char *http_version = req[2]; + printf("method=%s, path=%s, http_version=%s\n", method, path, http_version); + // 要求を処理 + if (strcmp(method, "GET") == 0) { + handle_get(fp, path); + } else { + // GET要求以外の場合,クライアントに 501 Not Implemented エラーを返す + printf("unsupported method: %s\n", method); + fprintf(fp, "HTTP/1.0 501 Not Implemented\r\n"); + } + bailout: fclose(fp); // fcloseは内部でcloseを呼ぶので,クライアントとのコネクションが切断される for (int i = 0; i < header_index; i++) { free(headers[i]); // strdup()でmallocした領域を開放 } } -int main(int argc, char **argv) +/* + * GET要求を処理する + */ +void handle_get(FILE *fp, const char *path) { - // socket()はソケットを生成し,ファイル記述子(ソケットに割り当てられた整数値)を返す - // - AF_INETはIPv4 - // - SOCK_STREAMは信頼性の高い全2重バイトストリームの指定 - // - IPPROTO_TCPはTCPの指定 - int server_fd; - if ((server_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { - // socket()はエラーのとき負の値を返す - perror("socket"); - exit(1); - } - - // プログラム再実行時に,サーバのポートをすぐに再利用できるようにする - int yes = 1; - setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)); - - // bind()で自ノード側のアドレスを設定 - struct sockaddr_in server_addr; // sockaddr_inはIPアドレスとポート番号を保持する構造体 - memset(&server_addr, 0, sizeof(server_addr)); // 構造体をいったんゼロクリア - server_addr.sin_family = AF_INET; // IPv4アドレス - // サーバ側のIPアドレスはANY(指定しない).htonlは4バイトをネットワークバイトオーダに変換 - server_addr.sin_addr.s_addr = htonl(INADDR_ANY); - server_addr.sin_port = htons(8000); // サーバ側のポート番号の指定.htonsは2バイトをネットワークバイトオーダに変換 - if (bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) { - perror("bind"); - exit(1); - } - - // ソケットをサーバ用に設定.5はコネクション要求を貯めておくキューの長さ - // - コネクション要求はaccept()で受け取る必要があるが,それをどのくらい溜めておけるか - if (listen(server_fd, 5) < 0) { - perror("listen"); - exit(1); - } - - printf("open http://localhost:8000/ with your browser!\n"); - - // クライアントからのコネクションを処理するためのループ - while (1) { - // accept()はクライアントとのコネクションが確立するのを待ってブロックする(待つ). - // 確立すると当該コネクションで通信するためのファイル記述子を返す. - // - client_addrはクライアントのIPアドレスとポート番号を受け取るための構造体 - int client_fd; - struct sockaddr_in client_addr; - socklen_t addr_length = sizeof client_addr; - if ((client_fd = accept(server_fd, (struct sockaddr*)&client_addr, &addr_length)) < 0) { - perror("accept"); - exit(1); - } - handle_client(client_fd, &client_addr); // クライアントとのやりとりを行う + if (strcmp(path, "/") == 0) { // http://localhost:8000/ へのアクセスの場合 + // C言語では連続した文字列は自動的に連結される + fprintf(fp, + "HTTP/1.0 200 OK\r\n" // ステータス行 + "Content-Type: text/html\r\n" // 本体がHTMLであることを伝える + "\r\n" // ヘッダの終了を表す空行 + "\r\n" // 以下はHTML (HTML5) + "\r\n" + "Sample\r\n" + "This server is implemented with C!\r\n" + "\r\n" + ); + } else { // 他のパス (http://localhost:8000/abc.html) などへのアクセスの場合,404エラー + fprintf(fp, + "HTTP/1.0 404 Not Found\r\n" // 404はNot Foundを表す + "Content-Type: text/html\r\n" + "\r\n" + "\r\n" + "\r\n" + "404 Not Found\r\n" + "%s is not found\r\n" + "\r\n", + path + ); } + fflush(fp); // 溜まっているバッファをフラッシュ(送信) } diff --git a/Go/webserver.go b/Go/webserver.go index c588942..947067f 100644 --- a/Go/webserver.go +++ b/Go/webserver.go @@ -14,37 +14,33 @@ import ( "strings" ) -// handleGet - GET要求を処理する -func handleGet(conn net.Conn, path string) { - var s string - if path == "/" { - s = `HTTP/1.0 200 OK -Content-Type: text/html - - - -Sample -This server is implemented with Go! -` - } else { - s = fmt.Sprintf(`HTTP/1.0 404 Not Found -Content-Type: text/html +func main() { + // 8000番ポートで listen する + listener, err := net.Listen("tcp", ":8000") + if err != nil { + panic("ListenTCP failed") + } + fmt.Println("open http://localhost:8000/ with your browser!") - - - 404 Not Found - %v is not found -`, path) + // クライアントからのコネクションを待ち受けるループ + for { + // TCPコネクションを確立されるまでブロックする + conn, err := listener.Accept() + if err != nil { + panic("AcceptTCP failed") + } + // 確立したら,goroutineを作って処理させる (goroutineは並行に動作する) + go handleClient(conn) } - replaced := strings.ReplaceAll(s, "\n", "\r\n") - conn.Write([]byte(replaced)) } // handleClient - クライアントとHTTP/1.0で通信する func handleClient(conn net.Conn) { - defer conn.Close() // この関数終了時にクローズするように要求 + defer conn.Close() // この関数終了時にクローズするように要求 + fmt.Printf("Connection from %v has been established!\n", conn.RemoteAddr()) headers := []string{} // ヘッダを覚えておくためのstringのスライス scanner := bufio.NewScanner(conn) // コネクションからの読み取りを行単位に行うためのもの + // HTTPヘッダを読むための繰り返し for scanner.Scan() { line := scanner.Text() // 1行取り出し if line == "" { // 空行が来たらbreak @@ -52,13 +48,12 @@ func handleClient(conn net.Conn) { } fmt.Printf("Received: %v\n", line) headers = append(headers, line) // headersに読み込んだ行を追加 - } if len(headers) == 0 { fmt.Println("no header!") return } - req := strings.Split(headers[0], " ") // リクエスト行を空白で分解 + req := strings.Split(headers[0], " ") // 先頭行を " " で分割する if len(req) != 3 { fmt.Printf("wrong request: %v\n", req) return @@ -77,23 +72,29 @@ func handleClient(conn net.Conn) { } } -func main() { - // 8000番ポートで listen する - listener, err := net.Listen("tcp", ":8000") - if err != nil { - panic("ListenTCP failed") - } - fmt.Println("open http://localhost:8000/ with your browser!") +// handleGet - GET要求を処理する +func handleGet(conn net.Conn, path string) { + var s string + if path == "/" { + s = `HTTP/1.0 200 OK +Content-Type: text/html - // クライアントからのコネクションを待ち受けるループ - for { - // TCPコネクションを確立されるまでブロックする - conn, err := listener.Accept() - if err != nil { - panic("AcceptTCP failed") - } - fmt.Printf("Connection from %v has been established!\n", conn.RemoteAddr()) - // 確立したら,goroutineを作って処理させる (goroutineは並行に動作する) - go handleClient(conn) + + +Sample +This server is implemented with Go! +` + } else { + s = fmt.Sprintf(`HTTP/1.0 404 Not Found +Content-Type: text/html + + + + 404 Not Found + %v is not found +`, path) } + // 改行を LF (\n) から CR+LF (\r\n) に置き換える + s = strings.ReplaceAll(s, "\n", "\r\n") + conn.Write([]byte(s)) } diff --git a/JavaScript/webserver.js b/JavaScript/webserver.js index ff0c475..5b941d5 100644 --- a/JavaScript/webserver.js +++ b/JavaScript/webserver.js @@ -11,42 +11,27 @@ // このプログラムでは低レベルの処理を見せるために"net"モジュールを使っている const net = require("net"); -/** - * GET要求を処理する - */ -function handleGet(conn, path) { - if (path === "/") { - // backquote(``)では文字列中の改行は\nになる - conn.write( - `HTTP/1.0 200 OK\r -Content-Type: text/html\r -\r - - -Sample -This server is implemented with JavaScript! - -`); - } else { - conn.write( - `HTTP/1.0 404 Not Found\r -Content-Type: text/html\r -\r - - -404 Not Found -${path} is not found - -`); - } +function main() { + const server = net.createServer(); // サーバの作成 + // JavaScriptでは基本的にイベントベースで処理を記述する + // - クライアントからのコネクションが確立すると"connection"というイベントが発生する. + // - そのときに conn => { handleClient(conn); } というアロー関数が実行されるように設定する. + // connは確立したコネクション(net.Socketのオブジェクト) + // https://nodejs.org/api/net.html#net_event_connection + server.on("connection", conn => { + handleClient(conn); + }); + server.listen(8000); // コネクションの待ち受けを開始する + console.log("open http://localhost:8000/ with your browser!"); + // ここでmain関数は終わるが,裏でコネクションの確立を待っている } /** * クライアントとHTTP/1.0で通信する. * @param {net.Socket} conn */ -function handleClient(conn) { - console.log(`connection established: remoteIP=${conn.remoteAddress}, remotePort=${conn.remotePort}`); + function handleClient(conn) { + console.log(`Connection from ${conn.remoteAddress}:${conn.remotePort} has been established!`) let buf = ""; // 読み込んだHTTPヘッダを覚えておくバッファ // クライアントからデータが届いた場合の処理を設定する // (設定するだけなので,この処理はデータが届く前に終了する) @@ -60,7 +45,7 @@ function handleClient(conn) { const headers = head.split("\r\n"); // 行単位に分割する // 先頭行を " " で分割する.分割代入構文を使って配列の要素を3つの変数に代入 let [ method, path, http_version ] = headers[0].split(" "); - console.log(`method = ${method}, path=${path}, http_version=${http_version}`); + console.log(`method=${method}, path=${path}, http_version=${http_version}`); switch (method) { case "GET": handleGet(conn, path); @@ -80,19 +65,36 @@ function handleClient(conn) { }); } -function main() { - const server = net.createServer(); // サーバの作成 - // JavaScriptでは基本的にイベントベースで処理を記述する - // - クライアントからのコネクションが確立すると"connection"というイベントが発生する. - // - そのときに conn => { handleClient(conn); } というアロー関数が実行されるように設定する. - // connは確立したコネクション(net.Socketのオブジェクト) - // https://nodejs.org/api/net.html#net_event_connection - server.on("connection", conn => { - handleClient(conn); - }); - server.listen(8000); // コネクションの待ち受けを開始する - console.log("open http://localhost:8000/ with your browser!"); - // ここでmain関数は終わるが,裏でコネクションの確立を待っている +/** + * GET要求を処理する + */ + function handleGet(conn, path) { + var s + if (path === "/") { + // backquote(``)では文字列中の改行は\nになる + s = `HTTP/1.0 200 OK +Content-Type: text/html + + + +Sample +This server is implemented with JavaScript! + +` + } else { + s = `HTTP/1.0 404 Not Found +Content-Type: text/html + + + +404 Not Found +${path} is not found + +` + } + // 改行を LF (\n) から CR+LF (\r\n) に置き換える + replaced = s.replace("\n", "\r\n") + conn.write(replaced) } main(); diff --git a/Python/webserver.py b/Python/webserver.py index 6ba2d3c..998b9af 100644 --- a/Python/webserver.py +++ b/Python/webserver.py @@ -10,84 +10,83 @@ # import socket +import threading +import typing +def main(): + server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_sock.bind(('', 8000)) + server_sock.listen(5) + print("open http://localhost:8000/ with your browser!") -# GET要求を処理する -def handle_get(client_sock, path): + while True: + client_sock, address = server_sock.accept() + print(f"Connection from {address} has been established!") + threading.Thread(target=handle_client, args=[client_sock]).start() + # handle_client(client_sock) + +def handle_client(client_sock: socket.socket): + """ クライアントとHTTP/1.0で通信する """ + # makefile()によってネットワーク通信をファイルのように扱える + with client_sock, client_sock.makefile(mode="rw") as f: + headers: list[str] = [] # HTTPヘッダを覚えておくリスト + # HTTPヘッダを読むための繰り返し + while True: + line = f.readline() # 1行読む + if line == "": # コネクションが切断された場合 + print("connection closed") + return + line = line.rstrip() # 改行文字を削除 + print("Received: " + line) + headers.append(line) + if line == "": # 空行判定 + break + + if len(headers) == 0: + print("no header!") + return + + req = headers[0].split(" ") # 先頭行を " " で分割する + if len(req) != 3: + print(f"wrong format: {req}") + return + + method, path, http_version = req # 先頭行の要素 + print(f"method={method}, path={path}, http_version={http_version}") + if method == "GET": + handle_get(f, path) + else: + print(f"unsupported method {method}") + f.write("HTTP/1.0 501 Not Implemented\r\n") + +def handle_get(f: typing.TextIO, path: str): + """ GET要求を処理する """ if path == "/": - client_sock.send("""\ -HTTP/1.0 200 OK\r -Content-Type: text/html\r -\r + s = """\ +HTTP/1.0 200 OK +Content-Type: text/html + Sample This server is implemented with Python! -""".encode()) - # encode()で文字列からUTF-8のバイト列にエンコードする(ここではASCIIと同じ) +""" else: - client_sock.send(f"""\ -HTTP/1.0 404 Not Found\r -Content-Type: text/html\r -\r + s = f"""\ +HTTP/1.0 404 Not Found +Content-Type: text/html + 404 Not Found {path} is not found -""".encode()) - - -# クライアントとHTTP/1.0で通信する -def handle_client_socket(client_sock): - buf = "" # HTTPヘッダを覚えておく文字列 - while True: - # ソケットから最大1024バイト読み,bytesオブジェクトで返す - chunk = client_sock.recv(1024) - if chunk == b"": - # closed! - print("connection closed!") - return - # asciiに変換し,bufに追加する - try: - data = chunk.decode("ascii") - buf += data - print("Received: " + data) - except UnicodeDecodeError: - print("non-ascii char") - return - - i = buf.find("\r\n\r\n") # 空行を探す - if i >= 0: - head = buf[0: i] # 先頭から空行までを取り出す - headers = head.split("\r\n") # 行単位に分割する - req = headers[0].split(" ") # 先頭行を " " で分割する - if len(req) != 3: - print("wrong format") - return - method, path, http_version = req # 配列を分割代入 - print(f"method={method}, path={path}, http_version={http_version}") - if method == "GET": - handle_get(client_sock, path) - else: - print(f"unsupported method {method}") - client_sock.send("HTTP/1.0 501 Not Implemented\r\n".encode()) - break - - -def main(): - server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - server_sock.bind(('', 8000)) - server_sock.listen(5) - print("open http://localhost:8000/ with your browser!") - - while True: - client_sock, address = server_sock.accept() - with client_sock: - print(f"Connection from {address} has been established!") - handle_client_socket(client_sock) - +""" + # 改行を LF (\n) から CR+LF (\r\n) に置き換える + s = s.replace("\n", "\r\n") + f.write(s) -main() +if __name__ == "__main__": + main() diff --git a/Ruby/webserver.rb b/Ruby/webserver.rb index 40eb851..6510ffb 100644 --- a/Ruby/webserver.rb +++ b/Ruby/webserver.rb @@ -11,14 +11,66 @@ require "socket" +def main() + puts("open http://localhost:8000/ with your browser!") + server = TCPServer.new('0.0.0.0', 8000) # サーバソケットの生成 + loop do + client_socket = server.accept # コネクション確立を待つ + # コネクションが確立したら,新しいスレッドを開始する + Thread.start { + handle_client(client_socket) + client_socket.close + } + end +end + +# クライアントとHTTP/1.0で通信する +def handle_client(sock) + puts("connection established: #{sock.peeraddr}") + headers = [] # HTTPヘッダを覚えておく配列 + # HTTPヘッダを読むための繰り返し + loop do + line = sock.gets(chomp: true) # 1行読む.chomp:trueは改行を削除する指定 + if line == nil + puts("connection closed!") # コネクションがクローズされた場合 + return + end + puts("Received: #{line}") + headers.push(line) + if line == "" # 空行判定 + break + end + end + + if headers.length == 0 + puts("no header!") + return + end + req = headers[0].split(" ") # 先頭行を " " で分割する + if req.length != 3 + puts("wrong request: #{req}") + return + end + method, path, http_version = req + puts("method=#{method}, path=#{path}, http_version=#{http_version}") + # 要求を処理 + case method + when "GET" + handle_get(sock, path) + else + puts("unsupported method: #{method}") + sock.write("HTTP/1.0 501 Not Implemented\r\n") + end +end + # GET要求を処理する def handle_get(sock, path) if path == "/" # <<はヒアドキュメント.EOSが現れるまでを1つの文字列にする. - sock.write < Sample @@ -26,10 +78,10 @@ def handle_get(sock, path) EOS else - sock.write < 404 Not Found @@ -37,45 +89,8 @@ def handle_get(sock, path) EOS end + s.gsub!("\n", "\r\n") + sock.write(s) end -# クライアントとHTTP/1.0で通信する -def handle_client(sock) - puts("connection established: #{sock.peeraddr}") - headers = [] # HTTPヘッダを覚えておく配列 - loop do - chunk = sock.gets(chomp: true) # 1行読む.chomp:trueは改行を削除する指定 - if chunk == nil - puts("connection closed!") # コネクションがクローズされた場合 - return - end - puts("Received: #{chunk}") - headers.push(chunk) - if chunk == "" # 改行かどうかの判定 - req = headers[0].split(" ") # 先頭行を " " で分割する - if req.length != 3 - puts("wrong format") - return - end - method, path, http_version = req - puts("method=#{method}, path=#{path}, http_version=#{http_version}") - # 要求を処理 - case method - when "GET" - handle_get(sock, path) - else - puts("unsupported method: #{method}") - sock.write("HTTP/1.0 501 Not Implemented\r\n") - end - break - end - end -end - -puts("open http://localhost:8000/ with your browser!") -server = TCPServer.new('0.0.0.0', 8000) # サーバソケットの生成 -loop do - client_socket = server.accept # コネクション確立を待つ - handle_client(client_socket) - client_socket.close -end +main()