MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Objective-C高性能网络编程中的跨域请求处理策略

2024-12-083.3k 阅读

跨域请求基础概念

在网络编程中,跨域请求指的是在一个网页中通过 AJAX 等方式向不同域名、端口或协议的服务器发起请求。例如,当你的网页位于 http://example.com,而你尝试请求 http://api.example.net 时,就涉及到了跨域请求。

浏览器出于安全考虑,默认禁止跨域请求。这是因为跨域请求可能导致恶意网站获取用户在其他网站的敏感信息,比如用户在银行网站登录后,恶意网站可能通过跨域请求获取用户的账户信息。这种安全策略被称为同源策略(Same-Origin Policy)。

同源策略

同源策略是一种约定,它由协议、域名和端口共同决定。当两个 URL 的协议、域名和端口都相同时,它们才属于同一个源。例如:

  • http://example.com:8080http://example.com:8080 属于同源。
  • http://example.com:8080https://example.com:8080 不同源,因为协议不同。
  • http://example.com:8080http://sub.example.com:8080 不同源,因为域名不同(子域名也算不同域名)。
  • http://example.com:8080http://example.com:8081 不同源,因为端口不同。

Objective - C 网络编程中的跨域挑战

在 Objective - C 进行网络编程时,同样会遇到跨域问题。当使用 NSURLSession 等网络请求工具尝试发起跨域请求时,即便请求发出,浏览器(如果是与 Web 交互相关的场景)也会拦截响应,导致无法正常获取数据。

例如,以下是一个简单的使用 NSURLSession 发起网络请求的代码示例:

NSURL *url = [NSURL URLWithString:@"http://api.example.net/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"Response: %@", responseString);
    } else {
        NSLog(@"Error: %@", error);
    }
}];
[task resume];

在上述代码中,如果 http://api.example.net 与应用所在的源不同,就会面临跨域问题。在实际运行中,虽然请求可以发出,但由于跨域限制,data 可能为 nil,并且 error 可能包含与跨域相关的错误信息。

处理跨域请求的策略

CORS(跨域资源共享)

CORS 是一种 W3C 标准,它允许服务器通过添加特定的 HTTP 头来声明哪些源站有权限访问资源。在 Objective - C 开发中,如果服务器端支持 CORS,客户端不需要做太多额外配置,NSURLSession 等请求工具就能正常处理跨域请求。

服务器端配置 CORS 的方式因使用的后端技术而异。例如,在使用 Node.js 和 Express 框架时,可以通过 cors 中间件来配置:

const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors());
// 其他路由和处理逻辑
app.listen(3000, () => {
    console.log('Server running on port 3000');
});

上述代码通过 app.use(cors()) 启用了 CORS,允许所有源访问服务器资源。在实际应用中,可能需要更精细的配置,比如只允许特定的源访问:

app.use(cors({
    origin: 'http://allowed - origin.com'
}));

在 Objective - C 客户端,无需额外代码来处理 CORS,只要服务器配置正确,请求就能正常进行。例如:

NSURL *url = [NSURL URLWithString:@"http://api.example.net/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"Response: %@", responseString);
    } else {
        NSLog(@"Error: %@", error);
    }
}];
[task resume];

只要 http://api.example.net 服务器正确配置了 CORS 允许当前应用所在的源访问,上述代码就能正常获取响应数据。

JSONP(JSON with Padding)

JSONP 是一种古老的跨域解决方案,它利用 <script> 标签不受同源策略限制的特点来实现跨域请求。

  1. 服务器端支持:服务器需要返回一段 JavaScript 代码,该代码调用客户端定义的回调函数,并将数据作为参数传递给该回调函数。例如,在 Node.js 中:
const express = require('express');
const app = express();
app.get('/data', (req, res) => {
    const callback = req.query.callback;
    const data = { message: 'Hello from server' };
    res.send(`${callback}(${JSON.stringify(data)})`);
});
app.listen(3000, () => {
    console.log('Server running on port 3000');
});

上述代码中,服务器接收 callback 参数,将数据包装在该回调函数调用中返回。

  1. Objective - C 客户端实现:在 Objective - C 中,我们可以通过创建 NSURLNSMutableURLRequest 来发起 JSONP 请求,并且使用 NSURLConnection(虽然 NSURLSession 更常用,但在 JSONP 场景下 NSURLConnection 更容易展示原理)。
NSString *callbackName = @"myCallback";
NSString *urlString = [NSString stringWithFormat:@"http://api.example.net/data?callback=%@", callbackName];
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
    if (data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        // 截取回调函数中的数据部分
        NSRange startRange = [responseString rangeOfString:@"("];
        NSRange endRange = [responseString rangeOfString:@")"];
        NSString *jsonString = [responseString substringWithRange:NSMakeRange(startRange.location + 1, endRange.location - startRange.location - 1)];
        NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
        NSError *jsonError;
        NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:&jsonError];
        if (jsonDict) {
            NSLog(@"Data: %@", jsonDict);
        } else {
            NSLog(@"JSON Error: %@", jsonError);
        }
    } else {
        NSLog(@"Connection Error: %@", connectionError);
    }
}];

上述代码创建了一个 JSONP 请求,获取服务器返回的包含回调函数调用的字符串,然后提取出其中的 JSON 数据部分进行解析。

代理服务器

使用代理服务器是另一种解决跨域问题的有效策略。代理服务器位于客户端和目标服务器之间,客户端向代理服务器发起请求,代理服务器再向目标服务器请求数据,并将响应返回给客户端。由于客户端与代理服务器属于同一个源(可以这样配置),所以不存在跨域问题。

  1. 代理服务器搭建:可以使用各种技术搭建代理服务器,例如使用 Python 的 Flask 框架:
from flask import Flask, request, jsonify, send_from_directory
import requests

app = Flask(__name__)

@app.route('/proxy', methods=['GET', 'POST'])
def proxy():
    target_url = request.args.get('url')
    if not target_url:
        return jsonify({'error': 'No target URL provided'}), 400
    try:
        if request.method == 'GET':
            response = requests.get(target_url)
        else:
            response = requests.post(target_url, data = request.get_data())
        return response.content, response.status_code, response.headers.items()
    except Exception as e:
        return jsonify({'error': str(e)}), 500


if __name__ == '__main__':
    app.run(debug = True, host='127.0.0.1', port = 5000)

上述代码创建了一个简单的代理服务器,它接收 url 参数,向目标 URL 发起请求,并将响应返回给客户端。

  1. Objective - C 客户端使用代理:在 Objective - C 中,我们需要修改请求的 URL 为代理服务器的 URL,并传递目标 URL 参数。
NSString *targetUrl = @"http://api.example.net/data";
NSString *proxyUrlString = [NSString stringWithFormat:@"http://127.0.0.1:5000/proxy?url=%@", targetUrl];
NSURL *proxyUrl = [NSURL URLWithString:proxyUrlString];
NSURLRequest *request = [NSURLRequest requestWithURL:proxyUrl];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"Response: %@", responseString);
    } else {
        NSLog(@"Error: %@", error);
    }
}];
[task resume];

上述代码将请求发送到代理服务器,代理服务器再转发请求到目标服务器,并将响应返回给客户端。

复杂场景下的跨域处理

预检请求(Pre - flight Request)

在 CORS 场景下,如果请求方法是 PUTDELETE 等非简单请求方法,或者请求头中包含自定义头信息,浏览器会先发送一个预检请求(OPTIONS 方法)。预检请求用于询问服务器是否允许该实际请求。

  1. 服务器端处理预检请求:服务器需要正确处理预检请求,返回允许的方法、头信息等。在 Node.js 和 Express 中,可以这样处理:
const express = require('express');
const cors = require('cors');
const app = express();

// 处理预检请求
app.options('*', cors());

app.put('/data', (req, res) => {
    // 处理 PUT 请求逻辑
    res.send('Data updated successfully');
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

上述代码中,app.options('*', cors()) 用于处理所有路径的预检请求,确保服务器正确响应预检请求。

  1. Objective - C 客户端处理:在 Objective - C 客户端,使用 NSURLSession 等工具发起复杂请求时,无需额外处理预检请求,只要服务器配置正确,NSURLSession 会自动处理预检请求和后续的实际请求。例如:
NSURL *url = [NSURL URLWithString:@"http://api.example.net/data"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"PUT";
// 添加自定义头信息
[request setValue:@"custom - value" forHTTPHeaderField:@"Custom - Header"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"Response: %@", responseString);
    } else {
        NSLog(@"Error: %@", error);
    }
}];
[task resume];

上述代码发起一个包含自定义头信息的 PUT 请求,只要服务器正确处理预检请求,该请求就能正常进行。

跨域请求与身份验证

当涉及到跨域请求且需要身份验证时,情况会变得更加复杂。例如,使用 Token 进行身份验证时,需要确保 Token 在跨域请求中正确传递。

  1. CORS 场景下的身份验证:在 CORS 配置中,需要允许 Authorization 等身份验证相关的头信息。在 Node.js 和 Express 中:
app.use(cors({
    origin: 'http://allowed - origin.com',
    allowedHeaders: ['Authorization', 'Content - Type']
}));

在 Objective - C 客户端,需要在请求头中添加身份验证信息,例如:

NSURL *url = [NSURL URLWithString:@"http://api.example.net/data"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setValue:@"Bearer your - token" forHTTPHeaderField:@"Authorization"];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"Response: %@", responseString);
    } else {
        NSLog(@"Error: %@", error);
    }
}];
[task resume];
  1. JSONP 场景下的身份验证:在 JSONP 场景下,由于请求是通过 <script> 标签发起,无法直接在请求头中添加身份验证信息。一种解决方法是将身份验证信息作为 URL 参数传递。例如:
NSString *token = @"your - token";
NSString *callbackName = @"myCallback";
NSString *urlString = [NSString stringWithFormat:@"http://api.example.net/data?callback=%@&token=%@", callbackName, token];
NSURL *url = [NSURL URLWithString:urlString];
// 后续 JSONP 请求处理代码

服务器端需要从 URL 参数中提取身份验证信息进行验证。

跨域请求与缓存

跨域请求的缓存策略也需要特别关注。在 CORS 场景下,服务器可以通过设置 Cache - Control 等 HTTP 头来控制缓存。

  1. 服务器端设置缓存:在 Node.js 和 Express 中:
app.get('/data', (req, res) => {
    res.set('Cache - Control','public, max - age = 3600');
    // 返回数据逻辑
    res.send({ message: 'Cached data' });
});

上述代码设置了数据可以被公共缓存,缓存有效期为 3600 秒。

  1. Objective - C 客户端处理缓存:在 Objective - C 中,NSURLSession 会根据服务器返回的缓存头信息来处理缓存。例如:
NSURL *url = [NSURL URLWithString:@"http://api.example.net/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        NSString *responseString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
        NSLog(@"Response: %@", responseString);
    } else {
        NSLog(@"Error: %@", error);
    }
}];
[task resume];

如果服务器正确设置了缓存头,NSURLSession 会在有效期内使用缓存数据,而不是再次发起请求。

性能优化与跨域请求

减少跨域请求次数

频繁的跨域请求会影响性能,因为每次跨域请求都可能涉及到 CORS 预检请求(复杂请求时)或者额外的处理(如 JSONP 的解析等)。可以通过合并请求的方式来减少跨域请求次数。

例如,假设需要从不同的跨域 API 获取用户信息和订单信息,可以在服务器端进行合并请求,然后客户端只向服务器发起一次请求。在 Node.js 中:

const express = require('express');
const axios = require('axios');
const app = express();

app.get('/combinedData', async (req, res) => {
    try {
        const userInfoResponse = await axios.get('http://user - api.example.net/userInfo');
        const orderInfoResponse = await axios.get('http://order - api.example.net/orderInfo');
        const combinedData = {
            userInfo: userInfoResponse.data,
            orderInfo: orderInfoResponse.data
        };
        res.send(combinedData);
    } catch (error) {
        res.send({ error: 'Failed to fetch data' });
    }
});

app.listen(3000, () => {
    console.log('Server running on port 3000');
});

在 Objective - C 客户端,只需要向 /combinedData 发起一次请求:

NSURL *url = [NSURL URLWithString:@"http://127.0.0.1:3000/combinedData"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (data) {
        NSError *jsonError;
        NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&jsonError];
        if (jsonDict) {
            NSLog(@"User Info: %@", jsonDict[@"userInfo"]);
            NSLog(@"Order Info: %@", jsonDict[@"orderInfo"]);
        } else {
            NSLog(@"JSON Error: %@", jsonError);
        }
    } else {
        NSLog(@"Error: %@", error);
    }
}];
[task resume];

优化请求参数与数据量

在跨域请求时,尽量减少请求参数和返回的数据量。精简请求参数可以减少请求体大小,加快请求传输速度。同样,服务器返回的数据也应该只包含必要的信息。

例如,在 Objective - C 中发起请求时,只传递必要的参数:

NSDictionary *parameters = @{
    @"userId": @"123",
    @"limit": @"10"
};
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://api.example.net/data"]];
request.HTTPMethod = @"POST";
request.HTTPBody = [NSJSONSerialization dataWithJSONObject:parameters options:0 error:nil];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    // 处理响应
}];
[task resume];

在服务器端,也只返回客户端需要的数据,避免返回大量冗余信息。

选择合适的跨域策略

不同的跨域策略在性能上也有所差异。CORS 是现代浏览器推荐的标准方法,性能相对较好,因为它直接在 HTTP 层面处理跨域,且浏览器原生支持。而 JSONP 由于需要额外的 JavaScript 解析步骤,在性能上可能稍逊一筹,特别是在处理大量数据时。代理服务器虽然可以解决跨域问题,但增加了服务器间的请求环节,可能会带来一定的延迟,所以在选择跨域策略时,需要根据具体的应用场景和性能要求来决定。

例如,如果应用主要是与现代浏览器交互,且服务器端可配置 CORS,优先选择 CORS 策略;如果是与一些不支持 CORS 的旧系统交互,或者需要兼容较老的浏览器,JSONP 可能是一个备选方案;而代理服务器适合在对安全性要求较高,且可以自行搭建和管理代理服务器的场景下使用。

错误处理与跨域请求

常见跨域错误及处理

  1. CORS 错误:最常见的 CORS 错误是 No 'Access - Control - Allow - Origin' header is present on the requested resource。这表示服务器没有正确配置 CORS,未返回允许的源站信息。在服务器端,需要检查 CORS 配置,确保正确设置了 Access - Control - Allow - Origin 头。在 Objective - C 客户端,可以通过捕获 NSURLErrorDomain 错误来处理:
NSURL *url = [NSURL URLWithString:@"http://api.example.net/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (error) {
        if (error.domain == NSURLErrorDomain && error.code == -1012) {
            NSLog(@"CORS error: %@", error);
            // 可以在这里提示用户或进行其他处理
        } else {
            NSLog(@"Other error: %@", error);
        }
    } else {
        // 处理响应数据
    }
}];
[task resume];
  1. JSONP 错误:在 JSONP 场景下,常见的错误是回调函数未定义或者服务器返回的数据格式不正确。在 Objective - C 中,需要确保回调函数名称正确,并且正确解析服务器返回的数据。例如,在解析数据时添加错误处理:
// 假设已经接收到服务器返回的响应字符串 responseString
NSRange startRange = [responseString rangeOfString:@"("];
NSRange endRange = [responseString rangeOfString:@")"];
if (startRange.location == NSNotFound || endRange.location == NSNotFound) {
    NSLog(@"Invalid JSONP response format");
    return;
}
NSString *jsonString = [responseString substringWithRange:NSMakeRange(startRange.location + 1, endRange.location - startRange.location - 1)];
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *jsonError;
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingAllowFragments error:&jsonError];
if (jsonError) {
    NSLog(@"JSON parsing error: %@", jsonError);
} else {
    // 处理 JSON 数据
}
  1. 代理服务器错误:代理服务器可能出现的错误包括无法连接到目标服务器、代理服务器自身配置错误等。在 Objective - C 客户端,同样可以通过捕获 NSURLErrorDomain 错误来处理与代理服务器相关的错误:
NSURL *proxyUrl = [NSURL URLWithString:@"http://127.0.0.1:5000/proxy?url=http://api.example.net/data"];
NSURLRequest *request = [NSURLRequest requestWithURL:proxyUrl];
NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
    if (error) {
        NSLog(@"Proxy - related error: %@", error);
        // 可以根据错误情况进行重试或提示用户
    } else {
        // 处理响应数据
    }
}];
[task resume];

错误重试机制

在跨域请求出现错误时,合理的错误重试机制可以提高应用的稳定性。可以在 Objective - C 中实现简单的重试逻辑。例如,对于 CORS 错误,可以尝试重新发起请求:

NSInteger maxRetries = 3;
NSInteger currentRetry = 0;
void (^retryRequest)(void) = ^{
    NSURL *url = [NSURL URLWithString:@"http://api.example.net/data"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            if (error.domain == NSURLErrorDomain && error.code == -1012 && currentRetry < maxRetries) {
                currentRetry++;
                retryRequest();
            } else {
                NSLog(@"Final error: %@", error);
            }
        } else {
            // 处理响应数据
        }
    }];
    [task resume];
};
retryRequest();

上述代码在遇到 CORS 错误时,最多重试 3 次请求,以提高请求成功的概率。

通过上述详细的跨域请求处理策略、性能优化以及错误处理等方面的内容,在 Objective - C 高性能网络编程中,可以更好地应对跨域请求带来的各种挑战,确保应用的高效、稳定运行。