How to Set Custom Headers in Alamofire Requests
Setting custom HTTP headers is a fundamental requirement when making API requests in iOS development. Alamofire, the popular Swift HTTP networking library, provides multiple flexible approaches to configure headers for your requests. This comprehensive guide covers all the methods to set custom headers, from simple one-off requests to global configurations.
Understanding HTTP Headers in Alamofire
HTTP headers contain important metadata about your requests, including authentication tokens, content types, user agents, and custom application-specific information. Alamofire handles headers through its HTTPHeaders
type, which provides a type-safe way to manage header key-value pairs.
Method 1: Setting Headers for Individual Requests
The most straightforward approach is to set headers directly when making a request:
import Alamofire
// Create headers for a single request
let headers: HTTPHeaders = [
"Authorization": "Bearer your-token-here",
"Content-Type": "application/json",
"X-API-Key": "your-api-key",
"User-Agent": "MyApp/1.0"
]
// Make request with custom headers
AF.request("https://api.example.com/data",
method: .get,
headers: headers)
.responseJSON { response in
switch response.result {
case .success(let value):
print("Response: \(value)")
case .failure(let error):
print("Error: \(error)")
}
}
Method 2: Using HTTPHeaders Initializer
Alamofire provides several ways to initialize HTTPHeaders
:
import Alamofire
// Method 1: Dictionary literal
let headers1: HTTPHeaders = [
"Authorization": "Bearer token123",
"Accept": "application/json"
]
// Method 2: Array of HTTPHeader objects
let headers2: HTTPHeaders = [
HTTPHeader(name: "Authorization", value: "Bearer token123"),
HTTPHeader(name: "Accept", value: "application/json")
]
// Method 3: Using HTTPHeaders initializer
var headers3 = HTTPHeaders()
headers3.add(name: "Authorization", value: "Bearer token123")
headers3.add(name: "Accept", value: "application/json")
// All methods can be used interchangeably
AF.request("https://api.example.com/users", headers: headers1)
Method 3: Global Headers with Session Configuration
For headers that should be included in all requests, configure them at the session level:
import Alamofire
// Create default headers
let defaultHeaders: HTTPHeaders = [
"User-Agent": "MyApp/1.0.0 (iOS)",
"Accept": "application/json",
"X-Client-Version": "1.0"
]
// Create session configuration
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = HTTPHeaders.default.dictionary.merging(defaultHeaders.dictionary) { _, new in new }
// Create custom session
let session = Session(configuration: configuration)
// All requests through this session will include default headers
session.request("https://api.example.com/profile")
.responseJSON { response in
// Handle response
}
Method 4: Request Interceptors for Dynamic Headers
For more complex scenarios like token refresh or dynamic header generation, use request interceptors:
import Alamofire
class AuthenticationInterceptor: RequestInterceptor {
private let token: String
init(token: String) {
self.token = token
}
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
var urlRequest = urlRequest
urlRequest.headers.add(.authorization(bearerToken: token))
urlRequest.headers.add(name: "X-Timestamp", value: "\(Date().timeIntervalSince1970)")
completion(.success(urlRequest))
}
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
// Handle retry logic if needed
completion(.doNotRetry)
}
}
// Use the interceptor
let interceptor = AuthenticationInterceptor(token: "your-token")
AF.request("https://api.example.com/secure-data", interceptor: interceptor)
Common Header Types and Examples
Authentication Headers
// Bearer Token
let authHeaders: HTTPHeaders = [
"Authorization": "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
]
// API Key
let apiKeyHeaders: HTTPHeaders = [
"X-API-Key": "your-api-key-here",
"X-RapidAPI-Key": "rapid-api-key"
]
// Basic Authentication
let basicAuth = HTTPHeaders()
basicAuth.add(.authorization(username: "user", password: "pass"))
Content Type Headers
// JSON Content
let jsonHeaders: HTTPHeaders = [
"Content-Type": "application/json",
"Accept": "application/json"
]
// Form Data
let formHeaders: HTTPHeaders = [
"Content-Type": "application/x-www-form-urlencoded"
]
// Multipart Form Data (handled automatically by Alamofire)
AF.upload(multipartFormData: { multipartFormData in
// Alamofire sets Content-Type automatically
}, to: "https://api.example.com/upload")
Custom Application Headers
let customHeaders: HTTPHeaders = [
"X-Client-ID": "ios-app",
"X-API-Version": "v2",
"X-Request-ID": UUID().uuidString,
"X-Platform": "iOS",
"X-App-Version": Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "unknown"
]
Advanced Header Management
Conditional Headers
func makeRequestWithConditionalHeaders(includeAuth: Bool = true) {
var headers = HTTPHeaders()
// Always include these headers
headers.add(name: "Accept", value: "application/json")
headers.add(name: "User-Agent", value: "MyApp/1.0")
// Conditionally add authentication
if includeAuth, let token = getStoredToken() {
headers.add(.authorization(bearerToken: token))
}
// Add debug headers in development
#if DEBUG
headers.add(name: "X-Debug-Mode", value: "true")
#endif
AF.request("https://api.example.com/data", headers: headers)
}
Header Validation and Error Handling
extension HTTPHeaders {
static func validateAndCreate(from dictionary: [String: String]) -> HTTPHeaders? {
var headers = HTTPHeaders()
for (key, value) in dictionary {
// Validate header key and value
guard !key.isEmpty, !value.isEmpty else {
print("Invalid header: \(key): \(value)")
return nil
}
headers.add(name: key, value: value)
}
return headers
}
}
// Usage
let headerDict = ["Authorization": "Bearer token", "Accept": "application/json"]
if let validHeaders = HTTPHeaders.validateAndCreate(from: headerDict) {
AF.request("https://api.example.com/data", headers: validHeaders)
}
Best Practices for Header Management
1. Use Enums for Header Names
enum APIHeaders {
static let authorization = "Authorization"
static let contentType = "Content-Type"
static let apiKey = "X-API-Key"
static let userAgent = "User-Agent"
}
let headers: HTTPHeaders = [
APIHeaders.authorization: "Bearer token",
APIHeaders.contentType: "application/json"
]
2. Create Header Builder Pattern
struct HeaderBuilder {
private var headers = HTTPHeaders()
func withAuth(token: String) -> HeaderBuilder {
var builder = self
builder.headers.add(.authorization(bearerToken: token))
return builder
}
func withContentType(_ type: String) -> HeaderBuilder {
var builder = self
builder.headers.add(name: "Content-Type", value: type)
return builder
}
func withCustomHeader(name: String, value: String) -> HeaderBuilder {
var builder = self
builder.headers.add(name: name, value: value)
return builder
}
func build() -> HTTPHeaders {
return headers
}
}
// Usage
let headers = HeaderBuilder()
.withAuth(token: "your-token")
.withContentType("application/json")
.withCustomHeader(name: "X-Client-Version", value: "1.0")
.build()
3. Environment-Specific Headers
struct EnvironmentHeaders {
static var current: HTTPHeaders {
var headers = HTTPHeaders()
#if DEBUG
headers.add(name: "X-Environment", value: "development")
headers.add(name: "X-Debug", value: "true")
#elseif STAGING
headers.add(name: "X-Environment", value: "staging")
#else
headers.add(name: "X-Environment", value: "production")
#endif
return headers
}
}
Working with Dynamic Headers
Token Refresh Implementation
class TokenManager {
private var currentToken: String?
private var refreshToken: String?
func getValidHeaders() async throws -> HTTPHeaders {
var headers = HTTPHeaders()
if let token = await getValidToken() {
headers.add(.authorization(bearerToken: token))
}
headers.add(name: "Content-Type", value: "application/json")
return headers
}
private func getValidToken() async -> String? {
// Check if current token is valid
if let token = currentToken, isTokenValid(token) {
return token
}
// Refresh token if needed
return await refreshTokenIfNeeded()
}
}
Request Signing with Headers
class RequestSigner {
private let secretKey: String
init(secretKey: String) {
self.secretKey = secretKey
}
func signedHeaders(for urlRequest: URLRequest) -> HTTPHeaders {
var headers = HTTPHeaders()
let timestamp = "\(Int(Date().timeIntervalSince1970))"
let nonce = UUID().uuidString
// Create signature
let stringToSign = "\(urlRequest.httpMethod ?? "GET"):\(urlRequest.url?.path ?? ""):\(timestamp):\(nonce)"
let signature = generateHMAC(string: stringToSign, key: secretKey)
headers.add(name: "X-Timestamp", value: timestamp)
headers.add(name: "X-Nonce", value: nonce)
headers.add(name: "X-Signature", value: signature)
return headers
}
}
Troubleshooting Common Issues
Headers Not Being Sent
Ensure headers are properly formatted and not overridden by default session headers:
// Debug headers being sent
AF.request("https://httpbin.org/headers", headers: customHeaders)
.cURL { curl in
print("cURL: \(curl)")
}
.responseJSON { response in
print("Response: \(response)")
}
Case Sensitivity Issues
// HTTP header names are case-insensitive, but be consistent
let headers: HTTPHeaders = [
"Content-Type": "application/json", // Preferred format
"authorization": "Bearer token" // Also valid but inconsistent
]
Header Size Limitations
func validateHeaderSize(_ headers: HTTPHeaders) -> Bool {
let totalSize = headers.dictionary.reduce(0) { size, header in
return size + header.key.count + header.value.count + 4 // +4 for ": " and CRLF
}
// HTTP specification suggests 8KB limit for all headers combined
return totalSize < 8192
}
Integration with Web Scraping and APIs
When building iOS applications that interact with web scraping services or complex APIs, proper header management becomes crucial. Similar to how browser sessions are handled in Puppeteer for web automation, Alamofire's header configuration helps your app maintain session state and avoid detection mechanisms.
For debugging network issues, you can monitor network requests in Puppeteer to understand what headers are expected by target services, then replicate those patterns in your Alamofire implementation.
Testing and Debugging Headers
Unit Testing Header Configuration
import XCTest
import Alamofire
class HeaderConfigurationTests: XCTestCase {
func testAuthenticationHeadersAreSet() {
let headers: HTTPHeaders = [
"Authorization": "Bearer test-token",
"X-API-Key": "test-key"
]
let expectation = expectation(description: "Request with headers")
AF.request("https://httpbin.org/headers", headers: headers)
.responseJSON { response in
switch response.result {
case .success(let data):
if let json = data as? [String: Any],
let requestHeaders = json["headers"] as? [String: String] {
XCTAssertEqual(requestHeaders["Authorization"], "Bearer test-token")
XCTAssertEqual(requestHeaders["X-Api-Key"], "test-key")
}
case .failure(let error):
XCTFail("Request failed: \(error)")
}
expectation.fulfill()
}
waitForExpectations(timeout: 10)
}
}
Network Debugging
// Log all network requests with headers
class NetworkLogger: EventMonitor {
func requestDidFinish(_ request: Request) {
print("Headers sent: \(request.request?.allHTTPHeaderFields ?? [:])")
}
func request(_ request: Request, didCompleteTask task: URLSessionTask, with error: Error?) {
if let error = error {
print("Request error: \(error)")
}
}
}
// Use with session
let session = Session(eventMonitors: [NetworkLogger()])
Performance Considerations
Header Caching
class HeaderCache {
private var cachedHeaders: HTTPHeaders?
private var cacheExpiry: Date?
func getCachedHeaders() -> HTTPHeaders? {
guard let expiry = cacheExpiry, Date() < expiry else {
return nil
}
return cachedHeaders
}
func cacheHeaders(_ headers: HTTPHeaders, for duration: TimeInterval = 300) {
self.cachedHeaders = headers
self.cacheExpiry = Date().addingTimeInterval(duration)
}
}
Memory Management
class RequestManager {
private let session: Session
init() {
// Configure session with appropriate limits
let configuration = URLSessionConfiguration.default
configuration.httpMaximumConnectionsPerHost = 6
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
self.session = Session(configuration: configuration)
}
deinit {
session.session.invalidateAndCancel()
}
}
Conclusion
Setting custom headers in Alamofire is essential for building robust iOS applications that interact with modern APIs. From simple authentication tokens to complex request signing, Alamofire provides flexible and powerful tools to handle any header configuration requirement.
Key takeaways include:
- Use dictionary literals for simple static headers
- Leverage HTTPHeaders class methods for dynamic scenarios
- Implement request interceptors for global header management
- Follow best practices with enums and builder patterns
- Test your header configurations thoroughly
- Consider performance implications for high-volume applications
By mastering these techniques, you'll be able to handle authentication, content negotiation, rate limiting, and custom API requirements effectively while maintaining clean, maintainable code in your iOS applications.