Chapter 3: Request and Response Configuration

Haiyue
23min

Chapter 3: Request and Response Configuration

Learning Objectives

  1. Understand the structure of the request configuration object
  2. Master setting and managing request headers
  3. Learn to configure timeout and retry mechanisms
  4. Understand the structure and handling of response data
  5. Master judging response status codes

3.1 Request Configuration Object Details

Complete Request Configuration Structure

// Axios complete request configuration options
const fullRequestConfig = {
  // Request URL
  url: '/api/users',

  // Request method
  method: 'get', // default is get

  // Base URL, will be concatenated with url
  baseURL: 'https://api.example.com',

  // Request headers
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer token',
    'Accept': 'application/json'
  },

  // URL parameters (will be appended to the URL)
  params: {
    page: 1,
    limit: 10
  },

  // Parameter serialization function
  paramsSerializer: function(params) {
    return Qs.stringify(params, { arrayFormat: 'brackets' });
  },

  // Request body data
  data: {
    name: 'John',
    email: 'john@example.com'
  },

  // Request timeout (milliseconds)
  timeout: 5000,

  // Whether cross-site requests should use credentials
  withCredentials: false,

  // Response data type
  responseType: 'json', // 'arraybuffer', 'blob', 'document', 'json', 'text', 'stream'

  // Response encoding
  responseEncoding: 'utf8',

  // XSRF settings
  xsrfCookieName: 'XSRF-TOKEN',
  xsrfHeaderName: 'X-XSRF-TOKEN',

  // Upload progress callback
  onUploadProgress: function(progressEvent) {
    console.log('Upload progress:', (progressEvent.loaded / progressEvent.total) * 100 + '%');
  },

  // Download progress callback
  onDownloadProgress: function(progressEvent) {
    console.log('Download progress:', (progressEvent.loaded / progressEvent.total) * 100 + '%');
  },

  // Maximum response body size
  maxContentLength: 2000,
  maxBodyLength: 2000,

  // Status code validation function
  validateStatus: function(status) {
    return status >= 200 && status < 300;
  },

  // Maximum number of redirects
  maxRedirects: 5,

  // Node.js specific configuration
  httpAgent: new http.Agent({ keepAlive: true }),
  httpsAgent: new https.Agent({ keepAlive: true }),

  // Proxy configuration
  proxy: {
    host: '127.0.0.1',
    port: 9000,
    auth: {
      username: 'mikeymike',
      password: 'rapunz3l'
    }
  },

  // Cancel token
  cancelToken: new axios.CancelToken(function(cancel) {
    // ...
  })
};

Configuration Priority

// Configuration priority demonstration
// Priority from high to low:
// 1. Request configuration
// 2. Instance configuration
// 3. Global configuration

// Global configuration
axios.defaults.timeout = 10000;
axios.defaults.baseURL = 'https://api.example.com';

// Instance configuration
const apiClient = axios.create({
  timeout: 5000,
  baseURL: 'https://api.myservice.com'
});

// Request configuration (highest priority)
apiClient.get('/users', {
  timeout: 3000  // This will override instance and global timeout configuration
});

3.2 Request Header Management

Common Request Header Settings

// Setting common request headers
const commonHeaders = {
  // Content type
  'Content-Type': 'application/json',
  'Accept': 'application/json',

  // Authorization related
  'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
  'X-API-Key': 'your-api-key',

  // Client information
  'User-Agent': 'MyApp/1.0.0',
  'X-Client-Version': '1.0.0',

  // Request tracking
  'X-Request-ID': 'req-' + Date.now(),
  'X-Correlation-ID': 'corr-' + Math.random().toString(36),

  // Cache control
  'Cache-Control': 'no-cache',
  'Pragma': 'no-cache',

  // Language and region
  'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
  'Accept-Encoding': 'gzip, deflate, br'
};

// Using request headers
async function requestWithHeaders() {
  try {
    const response = await axios.get('/api/users', {
      headers: commonHeaders
    });

    console.log('Request successful:', response.data);
    return response.data;
  } catch (error) {
    console.error('Request failed:', error);
    throw error;
  }
}

Dynamic Request Headers

