diff --git a/include/seastar/http/request.hh b/include/seastar/http/request.hh index 3e7757c1eae..a348915222d 100644 --- a/include/seastar/http/request.hh +++ b/include/seastar/http/request.hh @@ -422,6 +422,28 @@ public: */ static request make(httpd::operation_type type, sstring host, sstring path); + /** + * \brief Make simple request to an address + * + * \param method - method to use, e.g. "GET" + * \param addr - the address to contact. The "Host" header is its RFC 3986 + * authority form, i.e. an IPv6 address is bracketed. + * \path - the URL to send the request to + * + */ + static request make(sstring method, const socket_address& addr, sstring path); + + /** + * \brief Make simple request to an address + * + * \param method - method to use, e.g. operation_type::GET + * \param addr - the address to contact. The "Host" header is its RFC 3986 + * authority form, i.e. an IPv6 address is bracketed. + * \path - the URL to send the request to + * + */ + static request make(httpd::operation_type type, const socket_address& addr, sstring path); + sstring request_line() const; future<> write_request_headers(output_stream& out) const; private: @@ -430,6 +452,11 @@ private: }; namespace internal { + +/// The RFC 3986 ยง3.2.2 authority for \param addr: "host:port", with an IPv6 +/// address bracketed. A zone index is not part of an authority and is dropped. +sstring format_authority(const socket_address& addr); + #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" inline sstring& deprecated_content(request& req) noexcept { return req.content; } diff --git a/src/http/request.cc b/src/http/request.cc index 169e2d9432a..b4ecdf094ff 100644 --- a/src/http/request.cc +++ b/src/http/request.cc @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -168,5 +169,23 @@ request request::make(httpd::operation_type type, sstring host, sstring path) { return make(httpd::type2str(type), std::move(host), std::move(path)); } +request request::make(sstring method, const socket_address& addr, sstring path) { + return make(std::move(method), internal::format_authority(addr), std::move(path)); +} + +request request::make(httpd::operation_type type, const socket_address& addr, sstring path) { + return make(httpd::type2str(type), internal::format_authority(addr), std::move(path)); +} + +sstring internal::format_authority(const socket_address& addr) { + auto ip = addr.addr(); + if (ip.is_ipv6()) { + // inet_address prints a scope as "%", which an authority cannot + // carry; build the address without one. + return format("[{}]:{:d}", net::inet_address(static_cast<::in6_addr>(ip)), addr.port()); + } + return format("{}:{:d}", ip, addr.port()); +} + } // http namespace } // seastar namespace diff --git a/tests/unit/httpd_test.cc b/tests/unit/httpd_test.cc index 2f08c6ac8a9..3f207de008f 100644 --- a/tests/unit/httpd_test.cc +++ b/tests/unit/httpd_test.cc @@ -2434,6 +2434,22 @@ BOOST_AUTO_TEST_CASE(test_http_status_classification) { BOOST_REQUIRE_EQUAL(unclassified, 300); } +SEASTAR_TEST_CASE(test_format_authority) { + BOOST_REQUIRE_EQUAL(http::internal::format_authority(socket_address(ipv4_addr("10.0.0.1", 80))), "10.0.0.1:80"); + BOOST_REQUIRE_EQUAL(http::internal::format_authority(socket_address(ipv6_addr("2001:db8::1", 8080))), "[2001:db8::1]:8080"); + BOOST_REQUIRE_EQUAL(http::internal::format_authority(socket_address(ipv6_addr("::1", 0))), "[::1]:0"); + // a zone belongs to the host, not to the authority + BOOST_REQUIRE_EQUAL(http::internal::format_authority(socket_address(ipv6_addr("fe80::1", 8080), 2)), "[fe80::1]:8080"); + + // and the request overload puts exactly that in the Host header + auto v6 = http::request::make("GET", socket_address(ipv6_addr("::1", 8080)), "/test"); + BOOST_REQUIRE_EQUAL(v6.get_header("Host"), "[::1]:8080"); + BOOST_REQUIRE_EQUAL(v6.get_url(), "http://[::1]:8080/test"); + auto v4 = http::request::make(GET, socket_address(ipv4_addr("127.0.0.1", 80)), "/test"); + BOOST_REQUIRE_EQUAL(v4.get_header("Host"), "127.0.0.1:80"); + return make_ready_future(); +} + // An IPv4 client of a `[::]` listener is an IPv4 client to the handler, not the // ::ffff:a.b.c.d the kernel reports. SEASTAR_THREAD_TEST_CASE(test_dual_stack_listener_reports_ipv4_client) {