614 {
615
616 std::string full_url =
config_.base_url + path;
618 if (!url) {
619 return error_info(-1,
"Invalid URL: " + full_url,
"ai_service_connector");
620 }
621
622
623 struct addrinfo hints{};
624 hints.ai_family = AF_UNSPEC;
625 hints.ai_socktype = SOCK_STREAM;
626 hints.ai_protocol = IPPROTO_TCP;
627
628 struct addrinfo* addr_result = nullptr;
629 int gai_err = getaddrinfo(url->host.c_str(), url->port.c_str(),
630 &hints, &addr_result);
631 if (gai_err != 0) {
633 "DNS resolution failed for " + url->host + ": " +
634 gai_strerror(gai_err),
635 "ai_service_connector");
636 }
637
638
639 auto addr_cleanup = [](struct addrinfo* p) { freeaddrinfo(p); };
640 std::unique_ptr<struct addrinfo, decltype(addr_cleanup)>
641 addr_guard(addr_result, addr_cleanup);
642
643
645 ::socket(addr_result->ai_family, addr_result->ai_socktype,
646 addr_result->ai_protocol));
647 if (!sock.valid()) {
648 return error_info(-1,
"Failed to create socket",
"ai_service_connector");
649 }
650
651
652 auto timeout_sec = std::chrono::duration_cast<std::chrono::seconds>(
653 config_.connection_timeout).count();
654 if (timeout_sec <= 0) timeout_sec = 30;
655
656#ifdef _WIN32
657 DWORD tv = static_cast<DWORD>(timeout_sec * 1000);
658 setsockopt(sock.get(), SOL_SOCKET, SO_RCVTIMEO,
659 reinterpret_cast<const char*>(&tv), sizeof(tv));
660 setsockopt(sock.get(), SOL_SOCKET, SO_SNDTIMEO,
661 reinterpret_cast<const char*>(&tv), sizeof(tv));
662#else
663 struct timeval tv{};
664 tv.tv_sec = timeout_sec;
665 setsockopt(sock.get(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
666 setsockopt(sock.get(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
667#endif
668
669
670 if (::connect(sock.get(), addr_result->ai_addr,
671 static_cast<int>(addr_result->ai_addrlen)) != 0) {
673 "Connection failed to " + url->host + ":" + url->port,
674 "ai_service_connector");
675 }
676
677
678 std::ostringstream request_stream;
679 request_stream << method << " " << url->path << " HTTP/1.1\r\n";
680 request_stream << "Host: " << url->host << "\r\n";
681 request_stream << "Connection: close\r\n";
682
683 for (
const auto& [
name, value] : headers) {
684 request_stream <<
name <<
": " << value <<
"\r\n";
685 }
686
687 if (!body.empty()) {
688 request_stream << "Content-Length: " << body.size() << "\r\n";
689 }
690 request_stream << "\r\n";
691 request_stream << body;
692
693 std::string request_data = request_stream.str();
694
695
696 auto total_sent = std::size_t{0};
697 while (total_sent < request_data.size()) {
698 auto sent = ::send(sock.get(),
699 request_data.data() + total_sent,
700 static_cast<int>(request_data.size() - total_sent),
701 0);
702 if (sent <= 0) {
703 return error_info(-1,
"Failed to send HTTP request",
704 "ai_service_connector");
705 }
706 total_sent += static_cast<std::size_t>(sent);
707 }
708
709
710 std::string response_data;
711 char buffer[4096];
712 while (true) {
713 auto received = ::recv(sock.get(), buffer, sizeof(buffer), 0);
714 if (received < 0) {
715 return error_info(-1,
"Failed to receive HTTP response",
716 "ai_service_connector");
717 }
718 if (received == 0) break;
719 response_data.append(buffer, static_cast<std::size_t>(received));
720 }
721
722
724 }
static auto parse_http_response(const std::string &raw) -> Result< http_response >
RAII wrapper for platform socket handle.
static std::optional< parsed_url > parse(const std::string &url)