// Dynamically setting request headers
class HeaderManager {
  constructor() {
    this.headers = {};
  }

  // Set authentication header
  setAuthHeader(token, type = 'Bearer') {
    this.headers['Authorization'] = `${type} ${token}`;
  }

  // Set API key
  setApiKey(key, headerName = 'X-API-Key') {
    this.headers[headerName] = key;
  }

  // Set content type
  setContentType(type) {
    this.headers['Content-Type'] = type;
  }

  // Set user agent
  setUserAgent(userAgent) {
    this.headers['User-Agent'] = userAgent;
  }

  // Add custom header
  addHeader(name, value) {
    this.headers[name] = value;
  }

  // Remove header
  removeHeader(name) {
    delete this.headers[name];
  }

  // Get all headers
  getHeaders() {
    return { ...this.headers };
  }

  // Clear all headers
  clear() {
    this.headers = {};
  }
}

// Using header manager
const headerManager = new HeaderManager();
headerManager.setAuthHeader('your-jwt-token');
headerManager.setApiKey('your-api-key');
headerManager.addHeader('X-Custom-Header', 'custom-value');

async function requestWithDynamicHeaders() {
  const response = await axios.get('/api/protected', {
    headers: headerManager.getHeaders()
  });

  return response.data;
}

Conditional Header Settings

