Skip to content

Commit

Permalink
http: The big HTTP API refactoring of January 2025.
Browse files Browse the repository at this point in the history
This represents a major change in the HTTP code base, consisting
of a complete revamp of the HTTP API. The changes here are too
numerous to mention, but the end result should be a vastly
simpler API for both server and client applications.

Many needless allocations were removed by providing fixed buffers
for various parameters and headers when possible.

A few bugs were fixed. Most especially we have fixed some bugs
around very large URIs and headers, and we have also addressed
conformance bugs to more closely conform to RFCs 9110 and 9112.

As part of this work, the APIs for WebSockets changed slightly
as well.  In particular the properties available for accessing
headers have changed.

There is still documentation conversion work to do, and additional
functionality (such as proper support for chunked transfers), but
this is a big step in the right direction.
  • Loading branch information
gdamore committed Jan 10, 2025
1 parent a381af4 commit 73f50e2
Show file tree
Hide file tree
Showing 98 changed files with 2,251 additions and 5,372 deletions.
25 changes: 11 additions & 14 deletions demo/http_client/http_client.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2018 Staysail Systems, Inc. <[email protected]>
// Copyright 2025 Staysail Systems, Inc. <[email protected]>
// Copyright 2018 Capitar IT Group BV <[email protected]>
//
// This software is supplied under the terms of the MIT License, a
Expand Down Expand Up @@ -33,8 +33,8 @@
// % ./http_client http://httpbin.org/ip
//

#include <nng/http.h>
#include <nng/nng.h>
#include <nng/supplemental/http/http.h>
#include <stdio.h>
#include <stdlib.h>

Expand All @@ -49,11 +49,9 @@ int
main(int argc, char **argv)
{
nng_http_client *client;
nng_http_conn *conn;
nng_http *conn;
nng_url *url;
nng_aio *aio;
nng_http_req *req;
nng_http_res *res;
const char *hdr;
int rv;
int len;
Expand All @@ -66,12 +64,12 @@ main(int argc, char **argv)
}

if (((rv = nng_init(NULL)) != 0) ||
((rv = nng_aio_alloc(&aio, NULL, NULL)) != 0) ||
((rv = nng_url_parse(&url, argv[1])) != 0) ||
((rv = nng_http_client_alloc(&client, url)) != 0) ||
((rv = nng_http_req_alloc(&req, url)) != 0) ||
((rv = nng_http_res_alloc(&res)) != 0) ||
((rv = nng_aio_alloc(&aio, NULL, NULL)) != 0)) {
fatal(rv);
return 1;
}

// Start connection process...
Expand All @@ -90,30 +88,29 @@ main(int argc, char **argv)
// The Host: header is already set up too.

// Send the request, and wait for that to finish.
nng_http_conn_write_req(conn, req, aio);
nng_http_write_request(conn, aio);
nng_aio_wait(aio);

if ((rv = nng_aio_result(aio)) != 0) {
fatal(rv);
}

// Read a response.
nng_http_conn_read_res(conn, res, aio);
nng_http_read_response(conn, aio);
nng_aio_wait(aio);

if ((rv = nng_aio_result(aio)) != 0) {
fatal(rv);
}

if (nng_http_res_get_status(res) != NNG_HTTP_STATUS_OK) {
if (nng_http_get_status(conn) != NNG_HTTP_STATUS_OK) {
fprintf(stderr, "HTTP Server Responded: %d %s\n",
nng_http_res_get_status(res),
nng_http_res_get_reason(res));
nng_http_get_status(conn), nng_http_get_reason(conn));
}

