PACS System 0.1.0
PACS DICOM system library
Loading...
Searching...
No Matches
http_client Class Reference

HTTP client for AI service communication. More...

Collaboration diagram for http_client:
Collaboration graph

Public Member Functions

 http_client (const ai_service_config &config)
 
auto post (const std::string &path, const std::string &body, const std::string &content_type="application/json") -> Result< http_response >
 
auto get (const std::string &path) -> Result< http_response >
 
auto del (const std::string &path) -> Result< http_response >
 
auto check_connectivity () -> bool
 
auto measure_latency () -> std::optional< std::chrono::milliseconds >
 

Private Member Functions

auto send_request (const std::string &method, const std::string &path, const std::map< std::string, std::string > &headers, const std::string &body) -> Result< http_response >
 
std::map< std::string, std::string > build_headers () const
 

Static Private Member Functions

static auto parse_http_response (const std::string &raw) -> Result< http_response >
 

Private Attributes

ai_service_config config_
 

Detailed Description

HTTP client for AI service communication.

Uses synchronous POSIX/Winsock sockets for HTTP/1.1 operations. No dependency on network_system internals (messaging_client, thread_manager).

Definition at line 554 of file ai_service_connector.cpp.

Constructor & Destructor Documentation

◆ http_client()

http_client::http_client ( const ai_service_config & config)
inlineexplicit

Definition at line 556 of file ai_service_connector.cpp.

557 : config_(config) {}
ai_service_config config_

Member Function Documentation

◆ build_headers()

std::map< std::string, std::string > http_client::build_headers ( ) const
inlinenodiscardprivate

Definition at line 777 of file ai_service_connector.cpp.

777 {
778 std::map<std::string, std::string> headers;
779 headers["Content-Type"] = "application/json";
780
781 switch (config_.auth_type) {
782 case authentication_type::api_key:
783 headers["X-API-Key"] = config_.api_key;
784 break;
785 case authentication_type::bearer_token:
786 headers["Authorization"] = "Bearer " + config_.bearer_token;
787 break;
788 case authentication_type::basic: {
789 std::string credentials = config_.username + ":" + config_.password;
790 headers["Authorization"] = "Basic " + json_util::base64_encode(credentials);
791 break;
792 }
793 case authentication_type::none:
794 default:
795 break;
796 }
797
798 return headers;
799 }

◆ check_connectivity()

auto http_client::check_connectivity ( ) -> bool
inlinenodiscard

Definition at line 590 of file ai_service_connector.cpp.

590 {
591 auto result = get("/health");
592 return result.is_ok() && result.value().is_success();
593 }
auto get(const std::string &path) -> Result< http_response >

◆ del()

auto http_client::del ( const std::string & path) -> Result<http_response>
inlinenodiscard

Definition at line 581 of file ai_service_connector.cpp.

582 {
584 "AI service DELETE request: {}", config_.base_url + path);
585
586 auto headers = build_headers();
587 return send_request("DELETE", path, headers, "");
588 }
auto send_request(const std::string &method, const std::string &path, const std::map< std::string, std::string > &headers, const std::string &body) -> Result< http_response >
std::map< std::string, std::string > build_headers() const
static void debug(kcenon::pacs::compat::format_string< Args... > fmt, Args &&... args)
Log a debug-level message.

References kcenon::pacs::integration::logger_adapter::debug().

Here is the call graph for this function:

◆ get()

auto http_client::get ( const std::string & path) -> Result<http_response>
inlinenodiscard

Definition at line 572 of file ai_service_connector.cpp.

573 {
575 "AI service GET request: {}", config_.base_url + path);
576
577 auto headers = build_headers();
578 return send_request("GET", path, headers, "");
579 }

References kcenon::pacs::integration::logger_adapter::debug().

Here is the call graph for this function:

◆ measure_latency()

auto http_client::measure_latency ( ) -> std::optional<std::chrono::milliseconds>
inlinenodiscard

Definition at line 595 of file ai_service_connector.cpp.

595 {
596 auto start = std::chrono::steady_clock::now();
597 auto result = get("/health");
598 auto end = std::chrono::steady_clock::now();
599
600 if (!result.is_ok()) {
601 return std::nullopt;
602 }
603
604 return std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
605 }

◆ parse_http_response()

