| 367 | // ============================================================================ |
| 368 | |
| 369 | Error HttpCommand::execute(const char* value, AuthenticationLevel auth_level, Channel& out) { |
| 370 | // Check for command substitution (@commandname) |
| 371 | std::string command_value = value; |
| 372 | if (value && value[0] == '@') { |
| 373 | std::string command_name = value + 1; |
| 374 | auto it = _commands.find(command_name); |
| 375 | if (it != _commands.end()) { |
| 376 | command_value = it->second; |
| 377 | log_debug("HTTP: Substituting command @" << command_name); |
| 378 | } else { |
| 379 | log_error_to(out, "HTTP: Unknown command '@" << command_name << "'"); |
| 380 | return Error::InvalidValue; |
| 381 | } |
| 382 | } |
| 383 | |
| 384 | // Parse command first so we can check fail_on_error |
| 385 | std::string url; |
| 386 | std::string json_options; |
| 387 | if (!parse_command(command_value.c_str(), url, json_options)) { |
| 388 | log_error_to(out, "HTTP: Invalid command format. Use: $HTTP/COMMAND=url or $HTTP/COMMAND=url{json}"); |
| 389 | return Error::InvalidStatement; // Syntax errors always fail |
| 390 | } |
| 391 | |
| 392 | // Build request |
| 393 | HttpRequest request; |
| 394 | request.url = url; |
| 395 | |
| 396 | if (!json_options.empty() && !parse_json_options(json_options, request)) { |
| 397 | log_error_to(out, "HTTP: Failed to parse JSON options"); |
| 398 | return Error::InvalidValue; // Syntax errors always fail |
| 399 | } |
| 400 | |
| 401 | // Substitute tokens (${token_name} patterns) in URL, headers, and body |
| 402 | request.url = substitute_tokens(request.url); |
| 403 | request.body = substitute_tokens(request.body); |
| 404 | for (auto& header : request.headers) { |
| 405 | header.second = substitute_tokens(header.second); |
| 406 | } |
| 407 | |
| 408 | // Variables to hold response data |
| 409 | int status_code = 0; |
| 410 | uint32_t bytes_received = 0; |
| 411 | |
| 412 | // Check WiFi connection (runtime error - respects fail_on_error) |
| 413 | if (WiFi.status() != WL_CONNECTED) { |
| 414 | log_error_to(out, "HTTP: WiFi not connected"); |
| 415 | if (request.fail_on_error) { |
| 416 | return Error::MessageFailed; |
| 417 | } |
| 418 | // Create empty response for no-extraction case |
| 419 | status_code = 0; |
| 420 | bytes_received = 0; |
| 421 | store_response_params(status_code, bytes_received); |
| 422 | return Error::Ok; |
| 423 | } |
| 424 | |
| 425 | // Warn if in Cycle state |
| 426 | if (state_is(State::Cycle)) { |