背景

在iOS端由于文件系统的封闭性,文件的上传变得十分麻烦,一个比较好的解决方案是通过局域网WiFi来传输文件并存储到沙盒中。

简介

SGWiFiUpload是一个基于CocoaHTTPServer的WiFi上传框架。CocoaHTTPServer是一个可运行于iOS和OS X上的轻量级服务端框架,可以处理GET和POST请求,通过对代码的初步改造,实现了iOS端的WiFi文件上传与上传状态监听。

下载与使用

目前已经做成了易用的框架,上传到了GitHub,点击这里进入,欢迎Star!

请求的处理

CocoaHTTPServer通过HTTPConnection这一接口实现类来回调网络请求的各个状态,包括对请求头、响应体的解析等。为了实现文件上传,需要自定义一个继承HTTPConnection的类,这里命名为SGHTTPConnection,与文件上传有关的几个方法如下。

解析文件上传的请求头

  1. - (void)processStartOfPartWithHeader:(MultipartMessageHeader*) header {
  2. // in this sample, we are not interested in parts, other then file parts.
  3. // check content disposition to find out filename
  4. MultipartMessageHeaderField* disposition = [header.fields objectForKey:@"Content-Disposition"];
  5. NSString* filename = [[disposition.params objectForKey:@"filename"] lastPathComponent];
  6. if ( (nil == filename) || [filename isEqualToString: @""] ) {
  7. // it's either not a file part, or
  8. // an empty form sent. we won't handle it.
  9. return;
  10. }
  11. // 这里用于发出文件开始上传的通知
  12. dispatch_async(dispatch_get_main_queue(), ^{
  13. [[NSNotificationCenter defaultCenter] postNotificationName:SGFileUploadDidStartNotification object:@{@"fileName" : filename ?: @"File"}];
  14. });
  15. // 这里用于设置文件的保存路径,先预存一个空文件,然后进行追加写内容
  16. NSString *uploadDirPath = [SGWiFiUploadManager sharedManager].savePath;
  17. BOOL isDir = YES;
  18. if (![[NSFileManager defaultManager]fileExistsAtPath:uploadDirPath isDirectory:&isDir ]) {
  19. [[NSFileManager defaultManager]createDirectoryAtPath:uploadDirPath withIntermediateDirectories:YES attributes:nil error:nil];
  20. }
  21. NSString* filePath = [uploadDirPath stringByAppendingPathComponent: filename];
  22. if( [[NSFileManager defaultManager] fileExistsAtPath:filePath] ) {
  23. storeFile = nil;
  24. }
  25. else {
  26. HTTPLogVerbose(@"Saving file to %@", filePath);
  27. if(![[NSFileManager defaultManager] createDirectoryAtPath:uploadDirPath withIntermediateDirectories:true attributes:nil error:nil]) {
  28. HTTPLogError(@"Could not create directory at path: %@", filePath);
  29. }
  30. if(![[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil]) {
  31. HTTPLogError(@"Could not create file at path: %@", filePath);
  32. }
  33. storeFile = [NSFileHandle fileHandleForWritingAtPath:filePath];
  34. [uploadedFiles addObject: [NSString stringWithFormat:@"/upload/%@", filename]];
  35. }
  36. }

其中有中文注释的两处是比较重要的地方,这里根据请求头发出了文件开始上传的通知,并且往要存放的路径写一个空文件,以便后续追加内容。

上传过程中的处理

  1. - (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header
  2. {
  3. // here we just write the output from parser to the file.
  4. // 由于除去文件内容外,还有HTML内容和空文件通过此方法处理,因此需要过滤掉HTML和空文件内容
  5. if (!header.fields[@"Content-Disposition"]) {
  6. return;
  7. } else {
  8. MultipartMessageHeaderField *field = header.fields[@"Content-Disposition"];
  9. NSString *fileName = field.params[@"filename"];
  10. if (fileName.length == 0) return;
  11. }
  12. self.currentLength += data.length;
  13. CGFloat progress;
  14. if (self.contentLength == 0) {
  15. progress = 1.0f;
  16. } else {
  17. progress = (CGFloat)self.currentLength / self.contentLength;
  18. }
  19. dispatch_async(dispatch_get_main_queue(), ^{
  20. [[NSNotificationCenter defaultCenter] postNotificationName:SGFileUploadProgressNotification object:@{@"progress" : @(progress)}];
  21. });
  22. if (storeFile) {
  23. [storeFile writeData:data];
  24. }
  25. }

这里除了拼接文件内容以外,还发出了上传进度的通知,当前方法中只能拿到这一段文件的长度,总长度需要通过下面的方法拿到。

获取文件大小

  1. - (void)prepareForBodyWithSize:(UInt64)contentLength
  2. {
  3. HTTPLogTrace();
  4. // 设置文件总大小,并初始化当前已经传输的文件大小。
  5. self.contentLength = contentLength;
  6. self.currentLength = 0;
  7. // set up mime parser
  8. NSString* boundary = [request headerField:@"boundary"];
  9. parser = [[MultipartFormDataParser alloc] initWithBoundary:boundary formEncoding:NSUTF8StringEncoding];
  10. parser.delegate = self;
  11. uploadedFiles = [[NSMutableArray alloc] init];
  12. }

处理传输完毕

  1. - (void) processEndOfPartWithHeader:(MultipartMessageHeader*) header
  2. {
  3. // as the file part is over, we close the file.
  4. // 由于除去文件内容外,还有HTML内容和空文件通过此方法处理,因此需要过滤掉HTML和空文件内容
  5. if (!header.fields[@"Content-Disposition"]) {
  6. return;
  7. } else {
  8. MultipartMessageHeaderField *field = header.fields[@"Content-Disposition"];
  9. NSString *fileName = field.params[@"filename"];
  10. if (fileName.length == 0) return;
  11. }
  12. [storeFile closeFile];
  13. storeFile = nil;
  14. dispatch_async(dispatch_get_main_queue(), ^{
  15. [[NSNotificationCenter defaultCenter] postNotificationName:SGFileUploadDidEndNotification object:nil];
  16. });
  17. }

这里关闭了文件管道,并且发出了文件上传完毕的通知。

开启Server

CocoaHTTPServer默认的Web根目录为MainBundle,他会在目录下寻找index.html,文件上传的请求地址为upload.html,当以POST方式请求upload.html时,请求会被Server拦截,并且交由HTTPConnection处理。

  1. - (BOOL)startHTTPServerAtPort:(UInt16)port {
  2. HTTPServer *server = [HTTPServer new];
  3. server.port = port;
  4. self.httpServer = server;
  5. [self.httpServer setDocumentRoot:self.webPath];
  6. [self.httpServer setConnectionClass:[SGHTTPConnection class]];
  7. NSError *error = nil;
  8. [self.httpServer start:&error];
  9. return error == nil;
  10. }

在HTML中发送POST请求上传文件

在CocoaHTTPServer给出的样例中有用于文件上传的index.html,要实现文件上传,只需要一个POST方法的form表单,action为upload.html,每一个文件使用一个input标签,type为file即可,这里为了美观对input标签进行了自定义。

下面的代码演示了能同时上传3个文件的index.html代码。

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  2. <html>
  3. <head>
  4. <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">
  5. </head>
  6. <style>
  7. body {
  8. margin: 0px;
  9. padding: 0px;
  10. font-size: 12px;
  11. background-color: rgb(244,244,244);
  12. text-align: center;
  13. }
  14. #container {
  15. margin: auto;
  16. }
  17. #form {
  18. margin-top: 60px;
  19. }
  20. .upload {
  21. margin-top: 2px;
  22. }
  23. #submit input {
  24. background-color: #ea4c88;
  25. color: #eee;
  26. font-weight: bold;
  27. margin-top: 10px;
  28. text-align: center;
  29. font-size: 16px;
  30. border: none;
  31. width: 120px;
  32. height: 36px;
  33. }
  34. #submit input:hover {
  35. background-color: #d44179;
  36. }
  37. #submit input:active {
  38. background-color: #a23351;
  39. }
  40. .uploadField {
  41. margin-top: 2px;
  42. width: 200px;
  43. height: 22px;
  44. font-size: 12px;
  45. }
  46. .uploadButton {
  47. background-color: #ea4c88;
  48. color: #eee;
  49. font-weight: bold;
  50. text-align: center;
  51. font-size: 15px;
  52. border: none;
  53. width: 80px;
  54. height: 26px;
  55. }
  56. .uploadButton:hover {
  57. background-color: #d44179;
  58. }
  59. .uploadButton:active {
  60. background-color: #a23351;
  61. }
  62. </style>
  63. <body>
  64. <div id="container">
  65. <div id="form">
  66. <h2>WiFi File Upload</h2>
  67. <form name="form" action="upload.html" method="post" enctype="multipart/form-data" accept-charset="utf-8">
  68. <div class="upload">
  69. <input type="file" name="upload1" id="upload1" style="display:none" onChange="document.form.path1.value=this.value">
  70. <input class="uploadField" name="path1" readonly>
  71. <input class="uploadButton" type="button" value="Open" onclick="document.form.upload1.click()">
  72. </div>
  73. <div class="upload">
  74. <input type="file" name="upload2" id="upload2" style="display:none" onChange="document.form.path2.value=this.value">
  75. <input class="uploadField" name="path2" readonly>
  76. <input class="uploadButton" type="button" value="Open" onclick="document.form.upload2.click()">
  77. </div>
  78. <div class="upload">
  79. <input type="file" name="upload3" id="upload3" style="display:none" onChange="document.form.path3.value=this.value">
  80. <input class="uploadField" name="path3" readonly>
  81. <input class="uploadButton" type="button" value="Open" onclick="document.form.upload3.click()">
  82. </div>
  83. <div id="submit"><input type="submit" value="Submit"></div>
  84. </form>
  85. </div>
  86. </div>
  87. </body>
  88. </html>

