背景

在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. logging的使用方法

    logging的使用方法 1,简单使用方法 >>> import logging >>> logging.warning('this is a warning') ...

  2. find()用法

    >>> str = '编程改变世界'>>> str.find('编')0>>> str.find('程')1>>> str.fi ...

  3. Python中的上下文管理器和with语句

    Python2.5之后引入了上下文管理器(context manager),算是Python的黑魔法之一,它用于规定某个对象的使用范围.本文是针对于该功能的思考总结. 为什么需要上下文管理器? 首先, ...

  4. 两个css之间的切换

    需求: 头部两个按钮 两种样式之间的切换 解决办法: 结合JQ  三目运算 来处理 第一步: 把需要切换的样式设置为样式里背景,这样做的目的为了避免 js里出现过多 css代码 二来这样会显得更加的清 ...

  5. [LeetCode] Complex Number Multiplication 复数相乘

    Given two strings representing two complex numbers. You need to return a string representing their m ...

  6. [BZOJ 4589]Hard Nim

    Description 题库链接 两人玩 \(nim\) 游戏,\(n\) 堆石子,每堆石子初始数量是不超过 \(m\) 的质数,那么后手必胜的方案有多少种.对 \(10^9+7\) 取模. \(1\ ...

  7. [HNOI 2010]Planar

    Description 题库链接 给出 \(T\) 个 \(N\) 个节点 \(M\) 条边的无向图(无重边自环),并给出它们各自的哈密顿回路.分别判断每个图是否是平面图. \(T\leq 100,3 ...

  8. codeforces Gym - 100633J Ceizenpok’s formula

    拓展Lucas #include<cstdio> #include<cstdlib> #include<algorithm> #include<cstring ...

  9. ●BZOJ 4453 cys就是要拿英魂!

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=4453 题解: 后缀数组,离线询问,栈看了一堆题解才看懂,太弱啦 ~ 如果对于一个区间[l,r ...

  10. 51Nod 1530 稳定方块

    瓦西亚和皮台亚摆放了m个方块.方块被编号为0到m-1(每个号码出现恰好一次).现在建立一个座标系OX表示地面,OY的方向是竖直向上的.每一方块的左下角有一个座标而且是整点座标. 摆放好的方块一定要是稳 ...