|
/* |
|
* HTTP/2 + TLS Server - Minimal Configuration |
|
* Based on nghttp2 example with libevent |
|
*/ |
|
|
|
#include <sys/types.h> |
|
#include <sys/socket.h> |
|
#include <netdb.h> |
|
#include <signal.h> |
|
#include <unistd.h> |
|
#include <sys/stat.h> |
|
#include <fcntl.h> |
|
#include <ctype.h> |
|
#include <netinet/in.h> |
|
#include <netinet/tcp.h> |
|
#include <err.h> |
|
#include <string.h> |
|
#include <errno.h> |
|
#include <stdio.h> |
|
#include <stdlib.h> |
|
|
|
#include <openssl/ssl.h> |
|
#include <openssl/err.h> |
|
#include <openssl/conf.h> |
|
|
|
#include <event2/event.h> |
|
#include <event2/bufferevent_ssl.h> |
|
#include <event2/bufferevent.h> |
|
#include <event2/buffer.h> |
|
#include <event2/listener.h> |
|
|
|
#include <nghttp2/nghttp2.h> |
|
|
|
#define OUTPUT_WOULDBLOCK_THRESHOLD (1 << 16) |
|
#define ARRLEN(x) (sizeof(x) / sizeof(x[0])) |
|
|
|
#define MAKE_NV(NAME, VALUE) \ |
|
{ \ |
|
(uint8_t *)NAME, (uint8_t *)VALUE, sizeof(NAME) - 1, sizeof(VALUE) - 1, \ |
|
NGHTTP2_NV_FLAG_NONE \ |
|
} |
|
|
|
struct app_context; |
|
typedef struct app_context app_context; |
|
|
|
typedef struct http2_stream_data { |
|
struct http2_stream_data *prev, *next; |
|
char *request_path; |
|
int32_t stream_id; |
|
} http2_stream_data; |
|
|
|
typedef struct http2_session_data { |
|
struct http2_stream_data root; |
|
struct bufferevent *bev; |
|
app_context *app_ctx; |
|
nghttp2_session *session; |
|
char *client_addr; |
|
} http2_session_data; |
|
|
|
struct app_context { |
|
SSL_CTX *ssl_ctx; |
|
struct event_base *evbase; |
|
}; |
|
|
|
static unsigned char next_proto_list[256]; |
|
static size_t next_proto_list_len; |
|
|
|
#ifndef OPENSSL_NO_NEXTPROTONEG |
|
static int next_proto_cb(SSL *ssl, const unsigned char **data, |
|
unsigned int *len, void *arg) { |
|
(void)ssl; |
|
(void)arg; |
|
*data = next_proto_list; |
|
*len = (unsigned int)next_proto_list_len; |
|
return SSL_TLSEXT_ERR_OK; |
|
} |
|
#endif |
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L |
|
static int alpn_select_proto_cb(SSL *ssl, const unsigned char **out, |
|
unsigned char *outlen, const unsigned char *in, |
|
unsigned int inlen, void *arg) { |
|
int rv; |
|
(void)ssl; |
|
(void)arg; |
|
|
|
rv = nghttp2_select_next_protocol((unsigned char **)out, outlen, in, inlen); |
|
if (rv != 1) { |
|
return SSL_TLSEXT_ERR_NOACK; |
|
} |
|
return SSL_TLSEXT_ERR_OK; |
|
} |
|
#endif |
|
|
|
/* SSL_CTXを作成 */ |
|
static SSL_CTX *create_ssl_ctx(const char *key_file, const char *cert_file) { |
|
SSL_CTX *ssl_ctx; |
|
|
|
ssl_ctx = SSL_CTX_new(TLS_server_method()); |
|
if (!ssl_ctx) { |
|
errx(1, "Could not create SSL/TLS context: %s", |
|
ERR_error_string(ERR_get_error(), NULL)); |
|
} |
|
|
|
SSL_CTX_set_options(ssl_ctx, |
|
SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | |
|
SSL_OP_NO_COMPRESSION | |
|
SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION); |
|
|
|
/* 楕円曲線の設定 */ |
|
#if OPENSSL_VERSION_NUMBER >= 0x30000000L |
|
if (SSL_CTX_set1_curves_list(ssl_ctx, "P-256") != 1) { |
|
errx(1, "SSL_CTX_set1_curves_list failed: %s", |
|
ERR_error_string(ERR_get_error(), NULL)); |
|
} |
|
#else |
|
{ |
|
EC_KEY *ecdh; |
|
ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1); |
|
if (!ecdh) { |
|
errx(1, "EC_KEY_new_by_curve_name failed: %s", |
|
ERR_error_string(ERR_get_error(), NULL)); |
|
} |
|
SSL_CTX_set_tmp_ecdh(ssl_ctx, ecdh); |
|
EC_KEY_free(ecdh); |
|
} |
|
#endif |
|
|
|
/* 秘密鍵と証明書の読み込み */ |
|
if (SSL_CTX_use_PrivateKey_file(ssl_ctx, key_file, SSL_FILETYPE_PEM) != 1) { |
|
errx(1, "Could not read private key file %s", key_file); |
|
} |
|
if (SSL_CTX_use_certificate_chain_file(ssl_ctx, cert_file) != 1) { |
|
errx(1, "Could not read certificate file %s", cert_file); |
|
} |
|
|
|
/* HTTP/2プロトコル識別子の設定 */ |
|
next_proto_list[0] = NGHTTP2_PROTO_VERSION_ID_LEN; |
|
memcpy(&next_proto_list[1], NGHTTP2_PROTO_VERSION_ID, |
|
NGHTTP2_PROTO_VERSION_ID_LEN); |
|
next_proto_list_len = 1 + NGHTTP2_PROTO_VERSION_ID_LEN; |
|
|
|
#ifndef OPENSSL_NO_NEXTPROTONEG |
|
SSL_CTX_set_next_protos_advertised_cb(ssl_ctx, next_proto_cb, NULL); |
|
#endif |
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L |
|
SSL_CTX_set_alpn_select_cb(ssl_ctx, alpn_select_proto_cb, NULL); |
|
#endif |
|
|
|
return ssl_ctx; |
|
} |
|
|
|
/* SSL オブジェクト作成 */ |
|
static SSL *create_ssl(SSL_CTX *ssl_ctx) { |
|
SSL *ssl; |
|
ssl = SSL_new(ssl_ctx); |
|
if (!ssl) { |
|
errx(1, "Could not create SSL/TLS session object: %s", |
|
ERR_error_string(ERR_get_error(), NULL)); |
|
} |
|
return ssl; |
|
} |
|
|
|
/* ストリーム管理関数 */ |
|
static void add_stream(http2_session_data *session_data, |
|
http2_stream_data *stream_data) { |
|
stream_data->next = session_data->root.next; |
|
session_data->root.next = stream_data; |
|
stream_data->prev = &session_data->root; |
|
if (stream_data->next) { |
|
stream_data->next->prev = stream_data; |
|
} |
|
} |
|
|
|
static void remove_stream(http2_session_data *session_data, |
|
http2_stream_data *stream_data) { |
|
(void)session_data; |
|
stream_data->prev->next = stream_data->next; |
|
if (stream_data->next) { |
|
stream_data->next->prev = stream_data->prev; |
|
} |
|
} |
|
|
|
static http2_stream_data * |
|
create_http2_stream_data(http2_session_data *session_data, int32_t stream_id) { |
|
http2_stream_data *stream_data; |
|
stream_data = malloc(sizeof(http2_stream_data)); |
|
memset(stream_data, 0, sizeof(http2_stream_data)); |
|
stream_data->stream_id = stream_id; |
|
add_stream(session_data, stream_data); |
|
return stream_data; |
|
} |
|
|
|
static void delete_http2_stream_data(http2_stream_data *stream_data) { |
|
free(stream_data->request_path); |
|
free(stream_data); |
|
} |
|
|
|
/* セッション作成 */ |
|
static http2_session_data *create_http2_session_data(app_context *app_ctx, |
|
int fd, |
|
struct sockaddr *addr, |
|
int addrlen) { |
|
int rv; |
|
http2_session_data *session_data; |
|
SSL *ssl; |
|
char host[NI_MAXHOST]; |
|
int val = 1; |
|
|
|
ssl = create_ssl(app_ctx->ssl_ctx); |
|
session_data = malloc(sizeof(http2_session_data)); |
|
memset(session_data, 0, sizeof(http2_session_data)); |
|
session_data->app_ctx = app_ctx; |
|
|
|
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *)&val, sizeof(val)); |
|
session_data->bev = bufferevent_openssl_socket_new( |
|
app_ctx->evbase, fd, ssl, BUFFEREVENT_SSL_ACCEPTING, |
|
BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS); |
|
bufferevent_enable(session_data->bev, EV_READ | EV_WRITE); |
|
|
|
rv = getnameinfo(addr, (socklen_t)addrlen, host, sizeof(host), NULL, 0, |
|
NI_NUMERICHOST); |
|
if (rv != 0) { |
|
session_data->client_addr = strdup("(unknown)"); |
|
} else { |
|
session_data->client_addr = strdup(host); |
|
} |
|
|
|
return session_data; |
|
} |
|
|
|
/* セッション削除 */ |
|
static void delete_http2_session_data(http2_session_data *session_data) { |
|
http2_stream_data *stream_data; |
|
SSL *ssl = bufferevent_openssl_get_ssl(session_data->bev); |
|
|
|
fprintf(stderr, "%s disconnected\n", session_data->client_addr); |
|
|
|
if (ssl) { |
|
SSL_shutdown(ssl); |
|
} |
|
bufferevent_free(session_data->bev); |
|
nghttp2_session_del(session_data->session); |
|
|
|
for (stream_data = session_data->root.next; stream_data;) { |
|
http2_stream_data *next = stream_data->next; |
|
delete_http2_stream_data(stream_data); |
|
stream_data = next; |
|
} |
|
|
|
free(session_data->client_addr); |
|
free(session_data); |
|
} |
|
|
|
/* データ送信 */ |
|
static int session_send(http2_session_data *session_data) { |
|
int rv; |
|
rv = nghttp2_session_send(session_data->session); |
|
if (rv != 0) { |
|
warnx("Fatal error: %s", nghttp2_strerror(rv)); |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
/* データ受信 */ |
|
static int session_recv(http2_session_data *session_data) { |
|
ssize_t readlen; |
|
struct evbuffer *input = bufferevent_get_input(session_data->bev); |
|
size_t datalen = evbuffer_get_length(input); |
|
unsigned char *data = evbuffer_pullup(input, -1); |
|
|
|
readlen = nghttp2_session_mem_recv(session_data->session, data, datalen); |
|
if (readlen < 0) { |
|
warnx("Fatal error: %s", nghttp2_strerror((int)readlen)); |
|
return -1; |
|
} |
|
if (evbuffer_drain(input, (size_t)readlen) != 0) { |
|
warnx("Fatal error: evbuffer_drain failed"); |
|
return -1; |
|
} |
|
if (session_send(session_data) != 0) { |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
/* 送信コールバック */ |
|
static ssize_t send_callback(nghttp2_session *session, const uint8_t *data, |
|
size_t length, int flags, void *user_data) { |
|
http2_session_data *session_data = (http2_session_data *)user_data; |
|
struct bufferevent *bev = session_data->bev; |
|
(void)session; |
|
(void)flags; |
|
|
|
if (evbuffer_get_length(bufferevent_get_output(session_data->bev)) >= |
|
OUTPUT_WOULDBLOCK_THRESHOLD) { |
|
return NGHTTP2_ERR_WOULDBLOCK; |
|
} |
|
bufferevent_write(bev, data, length); |
|
return (ssize_t)length; |
|
} |
|
|
|
/* 簡単なHTMLレスポンス */ |
|
static const char HELLO_HTML[] = |
|
"<!DOCTYPE html>" |
|
"<html><head><title>HTTP/2 Server</title></head>" |
|
"<body><h1>Hello, HTTP/2!</h1>" |
|
"<p>This is a minimal HTTP/2 server with TLS.</p></body></html>"; |
|
|
|
/* レスポンス送信 */ |
|
static int send_response(nghttp2_session *session, int32_t stream_id) { |
|
nghttp2_nv hdrs[] = { |
|
MAKE_NV(":status", "200"), |
|
MAKE_NV("content-type", "text/html"), |
|
MAKE_NV("server", "nghttp2") |
|
}; |
|
|
|
nghttp2_data_provider data_prd; |
|
data_prd.source.ptr = (void*)HELLO_HTML; |
|
data_prd.read_callback = NULL; /* 自動的にdata_prd.source.ptrからデータを読む */ |
|
|
|
int rv = nghttp2_submit_response(session, stream_id, hdrs, ARRLEN(hdrs), &data_prd); |
|
if (rv != 0) { |
|
warnx("Fatal error: %s", nghttp2_strerror(rv)); |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
/* データ読み取りコールバック */ |
|
static ssize_t data_source_read_callback(nghttp2_session *session, int32_t stream_id, |
|
uint8_t *buf, size_t length, |
|
uint32_t *data_flags, |
|
nghttp2_data_source *source, |
|
void *user_data) { |
|
(void)session; |
|
(void)stream_id; |
|
(void)user_data; |
|
|
|
const char *data = (const char*)source->ptr; |
|
size_t len = strlen(data); |
|
|
|
if (len > length) { |
|
len = length; |
|
} |
|
|
|
memcpy(buf, data, len); |
|
*data_flags |= NGHTTP2_DATA_FLAG_EOF; |
|
|
|
return len; |
|
} |
|
|
|
/* 簡単なレスポンス送信(データコールバック付き) */ |
|
static int send_hello_response(nghttp2_session *session, int32_t stream_id) { |
|
nghttp2_nv hdrs[] = { |
|
MAKE_NV(":status", "200"), |
|
MAKE_NV("content-type", "text/html"), |
|
MAKE_NV("server", "nghttp2") |
|
}; |
|
|
|
nghttp2_data_provider data_prd; |
|
data_prd.source.ptr = (void*)HELLO_HTML; |
|
data_prd.read_callback = data_source_read_callback; |
|
|
|
int rv = nghttp2_submit_response(session, stream_id, hdrs, ARRLEN(hdrs), &data_prd); |
|
if (rv != 0) { |
|
warnx("Fatal error: %s", nghttp2_strerror(rv)); |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
/* ヘッダー受信コールバック */ |
|
static int on_header_callback(nghttp2_session *session, |
|
const nghttp2_frame *frame, const uint8_t *name, |
|
size_t namelen, const uint8_t *value, |
|
size_t valuelen, uint8_t flags, void *user_data) { |
|
http2_stream_data *stream_data; |
|
const char PATH[] = ":path"; |
|
(void)flags; |
|
(void)user_data; |
|
|
|
switch (frame->hd.type) { |
|
case NGHTTP2_HEADERS: |
|
if (frame->headers.cat != NGHTTP2_HCAT_REQUEST) { |
|
break; |
|
} |
|
stream_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); |
|
if (!stream_data || stream_data->request_path) { |
|
break; |
|
} |
|
if (namelen == sizeof(PATH) - 1 && memcmp(PATH, name, namelen) == 0) { |
|
stream_data->request_path = malloc(valuelen + 1); |
|
memcpy(stream_data->request_path, value, valuelen); |
|
stream_data->request_path[valuelen] = '\0'; |
|
} |
|
break; |
|
} |
|
return 0; |
|
} |
|
|
|
/* ヘッダー開始コールバック */ |
|
static int on_begin_headers_callback(nghttp2_session *session, |
|
const nghttp2_frame *frame, |
|
void *user_data) { |
|
http2_session_data *session_data = (http2_session_data *)user_data; |
|
http2_stream_data *stream_data; |
|
|
|
if (frame->hd.type != NGHTTP2_HEADERS || |
|
frame->headers.cat != NGHTTP2_HCAT_REQUEST) { |
|
return 0; |
|
} |
|
|
|
stream_data = create_http2_stream_data(session_data, frame->hd.stream_id); |
|
nghttp2_session_set_stream_user_data(session, frame->hd.stream_id, stream_data); |
|
return 0; |
|
} |
|
|
|
/* リクエスト処理 */ |
|
static int on_request_recv(nghttp2_session *session, |
|
http2_session_data *session_data, |
|
http2_stream_data *stream_data) { |
|
if (!stream_data->request_path) { |
|
stream_data->request_path = strdup("/"); |
|
} |
|
|
|
fprintf(stderr, "%s GET %s\n", session_data->client_addr, stream_data->request_path); |
|
|
|
if (send_hello_response(session, stream_data->stream_id) != 0) { |
|
return NGHTTP2_ERR_CALLBACK_FAILURE; |
|
} |
|
return 0; |
|
} |
|
|
|
/* フレーム受信コールバック */ |
|
static int on_frame_recv_callback(nghttp2_session *session, |
|
const nghttp2_frame *frame, void *user_data) { |
|
http2_session_data *session_data = (http2_session_data *)user_data; |
|
http2_stream_data *stream_data; |
|
|
|
switch (frame->hd.type) { |
|
case NGHTTP2_DATA: |
|
case NGHTTP2_HEADERS: |
|
if (frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { |
|
stream_data = nghttp2_session_get_stream_user_data(session, frame->hd.stream_id); |
|
if (!stream_data) { |
|
return 0; |
|
} |
|
return on_request_recv(session, session_data, stream_data); |
|
} |
|
break; |
|
default: |
|
break; |
|
} |
|
return 0; |
|
} |
|
|
|
/* ストリームクローズコールバック */ |
|
static int on_stream_close_callback(nghttp2_session *session, int32_t stream_id, |
|
uint32_t error_code, void *user_data) { |
|
http2_session_data *session_data = (http2_session_data *)user_data; |
|
http2_stream_data *stream_data; |
|
(void)error_code; |
|
|
|
stream_data = nghttp2_session_get_stream_user_data(session, stream_id); |
|
if (!stream_data) { |
|
return 0; |
|
} |
|
remove_stream(session_data, stream_data); |
|
delete_http2_stream_data(stream_data); |
|
return 0; |
|
} |
|
|
|
/* nghttp2セッション初期化 */ |
|
static void initialize_nghttp2_session(http2_session_data *session_data) { |
|
nghttp2_session_callbacks *callbacks; |
|
|
|
nghttp2_session_callbacks_new(&callbacks); |
|
nghttp2_session_callbacks_set_send_callback(callbacks, send_callback); |
|
nghttp2_session_callbacks_set_on_frame_recv_callback(callbacks, on_frame_recv_callback); |
|
nghttp2_session_callbacks_set_on_stream_close_callback(callbacks, on_stream_close_callback); |
|
nghttp2_session_callbacks_set_on_header_callback(callbacks, on_header_callback); |
|
nghttp2_session_callbacks_set_on_begin_headers_callback(callbacks, on_begin_headers_callback); |
|
|
|
nghttp2_session_server_new(&session_data->session, callbacks, session_data); |
|
nghttp2_session_callbacks_del(callbacks); |
|
} |
|
|
|
/* サーバー接続ヘッダー送信 */ |
|
static int send_server_connection_header(http2_session_data *session_data) { |
|
nghttp2_settings_entry iv[1] = { |
|
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}}; |
|
int rv; |
|
|
|
rv = nghttp2_submit_settings(session_data->session, NGHTTP2_FLAG_NONE, iv, ARRLEN(iv)); |
|
if (rv != 0) { |
|
warnx("Fatal error: %s", nghttp2_strerror(rv)); |
|
return -1; |
|
} |
|
return 0; |
|
} |
|
|
|
/* 読み込みコールバック */ |
|
static void readcb(struct bufferevent *bev, void *ptr) { |
|
http2_session_data *session_data = (http2_session_data *)ptr; |
|
(void)bev; |
|
|
|
if (session_recv(session_data) != 0) { |
|
delete_http2_session_data(session_data); |
|
return; |
|
} |
|
} |
|
|
|
/* 書き込みコールバック */ |
|
static void writecb(struct bufferevent *bev, void *ptr) { |
|
http2_session_data *session_data = (http2_session_data *)ptr; |
|
|
|
if (evbuffer_get_length(bufferevent_get_output(bev)) > 0) { |
|
return; |
|
} |
|
if (nghttp2_session_want_read(session_data->session) == 0 && |
|
nghttp2_session_want_write(session_data->session) == 0) { |
|
delete_http2_session_data(session_data); |
|
return; |
|
} |
|
if (session_send(session_data) != 0) { |
|
delete_http2_session_data(session_data); |
|
return; |
|
} |
|
} |
|
|
|
/* イベントコールバック */ |
|
static void eventcb(struct bufferevent *bev, short events, void *ptr) { |
|
http2_session_data *session_data = (http2_session_data *)ptr; |
|
|
|
if (events & BEV_EVENT_CONNECTED) { |
|
const unsigned char *alpn = NULL; |
|
unsigned int alpnlen = 0; |
|
SSL *ssl; |
|
(void)bev; |
|
|
|
fprintf(stderr, "%s connected\n", session_data->client_addr); |
|
ssl = bufferevent_openssl_get_ssl(session_data->bev); |
|
|
|
#ifndef OPENSSL_NO_NEXTPROTONEG |
|
SSL_get0_next_proto_negotiated(ssl, &alpn, &alpnlen); |
|
#endif |
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10002000L |
|
if (alpn == NULL) { |
|
SSL_get0_alpn_selected(ssl, &alpn, &alpnlen); |
|
} |
|
#endif |
|
|
|
if (alpn == NULL || alpnlen != 2 || memcmp("h2", alpn, 2) != 0) { |
|
fprintf(stderr, "%s h2 is not negotiated\n", session_data->client_addr); |
|
delete_http2_session_data(session_data); |
|
return; |
|
} |
|
|
|
initialize_nghttp2_session(session_data); |
|
|
|
if (send_server_connection_header(session_data) != 0 || |
|
session_send(session_data) != 0) { |
|
delete_http2_session_data(session_data); |
|
return; |
|
} |
|
return; |
|
} |
|
|
|
if (events & BEV_EVENT_EOF) { |
|
fprintf(stderr, "%s EOF\n", session_data->client_addr); |
|
} else if (events & BEV_EVENT_ERROR) { |
|
fprintf(stderr, "%s network error\n", session_data->client_addr); |
|
} else if (events & BEV_EVENT_TIMEOUT) { |
|
fprintf(stderr, "%s timeout\n", session_data->client_addr); |
|
} |
|
delete_http2_session_data(session_data); |
|
} |
|
|
|
/* 接続受付コールバック */ |
|
static void acceptcb(struct evconnlistener *listener, int fd, |
|
struct sockaddr *addr, int addrlen, void *arg) { |
|
app_context *app_ctx = (app_context *)arg; |
|
http2_session_data *session_data; |
|
(void)listener; |
|
|
|
session_data = create_http2_session_data(app_ctx, fd, addr, addrlen); |
|
bufferevent_setcb(session_data->bev, readcb, writecb, eventcb, session_data); |
|
} |
|
|
|
/* リスナー開始 */ |
|
static void start_listen(struct event_base *evbase, const char *service, |
|
app_context *app_ctx) { |
|
int rv; |
|
struct addrinfo hints; |
|
struct addrinfo *res, *rp; |
|
|
|
memset(&hints, 0, sizeof(hints)); |
|
hints.ai_family = AF_UNSPEC; |
|
hints.ai_socktype = SOCK_STREAM; |
|
hints.ai_flags = AI_PASSIVE; |
|
#ifdef AI_ADDRCONFIG |
|
hints.ai_flags |= AI_ADDRCONFIG; |
|
#endif |
|
|
|
rv = getaddrinfo(NULL, service, &hints, &res); |
|
if (rv != 0) { |
|
errx(1, "Could not resolve server address"); |
|
} |
|
|
|
for (rp = res; rp; rp = rp->ai_next) { |
|
struct evconnlistener *listener; |
|
listener = evconnlistener_new_bind( |
|
evbase, acceptcb, app_ctx, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE, |
|
16, rp->ai_addr, (int)rp->ai_addrlen); |
|
if (listener) { |
|
freeaddrinfo(res); |
|
return; |
|
} |
|
} |
|
errx(1, "Could not start listener"); |
|
} |
|
|
|
/* アプリケーションコンテキスト初期化 */ |
|
static void initialize_app_context(app_context *app_ctx, SSL_CTX *ssl_ctx, |
|
struct event_base *evbase) { |
|
memset(app_ctx, 0, sizeof(app_context)); |
|
app_ctx->ssl_ctx = ssl_ctx; |
|
app_ctx->evbase = evbase; |
|
} |
|
|
|
/* メイン実行関数 */ |
|
static void run(const char *service, const char *key_file, const char *cert_file) { |
|
SSL_CTX *ssl_ctx; |
|
app_context app_ctx; |
|
struct event_base *evbase; |
|
|
|
ssl_ctx = create_ssl_ctx(key_file, cert_file); |
|
evbase = event_base_new(); |
|
initialize_app_context(&app_ctx, ssl_ctx, evbase); |
|
start_listen(evbase, service, &app_ctx); |
|
|
|
fprintf(stderr, "HTTP/2 server listening on port %s\n", service); |
|
event_base_loop(evbase, 0); |
|
|
|
event_base_free(evbase); |
|
SSL_CTX_free(ssl_ctx); |
|
} |
|
|
|
int main(int argc, char **argv) { |
|
struct sigaction act; |
|
|
|
if (argc < 4) { |
|
fprintf(stderr, "Usage: %s PORT KEY_FILE CERT_FILE\n", argv[0]); |
|
exit(EXIT_FAILURE); |
|
} |
|
|
|
/* SIGPIPEを無視 */ |
|
memset(&act, 0, sizeof(struct sigaction)); |
|
act.sa_handler = SIG_IGN; |
|
sigaction(SIGPIPE, &act, NULL); |
|
|
|
/* OpenSSL初期化 */ |
|
#if OPENSSL_VERSION_NUMBER >= 0x1010000fL |
|
/* 明示的な初期化は不要 */ |
|
#elif defined(OPENSSL_IS_BORINGSSL) |
|
CRYPTO_library_init(); |
|
#else |
|
OPENSSL_config(NULL); |
|
SSL_load_error_strings(); |
|
SSL_library_init(); |
|
OpenSSL_add_all_algorithms(); |
|
#endif |
|
|
|
run(argv[1], argv[2], argv[3]); |
|
return 0; |
|
} |