前半でHTTPによるWebサーバの動作を理解するための基本的な事前知識を整理して理解します。そして、後半にそれらの知識を元にC++による簡易なHTTPサーバをフルスクラッチで実装して理解します。
今回はリクエストされたテキスト形式のHTMLを返すだけのサーバを実装します。
ブラウザからhttp://127.0.0.1:5000
とアクセスすると用意されたテキストのHTMLファイルをパースしてHello, world!
とだけ出力する以下のような画面が表示されるサーバとなります。
事前知識
HTTP
HTTPにはこれまで以下のようなバージョンが存在しています。
- HTTP 0.9
- HTTP 1.0
- HTTP 1.1
- HTTP 2
最近ではHTTP2の普及が進んていっていますが、現在はHTTP 1.1が広く使われています。
仕組み
Webでは、クライアント/サーバのアーキテクチャを取っており、クライアントがリクエストを送り、サーバがレスポンスを返すという仕組みとなっています。
例えば、Chromeからgoogle.comへアクセスするとき、ブラウザは以下のようなリクエストをサーバに対して送ります。
GET / HTTP/1.1
accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-encoding: gzip, deflate, sdch, br
accept-language: ja-JP,ja;q=0.8,en-US;q=0.6,en;q=0.4
cookie: [1057 bytes were stripped]
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.95 Safari/537.36
x-client-data: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
サーバから以下のようなレスポンスが返される。レスポンスを受け取ったクライアントはパースした結果をレンダリングして結果を出力します。
HTTP/1.1 200
status: 200
date: Sat, 18 Feb 2017 17:48:01 GMT
expires: -1
cache-control: private, max-age=0
content-type: text/html; charset=UTF-8
content-encoding: gzip
server: gws
x-xss-protection: 1; mode=block
x-frame-options: SAMEORIGIN
alt-svc: quic=":443"; ma=2592000; v="35,34"
流れ
クライアント
- リクエストメッセージの作成・送信
- レスポンスの待機
- レスポンスメッセージの受信・解析・処理
サーバ
- リクエストの待機
- リクエストメッセージの待機・解析
- 解析結果を元に各アプリケーションによる処理と結果の取得
- レスポンスメッセージの構築・送信
HTTPメッセージ
HTTPでは以下のようなリクエストとレスポンスでのメッセージによりステートレスでシンプルな通信を行っています。
メッセージはRFC7230中では以下のように定義されています。
HTTP-message = start-line
*( header-field CRLF )
CRLF
[ message-body ]
start-line = request-line / status-line
header-field = field-name ":" OWS field-value OWS
message-body = *OCTET
これはリクエスト、レスポンスで以下のように簡単に表されます。
リクエストメッセージ
- リクエストライン
- ヘッダ
- ボディ
レスポンスメッセージ
- ステータスライン
- ヘッダ
- ボディ
リクエストライン/ステータスライン
先程のChromeからアクセスした際に送られたリクエスト中の1行目の
GET / HTTP/1.1
がステータスラインとなります。
一方で、サーバから送られてきたレスポンス中の1行目の
HTTP/1.1 200
がステータスラインとなります。
####リクエストライン
request-line = method SP request-target SP HTTP-version CRLF
上記のように定義される。methodはHTTPメソッド。request-targetはリクエストするリソース。HTTP-versionはHTTPのバージョンを指定する。SPはスペースで、CRLFは改行をする。
ステータスライン
status-line = HTTP-version SP status-code SP reason-phrase CRLF
上記のように定義される。HTTP-versionはHTTPのバージョン。status-codeはステータスコード。reason-phreaseはステータスコードに対応するディスクリプションに対応する。
HTTPメソッド
HTTP 1.1には以下のメソッドが定義されています。
- OPTIONS
- GET
- HEAD
- POST
- PUT
- DELETE
- TRACE
- CONNECT
- PATCH
ステータスコード
分類
- 1xx
- 処理中
- 2xx
- 成功
- 3xx
- リダイレクト
- 4xx
- クライアントエラー
- 5xx
- サーバエラー
以下のよく用いられるステータスコードをいくつか上げてみます。
- 200 OK
- 302 Found
- 400 Bad Request
- 401 Unauthorized
- 403 Forbidden
- 404 Not Found
- 500 Internal Server Error
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
残りのステータスコードのAppendixを参照。
HTTPヘッダ
HTTPヘッダにはボディに対するメタデータを定義します。例えばリクエストでは以下のような複数のメタデータからHTTPヘッダを構成します。
- Host
- User-Agent
- Accept
- Accept-Language
- Accept-Encoding
- Accept-Charset
- Keep-Alive
- Connection
HTTPヘッダは、リクエストは、リクエストヘッダーフィールド、汎用ヘッダーフィールド、エンティティヘッダーフィールドから、レスポンスはレスポンスヘッダーフィールド、汎用ヘッダーフィールド、エンティティヘッダーフィールドから成ります。
残りのヘッダはAppendixを参照。
Socket通信
サーバ側で使用する主なSocket通信の関数と流れは以下の通りとなります。
###1. socket()
ソケット生成
###2. bind()
ソケット登録
###3.listen()
ソケット接続準備
###4.accept()
ソケット接続待機
###5.recv()/send()
パケットの送受信
###6.close()
ソケット切断
###実装
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(listenfd == -1) {
std::cout << "socket() failed." << std::endl;
exit(1);
}
int optval = 1;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
std::cout << "setsockopt() failed." << std::endl;
close(listenfd);
return -1;
}
int port = 5000;
struct sockaddr_in serv_addr;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(port);
if(bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
std::cout << "bind() failed.(" << errno << ")" << std::endl;
close(listenfd);
return -1;
}
if(listen(listenfd, SOMAXCONN) == -1) {
std::cout << "listen() failed." << std::endl;
close(this->listenfd);
return -1;
}
ソケット接続待機からはループ内で処理する。
int accfd = accept(sock->get_listenfd(), (struct sockaddr*)NULL, NULL);
if(accfd == -1) {
std::cout << "No acceptance" << std::endl;
}
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
std::string recv_str = "";
ssize_t read_size = 0;
read_size = recv(accfd, buf, sizeof(buf)-1, 0);
if(read_size == -1) {
std::cout << "ERROR: " << errno << std::endl;
close(accfd);
accfd = -1;
break;
}
if(read_size > 0) {
recv_str.append(buf);
}
if(write(accfd, server_response.c_str(), server_response.length()) == -1){
std::cout << "write() failed." << std::endl;
}
HTTPサーバをC++によるフルスクラッチで実装
上記の知識を元にC++で簡易なHTTPサーバを実装して理解を深めてみましょう。
その後、同時アクセス数を増やしても捌ける実装も行います。
(実装は最低限の動作確認のためのもので設計など細かい点は容赦ください^^;)
実装の流れ
1.初期化
2.リクエストからヘッダー部分を読み込む
3.リクエストをパースしてリソースのパスを取得する
4.取得したパスのファイルの内容を取得する
5.HTTPレスポンスのメッセージを作成する
6.ソケットにレスポンスを書き込む
7.使い終わったファイルのクローズ
1.初期化
始めに必要な変数の宣言とSocket通信の準備を行います。
std::string executive_file = HTML_FILE;
Socket *sock = new Socket(HTTP1_PORT);
sock->set_socket();
int body_length = 0;
int is_file_exist;
int accfd = -1;
以降、Socket通信のCloseまではwhile(1){}
のループ内での処理となります。
while(1) {
accfd = accept(sock->get_listenfd(), (struct sockaddr*)NULL, NULL);
//初期化
if(accfd == -1) {
continue;
}
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
std::string recv_str = "";
ssize_t read_size = 0;
}
定数とSocket通信の準備の際に利用したクラスの定義は以下の通りとなります。
#ifndef _CONFHET_H_
#define _CONFHET_H_
const int BUF_SIZE = 1024;
const int HTTP1_PORT = 5000;
const int HTTP_VERSION = 1;
const std::string HTML_FILE = "www";
#endif
#ifndef _SOCHET_H_
#define _SOCHET_H_
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include "configure.h"
class Socket {
int listenfd;
int port;
struct sockaddr_in serv_addr;
public:
explicit Socket(int port_): port{port_} {}
void set_listenfd();
void set_sockaddr_in();
int set_socket();
int get_listenfd() const { return this->listenfd; }
};
#endif
#include "socket.h"
void Socket::set_listenfd() {
this->listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(this->listenfd == -1) {
std::cout << "socket() failed." << std::endl;
exit(1);
}
}
void Socket::set_sockaddr_in() {
memset(&this->serv_addr, 0, sizeof(this->serv_addr));
this->serv_addr.sin_family = AF_INET;
this->serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
this->serv_addr.sin_port = htons(this->port);
}
int Socket::set_socket() {
Socket::set_listenfd();
int optval = 1;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
std::cout << "setsockopt() failed." << std::endl;
close(listenfd);
return -1;
}
Socket::set_sockaddr_in();
if(bind(this->listenfd, (struct sockaddr*)&this->serv_addr, sizeof(this->serv_addr)) == -1) {
std::cout << "bind() failed.(" << errno << ")" << std::endl;
close(this->listenfd);
return -1;
}
if(listen(this->listenfd, SOMAXCONN) == -1) {
std::cout << "listen() failed." << std::endl;
close(this->listenfd);
return -1;
}
return 0;
}
2.リクエストからヘッダー部分を読み込む
ループ内で先程の処理の続きにヘッダー部分を最後まで読み込む処理を記述します。
ヘッダー部分の最後はCRLFCRLFで終わるのでASCIIコードでこれに相当する文字列まで読み込む。
do {
read_size = recv(accfd, buf, sizeof(buf)-1, 0);
if(read_size == -1) {
std::cout << "read() failed." << std::endl;
std::cout << "ERROR: " << errno << std::endl;
close(accfd);
accfd = -1;
break;
}
if(read_size > 0) {
recv_str.append(buf);
}
if( (recv_str[recv_str.length()-4] == 0x0d) &&
(recv_str[recv_str.length()-3] == 0x0a) &&
(recv_str[recv_str.length()-2] == 0x0d) &&
(recv_str[recv_str.length()-1] == 0x0a)
){
break;
}
} while (read_size > 0);
3.リクエストをパースしてリソースのパスを取得する
std::string path = "", path_string = "";
std::string exe = executive_file;
std::size_t pos = exe.rfind('/');
if (pos != std::string::npos) {
exe = exe.substr(pos + 1);
}
path_string.clear();
path = HTTP1_Parser::get_requestline_path(buf);
path_string = HTTP1_Parser::argv_path_analyzer(path, executive_file.c_str(), exe.c_str());
std::cout << "path_string : " << path_string << std::endl;
上記で使用したヘッダーのパーサクラスの定義は以下の通りとなります。
#ifndef _HTTP1_PARSER_H_
#define _HTTP1_PARSER_H_
#include <string>
#include <cstring>
#include <unistd.h>
#include "configure.h"
class HTTP1_Parser {
public:
static char *get_filename(const char *excutive_file);
static std::string argv_path_analyzer(std::string request_path, const char *path, const char *executive_file);
static std::string get_requestline_path(char _pbuf[BUF_SIZE]);
};
#endif
#include "http1parser.h"
#include <iostream>
char *HTTP1_Parser::get_filename(const char *excutive_file)
{
int length = strlen(excutive_file);
static char buf[1024]={};
readlink( "/proc/self/exe", buf, sizeof(buf)-1 );
int buf_length = strlen(buf);
for(int i = 0; i <= length; i++){
buf[buf_length-i-1] = 0;
}
return buf;
}
std::string HTTP1_Parser::argv_path_analyzer(std::string request_path, const char *path, const char *executive_file) {
std::string path_string;
if(strcmp(path,"") == 0){
if (request_path == std::string("/")) {
path_string = std::string(HTTP1_Parser::get_filename(executive_file)) + std::string("/index.html");
}
else {
int judge = request_path.rfind(".",20);
if (judge >= 0 ) {
path_string = std::string(HTTP1_Parser::get_filename(executive_file)) + request_path;
} else {
path_string = std::string(HTTP1_Parser::get_filename(executive_file)) + request_path + std::string("/index.html");
}
}
}
else {
int judge = request_path.rfind(".",20);
if (judge >= 0 ) {
path_string = std::string(path) + request_path;
} else {
path_string = std::string(path) + request_path + std::string("/index.html");
}
}
return path_string;
}
std::string HTTP1_Parser::get_requestline_path(char _pbuf[BUF_SIZE]) {
int space_num = 0;
char path_buffer[32];
int current_point = 0;
std::string path_string;
for (int i = 0; i < BUF_SIZE; i++) {
if(space_num == 2){
path_buffer[current_point] = 0;
path_string = std::string(path_buffer);
return path_string;
}
if(space_num == 1){
if(_pbuf[i] != ' '){
path_buffer[current_point++] = _pbuf[i];
}
}
if(_pbuf[i] == ' '){
++space_num;
}
}
return "";
}
4.取得したパスのファイルの内容を取得する
std::ifstream output_file(path_string.c_str());
char line[256];
is_file_exist = output_file.fail();
body_length = 0;
std::vector<std::string> message_body;
while (output_file.getline(line, 256-1)) {
body_length += strlen(line);
message_body.push_back(std::string(line));
}
5.HTTPレスポンスのメッセージを作成する
std::string server_response;
std::vector<std::string> header = HTTP1_Response::make_header(3, body_length, is_file_exist, path);
server_response = HTTP1_Response::make_response(header, message_body);
std::cout << server_response << std::endl;
#ifndef _HTTP1_RESHET_H_
#define _HTTP1_RESHET_H_
#include <string>
#include <cstring>
#include <vector>
#include <fstream>
#include "configure.h"
class HTTP1_Response {
public:
static std::string make_response(std::vector<std::string> &header, std::vector<std::string> &message_body);
static std::vector<std::string>& make_header(int version, int body_length, int is_file_exist, std::string path);
static void make_body (std::vector<std::string> &body_content, int &body_content_length, std::ifstream &output_file);
};
#endif
#include "http1response.h"
#include "http1header.h"
#include <iostream>
#include <iterator>
std::string HTTP1_Response::make_response(std::vector<std::string> &header, std::vector<std::string> &message_body) {
std::string server_response;
int header_size = header.size();
int body_size = message_body.size();
for (int i = 0; i < header_size; i++){
server_response.append(header[i].c_str());
}
for (int i = 0; i < body_size; i++){
server_response.append(message_body[i].c_str());
}
return server_response;
}
std::vector<std::string>& HTTP1_Response::make_header(int version, int body_length, int is_file_exist, std::string path) {
if (HTTP_VERSION == 1 && path != "") {
return HTTP1_Header::make_response302(path);
}
else if (HTTP_VERSION == 2) {
return HTTP1_Header::make_responseUpgrade();
}
else if (is_file_exist == 1) {
return HTTP1_Header::make_response404();
}
else {
return HTTP1_Header::make_response200(body_length);
}
static std::vector<std::string> response_header;
return response_header;
}
void HTTP1_Response::make_body(std::vector<std::string> &body_content, int &body_content_length, std::ifstream &output_file){
if (output_file.fail() != 0) {
std::cout << "File was not found." << std::endl;
return;
}
char read_file_buf[BUF_SIZE];
output_file.read(read_file_buf, BUF_SIZE);
body_content.push_back(read_file_buf);
body_content_length = output_file.gcount();
}
6.ソケットディスクリプタにレスポンス内容を書き込む
while(1){}
のループ内の処理での最後にソケットにレスポンス内容を渡します。
if(send(accfd, server_response.c_str(), server_response.length(), 0) == -1){
std::cout << "write() failed." << std::endl;
}
上記の処理が終わったらまたループの最初に戻り、リクエストを待機する。
7.使い終わったファイルのクローズ
while(1){}
のループから出たあとに最後にSocketのファイルディスクリプタをCloseしておきます。
close(sock->get_listenfd());
以上で終了です。同ディレクトリにwww
というディレクトリを作成し、index.html
というファイル名で以下の内容で保存します。
./server
を実行して、ブラウザからhttp://127.0.0.1:5000
にアクセスして最初に載せた画像のような画面になればOK。
<html>
<body>
Hello, world!
</body>
</html>
これまでのファイルの全体は次章に載せます。
全体
先程までのプログラム全体は以下の通りとなります。
Makefileがある同階層のディレクトリでmake
を実行して./server
を実行するとサーバが立ち上がります。あとは、www/
以下にindex.html
を置いて、ブラウザからhttp://127.0.0.1:5000
にアクセスすると、HTMLの内容が表示される。
CC = g++
HTTP1 = server
HTTP1_SOURCE = server.cpp socket.cpp http1header.cpp http1parser.cpp http1response.cpp
.PHONY: all clean
all: $(HTTP1)
$(HTTP1): $(HTTP1_SOURCE)
g++ $(HTTP1_SOURCE) -o $(HTTP1) -g -std=c++11 -O0 -Wall
clean:
rm -rf *.o $(HTTP1)
#ifndef _CONFHET_H_
#define _CONFHET_H_
const int BUF_SIZE = 1024;
const int HTTP1_PORT = 5000;
const int HTTP_VERSION = 1;
const std::string HTML_FILE = "www";
#endif
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <errno.h>
#include <sstream>
#include <string>
#include <iomanip>
#include <fstream>
#include <vector>
#include <future>
#include <thread>
#include "socket.h"
#include "http1response.h"
#include "http1header.h"
#include "http1parser.h"
int http1()
{
std::string executive_file = HTTP1_FILE;
Socket *sock = new Socket(HTTP1_PORT);
sock->set_socket();
int body_length = 0;
int is_file_exist;
int accfd = -1;
while(1) {
accfd = accept(sock->get_listenfd(), (struct sockaddr*)NULL, NULL);
//初期化
if(accfd == -1) {
continue;
}
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
std::string recv_str = "";
ssize_t read_size = 0;
// \r\n\r\nが来るまでメッセージ受信
do {
read_size = recv(accfd, buf, sizeof(buf)-1, 0);
if(read_size == -1) {
std::cout << "read() failed." << std::endl;
std::cout << "ERROR: " << errno << std::endl;
close(accfd);
accfd = -1;
break;
}
if(read_size > 0) {
recv_str.append(buf);
}
if( (recv_str[recv_str.length()-4] == 0x0d) &&
(recv_str[recv_str.length()-3] == 0x0a) &&
(recv_str[recv_str.length()-2] == 0x0d) &&
(recv_str[recv_str.length()-1] == 0x0a)
){
break;
}
} while (read_size > 0);
//リクエストされたパスを取得する
std::string path = "", path_string = "";
std::string exe = executive_file;
std::size_t pos = exe.rfind('/');
if (pos != std::string::npos) {
exe = exe.substr(pos + 1);
}
path_string.clear();
path = HTTP1_Parser::get_requestline_path(buf);
path_string = HTTP1_Parser::argv_path_analyzer(path, executive_file.c_str(), exe.c_str());
std::cout << "path_string : " << path_string << std::endl;
//取得したパスのファイルを開いて内容を取得する
std::ifstream output_file(path_string.c_str());
char line[256];
is_file_exist = output_file.fail();
body_length = 0;
std::vector<std::string> message_body;
while (output_file.getline(line, 256-1)) {
body_length += strlen(line);
message_body.push_back(std::string(line));
}
//HTTPレスポンスを作成する
std::string server_response;
std::vector<std::string> header = HTTP1_Response::make_header(3, body_length, is_file_exist, path);
server_response = HTTP1_Response::make_response(header, message_body);
std::cout << server_response << std::endl;
//ソケットディスクリプタにレスポンス内容を書き込む
if(send(accfd, server_response.c_str(), server_response.length(), 0) == -1){
std::cout << "write() failed." << std::endl;
}
//使い終わったファイルのクローズ
output_file.close();
close(accfd);
accfd = -1;
}
close(sock->get_listenfd());
return 0;
}
int main(int argc, char *argv[])
{
http1();
return 0;
}
#ifndef _SOCHET_H_
#define _SOCHET_H_
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include "configure.h"
class Socket {
int listenfd;
int port;
struct sockaddr_in serv_addr;
public:
explicit Socket(int port_): port{port_} {}
void set_listenfd();
void set_sockaddr_in();
int set_socket();
int get_listenfd() const { return this->listenfd; }
};
#endif
#include "socket.h"
void Socket::set_listenfd() {
this->listenfd = socket(AF_INET, SOCK_STREAM, 0);
if(this->listenfd == -1) {
std::cout << "socket() failed." << std::endl;
exit(1);
}
}
void Socket::set_sockaddr_in() {
memset(&this->serv_addr, 0, sizeof(this->serv_addr));
this->serv_addr.sin_family = AF_INET;
this->serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
this->serv_addr.sin_port = htons(this->port);
}
int Socket::set_socket() {
Socket::set_listenfd();
int optval = 1;
if(setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval)) == -1) {
std::cout << "setsockopt() failed." << std::endl;
close(listenfd);
return -1;
}
Socket::set_sockaddr_in();
if(bind(this->listenfd, (struct sockaddr*)&this->serv_addr, sizeof(this->serv_addr)) == -1) {
std::cout << "bind() failed.(" << errno << ")" << std::endl;
close(this->listenfd);
return -1;
}
if(listen(this->listenfd, SOMAXCONN) == -1) {
std::cout << "listen() failed." << std::endl;
close(this->listenfd);
return -1;
}
return 0;
}
#ifndef _HTTP1_PARSER_H_
#define _HTTP1_PARSER_H_
#include <string>
#include <cstring>
#include <unistd.h>
#include "configure.h"
class HTTP1_Parser {
public:
static char *get_filename(const char *excutive_file);
static std::string argv_path_analyzer(std::string request_path, const char *path, const char *executive_file);
static std::string get_requestline_path(char _pbuf[BUF_SIZE]);
};
#endif
#include "http1parser.h"
#include <iostream>
char *HTTP1_Parser::get_filename(const char *excutive_file)
{
int length = strlen(excutive_file);
static char buf[1024]={};
readlink( "/proc/self/exe", buf, sizeof(buf)-1 );
int buf_length = strlen(buf);
for(int i = 0; i <= length; i++){
buf[buf_length-i-1] = 0;
}
return buf;
}
std::string HTTP1_Parser::argv_path_analyzer(std::string request_path, const char *path, const char *executive_file) {
std::string path_string;
if(strcmp(path,"") == 0){
if (request_path == std::string("/")) {
path_string = std::string(HTTP1_Parser::get_filename(executive_file)) + std::string("index.html");
}
else {
int judge = request_path.rfind(".",20);
if (judge >= 0 ) {
path_string = std::string(HTTP1_Parser::get_filename(executive_file)) + request_path;
} else {
path_string = std::string(HTTP1_Parser::get_filename(executive_file)) + request_path + std::string("index.html");
}
}
}
else {
int judge = request_path.rfind(".",20);
if (judge >= 0 ) {
path_string = std::string(path) + request_path;
} else {
path_string = std::string(path) + request_path + std::string("index.html");
}
}
return path_string;
}
std::string HTTP1_Parser::get_requestline_path(char _pbuf[BUF_SIZE]) {
int space_num = 0;
char path_buffer[32];
int current_point = 0;
std::string path_string;
for (int i = 0; i < BUF_SIZE; i++) {
if(space_num == 2){
path_buffer[current_point] = 0;
path_string = std::string(path_buffer);
return path_string;
}
if(space_num == 1){
if(_pbuf[i] != ' '){
path_buffer[current_point++] = _pbuf[i];
}
}
if(_pbuf[i] == ' '){
++space_num;
}
}
return "";
}
#ifndef _HTTP1_HEAD_H_
#define _HTTP1_HEAD_H_
#include <vector>
#include <string>
#include <map>
#include <sstream>
class HTTP1_Header {
public:
static std::vector<std::string>& make_response404();
static std::vector<std::string>& make_response200(int body_length);
static std::vector<std::string>& make_response302(std::string path);
static std::vector<std::string>& make_responseUpgrade();
};
#endif
#include "http1header.h"
#include <iostream>
std::vector<std::string>& HTTP1_Header::make_response404() {
static std::vector<std::string> header ={
"HTTP/1.1 404 Not Found\r\n",
"Content-Type: text/html; charset=UTF-8\r\n",
"Connection: close\r\n",
"\r\n",
"<html><body><h1>404 Not found</h1><p>The requested URL was not found on this server.</p><hr><address>Original Server</address></body></html>\r\n"
};
return header;
}
std::vector<std::string>& HTTP1_Header::make_response200(int body_length) {
std::ostringstream oss;
oss << "Content-Length: " << body_length << "\r\n";
static std::vector<std::string> header = {
"HTTP/1.1 200 OK\r\n",
"Content-Type: text/html; charset=UTF-8\r\n",
oss.str(),
"Connection: Keep-Alive\r\n",
"\r\n"
};
return header;
}
std::vector<std::string>& HTTP1_Header::make_response302(std::string path) {
std::ostringstream oss;
if (path != "") {
oss << "Location: https://127.0.0.1:5001" << path.c_str() << "\r\n";
}
else {
oss << "Location: https://127.0.0.1:5001\r\n";
}
static std::vector<std::string> header = {
"HTTP/1.1 302 Found\r\n",
oss.str(),
"\r\n"
};
return header;
}
std::vector<std::string>& HTTP1_Header::make_responseUpgrade() {
static std::vector<std::string> header = {
"HTTP/1.1 101 Switching Protocols\r\n",
"Connection: Upgrade\r\n",
"Upgrade: h2c\r\n",
"\r\n"
};
return header;
}
#ifndef _HTTP1_RESHET_H_
#define _HTTP1_RESHET_H_
#include <string>
#include <cstring>
#include <vector>
#include <fstream>
#include "configure.h"
class HTTP1_Response {
public:
static std::string make_response(std::vector<std::string> &header, std::vector<std::string> &message_body);
//static std::vector<std::string>& make_header(int version, int body_length, std::ifstream &output_file);
static std::vector<std::string>& make_header(int version, int body_length, int is_file_exist, std::string path);
static void make_body (std::vector<std::string> &body_content, int &body_content_length, std::ifstream &output_file);
};
#endif
#include "http1response.h"
#include "http1header.h"
#include <iostream>
#include <iterator>
std::string HTTP1_Response::make_response(std::vector<std::string> &header, std::vector<std::string> &message_body) {
std::string server_response;
int header_size = header.size();
int body_size = message_body.size();
std::string tmp;
for (int i = 0; i < header_size; i++){
server_response.append(header[i].c_str());
}
for (int i = 0; i < body_size; i++){
server_response.append(message_body[i].c_str());
}
return server_response;
}
std::vector<std::string>& HTTP1_Response::make_header(int version, int body_length, int is_file_exist, std::string path) {
if (HTTP_VERSION == 1 && path != "") {
return HTTP1_Header::make_response302(path);
}
else if (HTTP_VERSION == 2) {
return HTTP1_Header::make_responseUpgrade();
}
else if (is_file_exist == 1) {
return HTTP1_Header::make_response404();
}
else {
return HTTP1_Header::make_response200(body_length);
}
static std::vector<std::string> response_header;
return response_header;
}
void HTTP1_Response::make_body(std::vector<std::string> &body_content, int &body_content_length, std::ifstream &output_file){
if (output_file.fail() != 0) {
std::cout << "File was not found." << std::endl;
return;
}
char read_file_buf[BUF_SIZE];
output_file.read(read_file_buf, BUF_SIZE);
body_content.push_back(read_file_buf);
body_content_length = output_file.gcount();
}
同時アクセス数の増加に対応する
int accfd = -1;
だったところを下のようにint型からint型の配列に修正します。
const int MAX_SESSION = 10;
int accfd[MAX_SESSION];
for(int i=0; i < MAX_SESSION; i++){
accfd[i] = -1;
}
先程に続けて以下の宣言をします。
fd_set fds;
while(1){}
のループ内に入り、最初に複数ソケットの通信の準備をしておきます。
FD_ZERO(&fds);
FD_SET(sock->get_listenfd(), &fds);
int width = sock->get_listenfd() + 1;
for (int i=0; i < MAX_SESSION; i++){
if(accfd[i] != -1){
FD_SET(accfd[i], &fds);
if(width < (accfd[i]+1)){
width = accfd[i]+1;
}
}
}
if(select(width, &fds, NULL,NULL, NULL) == -1) {
std::cout << "select() failed." << std::endl;
break;
}
if(FD_ISSET(sock->get_listenfd(), &fds)){
int connfd = accept(sock->get_listenfd(), (struct sockaddr*)NULL, NULL);
bool limit_over = true;
for(int i = 0; i < MAX_SESSION; i++) {
if(accfd[i] == -1) {
accfd[i] = connfd;
limit_over = false;
break;
}
}
if(limit_over) {
close(connfd);
std::cout << "over max connection." << std::endl;
}
}
先程accept()の返り値をチェックして以下のような記述で処理を続けていました。
if(accfd == -1) {
continue;
}
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
複数ソケットでの通信のときはセッション毎にこれらを行うためにセッションの数だけループを回して処理をします。
for( int i = 0; i < MAX_SESSION; i++) {
if(accfd[i] == -1) {
continue;
}
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
if(FD_ISSET(accfd[i], &fds)) {
std::string recv_str = "";
ssize_t read_size = 0;
︙
︙
}
}
以上となります。
全体
先ほどとはserver.cppのみを修正します。
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <iostream>
#include <algorithm>
#include <errno.h>
#include <sstream>
#include <string>
#include <iomanip>
#include <fstream>
#include <vector>
#include <future>
#include <thread>
#include "socket.h"
#include "http1response.h"
#include "http1header.h"
#include "http1parser.h"
#include "socket.h"
const int MAX_SESSION = 10;
int http1()
{
std::string executive_file = HTTP1_FILE;
Socket *sock = new Socket(HTTP1_PORT);
sock->set_socket();
int body_length = 0;
int is_file_exist;
int accfd[MAX_SESSION];
for(int i=0; i < MAX_SESSION; i++){
accfd[i] = -1;
}
fd_set fds;
while(1) {
FD_ZERO(&fds);
FD_SET(sock->get_listenfd(), &fds);
int width = sock->get_listenfd() + 1;
for (int i=0; i < MAX_SESSION; i++){
if(accfd[i] != -1){
FD_SET(accfd[i], &fds);
if(width < (accfd[i]+1)){
width = accfd[i]+1;
}
}
}
if(select(width, &fds, NULL,NULL, NULL) == -1) {
std::cout << "select() failed." << std::endl;
break;
}
if(FD_ISSET(sock->get_listenfd(), &fds)){
int connfd = accept(sock->get_listenfd(), (struct sockaddr*)NULL, NULL);
bool limit_over = true;
for(int i = 0; i < MAX_SESSION; i++) {
if(accfd[i] == -1) {
accfd[i] = connfd;
limit_over = false;
break;
}
}
if(limit_over) {
close(connfd);
std::cout << "over max connection." << std::endl;
}
}
//Start reading message
for( int i = 0; i < MAX_SESSION; i++) {
if(accfd[i] == -1) {
continue;
}
char buf[BUF_SIZE];
memset(buf, 0, sizeof(buf));
if(FD_ISSET(accfd[i], &fds)) {
std::string recv_str = "";
ssize_t read_size = 0;
do {
read_size = recv(accfd[i], buf, sizeof(buf)-1, 0);
if(read_size == -1) {
std::cout << "read() failed." << std::endl;
std::cout << "ERROR: " << errno << std::endl;
close(accfd[i]);
accfd[i] = -1;
break;
}
if(read_size > 0) {
recv_str.append(buf);
}
if( (recv_str[recv_str.length()-4] == 0x0d) &&
(recv_str[recv_str.length()-3] == 0x0a) &&
(recv_str[recv_str.length()-2] == 0x0d) &&
(recv_str[recv_str.length()-1] == 0x0a)
){
break;
}
} while (read_size > 0);
std::string path = "", path_string = "";
std::string exe = executive_file;
std::size_t pos = exe.rfind('/');
if (pos != std::string::npos) {
exe = exe.substr(pos + 1);
}
path_string.clear();
path = HTTP1_Parser::get_requestline_path(buf);
path_string = HTTP1_Parser::argv_path_analyzer(path, executive_file.c_str(), exe.c_str());
std::cout << "path_string : " << path_string << std::endl;
std::ifstream output_file(path_string.c_str());
char line[256];
is_file_exist = output_file.fail();
body_length = 0;
std::vector<std::string> message_body;
while (output_file.getline(line, 256-1)) {
body_length += strlen(line);
message_body.push_back(std::string(line));
}
recv_str.append(buf);
std::string server_response;
std::vector<std::string> header = HTTP1_Response::make_header(3, body_length, is_file_exist, path);
server_response = HTTP1_Response::make_response(header, message_body);
std::cout << server_response << std::endl;
if(send(accfd[i], server_response.c_str(), server_response.length(), 0) == -1){
std::cout << "write() failed." << std::endl;
}
output_file.close();
close(accfd[i]);
accfd[i] = -1;
}
}
}
close(sock->get_listenfd());
return 0;
}
int main(int argc, char *argv[])
{
http1();
return 0;
}
##HTTP/2
HTTP/2はSPDYをベースに従来のHTTP/1.xと互換性を保ちながら1つのコネクション上で複数のストリームを使うことやヘッダのバイナリ表現や圧縮、リクエストの優先度付等で効率化を図ったものです。
HTTP/2を使うかの確認は以下の通りとなります。httpスキームでHTTP/1.1からHTTP/2を使用するにはUpgradeヘッダフィールを使用するかHTTP Alternative Servicesを使用します。
- TLS-NPN,TLS-ALPN
- Upgrade, Alt-Svc
- 相手がHTTP/2をつかっていることを前提に通信
Appendix
ステータスコード
###1xx
- 100 Continue
- 101 Switching Protocols
###2xx
- 200 OK
- 201 Created
- 202 Accepted
- 203 Non-Authoritative Information
- 204 No Content
- 205 Reset Content
- 206 Partial Content
###3xx
- 300 Multiple Choices
- 301 Moved Permanently
- 302 Found
- 303 See Other
- 304 Not Modified
- 305 Use Proxy
- 306 (Unused)
- 307 Temporary Redirect
- 308 Parmenent Redirect
###4xx
- 400 Bad Request
- 401 Unauthorized
- 402 Payment Required
- 403 Forbidden
- 404 Not Found
- 405 Method Not Allowed
- 406 Not Acceptable
- 407 Proxy Authentication Required
- 408 Request Timeout
- 409 Conflict
- 410 Gone
- 411 Length Required
- 412 Precondition Failed
- 413 Payment Too Large
- 414 URI Too Long
- 415 Unsupported Media Type
- 416 Range Not Satisfiable
- 417 Expectation Failed
- 426 Upgrade Required
- 428 Precondition Required
- 429 Too Many Requests
- 431 Request Header Fields Too Large
###5xx
- 500 Internal Server Error
- 501 Not Implemented
- 502 Bad Gateway
- 503 Service Unavailable
- 504 Gateway Timeout
- 505 HTTP Version Not Supported
HTTPヘッダー
- Accept
- Accept-Charset
- Accept-Encoding
- Accept-Language
- Accept-Ranges
- Age
- Allow
- Authorization
- Cache-Control
- Connection
- Content-Encoding
- Content-Language
- Content-Length
- Content-Location
- Content-Range
- Content-Type
- Date
- ETag
- Expect
- Expires
- From
- Host
- If-Match
- If-Modified-Since
- If-None-Match
- If-Range
- If-Unmodified-Since
- Location
- Max-Forwards
- Pragma
- Proxy-Authenticate
- Proxy-Authorization
- Range
- Referer
- Retry-After
- Server
- TE
- Trailer
- Transfer-Encoding
- Upgrade
- User-Agent
- Vary
- Via
- Warning
- WWW-Authenticate
参考
ftp://ftp.rfc-editor.org/in-notes/rfc2616.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7230.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7231.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7232.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7233.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7234.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7235.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7236.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7237.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7238.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7239.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7540.txt
ftp://ftp.rfc-editor.org/in-notes/rfc7541.txt
ftp://ftp.rfc-editor.org/in-notes/rfc5789.txt
https://tools.ietf.org/html/draft-nottingham-http-new-status-04
http://www.asahi-net.or.jp/~ax2s-kmtn/ref/status.html
http://d.hatena.ne.jp/s-kita/20080927/1222505067
https://techblog.yahoo.co.jp/infrastructure/http2/ats_http2_pn/
http://syucream.hatenablog.jp/entry/2014/12/20/160552