From c02b3d845ff0670bc93f285c15c9c6ffe7a457c5 Mon Sep 17 00:00:00 2001 From: Jason Volk Date: Tue, 4 Apr 2023 00:35:48 -0700 Subject: [PATCH] ircd::rest: Add web request interface which isn't orchestrally complicated. --- include/ircd/ircd.h | 1 + include/ircd/rest.h | 197 ++++++++++++++++++++++++++++++++++++++++++++ ircd/Makefile.am | 1 + ircd/rest.cc | 138 +++++++++++++++++++++++++++++++ 4 files changed, 337 insertions(+) create mode 100644 include/ircd/rest.h create mode 100644 ircd/rest.cc diff --git a/include/ircd/ircd.h b/include/ircd/ircd.h index 5e423432f..53312b779 100644 --- a/include/ircd/ircd.h +++ b/include/ircd/ircd.h @@ -107,6 +107,7 @@ #include "mods/mods.h" #include "net/net.h" #include "server/server.h" +#include "rest.h" #include "png.h" #include "beep.h" #include "magick.h" diff --git a/include/ircd/rest.h b/include/ircd/rest.h new file mode 100644 index 000000000..313c74108 --- /dev/null +++ b/include/ircd/rest.h @@ -0,0 +1,197 @@ +// The Construct +// +// Copyright (C) The Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +#pragma once +#define HAVE_IRCD_REST_H + +/// Simple highlevel interface for web/http requests. +/// +/// Prior to this it was too difficult to orchestrate all the objects and +/// buffers and low-level non-ergonomic procedures split between ircd::http +/// and ircd::server. This should instead have some familiarity to the +/// browser-js environment which developers can easily commit to their memory. +namespace ircd::rest +{ + struct opts; + struct request; + + struct get; + struct put; + struct post; +} + +struct ircd::rest::request +:returns +{ + unique_const_buffer out; + + request(const mutable_buffer &out, const rfc3986::uri &, opts); + request(const rfc3986::uri &, opts); +}; + +struct ircd::rest::get +:request +{ + get(const mutable_buffer &out, const rfc3986::uri &, opts); + get(const rfc3986::uri &, opts); +}; + +struct ircd::rest::put +:request +{ + put(const mutable_buffer &out, const rfc3986::uri &, const string_view &content, opts); + put(const rfc3986::uri &, const string_view &content, opts); +}; + +struct ircd::rest::post +:request +{ + post(const mutable_buffer &out, const rfc3986::uri &, const string_view &content, opts); + post(const mutable_buffer &out, const rfc3986::uri &, opts); + post(const rfc3986::uri &, const string_view &content, opts); + post(const rfc3986::uri &, opts); +}; + +struct ircd::rest::opts +{ + /// The HTTP method to use. This is overridden and should not be set unless + /// using the generic rest::request() call where it must be set. + string_view method; + + /// The HTTP request body. This is overridden and should not be set unless + /// using the generic rest::request() call where it's set as needed. + string_view content; + + /// The HTTP request body content-type. It is a good idea to set this when + /// there is request body content. + string_view content_type; + + /// Additional request headers to send. These are pairs of string_views. + vector_view headers; + + /// This is set automatically from the URI argument's domain and scheme + /// (service) by default. Setting it here will override. + net::hostport remote; + + /// Managed internally by default and passed to server::request. Setting + /// things here will override. + server::out sout; + + /// Managed internally by default and passed to server::request. Setting + /// things here will override. + server::in sin; + + /// Passed to server::request. The http_exceptions option is useful here + /// to prevent this suite from throwing on non-2xx codes. + server::request::opts sopts; + + /// Allows the HTTP response code to be returned to the caller. This may + /// not be initialized if the call throws any error first. + http::code *code {nullptr}; + + /// Allows the user to override the request::out with their own for + /// receiving dynamic content. Supply an empty unique_buffer instance. + unique_const_buffer *out {nullptr}; + + /// Timeout for the yielding/synchronous calls of this interface. + seconds timeout {20s}; + + /// Internal use + opts &&set(const string_view &method, const string_view &content = {}); +}; + +inline +ircd::rest::post::post(const rfc3986::uri &uri, + opts opts) +:request +{ + uri, opts.set("POST") +} +{} + +inline +ircd::rest::post::post(const rfc3986::uri &uri, + const string_view &content, + opts opts) +:request +{ + uri, opts.set("POST", content) +} +{} + +inline +ircd::rest::post::post(const mutable_buffer &out, + const rfc3986::uri &uri, + opts opts) +:request +{ + out, uri, opts.set("POST") +} +{} + +inline +ircd::rest::post::post(const mutable_buffer &out, + const rfc3986::uri &uri, + const string_view &content, + opts opts) +:request +{ + out, uri, opts.set("POST", content) +} +{} + +inline +ircd::rest::put::put(const rfc3986::uri &uri, + const string_view &content, + opts opts) +:request +{ + uri, opts.set("PUT", content) +} +{} + +inline +ircd::rest::put::put(const mutable_buffer &out, + const rfc3986::uri &uri, + const string_view &content, + opts opts) +:request +{ + out, uri, opts.set("PUT", content) +} +{} + +inline +ircd::rest::get::get(const rfc3986::uri &uri, + opts opts) +:request +{ + uri, opts.set("GET") +} +{} + +inline +ircd::rest::get::get(const mutable_buffer &out, + const rfc3986::uri &uri, + opts opts) +:request +{ + out, uri, opts.set("GET") +} +{} + +inline ircd::rest::opts && +ircd::rest::opts::set(const string_view &method, + const string_view &content) +{ + this->method = method; + this->content = content?: this->content; + return std::move(*this); +} diff --git a/ircd/Makefile.am b/ircd/Makefile.am index af642abbd..94a075264 100644 --- a/ircd/Makefile.am +++ b/ircd/Makefile.am @@ -237,6 +237,7 @@ endif libircd_la_SOURCES += server.cc libircd_la_SOURCES += client.cc libircd_la_SOURCES += resource.cc +libircd_la_SOURCES += rest.cc if JS libircd_la_SOURCES += js.cc endif diff --git a/ircd/rest.cc b/ircd/rest.cc new file mode 100644 index 000000000..eb5c90fa1 --- /dev/null +++ b/ircd/rest.cc @@ -0,0 +1,138 @@ +// The Construct +// +// Copyright (C) The Construct Developers, Authors & Contributors +// Copyright (C) 2016-2023 Jason Volk +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice is present in all copies. The +// full license for this software is available in the LICENSE file. + +ircd::rest::request::request(const rfc3986::uri &uri, + opts opts) +{ + if(!opts.remote) + opts.remote = net::hostport{uri}; + + const unique_mutable_buffer buf + { + empty(opts.sout.head) || empty(opts.sin.head)? + 16_KiB: 0_KiB + }; + + window_buffer window{buf}; + if(empty(opts.sout.head)) + { + assert(opts.remote); + assert(opts.method); + http::request + { + window, + host(opts.remote), + opts.method, + uri.resource(), + size(opts.content), + opts.content_type, + opts.headers + }; + + opts.sout.head = window.completed(); + } + + if(empty(opts.sout.content)) + opts.sout.content = opts.content; + + if(empty(opts.sin.head)) + opts.sin.head = mutable_buffer{window}; + + // server::request will allocate dynamic content + opts.sin.content = mutable_buffer{}; + + assert(opts.remote); + server::request request + { + opts.remote, + std::move(opts.sout), + std::move(opts.sin), + &opts.sopts, + }; + + const auto code + { + request.get(opts.timeout) + }; + + if(opts.code) + *opts.code = code; + + if(opts.out) + *opts.out = std::move(request.in.dynamic); + else + this->out = std::move(request.in.dynamic); + + ret = request.in.content; +} + +ircd::rest::request::request(const mutable_buffer &out, + const rfc3986::uri &uri, + opts opts) +{ + if(!opts.remote) + opts.remote = net::hostport{uri}; + + const unique_mutable_buffer buf + { + empty(opts.sout.head) || empty(opts.sin.head)? + 16_KiB: 0_KiB + }; + + window_buffer window{buf}; + if(empty(opts.sout.head)) + { + assert(opts.remote); + assert(opts.method); + http::request + { + window, + host(opts.remote), + opts.method, + uri.resource(), + size(opts.content), + opts.content_type, + opts.headers + }; + + opts.sout.head = window.completed(); + } + + if(empty(opts.sout.content)) + opts.sout.content = opts.content; + + if(empty(opts.sin.head)) + opts.sin.head = mutable_buffer{window}; + + if(empty(opts.sin.content)) + opts.sin.content = out; + + if(empty(opts.sin.content)) + opts.sin.content = opts.sin.head; + + assert(opts.remote); + server::request request + { + opts.remote, + std::move(opts.sout), + std::move(opts.sin), + &opts.sopts, + }; + + const auto code + { + request.get(opts.timeout) + }; + + if(opts.code) + *opts.code = code; + + ret = request.in.content; +}