// This only supports regular transfer encoding (no Chunked-Encoding,
// and a Content-Length header is required.)
if ((hdr = nng_http_res_get_header(res, "Content-Length")) == NULL) {
if ((hdr = nng_http_get_header(conn, "Content-Length")) == NULL) {
fprintf(stderr, "Missing Content-Length header.\n");
exit(1);
}
Expand All @@ -134,7 +131,7 @@ main(int argc, char **argv)
nng_aio_set_iov(aio, 1, &iov);

// Now attempt to receive the data.
nng_http_conn_read_all(conn, aio);
nng_http_read_all(conn, aio);

// Wait for it to complete.
nng_aio_wait(aio);
Expand Down
1 change: 0 additions & 1 deletion demo/reqrep/reqrep.c
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,6 @@ client(const char *url)
nng_socket sock;
nng_dialer dialer;
int rv;
size_t sz;
int sleep = 0;

if ((rv = nng_init(NULL)) != 0) {
Expand Down
63 changes: 21 additions & 42 deletions demo/rest/server.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,8 @@
// GRFG
//

#include <nng/http.h>
#include <nng/nng.h>
#include <nng/protocol/reqrep0/rep.h>
#include <nng/protocol/reqrep0/req.h>
#include <nng/supplemental/http/http.h>
#include <nng/supplemental/util/platform.h>

#include <ctype.h>
#include <stdio.h>
Expand Down Expand Up @@ -74,12 +71,12 @@ typedef enum {

typedef struct rest_job {
nng_aio *http_aio; // aio from HTTP we must reply to
nng_http_res *http_res; // HTTP response object
job_state state; // 0 = sending, 1 = receiving
nng_msg *msg; // request message
nng_aio *aio; // request flow
nng_ctx ctx; // context on the request socket
struct rest_job *next; // next on the freelist
nng_http *conn;
struct rest_job *next; // next on the freelist
} rest_job;

nng_socket req_sock;
Expand All @@ -94,10 +91,6 @@ static void rest_job_cb(void *arg);
static void
rest_recycle_job(rest_job *job)
{
if (job->http_res != NULL) {
nng_http_res_free(job->http_res);
job->http_res = NULL;
}
if (job->msg != NULL) {
nng_msg_free(job->msg);
job->msg = NULL;
Expand Down Expand Up @@ -136,20 +129,13 @@ rest_get_job(void)
}

static void
rest_http_fatal(rest_job *job, const char *fmt, int rv)
rest_http_fatal(rest_job *job, int rv)
{
char buf[128];
nng_aio *aio = job->http_aio;
nng_http_res *res = job->http_res;

job->http_res = NULL;
job->http_aio = NULL;
snprintf(buf, sizeof(buf), fmt, nng_strerror(rv));
nng_http_res_set_status(res, NNG_HTTP_STATUS_INTERNAL_SERVER_ERROR);
nng_http_res_set_reason(res, buf);

nng_aio_set_output(aio, 0, res);
nng_aio_finish(aio, 0);
nng_aio *aio = job->http_aio;

// let the server give the details, we could have done more here
// ourselves if we wanted a detailed message
nng_aio_finish(aio, rv);
rest_recycle_job(job);
}

Expand All @@ -163,7 +149,7 @@ rest_job_cb(void *arg)
switch (job->state) {
case SEND_REQ:
if ((rv = nng_aio_result(aio)) != 0) {
rest_http_fatal(job, "send REQ failed: %s", rv);
rest_http_fatal(job, rv);
return;
}
job->msg = NULL;
Expand All @@ -174,23 +160,20 @@ rest_job_cb(void *arg)
break;
case RECV_REP:
if ((rv = nng_aio_result(aio)) != 0) {
rest_http_fatal(job, "recv reply failed: %s", rv);
rest_http_fatal(job, rv);
return;
}
job->msg = nng_aio_get_msg(aio);
// We got a reply, so give it back to the server.
rv = nng_http_res_copy_data(job->http_res,
nng_msg_body(job->msg), nng_msg_len(job->msg));
rv = nng_http_copy_body(
job->conn, nng_msg_body(job->msg), nng_msg_len(job->msg));
if (rv != 0) {
rest_http_fatal(job, "nng_http_res_copy_data: %s", rv);
rest_http_fatal(job, rv);
return;
}
// Set the output - the HTTP server will send it back to the
// user agent with a 200 response.
nng_aio_set_output(job->http_aio, 0, job->http_res);
nng_http_set_status(job->conn, NNG_HTTP_STATUS_OK, NULL);
nng_aio_finish(job->http_aio, 0);
job->http_aio = NULL;
job->http_res = NULL;
// We are done with the job.
rest_recycle_job(job);
return;
Expand All @@ -203,33 +186,29 @@ rest_job_cb(void *arg)
// Our rest server just takes the message body, creates a request ID
// for it, and sends it on. This runs in raw mode, so
void
rest_handle(nng_aio *aio)
rest_handle(nng_http *conn, void *arg, nng_aio *aio)
{
struct rest_job *job;
nng_http_req *req = nng_aio_get_input(aio, 0);
nng_http_conn *conn = nng_aio_get_input(aio, 2);
const char *clen;
size_t sz;
nng_iov iov;
int rv;
void *data;

if ((job = rest_get_job()) == NULL) {
nng_aio_finish(aio, NNG_ENOMEM);
return;
}
if (((rv = nng_http_res_alloc(&job->http_res)) != 0) ||
((rv = nng_ctx_open(&job->ctx, req_sock)) != 0)) {
job->conn = conn;
if (((rv = nng_ctx_open(&job->ctx, req_sock)) != 0)) {
rest_recycle_job(job);
nng_aio_finish(aio, rv);
return;
}

nng_http_req_get_data(req, &data, &sz);
nng_http_get_body(conn, &data, &sz);
job->http_aio = aio;

if ((rv = nng_msg_alloc(&job->msg, sz)) != 0) {
rest_http_fatal(job, "nng_msg_alloc: %s", rv);
rest_http_fatal(job, rv);
return;
}

Expand Down Expand Up @@ -329,7 +308,7 @@ inproc_server(void *arg)
fatal("inproc recvmsg", rv);
}
body = nng_msg_body(msg);
for (int i = 0; i < nng_msg_len(msg); i++) {
for (size_t i = 0; i < nng_msg_len(msg); i++) {
// Table lookup would be faster, but this works.
if (isupper(body[i])) {
char base = body[i] - 'A';
Expand Down
39 changes: 0 additions & 39 deletions docs/man/libnng.3.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -99,61 +99,23 @@ The following functions are used to work with HTTP requests, responses,
and connections.

|===
|xref:nng_http_conn_close.3http.adoc[nng_http_conn_close()]|close HTTP connection
|xref:nng_http_conn_read.3http.adoc[nng_http_conn_read()]|read from HTTP connection
|xref:nng_http_conn_read_all.3http.adoc[nng_http_conn_read_all()]|read all from HTTP connection
|xref:nng_http_conn_read_req.3http.adoc[nng_http_conn_read_req()]|read HTTP request
|xref:nng_http_conn_read_res.3http.adoc[nng_http_conn_read_res()]|read HTTP response
|xref:nng_http_conn_write.3http.adoc[nng_http_conn_write()]|write to HTTP connection
|xref:nng_http_conn_write_all.3http.adoc[nng_http_conn_write_all()]|write all to HTTP connection
|xref:nng_http_conn_write_req.3http.adoc[nng_http_conn_write_req()]|write HTTP request
|xref:nng_http_conn_write_res.3http.adoc[nng_http_conn_write_res()]|write HTTP response
|xref:nng_http_req_add_header.3http.adoc[nng_http_req_add_header()]|add HTTP request header
|xref:nng_http_req_alloc.3http.adoc[nng_http_req_alloc()]|allocate HTTP request structure
|xref:nng_http_req_copy_data.3http.adoc[nng_http_req_copy_data()]|copy HTTP request body
|xref:nng_http_req_del_header.3http.adoc[nng_http_req_del_header()]|delete HTTP request header
|xref:nng_http_req_free.3http.adoc[nng_http_req_free()]|free HTTP request structure
|xref:nng_http_req_get_data.3http.adoc[nng_http_req_get_data()]|get HTTP request body
|xref:nng_http_req_get_header.3http.adoc[nng_http_req_get_header()]|return HTTP request header
|xref:nng_http_req_get_method.3http.adoc[nng_http_req_get_method()]|return HTTP request method
|xref:nng_http_req_get_uri.3http.adoc[nng_http_req_get_uri()]|return HTTP request URI
|xref:nng_http_req_get_version.3http.adoc[nng_http_req_get_version()]|return HTTP request protocol version
|xref:nng_http_req_reset.3http.adoc[nng_http_req_reset()]|reset HTTP request structure
|xref:nng_http_req_set_data.3http.adoc[nng_http_req_set_data()]|set HTTP request body
|xref:nng_http_req_set_header.3http.adoc[nng_http_req_set_header()]|set HTTP request header
|xref:nng_http_req_set_method.3http.adoc[nng_http_req_set_method()]|set HTTP request method
|xref:nng_http_req_set_uri.3http.adoc[nng_http_req_set_uri()]|set HTTP request URI
|xref:nng_http_req_set_version.3http.adoc[nng_http_req_set_version()]|set HTTP request protocol version
|xref:nng_http_res_add_header.3http.adoc[nng_http_res_add_header()]|add HTTP response header
|xref:nng_http_res_alloc.3http.adoc[nng_http_res_alloc()]|allocate HTTP response structure
|xref:nng_http_res_alloc_error.3http.adoc[nng_http_res_alloc_error()]|allocate HTTP error response
|xref:nng_http_res_copy_data.3http.adoc[nng_http_res_copy_data()]|copy HTTP response body
|xref:nng_http_res_del_header.3http.adoc[nng_http_res_del_header()]|delete HTTP response header
|xref:nng_http_res_free.3http.adoc[nng_http_res_free()]|free HTTP response structure
|xref:nng_http_res_get_data.3http.adoc[nng_http_res_get_data()]|get HTTP response body
|xref:nng_http_res_get_header.3http.adoc[nng_http_res_get_header()]|return HTTP response header
|xref:nng_http_res_get_reason.3http.adoc[nng_http_res_get_reason()]|return HTTP response reason
|xref:nng_http_res_get_status.3http.adoc[nng_http_res_get_status()]|return HTTP response status
|xref:nng_http_res_get_version.3http.adoc[nng_http_res_get_version()]|return HTTP response protocol version
|xref:nng_http_res_reset.3http.adoc[nng_http_res_reset()]|reset HTTP response structure
|xref:nng_http_res_set_data.3http.adoc[nng_http_res_set_data()]|set HTTP response body
|xref:nng_http_res_set_header.3http.adoc[nng_http_res_set_header()]|set HTTP response header
|xref:nng_http_res_set_reason.3http.adoc[nng_http_res_set_reason()]|set HTTP response reason
|xref:nng_http_res_set_status.3http.adoc[nng_http_res_set_status()]|set HTTP response status
|xref:nng_http_res_set_version.3http.adoc[nng_http_res_set_version()]|set HTTP response protocol version
|===

==== HTTP Client Functions

These functions are intended for use with HTTP client applications.

|===
|xref:nng_http_client_alloc.3http.adoc[nng_http_client_alloc()]|allocate HTTP client
|xref:nng_http_client_connect.3http.adoc[nng_http_client_connect()]|establish HTTP client connection
|xref:nng_http_client_free.3http.adoc[nng_http_client_free()]|free HTTP client
|xref:nng_http_client_get_tls.3http.adoc[nng_http_client_get_tls()]|get HTTP client TLS configuration
|xref:nng_http_client_set_tls.3http.adoc[nng_http_client_set_tls()]|set HTTP client TLS configuration
|xref:nng_http_client_transact.3http.adoc[nng_http_client_transact()]|perform one HTTP transaction
|xref:nng_http_conn_transact.3http.adoc[nng_http_conn_transact()]|perform one HTTP transaction on connection
|===

Expand All @@ -170,7 +132,6 @@ These functions are intended for use with HTTP server applications.
|xref:nng_http_handler_set_host.3http.adoc[nng_http_handler_set_host()]|set host for HTTP handler
|xref:nng_http_handler_set_method.3http.adoc[nng_http_handler_set_method()]|set HTTP handler method
|xref:nng_http_handler_set_tree.3http.adoc[nng_http_handler_set_tree()]|set HTTP handler to match trees
|xref:nng_http_hijack.3http.adoc[nng_http_hijack()]|hijack HTTP server connection
|xref:nng_http_server_add_handler.3http.adoc[nng_http_server_add_handler()]|add HTTP server handler
|xref:nng_http_server_del_handler.3http.adoc[nng_http_server_del_handler()]|delete HTTP server handler
|xref:nng_http_server_get_addr.3http.adoc[nng_http_server_get_addr()]|get HTTP server address
Expand Down
1 change: 0 additions & 1 deletion docs/man/nng.7.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ xref:nng_socket.7.adoc[nng_socket(7)]:: BSD socket transport
xref:nng_tls.7.adoc[nng_tls(7)]:: TLSv1.2 over TCP transport
xref:nng_tcp.7.adoc[nng_tcp(7)]:: TCP (and TCPv6) transport
xref:nng_ws.7.adoc[nng_ws(7)]:: WebSocket transport
xref:nng_zerotier.7.adoc[nng_zerotier(7)]:: ZeroTier transport

=== Conceptual Overview

Expand Down
49 changes: 0 additions & 49 deletions docs/man/nng_http_client_alloc.3http.adoc

This file was deleted.

Loading

0 comments on commit 73f50e2

Please sign in to comment.