Files
keyboard/CustomKeyboard/Network/NetworkStreamHandler.m
2025-11-12 14:18:56 +08:00

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