API Request Flow
This documentation explains the complete flow of an API request in the OpenCloud Mobile app, from initiation to response processing.
Request Flow Overview
A typical API request in the OpenCloud Mobile app goes through the following phases:
- Request Initiation: A component or hook requests data
- Header Creation: Standardized headers are created
- Request Preparation: The request is configured
- Logging (Pre-Request): The request is logged
- Network Request: The actual HTTP request is sent
- Response Processing: The server response is processed
- Logging (Post-Request): The response is logged
- Error Handling: Any errors are handled
- Data Return: The processed data is returned
Detailed Phases
1. Request Initiation
API requests are typically initiated from React components via a hook or directly through a service:
// Example: Initiation via a hook
function UserProfileScreen() {
const { userData, loading, error } = useUserData();
// ...
}
// Example: Direct use of the API service
async function fetchUserData() {
const userData = await ApiService.getCurrentUser();
// ...
}
2. Header Creation
All requests use standardized headers generated via the HttpUtil.createStandardHeaders()
method:
// Create standard headers
const headers = HttpUtil.createStandardHeaders(
true, // includeAuth: Include authentication header?
this.accessToken, // token: The auth token, if included
'application/json' // contentType: Format of the request body
);
Headers include:
Content-Type
: Defines the request body format (default: 'application/json')Accept
: Defines accepted response formatsUser-Agent
: Identifies the app version and platformX-Request-ID
: Unique UUID for request correlationAuthorization
: Auth token, if authentication is required
3. Request Preparation
Request options are created with HttpUtil.createRequestOptions()
:
// Create request options
const options = HttpUtil.createRequestOptions(
'GET', // HTTP method
headers, // Prepared headers
requestBody // Optional: Request body as string
);
Important options:
method
: HTTP method (GET, POST, PUT, DELETE, etc.)redirect: 'manual'
: Prevents automatic following of redirectsheaders
: The standardized headersbody
: Request body (if present)
4. Logging (Pre-Request)
Before sending, the request is logged:
// Log request
HttpUtil.logRequest(
requestId, // UUID for tracking
'API', // Service prefix for logs
url, // Target URL
method, // HTTP method
headers, // Headers (sensitive data is redacted)
body // Request body (if present)
);
// Optional: Generate cURL command for debugging
if (apiConfig.logging?.generateCurlCommands) {
const curlCommand = HttpUtil.generateCurlCommand(url, options);
console.log(`[API:${requestId}] Equivalent curl command:\n${curlCommand}`);
}
5. Network Request
The actual HTTP request is sent using the Fetch API:
// Start timing
const requestStartTime = Date.now();
// Execute request
const response = await fetch(url, options);
// End timing
const requestDuration = Date.now() - requestStartTime;
6. Response Processing
The response is processed based on status code and content type:
// Log response
await HttpUtil.logResponse(requestId, 'API', response, requestDuration);
// Check status code
if (!response.ok) {
// Error handling (see point 8)
throw new Error(`API request failed: ${response.status}`);
}
// Check content type and process response accordingly
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return await response.json();
} else {
return await response.text();
}
7. Logging (Post-Request)
After receipt, the response is logged:
// Response logging with HttpUtil
await HttpUtil.logResponse(
requestId, // UUID for tracking
'API', // Service prefix for logs
response, // The complete response
requestDuration // Request duration in ms
);
Logging includes:
- Response status and status text
- Response headers
- Response time in milliseconds
- With debug logging enabled: Response body (truncated to configured maximum length)
8. Error Handling
Errors are handled in a structured way and enriched with context:
try {
// API request...
} catch (error) {
// Error logging
console.error(`[API:${requestId}] Request failed after ${requestDuration}ms: ${error.message}`);
// Error classification
if (error.name === 'AbortError') {
console.error(`[API:${requestId}] Request timeout`);
} else if (error.name === 'TypeError' && error.message.includes('Network request failed')) {
console.error(`[API:${requestId}] Network connection error`);
}
// Forward error for higher-level handling
throw error;
}
9. Data Return
After successful processing, the data is returned:
// Return data to the caller
return processedData;
Special Use Cases
Handling Redirects
For redirect responses (status codes 300-399), these are processed manually:
if (response.status >= 300 && response.status < 400) {
const redirectUrl = response.headers.get('location');
return { redirectUrl, status: response.status };
}
Authenticated vs. Non-Authenticated Requests
- Authenticated Requests: Use the stored access token in the Authorization header
- Non-Authenticated Requests: Send no auth headers (e.g., WebFinger or OIDC configuration requests)
Disabling Logging Functionality
Logging can be controlled via configuration:
// In config/app.config.ts
logging: {
maxBodyLogLength: 1000,
generateCurlCommands: false, // Set to true to enable curl command generation
enableDebugLogging: false, // Set to true to enable detailed logging
}
Example of a Complete Request Flow
Here is a complete example of the API request flow for retrieving user information:
async function getCurrentUser() {
const operationId = HttpUtil.generateUuid();
try {
console.log(`[API:${operationId}] Retrieving user information`);
// Try different API endpoints (with fallback)
try {
console.log(`[API:${operationId}] Trying Graph endpoint: /graph/v1.0/me`);
// Create headers
const headers = HttpUtil.createStandardHeaders(true, this.accessToken);
// Create request options
const options = HttpUtil.createRequestOptions('GET', headers);
// Create URL
const url = `${this.baseUrl}/graph/v1.0/me?$expand=memberOf`;
// Log request
HttpUtil.logRequest(operationId, 'API', url, 'GET', headers);
// Optional: Generate cURL command
if (apiConfig.logging?.generateCurlCommands) {
const curlCommand = HttpUtil.generateCurlCommand(url, options);
console.log(`[API:${operationId}] Equivalent curl command:\n${curlCommand}`);
}
// Send request
const startTime = Date.now();
const response = await fetch(url, options);
const duration = Date.now() - startTime;
// Log response
await HttpUtil.logResponse(operationId, 'API', response, duration);
// Check for errors
if (!response.ok) {
throw new Error(`API request failed: ${response.status}`);
}
// Process and return response
return await response.json();
} catch (graphError) {
// Fallback to regular endpoint on error
console.error(`[API:${operationId}] Graph endpoint failed:`, graphError.message);
// Similar process for the fallback endpoint...
// [...]
}
} catch (error) {
// General error handling
console.error(`[API:${operationId}] Error retrieving user information:`, error.message);
throw error;
}
}
Key Concepts and Best Practices
- Uniformity: All requests use the same utilities and standards
- Traceability: Each request has a unique ID for end-to-end tracking
- Security: Manual redirect control and redaction of sensitive data in logs
- Error Handling: Structured error capture and reporting
- Performance Measurement: Time measurement for each request for performance monitoring
- Configurability: Logging behavior is controllable via configuration
- Unified API: Consider using the new
HttpUtil.performRequest()
method for all requests