R/http.R

Defines functions build_query_string http_request_with_retry http_post http_get

Documented in build_query_string http_get http_post http_request_with_retry

#' Make HTTP GET Request
#'
#' Internal function to make authenticated GET requests to Lifebit Platform API.
#'
#' @param profile List. Profile configuration from load_profile().
#' @param endpoint Character. API endpoint path (without base URL).
#' @param query_params List. Query parameters to include in request.
#'
#' @return Parsed JSON response.
#' @keywords internal
http_get <- function(profile, endpoint, query_params = list()) {
  
  # Build full URL
  base_url <- profile$base_url
  url <- paste0(base_url, endpoint)
  
  # Add teamId to query params if not present
  if (!"teamId" %in% names(query_params)) {
    query_params$teamId <- profile$workspace_id
  }
  
  # Create request with specific options to avoid hanging
  req <- httr2::request(url) |>
    httr2::req_url_query(!!!query_params) |>
    httr2::req_headers(
      "apikey" = profile$apikey,
      "Content-Type" = "application/json",
      "Accept" = "application/json"
    ) |>
    httr2::req_timeout(60) |>
    httr2::req_options(
      connecttimeout = 10,
      tcp_keepalive = 1L,
      tcp_keepidle = 120L,
      tcp_keepintvl = 60L
    ) |>
    httr2::req_retry(max_tries = 1)  # No retries to avoid hanging
  
  # Perform request
  response <- tryCatch({
    httr2::req_perform(req)
  }, error = function(e) {
    stop(sprintf("HTTP request failed: %s", e$message), call. = FALSE)
  })
  
  # Check status
  status <- httr2::resp_status(response)
  if (!(status %in% c(200, 202))) {
    handle_api_error(response, endpoint)
  }
  
  # Parse and return response
  return(parse_json_response(response))
}


#' Make HTTP POST Request
#'
#' Internal function to make authenticated POST requests to Lifebit Platform API.
#'
#' @param profile List. Profile configuration from load_profile().
#' @param endpoint Character. API endpoint path (without base URL).
#' @param body List. Request body to send as JSON.
#' @param query_params List. Query parameters to include in request.
#'
#' @return Parsed JSON response.
#' @keywords internal
http_post <- function(profile, endpoint, body = list(), query_params = list()) {
  
  # Build full URL
  base_url <- profile$base_url
  url <- paste0(base_url, endpoint)
  
  # Add teamId to query params if not present
  if (!"teamId" %in% names(query_params)) {
    query_params$teamId <- profile$workspace_id
  }
  
  # Create request with specific options to avoid hanging
  req <- httr2::request(url) |>
    httr2::req_url_query(!!!query_params) |>
    httr2::req_headers(
      "apikey" = profile$apikey,
      "Content-Type" = "application/json",
      "Accept" = "application/json"
    ) |>
    httr2::req_body_json(body) |>
    httr2::req_timeout(60) |>
    httr2::req_options(
      connecttimeout = 10,
      tcp_keepalive = 1L,
      tcp_keepidle = 120L,
      tcp_keepintvl = 60L
    ) |>
    httr2::req_retry(max_tries = 1)  # No retries to avoid hanging
  
  # Perform request
  response <- tryCatch({
    httr2::req_perform(req)
  }, error = function(e) {
    stop(sprintf("HTTP request failed: %s", e$message), call. = FALSE)
  })
  
  # Check status
  status <- httr2::resp_status(response)
  if (!(status %in% c(200, 202))) {
    handle_api_error(response, endpoint)
  }
  
  # Parse and return response
  return(parse_json_response(response))
}


#' Make HTTP Request with Retry
#'
#' Internal function to make HTTP requests with retry logic for transient failures.
#'
#' @param method Character. HTTP method ("GET" or "POST").
#' @param profile List. Profile configuration from load_profile().
#' @param endpoint Character. API endpoint path (without base URL).
#' @param body List. Request body (for POST requests).
#' @param query_params List. Query parameters.
#' @param max_retries Integer. Maximum number of retries (default: 3).
#' @param retry_delay Integer. Delay between retries in seconds (default: 2).
#'
#' @return Parsed JSON response.
#' @keywords internal
http_request_with_retry <- function(method, 
                                    profile, 
                                    endpoint, 
                                    body = list(), 
                                    query_params = list(),
                                    max_retries = 3,
                                    retry_delay = 2) {
  
  last_error <- NULL
  
  for (attempt in 1:max_retries) {
    tryCatch({
      if (method == "GET") {
        return(http_get(profile, endpoint, query_params))
      } else if (method == "POST") {
        return(http_post(profile, endpoint, body, query_params))
      } else {
        stop(sprintf("Unsupported HTTP method: %s", method))
      }
    }, error = function(e) {
      last_error <<- e
      
      # Don't retry on authentication or authorization errors
      if (grepl("401|403", e$message)) {
        stop(e)
      }
      
      # Don't retry on client errors (4xx except 408)
      if (grepl("40[024-9]", e$message)) {
        stop(e)
      }
      
      # Retry on server errors (5xx) or network errors
      if (attempt < max_retries) {
        message(sprintf(
          "Request failed (attempt %d/%d): %s\nRetrying in %d seconds...",
          attempt,
          max_retries,
          e$message,
          retry_delay
        ))
        Sys.sleep(retry_delay)
      }
    })
  }
  
  # If we get here, all retries failed
  stop(sprintf(
    "Request failed after %d attempts. Last error: %s",
    max_retries,
    last_error$message
  ), call. = FALSE)
}


#' Build Query String
#'
#' Internal function to build URL query string from list of parameters.
#'
#' @param params List. Named list of query parameters.
#'
#' @return Character. URL-encoded query string.
#' @keywords internal
build_query_string <- function(params) {
  if (length(params) == 0) {
    return("")
  }
  
  # Filter out NULL and empty values
  params <- params[!sapply(params, is.null)]
  params <- params[sapply(params, function(x) length(x) > 0 && x != "")]
  
  if (length(params) == 0) {
    return("")
  }
  
  # Build query string
  pairs <- mapply(function(name, value) {
    if (length(value) > 1) {
      # Handle array parameters (e.g., datasourceIds[])
      paste(sapply(value, function(v) {
        sprintf("%s=%s", utils::URLencode(name, reserved = TRUE), utils::URLencode(as.character(v), reserved = TRUE))
      }), collapse = "&")
    } else {
      sprintf("%s=%s", utils::URLencode(name, reserved = TRUE), utils::URLencode(as.character(value), reserved = TRUE))
    }
  }, names(params), params, SIMPLIFY = FALSE, USE.NAMES = FALSE)
  
  paste0("?", paste(unlist(pairs), collapse = "&"))
}

Try the cloudosR package in your browser

Any scripts or data that you put into this service are public.

cloudosR documentation built on June 1, 2026, 5:07 p.m.