1.前言
iOS客户端本地服务器
2.为什么需要 Local Server?
场景:
离线加载前端网页时。使用 WKWebView 打开本地的 html 文件,WKWebView 需要开启allowFileAccessFromFileURLs 和 allowUniversalAccessFromFileURLs 配置。
allowFileAccessFromFileURLs:
允许加载file URL的页面,同时页面的js代码可以通过file URL形式访问本地文件,从而造成沙盒内任意文件读取。导致攻击者只要保证目标APP使用file URL加载web页面就可以实现攻击。
allowUniversalAccessFromFileURLs:
允许使用file URL加载的页面访问其他的源(如HTTP或HTTPS),通过此选项攻击者就可以将上一步读取到的文件内容发送到远端服务器,从而实现了“克隆”攻击和照片窃取。
问题:
开启后该WebView允许通过 file url 对 http 域或 https域 进行访问,若未对访问的路径进行严格校验,攻击者可利用该漏洞,远程获取手机应用沙盒内所有本地文件系统内容,包括浏览器的 Cookies、用户的配置文件、文档等敏感信息。可以远程打开并加载恶意 HTML 文件等。
解决:
开启 Local Server ,来访问本地的 html 文件。离线加载前端网页
2.1 简单介绍一下访问html文件的二种方式:
1)http://
http协议(超文本传输协议)
浏览器打开
访问服务器上的html文件,是以http的协议方式去打开, 有网络交互
localhost://
(localhost:// 即本地的http://)
本地服务器localhost(一般情况下是 127.0.0.1)
查看本机IP :ifconfig -a | grep inet
访问本地的html文件,相当于将本机作为一台http服务器,然后通过localhost访问本地服务器,再通过http服务器访问本机的文件资源。
本地搭建的http服务器开放端口后,其他设备也可以通过http访问到你设备中的文件,file://做不到
2)file://
file协议(本地文件传输协议)
访问本地设备中的文件资源,将该请求视为一个本地资源访问请求
只能在本地访问,没有网络交互
file协议无法实现跨域
(跨域:浏览器的同源策略导致了跨域。是一个用于隔离潜在恶意文件的安全机制。)
3.使用场景实战
3.1 封装的提供localServer服务的管理类
LocalHTTPServerManager.h
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface LocalHTTPServerManager : NSObject
// 本地服务器根路径
@property(nonatomic, copy)NSString *desPath;
// 本地服务器端口
@property (nonatomic,copy) NSString *serverPort;
+ (instancetype)sharedInstance;
// 启动服务
- (BOOL)startServer;
// 停止服务
- (void)stopServer;
@end
NS_ASSUME_NONNULL_END
LocalHTTPServerManager.m
#import "LocalHTTPServerManager.h"
#import "HTTPServer.h"
@interface LocalHTTPServerManager()
// 本地服务器
@property(nonatomic,strong) HTTPServer *httpServer;
@end
@implementation LocalHTTPServerManager
+ (instancetype)sharedInstance {
static LocalHTTPServerManager *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[LocalHTTPServerManager alloc] init];
});
return instance;
}
// 启动服务
- (BOOL)startServer {
if (!_httpServer) {
_httpServer = [[HTTPServer alloc] init];
// 如果要设置传统的 HTTP 服务器,类型需要设置为“_http._tcp”
[_httpServer setType: @"_http._tcp."];
// 端口号任意什么都行
[_httpServer setPort: 8080];
// 设置服务器根路径,必须在目标index.html的上一级
[_httpServer setDocumentRoot:_desPath];
}
if (_httpServer && ![_httpServer isRunning]) {
NSError *error;
if ([_httpServer start: &error]) {
self.serverPort = [NSString stringWithFormat:@"%d",[_httpServer listeningPort]];
NSLog(@"LocalServer启动成功");
return YES;
} else {
NSLog(@"LocalServer启动失败:%@",[error localizedDescription]);
return NO;
}
}
return NO;
}
// 停止服务
- (void)stopServer {
if (_httpServer && [_httpServer isRunning]) {
[_httpServer stop];
NSLog(@"LocalServer停止成功");
}
}
// 判断服务是否正在运行
-(BOOL)serverIsRunning {
return _httpServer && [_httpServer isRunning];
}
@end
3.2 实例1:将H5文件先下载到沙盒中,然后再用手机开启本地服务器来进行访问
关键代码段:
// 下载并解压成功后,开启localServer,加载本地网页
// 需要下载的 zip 包地址
var zipUrl = ""
// 存储目录路径
let desDirPath = FileManager.AIFile
// 文件夹名称
let fileName = "AIFiles"
let localManager = LocalHTTPServerManager.sharedInstance()
func downloadZip() {
DispatchQueue.global().async {
ZipDownloadManager.shared.commonDownload(withZipPath: self.zipUrl, desDirPath: self.desDirPath, saveFileName: self.fileName, downloadProgress: { (progress) in
// 下载进度展示
self.loadingView.progress = CGFloat(progress)
// 进度100时,隐藏下载进度条
if progress >= 100 {
self.loadingView.isHidden = true
}
}, success: { [weak self](_) in
// 下载并解压成功,隐藏下载进度条
self?.loadingView.isHidden = true
// 开启本地 server,打开本地文件
self?.loadLocalHttpServer()
}) { [weak self](error) in
// 下载失败,隐藏下载进度条,弹出toast
self?.loadingView.isHidden = true
let errorString = "zip下载失败-\(String(describing: error))"
NSObject.showHUDTipStr(errorString)
}
}
}
// 开启本地server
func loadLocalHttpServer() {
// 设置本地服务器根路径
localManager.desPath = desDirPath + "/" + fileName
// 开启服务
let startBool = localManager.startServer()
if startBool {
let port = localManager.serverPort
guard let url = URL.init(string: "http://localhost:\(port)/index.html") else { return }
let request = URLRequest.init(url: url)
self.tmpWebView.load(request)
}
}
deinit {
// 停止服务
localManager.stopServer()
}
引申问题1:
如果手机上本地缓存了多个文件,在safari中如何找到其中一个?
答: 搭建本地服务器时,设置本地服务器根路径,必须在目标index.html的上一级,所以是唯一的
引申问题2:
后台运行?后台是否会断开?该怎么处理
当程序在后台的时候,可能导致服务器断开,所以需要重启服务。解决方式之一:在AppDelegate中重启服务,
关键代码片段:
- (void)applicationDidEnterBackground:(UIApplication *)application {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
if ([self serverIsRunning]){//停止本地服务器
[self stopServer];
}
}
- (void)applicationWillEnterForeground:(UIApplication *)application {
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
if (![self serverIsRunning]){
_startServer = [self startServer];
}
}
网友评论