static auto http_client::parse_http_response ( const std::string & raw) -> Result<http_response>
inlinestaticnodiscardprivate

Definition at line 726 of file ai_service_connector.cpp.

727 {
728 http_response response;
729
730 auto header_end = raw.find("\r\n\r\n");
731 if (header_end == std::string::npos) {
732 return error_info(-1, "Malformed HTTP response", "ai_service_connector");
733 }
734
735 // Parse status line
736 auto first_line_end = raw.find("\r\n");
737 std::string status_line = raw.substr(0, first_line_end);
738
739 // Parse "HTTP/1.x STATUS_CODE REASON"
740 auto first_space = status_line.find(' ');
741 if (first_space == std::string::npos) {
742 return error_info(-1, "Invalid HTTP status line", "ai_service_connector");
743 }
744 auto code_start = first_space + 1;
745 try {
746 response.status_code = std::stoi(status_line.substr(code_start));
747 } catch (...) {
748 return error_info(-1, "Invalid HTTP status code", "ai_service_connector");
749 }
750
751 // Parse headers
752 auto headers_str = raw.substr(first_line_end + 2,
753 header_end - first_line_end - 2);
754 std::istringstream header_stream(headers_str);
755 std::string line;
756 while (std::getline(header_stream, line)) {
757 if (!line.empty() && line.back() == '\r') line.pop_back();
758 auto colon = line.find(':');
759 if (colon != std::string::npos) {
760 auto name = line.substr(0, colon);
761 auto value = line.substr(colon + 1);
762 // Trim leading whitespace from value
763 auto val_start = value.find_first_not_of(' ');
764 if (val_start != std::string::npos) {
765 value = value.substr(val_start);
766 }
767 response.headers[name] = value;
768 }
769 }
770
771 // Extract body
772 response.body = raw.substr(header_end + 4);
773
774 return response;
775 }
kcenon::common::error_info error_info
Error information type.
Definition result.h:40
Simple HTTP response structure.
std::map< std::string, std::string > headers
std::string_view name

References http_response::body, http_response::headers, name, and http_response::status_code.

◆ post()

auto http_client::post ( const std::string & path,
const std::string & body,
const std::string & content_type = "application/json" ) -> Result<http_response>
inlinenodiscard

Definition at line 559 of file ai_service_connector.cpp.

562 {
564 "AI service POST request: {} (body size: {} bytes)",
565 config_.base_url + path, body.size());
566
567 auto headers = build_headers();
568 headers["Content-Type"] = content_type;
569 return send_request("POST", path, headers, body);
570 }

References kcenon::pacs::integration::logger_adapter::debug().

Here is the call graph for this function:

◆ send_request()

auto http_client::send_request ( const std::string & method,
const std::string & path,
const std::map< std::string, std::string > & headers,
const std::string & body ) -> Result<http_response>
inlinenodiscardprivate

Definition at line 610 of file ai_service_connector.cpp.

614 {
615
616 std::string full_url = config_.base_url + path;
617 auto url = parsed_url::parse(full_url);
618 if (!url) {
619 return error_info(-1, "Invalid URL: " + full_url, "ai_service_connector");
620 }
621
622 // Resolve hostname
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) {
632 return error_info(-1,
633 "DNS resolution failed for " + url->host + ": " +
634 gai_strerror(gai_err),
635 "ai_service_connector");
636 }
637
638 // RAII cleanup for addrinfo
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 // Create and connect socket
644 socket_handle sock(
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 // Set socket timeout
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 // Connect
670 if (::connect(sock.get(), addr_result->ai_addr,
671 static_cast<int>(addr_result->ai_addrlen)) != 0) {
672 return error_info(-1,
673 "Connection failed to " + url->host + ":" + url->port,
674 "ai_service_connector");
675 }
676
677 // Build HTTP request
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 // Send request
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 // Receive response
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; // Connection closed
719 response_data.append(buffer, static_cast<std::size_t>(received));
720 }
721
722 // Parse HTTP response
723 return parse_http_response(response_data);
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)

References name, and parsed_url::parse().

Here is the call graph for this function:

Member Data Documentation

◆ config_

ai_service_config http_client::config_
private

Definition at line 608 of file ai_service_connector.cpp.


The documentation for this class was generated from the following file: