254 lines
8.7 KiB
Objective-C
254 lines
8.7 KiB
Objective-C
//
|
|
// NetworkStreamHandler.m
|
|
// CustomKeyboard
|
|
//
|
|
// Created by Mac on 2025/11/12.
|
|
//
|
|
|
|
#import "NetworkStreamHandler.h"
|
|
|
|
@interface NetworkStreamHandler ()
|
|
|
|
@property (nonatomic, strong) NSURLSession *session;
|
|
@property (nonatomic, strong) NSURLSessionDataTask *dataTask;
|
|
@property (nonatomic, strong) NSURLRequest *request;
|
|
@property (nonatomic, strong) NSMutableData *receivedData;
|
|
@property (nonatomic, assign) long long expectedContentLength;
|
|
@property (nonatomic, assign) NetworkStreamState state;
|
|
@property (nonatomic, strong) NSURLResponse *response;
|
|
|
|
// Block 回调
|
|
@property (nonatomic, copy) NetworkStreamProgressBlock progressBlock;
|
|
@property (nonatomic, copy) NetworkStreamDataBlock dataBlock;
|
|
@property (nonatomic, copy) NetworkStreamTextBlock textBlock;
|
|
@property (nonatomic, copy) NetworkStreamCompletionBlock completionBlock;
|
|
|
|
@end
|
|
|
|
@implementation NetworkStreamHandler
|
|
|
|
- (instancetype)initWithURL:(NSURL *)url {
|
|
NSURLRequest *request = [NetworkStreamHandler createDefaultRequestWithURL:url method:@"GET"];
|
|
return [self initWithRequest:request];
|
|
}
|
|
|
|
- (instancetype)initWithRequest:(NSURLRequest *)request {
|
|
self = [super init];
|
|
if (self) {
|
|
_request = request;
|
|
_receivedData = [NSMutableData data];
|
|
_state = NetworkStreamStateIdle;
|
|
_totalBytesReceived = 0;
|
|
|
|
// 创建 URLSession 配置
|
|
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
|
|
config.timeoutIntervalForRequest = 30.0;
|
|
config.timeoutIntervalForResource = 300.0;
|
|
config.requestCachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
|
|
|
|
// 创建 URLSession
|
|
_session = [NSURLSession sessionWithConfiguration:config
|
|
delegate:self
|
|
delegateQueue:[NSOperationQueue mainQueue]];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc {
|
|
[self cancelRequest];
|
|
}
|
|
|
|
#pragma mark - Public Methods
|
|
|
|
- (void)startRequest {
|
|
if (self.state != NetworkStreamStateIdle) {
|
|
NSLog(@"Request already in progress");
|
|
return;
|
|
}
|
|
|
|
[self updateState:NetworkStreamStateConnecting];
|
|
self.dataTask = [self.session dataTaskWithRequest:self.request];
|
|
[self.dataTask resume];
|
|
}
|
|
|
|
- (void)startRequestWithProgress:(NetworkStreamProgressBlock)progress
|
|
onData:(NetworkStreamDataBlock)dataBlock
|
|
onText:(NetworkStreamTextBlock)textBlock
|
|
completion:(NetworkStreamCompletionBlock)completion {
|
|
|
|
self.progressBlock = progress;
|
|
self.dataBlock = dataBlock;
|
|
self.textBlock = textBlock;
|
|
self.completionBlock = completion;
|
|
|
|
[self startRequest];
|
|
}
|
|
|
|
- (void)cancelRequest {
|
|
if (self.dataTask) {
|
|
[self.dataTask cancel];
|
|
self.dataTask = nil;
|
|
}
|
|
[self updateState:NetworkStreamStateIdle];
|
|
}
|
|
|
|
+ (NSURLRequest *)createDefaultRequestWithURL:(NSURL *)url method:(NSString *)method {
|
|
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
|
|
request.HTTPMethod = method;
|
|
request.timeoutInterval = 30.0;
|
|
|
|
// 设置常见的请求头(根据您的截图)
|
|
[request setValue:@"text/html, application/xhtml+xml, application/xml; q=0.9, image/avif, image/webp, image/apng, */*; q=0.8, application/signed-exchange; v=b3; q=0.7" forHTTPHeaderField:@"Accept"];
|
|
[request setValue:@"gzip, deflate" forHTTPHeaderField:@"Accept-Encoding"];
|
|
[request setValue:@"zh-CN, zh; q=0.9, ko; q=0.8, ja; q=0.7" forHTTPHeaderField:@"Accept-Language"];
|
|
[request setValue:@"keep-alive" forHTTPHeaderField:@"Connection"];
|
|
[request setValue:@"1" forHTTPHeaderField:@"Upgrade-Insecure-Requests"];
|
|
|
|
// 用户代理(可选)
|
|
NSString *userAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 15_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.0 Mobile/15E148 Safari/604.1";
|
|
[request setValue:userAgent forHTTPHeaderField:@"User-Agent"];
|
|
|
|
return [request copy];
|
|
}
|
|
|
|
#pragma mark - Private Methods
|
|
|
|
- (void)updateState:(NetworkStreamState)newState {
|
|
if (_state != newState) {
|
|
_state = newState;
|
|
|
|
// 通知代理状态改变
|
|
if ([self.delegate respondsToSelector:@selector(networkStream:stateChanged:)]) {
|
|
[self.delegate networkStream:self stateChanged:newState];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)notifyProgress:(float)progress {
|
|
if (self.progressBlock) {
|
|
self.progressBlock(progress);
|
|
}
|
|
|
|
if ([self.delegate respondsToSelector:@selector(networkStream:downloadProgress:)]) {
|
|
[self.delegate networkStream:self downloadProgress:progress];
|
|
}
|
|
}
|
|
|
|
- (void)notifyReceivedData:(NSData *)data {
|
|
if (self.dataBlock) {
|
|
self.dataBlock(data);
|
|
}
|
|
|
|
if ([self.delegate respondsToSelector:@selector(networkStream:didReceiveData:)]) {
|
|
[self.delegate networkStream:self didReceiveData:data];
|
|
}
|
|
|
|
// 如果是文本数据,尝试转换为字符串
|
|
if (self.textBlock || [self.delegate respondsToSelector:@selector(networkStream:didReceiveText:)]) {
|
|
NSString *text = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
|
if (text) {
|
|
if (self.textBlock) {
|
|
self.textBlock(text);
|
|
}
|
|
if ([self.delegate respondsToSelector:@selector(networkStream:didReceiveText:)]) {
|
|
[self.delegate networkStream:self didReceiveText:text];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)notifyCompletionWithError:(NSError * _Nullable)error {
|
|
if (self.completionBlock) {
|
|
self.completionBlock(error);
|
|
}
|
|
|
|
if ([self.delegate respondsToSelector:@selector(networkStream:didCompleteWithError:)]) {
|
|
[self.delegate networkStream:self didCompleteWithError:error];
|
|
}
|
|
}
|
|
|
|
#pragma mark - NSURLSessionDataDelegate
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
dataTask:(NSURLSessionDataTask *)dataTask
|
|
didReceiveResponse:(NSURLResponse *)response
|
|
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
|
|
|
|
self.response = response;
|
|
self.expectedContentLength = response.expectedContentLength;
|
|
_totalBytesReceived = 0;
|
|
[self.receivedData setLength:0];
|
|
|
|
[self updateState:NetworkStreamStateReceiving];
|
|
|
|
// 检查响应头,处理 CORS 等
|
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
|
|
NSLog(@"Response headers: %@", httpResponse.allHeaderFields);
|
|
|
|
// 可以在这里检查 CORS 头等信息
|
|
NSString *allowOrigin = httpResponse.allHeaderFields[@"Access-Control-Allow-Origin"];
|
|
if (allowOrigin) {
|
|
NSLog(@"CORS Allow Origin: %@", allowOrigin);
|
|
}
|
|
}
|
|
|
|
completionHandler(NSURLSessionResponseAllow);
|
|
}
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
dataTask:(NSURLSessionDataTask *)dataTask
|
|
didReceiveData:(NSData *)data {
|
|
|
|
_totalBytesReceived += data.length;
|
|
[self.receivedData appendData:data];
|
|
|
|
// 通知接收到数据块
|
|
[self notifyReceivedData:data];
|
|
|
|
// 计算并通知进度
|
|
if (self.expectedContentLength != NSURLResponseUnknownLength) {
|
|
float progress = (float)self.totalBytesReceived / (float)self.expectedContentLength;
|
|
[self notifyProgress:progress];
|
|
} else {
|
|
// 对于 chunked 传输,可能没有确切的内容长度
|
|
[self notifyProgress:-1]; // 使用 -1 表示未知进度
|
|
}
|
|
}
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
task:(NSURLSessionTask *)task
|
|
didCompleteWithError:(NSError *)error {
|
|
|
|
if (error) {
|
|
[self updateState:NetworkStreamStateError];
|
|
NSLog(@"Request failed with error: %@", error);
|
|
} else {
|
|
[self updateState:NetworkStreamStateCompleted];
|
|
NSLog(@"Request completed successfully. Total bytes: %lld", self.totalBytesReceived);
|
|
}
|
|
|
|
[self notifyCompletionWithError:error];
|
|
|
|
// 清理
|
|
[self.session finishTasksAndInvalidate];
|
|
self.dataTask = nil;
|
|
}
|
|
|
|
#pragma mark - URL Session Delegate (处理 SSL/认证)
|
|
|
|
- (void)URLSession:(NSURLSession *)session
|
|
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
|
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
|
|
|
|
// 处理 SSL 认证挑战
|
|
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
|
|
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
|
|
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
|
|
} else {
|
|
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
|
|
}
|
|
}
|
|
|
|
@end
|