// Dynamically setting headers based on conditions
function buildHeaders(options = {}) {
  const headers = {
    'Accept': 'application/json'
  };

  // Set Content-Type based on request type
  if (options.hasFormData) {
    // FormData automatically sets Content-Type, no need to set manually
    // headers['Content-Type'] = 'multipart/form-data'; // Don't set this
  } else if (options.isFormUrlEncoded) {
    headers['Content-Type'] = 'application/x-www-form-urlencoded';
  } else if (options.isXml) {
    headers['Content-Type'] = 'application/xml';
  } else {
    headers['Content-Type'] = 'application/json';
  }

  // Authentication header
  if (options.token) {
    headers['Authorization'] = `Bearer ${options.token}`;
  }

  // API key
  if (options.apiKey) {
    headers['X-API-Key'] = options.apiKey;
  }

  // Request ID for tracking
  if (options.includeRequestId) {
    headers['X-Request-ID'] = `req-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }

  // Client information
  if (options.clientInfo) {
    headers['X-Client-Info'] = JSON.stringify(options.clientInfo);
  }

  return headers;
}

// Usage example
async function apiRequest(url, data, options = {}) {
  const headers = buildHeaders(options);

  const response = await axios.post(url, data, { headers });
  return response.data;
}

3.3 Timeout Configuration

Basic Timeout Settings

// Global timeout setting
axios.defaults.timeout = 10000; // 10 seconds

// Individual request timeout
async function requestWithTimeout() {
  try {
    const response = await axios.get('/api/slow-endpoint', {
      timeout: 5000 // 5 second timeout
    });

    console.log('Request successful:', response.data);
    return response.data;
  } catch (error) {
    if (error.code === 'ECONNABORTED') {
      console.error('Request timeout');
      throw new Error('Request timeout, please try again later');
    }
    throw error;
  }
}

Timeout Strategies for Different Request Types

// Timeout strategies for different API endpoints
class TimeoutManager {
  constructor() {
    this.timeouts = {
      // Fast query interface
      'fast': 3000,
      // Normal CRUD operations
      'normal': 10000,
      // Data analysis interface
      'analysis': 30000,
      // File upload
      'upload': 60000,
      // Report generation
      'report': 120000
    };
  }

  getTimeout(type) {
    return this.timeouts[type] || this.timeouts.normal;
  }

  setCustomTimeout(type, timeout) {
    this.timeouts[type] = timeout;
  }
}

const timeoutManager = new TimeoutManager();

// Set different timeouts based on interface type
async function smartRequest(url, options = {}) {
  const { type = 'normal', ...otherOptions } = options;
  const timeout = timeoutManager.getTimeout(type);

  try {
    const response = await axios({
      url,
      timeout,
      ...otherOptions
    });

    return response.data;
  } catch (error) {
    if (error.code === 'ECONNABORTED') {
      console.error(`${type} type request timeout (${timeout}ms)`);
    }
    throw error;
  }
}

// Usage examples
smartRequest('/api/users', { type: 'fast' });
smartRequest('/api/upload', { type: 'upload', method: 'post', data: formData });
smartRequest('/api/generate-report', { type: 'report' });

Timeout Retry Mechanism

// Timeout handling with retry
async function requestWithTimeoutRetry(url, config = {}, maxRetries = 3) {
  const baseTimeout = config.timeout || 5000;

  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      // Increase timeout incrementally
      const timeout = baseTimeout * attempt;

      console.log(`Attempt ${attempt}, timeout: ${timeout}ms`);

      const response = await axios({
        url,
        timeout,
        ...config
      });

      console.log(`Attempt ${attempt} successful`);
      return response.data;
    } catch (error) {
      if (error.code === 'ECONNABORTED') {
        console.log(`Attempt ${attempt} timeout`);

        if (attempt === maxRetries) {
          throw new Error(`Request failed: still timeout after ${maxRetries} retries`);
        }

        // Wait before retrying
        await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
      } else {
        // Non-timeout error, throw directly
        throw error;
      }
    }
  }
}

3.4 Response Configuration and Handling

Response Data Types

// Handling different response types
const responseTypeExamples = {
  // JSON data (default)
  json: async () => {
    const response = await axios.get('/api/users', {
      responseType: 'json'
    });
    console.log('JSON data:', response.data);
    return response.data;
  },

  // Plain text
  text: async () => {
    const response = await axios.get('/api/readme', {
      responseType: 'text'
    });
    console.log('Text content:', response.data);
    return response.data;
  },

  // Binary data
  arraybuffer: async () => {
    const response = await axios.get('/api/file.pdf', {
      responseType: 'arraybuffer'
    });
    console.log('Binary data length:', response.data.byteLength);
    return response.data;
  },

  // Blob object (in browser)
  blob: async () => {
    const response = await axios.get('/api/image.jpg', {
      responseType: 'blob'
    });
    console.log('Blob type:', response.data.type);
    return response.data;
  },

  // Stream data (in Node.js)
  stream: async () => {
    const response = await axios.get('/api/large-file', {
      responseType: 'stream'
    });

    // Handle stream data
    response.data.on('data', chunk => {
      console.log('Received chunk:', chunk.length);
    });

    response.data.on('end', () => {
      console.log('Stream reception complete');
    });

    return response.data;
  }
};

Response Status Code Validation

// Custom status code validation
const statusValidators = {
  // Default validation (200-299 is success)
  default: (status) => status >= 200 && status < 300,

  // Lenient validation (includes 304 Not Modified)
  lenient: (status) => (status >= 200 && status < 300) || status === 304,

  // Strict validation (only 200 is success)
  strict: (status) => status === 200,

  // Custom business validation
  business: (status) => {
    // 200: Success
    // 201: Created successfully
    // 204: Deleted successfully
    // 400: Parameter error but can be handled
    return [200, 201, 204, 400].includes(status);
  }
};

async function requestWithStatusValidation(url, validationType = 'default') {
  try {
    const response = await axios.get(url, {
      validateStatus: statusValidators[validationType]
    });

    console.log('Status code:', response.status);
    console.log('Response data:', response.data);

    return response.data;
  } catch (error) {
    console.error('Request failed:', error.response?.status, error.response?.data);
    throw error;
  }
}

Response Data Transformation

// Custom response data transformers
const responseTransformers = {
  // Add received timestamp
  addTimestamp: (data) => {
    if (typeof data === 'object' && data !== null) {
      data._receivedAt = new Date().toISOString();
    }
    return data;
  },

  // Convert date strings to Date objects
  parseDates: (data) => {
    if (typeof data === 'object' && data !== null) {
      const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;

      function parseObject(obj) {
        for (const key in obj) {
          if (obj.hasOwnProperty(key)) {
            const value = obj[key];
            if (typeof value === 'string' && dateRegex.test(value)) {
              obj[key] = new Date(value);
            } else if (typeof value === 'object' && value !== null) {
              parseObject(value);
            }
          }
        }
      }

      parseObject(data);
    }
    return data;
  },

  // Normalize API response format
  normalizeResponse: (data) => {
    // Assuming backend returns format like { code: 200, message: 'success', data: {...} }
    if (data && typeof data === 'object' && 'code' in data) {
      if (data.code === 200) {
        return data.data;
      } else {
        throw new Error(data.message || 'Request failed');
      }
    }
    return data;
  }
};

// Using custom transformers
async function requestWithTransforms() {
  const response = await axios.get('/api/users', {
    transformResponse: [
      ...axios.defaults.transformResponse, // Keep default transformers
      responseTransformers.addTimestamp,
      responseTransformers.parseDates,
      responseTransformers.normalizeResponse
    ]
  });

  console.log('Transformed data:', response.data);
  return response.data;
}

3.5 Request Configuration Templates

Configuration Template Class

// Request configuration template manager
class RequestConfigManager {
  constructor() {
    this.templates = {
      // API request template
      api: {
        baseURL: '/api/v1',
        timeout: 10000,
        headers: {
          'Content-Type': 'application/json',
          'Accept': 'application/json'
        },
        validateStatus: (status) => status >= 200 && status < 300
      },

      // File upload template
      upload: {
        timeout: 60000,
        headers: {
          'Content-Type': 'multipart/form-data'
        },
        onUploadProgress: (progressEvent) => {
          const percent = Math.round(
            (progressEvent.loaded * 100) / progressEvent.total
          );
          console.log(`Upload progress: ${percent}%`);
        }
      },

      // File download template
      download: {
        responseType: 'blob',
        timeout: 120000,
        onDownloadProgress: (progressEvent) => {
          if (progressEvent.lengthComputable) {
            const percent = Math.round(
              (progressEvent.loaded * 100) / progressEvent.total
            );
            console.log(`Download progress: ${percent}%`);
          }
        }
      },

      // Long polling template
      longPolling: {
        timeout: 30000,
        headers: {
          'Cache-Control': 'no-cache',
          'Pragma': 'no-cache'
        }
      }
    };
  }

  // Get template
  getTemplate(name) {
    return { ...this.templates[name] };
  }

  // Merge configuration
  mergeConfig(templateName, customConfig = {}) {
    const template = this.getTemplate(templateName);

    return {
      ...template,
      ...customConfig,
      headers: {
        ...template.headers,
        ...customConfig.headers
      }
    };
  }

  // Add custom template
  addTemplate(name, config) {
    this.templates[name] = config;
  }
}

// Using configuration templates
const configManager = new RequestConfigManager();

// API request
async function apiRequest(url, data, customConfig = {}) {
  const config = configManager.mergeConfig('api', {
    method: 'post',
    url,
    data,
    ...customConfig
  });

  const response = await axios(config);
  return response.data;
}

// File upload
async function uploadFile(file, customConfig = {}) {
  const formData = new FormData();
  formData.append('file', file);

  const config = configManager.mergeConfig('upload', {
    method: 'post',
    url: '/api/upload',
    data: formData,
    ...customConfig
  });

  const response = await axios(config);
  return response.data;
}

// File download
async function downloadFile(url, filename) {
  const config = configManager.mergeConfig('download', {
    method: 'get',
    url
  });

  const response = await axios(config);

  // Create download link (browser environment)
  const blob = new Blob([response.data]);
  const downloadUrl = window.URL.createObjectURL(blob);
  const link = document.createElement('a');
  link.href = downloadUrl;
  link.download = filename;
  link.click();
  window.URL.revokeObjectURL(downloadUrl);

  return true;
}

3.6 Practical Application Examples

Complete API Client Configuration

// Enterprise-level API client configuration
class EnterpriseApiClient {
  constructor(config = {}) {
    this.config = {
      baseURL: config.baseURL || '/api/v1',
      timeout: config.timeout || 10000,
      retries: config.retries || 3,
      retryDelay: config.retryDelay || 1000,
      ...config
    };

    this.setupClient();
  }

  setupClient() {
    // Create axios instance
    this.client = axios.create({
      baseURL: this.config.baseURL,
      timeout: this.config.timeout,
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'X-Client-Version': '1.0.0'
      }
    });

    // Setup request interceptor
    this.client.interceptors.request.use(
      (config) => {
        // Add request timestamp
        config.metadata = { startTime: Date.now() };

        // Add request ID
        config.headers['X-Request-ID'] = this.generateRequestId();

        console.log(`Sending request: ${config.method?.toUpperCase()} ${config.url}`);
        return config;
      },
      (error) => {
        console.error('Request configuration error:', error);
        return Promise.reject(error);
      }
    );

    // Setup response interceptor
    this.client.interceptors.response.use(
      (response) => {
        const duration = Date.now() - response.config.metadata.startTime;
        console.log(`Request completed: ${response.status} (${duration}ms)`);
        return response;
      },
      (error) => {
        const duration = error.config?.metadata
          ? Date.now() - error.config.metadata.startTime
          : 0;

        console.error(`Request failed: ${error.response?.status} (${duration}ms)`);

        // Retry logic
        return this.handleRetry(error);
      }
    );
  }

  generateRequestId() {
    return `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }

  async handleRetry(error) {
    const config = error.config;

    if (!config || !this.shouldRetry(error)) {
      return Promise.reject(error);
    }

    config.__retryCount = config.__retryCount || 0;

    if (config.__retryCount >= this.config.retries) {
      return Promise.reject(error);
    }

    config.__retryCount++;

    console.log(`Retrying request (${config.__retryCount}/${this.config.retries})`);

    // Wait for retry delay
    await new Promise(resolve =>
      setTimeout(resolve, this.config.retryDelay * config.__retryCount)
    );

    return this.client(config);
  }

  shouldRetry(error) {
    // Only retry network errors or 5xx errors
    return !error.response || (error.response.status >= 500);
  }

  // API methods
  async get(url, config = {}) {
    const response = await this.client.get(url, config);
    return response.data;
  }

  async post(url, data, config = {}) {
    const response = await this.client.post(url, data, config);
    return response.data;
  }

  async put(url, data, config = {}) {
    const response = await this.client.put(url, data, config);
    return response.data;
  }

  async patch(url, data, config = {}) {
    const response = await this.client.patch(url, data, config);
    return response.data;
  }

  async delete(url, config = {}) {
    const response = await this.client.delete(url, config);
    return response.data;
  }
}

