#include "ayla/httpc.hpp" #include #include #include #include "ayla/log.hpp" #include "ayla/platform/platform.hpp" namespace fgl::ayla { namespace { // Читает статус-строку "HTTP/1.x NNN ..."; возвращает код или -1. int read_status(int fd) { char line[64]; size_t len = 0; for (;;) { if (len + 1 >= sizeof(line)) return -1; uint8_t ch; long n = fgl::plat::tcp_recv(fd, &ch, 1); if (n <= 0) return -1; if (ch == '\n') break; line[len++] = static_cast(ch); } // "HTTP/1.1 202 Accepted" if (len < 12 || strncmp(line, "HTTP/", 5) != 0) return -1; const char* sp = strchr(line, ' '); if (sp == nullptr) return -1; int code = atoi(sp + 1); return (code >= 200 && code <= 599) ? code : -1; } // Дочитывает заголовки до пустой строки и тело по Content-Length (до лимита). bool drain_response(int fd) { char line[256]; long content_length = 0; for (;;) { size_t i = 0; for (;;) { if (i + 1 >= sizeof(line)) return false; uint8_t ch; long n = fgl::plat::tcp_recv(fd, &ch, 1); if (n <= 0) return false; if (ch == '\n') break; line[i++] = static_cast(ch); } if (i > 0 && line[i - 1] == '\r') i--; line[i] = '\0'; if (i == 0) break; // конец заголовков if ((line[0] | 0x20) == 'c' && strncasecmp(line, "Content-Length:", 15) == 0) { content_length = strtol(line + 15, nullptr, 10); } } if (content_length > 0) { uint8_t sink[256]; long remaining = content_length; if (remaining > 4096) remaining = 4096; // тела local_reg нет — ограничим while (remaining > 0) { size_t chunk = remaining < static_cast(sizeof(sink)) ? static_cast(remaining) : sizeof(sink); long n = fgl::plat::tcp_recv(fd, sink, chunk); if (n <= 0) break; remaining -= n; } } return true; } } // namespace bool httpc_perform(const HttpcRequest& req, HttpcResponse* resp) { if (resp == nullptr || req.host == nullptr) return false; resp->status = 0; resp->transport_ok = false; int fd = fgl::plat::tcp_connect(req.host, req.port, req.timeout_ms); if (fd < 0) { FGL_LOGD("httpc: connect %s:%u failed", req.host, static_cast(req.port)); return false; } fgl::plat::tcp_set_timeout(fd, req.timeout_ms, req.timeout_ms); char head[256]; int n = snprintf(head, sizeof(head), "%s %s%s%s HTTP/1.1\r\n" "Host: %s\r\n" "Accept: application/json\r\n" "Connection: close\r\n" "Content-Type: %s\r\n" "Content-Length: %u\r\n" "\r\n", req.method, req.path, req.query != nullptr ? "?" : "", req.query != nullptr ? req.query : "", req.host, req.content_type, static_cast(req.body_len)); if (n <= 0 || static_cast(n) >= sizeof(head)) { fgl::plat::tcp_close(fd); return false; } bool sent = fgl::plat::tcp_send(fd, head, static_cast(n)) == n; if (sent && req.body != nullptr && req.body_len > 0) { sent = fgl::plat::tcp_send(fd, req.body, req.body_len) == static_cast(req.body_len); } if (!sent) { FGL_LOGD("httpc: send failed"); fgl::plat::tcp_close(fd); return false; } int status = read_status(fd); if (status < 0) { fgl::plat::tcp_close(fd); return false; } drain_response(fd); fgl::plat::tcp_shutdown(fd); fgl::plat::tcp_close(fd); resp->status = status; resp->transport_ok = true; FGL_LOGD("httpc: %s %s -> %d", req.method, req.path, status); return true; } } // namespace fgl::ayla