1
This commit is contained in:
253
CustomKeyboard/Network/NetworkStreamHandler.m
Normal file
253
CustomKeyboard/Network/NetworkStreamHandler.m
Normal file
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// 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
|
||||
Reference in New Issue
Block a user