表单提交后,会进入upload.html页面,该页面用于说明上传完毕,下面的代码实现了3秒后的重定向返回。

  1. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
  2. <html>
  3. <head>
  4. <meta http-equiv=\"content-type\" content=\"text/html; charset=UTF-8\">
  5. <meta http-equiv=refresh content="3;url=index.html">
  6. </head>
  7. <body>
  8. <h3>Upload Succeeded!</h3>
  9. <p>The Page will be back in 3 seconds</p>
  10. </body>
  11. </html>

WiFi文件上传框架SGWiFiUpload的更多相关文章

  1. upload4j安全、高效、易用的java http文件上传框架

    简介 upload4j是一款轻量级http文件上传框架,使用简单,实现高效,功能专一,摆脱传统http文件上传框架的繁琐. upload4j的诞生并不是为了解决所有上传需求,而是专注于基础通用需求. ...

  2. Struts2文件上传和下载(原理)

    转自:http://zhou568xiao.iteye.com/blog/220732 1.    文件上传的原理:表单元素的enctype属性指定的是表单数据的编码方式,该属性有3个值:1)     ...

  3. Struts2 文件上传,下载,删除

    本文介绍了: 1.基于表单的文件上传 2.Struts 2 的文件下载 3.Struts2.文件上传 4.使用FileInputStream FileOutputStream文件流来上传 5.使用Fi ...

  4. 2013第38周日Java文件上传下载收集思考

    2013第38周日Java文件上传&下载收集思考 感觉文件上传及下载操作很常用,之前简单搜集过一些东西,没有及时学习总结,现在基本没啥印象了,今天就再次学习下,记录下自己目前知识背景下对该类问 ...

  5. 笔记:Struts2 文件上传和下载

    为了上传文件必须将表单的method设置为POST,将 enctype 设置为 muiltipart/form-data,只有设置为这种情况下,浏览器才会把用户选择文件的二进制数据发送给服务器. 上传 ...

  6. Struts2单文件上传原理及示例

    一.文件上传的原理 表单元素的enctype属性指定的是表单数据的编码方式,该属性有3个值: 1.application/x-www-form-urlencoded:这是默认编码方式,它只处理表单域里 ...

  7. [转]Struts2多个文件上传

    转载至:http://blog.csdn.net/hanxiaoshuang123/article/details/7342091 Struts2多个文件上传多个文件上传分为List集合和数组,下面我 ...

  8. 使用apache-fileupload处理文件上传与上传多个文件 二(60)

    一 使用apache-fileupload处理文件上传 框架:是指将用户经常处理的业务进行一个代码封装.让用户可以方便的调用. 目前文件上传的(框架)组件: Apache----fileupload ...

  9. Struts2笔记--文件上传

    Servlet 3.0规范的HttpServletRequest已经提供了方法来处理文件上传但这种上传需要在Servlet中完成.而Struts2则提供了更简单的封装. Struts2默认使用的是Ja ...