// Using enterprise client
const apiClient = new EnterpriseApiClient({
  baseURL: 'https://api.myservice.com/v1',
  timeout: 15000,
  retries: 3,
  retryDelay: 1000
});

// Business API encapsulation
class UserService {
  constructor(apiClient) {
    this.api = apiClient;
  }

  async getUsers(params = {}) {
    return this.api.get('/users', { params });
  }

  async getUser(id) {
    return this.api.get(`/users/${id}`);
  }

  async createUser(userData) {
    return this.api.post('/users', userData);
  }

  async updateUser(id, userData) {
    return this.api.put(`/users/${id}`, userData);
  }

  async deleteUser(id) {
    return this.api.delete(`/users/${id}`);
  }
}

const userService = new UserService(apiClient);

Chapter Summary

In Chapter 3, we learned in depth about:

  1. Request Configuration Object: Understood complete configuration options and priority mechanisms
  2. Request Header Management: Mastered setting static and dynamic request headers
  3. Timeout Configuration: Learned to set timeout and retry mechanisms
  4. Response Handling: Understood different response types and data transformation
  5. Configuration Templates: Learned to create reusable configuration templates
  6. Practical Applications: Mastered building enterprise-level API clients

This knowledge enables us to precisely control every aspect of HTTP requests, laying the foundation for building stable and reliable network applications.

Key Points

  • Configuration objects provide comprehensive request control capabilities
  • Request header management is an important part of API interaction
  • Reasonable timeout and retry mechanisms improve application robustness
  • Proper response data handling affects user experience
  • Configuration templates can improve development efficiency and consistency
  • Enterprise-level applications need comprehensive error handling and monitoring

Next Chapter Preview

The next chapter will learn about interceptor mechanisms, one of Axios’s most powerful features, which can implement unified request/response handling and middleware functionality.