gcc -Wall -Wextra -O2 server.c -o server $(pkg-config --cflags --libs libnghttp2)
masakielastic@penguin:~/http-server$ ./server
Listening on 127.0.0.1:8080 (h2c prior-knowledge)
Try: nghttp -v http://127.0.0.1:8080/ --no-tls --pri
nghttp -v http://127.0.0.1:8080/ --ktls
[ 0.000] Connected
[ 0.000] send SETTINGS frame <length=12, flags=0x00, stream_id=0>
(niv=2)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[SETTINGS_INITIAL_WINDOW_SIZE(0x04):65535]
[ 0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=3>
(dep_stream_id=0, weight=201, exclusive=0)
[ 0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=5>
(dep_stream_id=0, weight=101, exclusive=0)
[ 0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=7>
(dep_stream_id=0, weight=1, exclusive=0)
[ 0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=9>
(dep_stream_id=7, weight=1, exclusive=0)
[ 0.000] send PRIORITY frame <length=5, flags=0x00, stream_id=11>
(dep_stream_id=3, weight=1, exclusive=0)
[ 0.000] send HEADERS frame <length=38, flags=0x25, stream_id=13>
; END_STREAM | END_HEADERS | PRIORITY
(padlen=0, dep_stream_id=11, weight=16, exclusive=0)
; Open new stream
:method: GET
:path: /
:scheme: http
:authority: 127.0.0.1:8080
accept: */*
accept-encoding: gzip, deflate
user-agent: nghttp2/1.52.0
[ 0.000] recv SETTINGS frame <length=6, flags=0x00, stream_id=0>
(niv=1)
[SETTINGS_MAX_CONCURRENT_STREAMS(0x03):100]
[ 0.000] send SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.000] recv SETTINGS frame <length=0, flags=0x01, stream_id=0>
; ACK
(niv=0)
[ 0.041] recv (stream_id=13) :status: 200
[ 0.041] recv (stream_id=13) content-type: text/plain; charset=utf-
[ 0.041] recv (stream_id=13) content-length: 13
[ 0.041] recv HEADERS frame <length=25, flags=0x04, stream_id=13>
; END_HEADERS
(padlen=0)
; First response header
Hello HTTP/2
[ 0.041] recv DATA frame <length=13, flags=0x01, stream_id=13>
; END_STREAM
[ 0.042] send GOAWAY frame <length=8, flags=0x00, stream_id=0>
(last_stream_id=0, error_code=NO_ERROR(0x00), opaque_data(0)=[])
server.c
// blocking_h2_server.c
// Minimal blocking HTTP/2 server (cleartext, prior-knowledge) using libnghttp2.
// Handles a single connection at a time, responds "Hello HTTP/2\n" to any request.
#define _POSIX_C_SOURCE 200809L
#include <nghttp2/nghttp2.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <signal.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif
typedef struct {
int fd;
} conn_t;
typedef struct {
const uint8_t *data;
size_t len;
size_t off;
} stream_body_t;
static void die(const char *msg) {
perror(msg);
exit(1);
}
static ssize_t send_callback(nghttp2_session *session,
const uint8_t *data, size_t length,
int flags, void *user_data) {
(void)session; (void)flags;
conn_t *conn = (conn_t *)user_data;
// Blocking send (may block). For a production server you should handle
// partial writes and EAGAIN with nonblocking sockets.
ssize_t n = send(conn->fd, data, length, MSG_NOSIGNAL);
if (n < 0) {
if (errno == EINTR) return NGHTTP2_ERR_WOULDBLOCK;
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
return n;
}
static ssize_t data_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;
stream_body_t *body = (stream_body_t *)source->ptr;
size_t remain = body->len - body->off;
size_t ncopy = remain < length ? remain : length;
if (ncopy > 0) {
memcpy(buf, body->data + body->off, ncopy);
body->off += ncopy;
}
if (body->off >= body->len) {
*data_flags |= NGHTTP2_DATA_FLAG_EOF;
}
return (ssize_t)ncopy;
}
static int on_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame,
void *user_data) {
(void)user_data;
// When we get the request HEADERS with END_STREAM, respond immediately.
if (frame->hd.type == NGHTTP2_HEADERS &&
frame->headers.cat == NGHTTP2_HCAT_REQUEST &&
(frame->hd.flags & NGHTTP2_FLAG_END_STREAM)) {
static const char resp_payload[] = "Hello HTTP/2\n";
stream_body_t *body = (stream_body_t *)calloc(1, sizeof(stream_body_t));
if (!body) return NGHTTP2_ERR_CALLBACK_FAILURE;
body->data = (const uint8_t *)resp_payload;
body->len = sizeof(resp_payload) - 1;
body->off = 0;
char content_length[32];
snprintf(content_length, sizeof(content_length), "%zu", body->len);
nghttp2_nv hdrs[] = {
{(uint8_t *)":status", (uint8_t *)"200", 7, 3, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-type", (uint8_t *)"text/plain; charset=utf-8",
12, 24, NGHTTP2_NV_FLAG_NONE},
{(uint8_t *)"content-length", (uint8_t *)content_length,
14, (uint16_t)strlen(content_length), NGHTTP2_NV_FLAG_NONE},
};
nghttp2_data_provider dp;
memset(&dp, 0, sizeof(dp));
dp.source.ptr = body;
dp.read_callback = data_read_callback;
int rv = nghttp2_submit_response(session, frame->hd.stream_id,
hdrs, sizeof(hdrs)/sizeof(hdrs[0]),
&dp);
if (rv != 0) {
free(body);
return NGHTTP2_ERR_CALLBACK_FAILURE;
}
}
return 0;
}
static int on_stream_close_callback(nghttp2_session *session,
int32_t stream_id,
uint32_t error_code,
void *user_data) {
(void)session; (void)error_code; (void)user_data;
// Free the body object we attached via data provider:
// We stored it in nghttp2_data_source.ptr, but nghttp2 doesn't keep that
// accessible here. For a real server, keep per-stream state in your own map.
// This minimal example uses static body payload, so we skip cleanup complexity.
(void)stream_id;
return 0;
}
static nghttp2_session *setup_h2_session(conn_t *conn) {
nghttp2_session_callbacks *cbs = NULL;
nghttp2_session *session = NULL;
if (nghttp2_session_callbacks_new(&cbs) != 0) return NULL;
nghttp2_session_callbacks_set_send_callback(cbs, send_callback);
nghttp2_session_callbacks_set_on_frame_recv_callback(cbs, on_frame_recv_callback);
nghttp2_session_callbacks_set_on_stream_close_callback(cbs, on_stream_close_callback);
// Create server session
if (nghttp2_session_server_new(&session, cbs, conn) != 0) { // :contentReference[oaicite:1]{index=1}
nghttp2_session_callbacks_del(cbs);
return NULL;
}
nghttp2_session_callbacks_del(cbs);
// Server must send SETTINGS first
nghttp2_settings_entry iv[1] = {
{NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, 100}
};
if (nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1) != 0) {
nghttp2_session_del(session);
return NULL;
}
return session;
}
static int create_listen_socket(const char *ip, uint16_t port) {
int fd = socket(AF_INET, SOCK_STREAM, 0);
if (fd < 0) die("socket");
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) < 0) {
die("setsockopt");
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
fprintf(stderr, "inet_pton failed for %s\n", ip);
exit(1);
}
if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) die("bind");
if (listen(fd, 16) < 0) die("listen");
return fd;
}
static void serve_one_connection(int client_fd) {
conn_t conn = {.fd = client_fd};
nghttp2_session *session = setup_h2_session(&conn);
if (!session) {
fprintf(stderr, "failed to setup nghttp2 session\n");
close(client_fd);
return;
}
// Send initial SETTINGS
if (nghttp2_session_send(session) != 0) {
nghttp2_session_del(session);
close(client_fd);
return;
}
uint8_t buf[16 * 1024];
for (;;) {
ssize_t r = recv(client_fd, buf, sizeof(buf), 0);
if (r == 0) {
// peer closed
break;
}
if (r < 0) {
if (errno == EINTR) continue;
perror("recv");
break;
}
// Feed received bytes into nghttp2
ssize_t nread = nghttp2_session_mem_recv(session, buf, (size_t)r);
if (nread < 0) {
fprintf(stderr, "nghttp2_session_mem_recv error: %zd\n", nread);
break;
}
// Let nghttp2 send pending frames (responses, WINDOW_UPDATE, etc.)
int rv = nghttp2_session_send(session);
if (rv != 0) {
fprintf(stderr, "nghttp2_session_send error: %d\n", rv);
break;
}
if (nghttp2_session_want_read(session) == 0 &&
nghttp2_session_want_write(session) == 0) {
break;
}
}
nghttp2_session_del(session);
close(client_fd);
}
int main(void) {
signal(SIGPIPE, SIG_IGN);
const char *ip = "127.0.0.1";
uint16_t port = 8080;
int lfd = create_listen_socket(ip, port);
fprintf(stderr, "Listening on %s:%u (h2c prior-knowledge)\n", ip, port);
fprintf(stderr, "Try: nghttp -v http://%s:%u/ --no-tls --pri\n", ip, port);
for (;;) {
struct sockaddr_in caddr;
socklen_t clen = sizeof(caddr);
int cfd = accept(lfd, (struct sockaddr *)&caddr, &clen);
if (cfd < 0) {
if (errno == EINTR) continue;
die("accept");
}
serve_one_connection(cfd); // sequential / blocking
}
close(lfd);
return 0;
}