随机推荐

  1. Unity3D input.GetAxis

    input.GetAxis用法:(GetAxis("Mouse X"),GetAxis("Mouse Y"),GetAxis("Mouse Scrol ...

  2. 从三个开源项目认识OpenFlow交换机 - OVS

    在SDN/NFV的网络革新技术浪潮的引领下,催生了诸多数据面开源方案的诞生.业界知名度较高的有OVS(Open vSwitch).FD.io (Fast Data I/O).ODP(Open Data ...

  3. 将 Net 项目升级 Core项目经验:(二)修复迁移后Net Standard项目中的错误

    修复迁移后Net Standard项目中的错误 接上一章,项目编译结果如下: 解决依赖dll引用 在Net Framework项目的引用如下: 各引用和作用: log4net(1.10.0.0) 用于 ...

  4. flex布局小记

    越来越深刻的感到日事日毕的必要性,很久之前就做了备忘说要深刻学习flex布局,没想到一拖就拖到了这个时候! 一,什么是flex布局: flex布局即flexible box布局,也就是弹性盒模型或者弹 ...

  5. 持久化 XSS:ServiceWorkers 利用

    来源:http://www.mottoin.com/95058.html 来源:https://www.owasp.org/images/3/35/2017-04-20-JSONPXSS.pdf Se ...

  6. [NOIp 2014]联合权值

    Description 无向连通图G 有n 个点,n - 1 条边.点从1 到n 依次编号,编号为 i 的点的权值为W i ,每条边的长度均为1 .图上两点( u , v ) 的距离定义为u 点到v ...

  7. [JLOI2015]管道连接

    题目描述 小铭铭最近进入了某情报部门,该部门正在被如何建立安全的通道连接困扰.该部门有 n 个情报站,用 1 到 n 的整数编号.给出 m 对情报站 ui;vi 和费用 wi,表示情报站 ui 和 v ...

  8. ●洛谷P1903 [国家集训队]数颜色

    题链: https://www.luogu.org/problemnew/show/P1903题解: 序列带修莫队, 推荐博客https://www.cnblogs.com/Paul-Guderian ...

  9. ●Joyoi Easy

    题链: http://www.joyoi.cn/problem/tyvj-1952题解: 概率dp (先做的BZOJ 4318: OSU![本人题解],然后就感觉这个题很简单了) 令p[i]表示第i个 ...

  10. ●Codevs 4158 残缺的字符串

    题链: http://codevs.cn/problem/4158/ 题解: FFT. 定义两个相同长度的字符串s1,s2的距离为 $$dis(s1,s2)=\sum_{i=0}^{len-1}(s1 ...