• WiFi文件上传框架SGWiFiUpload


    背景

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

    简介

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

    下载与使用

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

    请求的处理

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

    解析文件上传的请求头

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

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

    上传过程中的处理

    - (void) processContent:(NSData*) data WithHeader:(MultipartMessageHeader*) header 
    {
        // here we just write the output from parser to the file.
        // 由于除去文件内容外,还有HTML内容和空文件通过此方法处理,因此需要过滤掉HTML和空文件内容
        if (!header.fields[@"Content-Disposition"]) {
            return;
        } else {
            MultipartMessageHeaderField *field = header.fields[@"Content-Disposition"];
            NSString *fileName = field.params[@"filename"];
            if (fileName.length == 0) return;
        }
        self.currentLength += data.length;
        CGFloat progress;
        if (self.contentLength == 0) {
            progress = 1.0f;
        } else {
            progress = (CGFloat)self.currentLength / self.contentLength;
        }
        dispatch_async(dispatch_get_main_queue(), ^{
           [[NSNotificationCenter defaultCenter] postNotificationName:SGFileUploadProgressNotification object:@{@"progress" : @(progress)}]; 
        });
        if (storeFile) {
            [storeFile writeData:data];
        }
    }

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

    获取文件大小

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

    处理传输完毕

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

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

    开启Server

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

    - (BOOL)startHTTPServerAtPort:(UInt16)port {
        HTTPServer *server = [HTTPServer new];
        server.port = port;
        self.httpServer = server;
        [self.httpServer setDocumentRoot:self.webPath];
        [self.httpServer setConnectionClass:[SGHTTPConnection class]];
        NSError *error = nil;
        [self.httpServer start:&error];
        return error == nil;
    }

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

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

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
        <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
        </head>
        <style>
        body {
            margin: 0px;
            padding: 0px;
            font-size: 12px;
            background-color: rgb(244,244,244);
            text-align: center;
        }
        #container {
            margin: auto;
        }
    
        #form {
            margin-top: 60px;
        }
    
        .upload {
            margin-top: 2px;
        }
    
        #submit input {
            background-color: #ea4c88;
            color: #eee;
            font-weight: bold;
            margin-top: 10px;
            text-align: center;
            font-size: 16px;
            border: none;
            width: 120px;
            height: 36px;
        }
    
        #submit input:hover {
            background-color: #d44179;
        }
    
        #submit input:active {
            background-color: #a23351;
        }
    
        .uploadField {
            margin-top: 2px;
            width: 200px;
            height: 22px;
            font-size: 12px;
        }
    
        .uploadButton {
            background-color: #ea4c88;
            color: #eee;
            font-weight: bold;
            text-align: center;
            font-size: 15px;
            border: none;
            width: 80px;
            height: 26px;
        }
    
        .uploadButton:hover {
            background-color: #d44179;
        }
    
        .uploadButton:active {
            background-color: #a23351;
        }
    
        </style>
        <body>
            <div id="container">
                <div id="form">
                    <h2>WiFi File Upload</h2>
                    <form name="form" action="upload.html" method="post" enctype="multipart/form-data" accept-charset="utf-8">
                        <div class="upload">
                            <input type="file" name="upload1" id="upload1" style="display:none" onChange="document.form.path1.value=this.value">
                                <input class="uploadField" name="path1" readonly>
                                    <input class="uploadButton" type="button" value="Open" onclick="document.form.upload1.click()">
                        </div>
                        <div class="upload">
                            <input type="file" name="upload2" id="upload2" style="display:none" onChange="document.form.path2.value=this.value">
                                <input class="uploadField" name="path2" readonly>
                                    <input class="uploadButton" type="button" value="Open" onclick="document.form.upload2.click()">
                        </div>
                        <div class="upload">
                            <input type="file" name="upload3" id="upload3" style="display:none" onChange="document.form.path3.value=this.value">
                                <input class="uploadField" name="path3" readonly>
                                    <input class="uploadButton" type="button" value="Open" onclick="document.form.upload3.click()">
                                        </div>
                        <div id="submit"><input type="submit" value="Submit"></div>
                    </form>
                </div>
            </div>
        </body>
    </html>

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

    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
    <html>
        <head>
            <meta http-equiv="content-type" content="text/html; charset=UTF-8">
            <meta http-equiv=refresh content="3;url=index.html">
        </head>
        <body>
            <h3>Upload Succeeded!</h3>
            <p>The Page will be back in 3 seconds</p>
        </body>
    </html>
  • 相关阅读:
    Linux如何设置时区/时间/上海时间
    Anaconda Python3.7环境 import _ssl DLL load failed(ImportError:DLL load failed:找不到指定模块)
    一行命令搞定/usr/bin/perl^M: bad interpreter
    Embed MP4 in HTML using flash-player(html5 video player)
    mp4文件转码为m3u8
    Python 下载图片的三种方法
    图解MySQL 内连接、外连接、左连接、右连接、全连接……太多了
    DOS批处理中%~dp0表示什么意思
    Android学习探索之App多渠道打包及动态添加修改资源属性
    Android学习探索之运用MVP设计模式实现项目解耦
  • 原文地址:https://www.cnblogs.com/aiwz/p/6154009.html
Copyright © 2020-2023  润新知