// // 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