goahead内嵌web——文件上传

goahead设备内嵌web——环境搭建

goahead内嵌web——用户登录

        goahead内嵌web文件上传,对于嵌入式设备而言可以实现设备通过web升级或者配置参数通过web导入到设备中。goahead文件上传处理逻辑基本上都是在upload.c代码中,我们通过结合goahead中给的test demo对文件上传这部分代码逻辑进行阐述以及实现文件上传。

        通过在web资源文件中重新创建一个HTML文件,这里我创建了一个叫upload.html文件,文件内容是实现选择需要上传的文件,通过选择文件,点击确认后将文件上传到我们web资源路径下。

<html>
<head>
<meta charset="utf-8"/>
<title>Upload</title>
</head>
<body>
    <h2>File Upload to HTML</h2>
    <form method="post" action="/action/uploadTest" enctype="multipart/form-data">
        <input type="hidden" name="MAX_FILE_SIZE" value="30000">
        <table border="0">
            <tr><td>File:</td><td><input type="file" name="file"></td></tr>
        </table>
        <input type="submit" value="send">
        <input type="reset" value="cancel">
    </form>
</body>
</html>
~       

这里action指定action="/action/uploadTest"

        通过http://localhost/upload.html进行访问,展示的界面如下所示,通过Browse 选择需要上传的文件。

还需要在代码中对uploadTest的处理方式。具体代码如下

websDefineAction("uploadTest", uploadTest);


static void uploadTest(Webs *wp)
{
    WebsKey         *s;
    WebsUpload      *up;
    char            *upfile;

    websSetStatus(wp, 200);
    websWriteHeaders(wp, -1, 0);
    websWriteHeader(wp, "Content-Type", "text/plain");
    websWriteEndHeaders(wp);
    if (scaselessmatch(wp->method, "POST")) {
        for (s = hashFirst(wp->files); s; s = hashNext(wp->files, s)) {
            up = s->content.value.symbol;
            websWrite(wp, "FILE: %s\r\n", s->name.value.string);
            websWrite(wp, "FILENAME=%s\r\n", up->filename);
            websWrite(wp, "CLIENT=%s\r\n", up->clientFilename);
            websWrite(wp, "TYPE=%s\r\n", up->contentType);
            websWrite(wp, "SIZE=%d\r\n", up->size);
            upfile = sfmt("%s/tmp/%s", websGetDocuments(), up->clientFilename);
            if (rename(up->filename, upfile) < 0) {
                error("Cannot rename uploaded file: %s to %s, errno %d", up->filename, upfile, errno);
            }
            wfree(upfile);
        }
        websWrite(wp, "\r\nVARS:\r\n");
        for (s = hashFirst(wp->vars); s; s = hashNext(wp->vars, s)) {
            websWrite(wp, "%s=%s\r\n", s->name.value.string, s->content.value.string);
        }
    }
    websDone(wp);
}

         编译执行后,选择待上传的文件,发现提示 goahead: 2: Cannot open upload temp file tmp/tmp-0.tmp 通过阅读代码了解到原来goahead默认上传文件到tmp路径下,这个tmp路径是相对路径,也就是当前路径下的tmp文件,这里我们没有创建tmp文件,可以创建一个tmp文件从而解决该问题,亦可通过修改me.h中的头文件进行修改,在编译源码目录下projects/goahead-linux-default-me.h 修改ME_GOAHEAD_UPLOAD_DIR宏定义,这里我们先修改为绝对路径 /tmp下面#define ME_GOAHEAD_UPLOAD_DIR "/tmp" ,重新编译源码生成新的libgo.so以及头文件me.h。

        修改后运行,选择文件删除,提示goahead: 0: Cannot rename uploaded file: /tmp/tmp-0.tmp to web/tmp/wei_upload_file, errno 2 也就是说web路径下无tmp目录,因此在web目录下创建tmp路径,这个是action相信uploadTest中我们重命名打印的错误,因为不重命名的话web链接断开后会自动清理/tmp下面的文件。然后选择上传文件,这时候我们可以看到web/tmp/路径下可以看到我们上传成功的文件。

代码分析

在http.c文件中,对于http请求头部解析中parseHeaders

else if (strcmp(key, "content-length") == 0) {
            if ((wp->rxLen = atoi(value)) < 0) {
                websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Invalid content length");
                return;
            }
            if (smatch(wp->method, "PUT")) {
                if (wp->rxLen > ME_GOAHEAD_LIMIT_PUT) {
                    websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Too big");
                    return;
                }
            } else {
                if (wp->rxLen > ME_GOAHEAD_LIMIT_POST) {
                    websError(wp, HTTP_CODE_REQUEST_TOO_LARGE | WEBS_CLOSE, "Too big");
                    return;
                }
            }
            if (!smatch(wp->method, "HEAD")) {
                wp->rxRemaining = wp->rxLen;
            }

        } else if (strcmp(key, "content-type") == 0) {
            wfree(wp->contentType);
            wp->contentType = sclone(value);
            if (strstr(value, "application/x-www-form-urlencoded")) {
                wp->flags |= WEBS_FORM;
            } else if (strstr(value, "application/json")) {
                wp->flags |= WEBS_JSON;
            } else if (strstr(value, "multipart/form-data")) {
                wp->flags |= WEBS_UPLOAD;
            }

        }

其中对于请求数据大小进行了限制,put限制为ME_GOAHEAD_LIMIT_PUT,post限制为ME_GOAHEAD_LIMIT_POST,该宏定义均在me.h中定义。这里可以通过上传一个5M的文件则提示

goahead: 2: POST /action/uploadTest HTTP/1.1
goahead: 2: Too big

#ifndef ME_GOAHEAD_LIMIT_PUT
    #define ME_GOAHEAD_LIMIT_PUT 204800000
#endif

#ifndef ME_GOAHEAD_LIMIT_POST
    #define ME_GOAHEAD_LIMIT_POST 16384
#endif

通过multipart/form-data判断时候是upload file标志位。

        在processContent函数中,通过判断时候是WEBS_UPLOAD标志,从而进行websProcessUploadData

static bool processContent(Webs *wp)
{
    bool    canProceed;

    if (!wp->eof) {
        canProceed = filterChunkData(wp);
        if (!canProceed || wp->finalized) {
            return canProceed;
        }
#if ME_GOAHEAD_UPLOAD
        if (wp->flags & WEBS_UPLOAD) {
            canProceed = websProcessUploadData(wp);
            if (!canProceed || wp->finalized) {
                return canProceed;
            }
        }
#endif

}
PUBLIC bool websProcessUploadData(Webs *wp)
{
    char    *line, *nextTok;
    ssize   nbytes;
    bool    canProceed;

    line = 0;
    canProceed = 1;
    while (canProceed && !wp->finalized && wp->uploadState != UPLOAD_CONTENT_END) {
        if  (wp->uploadState == UPLOAD_BOUNDARY || wp->uploadState == UPLOAD_CONTENT_HEADER) {
            /*
                Parse the next input line
             */
            line = wp->input.servp;
            if ((nextTok = memchr(line, '\n', bufLen(&wp->input))) == 0) {
                /* Incomplete line */
                canProceed = 0;
                break;
            }
            *nextTok++ = '\0';
            nbytes = nextTok - line;
            assert(nbytes > 0);
            websConsumeInput(wp, nbytes);
            strim(line, "\r", WEBS_TRIM_END);
        }
        switch (wp->uploadState) {
        case 0:
            initUpload(wp);
            break;

        case UPLOAD_BOUNDARY:
            processContentBoundary(wp, line);
            break;

        case UPLOAD_CONTENT_HEADER:
            processUploadHeader(wp, line);
            break;

        case UPLOAD_CONTENT_DATA:
            canProceed = processContentData(wp);
            if (bufLen(&wp->input) < wp->boundaryLen) {
                /*  Incomplete boundary - return to get more data */
                canProceed = 0;
            }
            break;

        case UPLOAD_CONTENT_END:
            break;
        }
    }
    bufCompact(&wp->input);
    return canProceed;
}

分析一下processUploadHeader函数,websTempFile根据上传路径,生成一个临时文件路径的绝对地址,由于前面我们在头文件中已经定义上传文件的路径为/tmp路径,因此websTempFile根据/tmp生成一个/tmp/tmp-0.tmp 数字是websTempFile中根据count++生成。open函数回创建这样一个临时文件。

static void processUploadHeader(Webs *wp, char *line)
{
    ............
else if (scaselesscmp(key, "filename") == 0) {
                if (wp->uploadVar == 0) {
                    websError(wp, HTTP_CODE_BAD_REQUEST, "Bad upload state. Missing name field");
                    return;
                }
                value = websNormalizeUriPath(value);
                if (*value == '.' || !websValidUriChars(value) || strpbrk(value, "\\/:*?<>|~\"'%`^\n\r\t\f")) {
                    websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Bad upload client filename");
                    wfree(value);
                    return;
                }
                wfree(wp->clientFilename);
                wp->clientFilename = value;

                /*
                    Create the file to hold the uploaded data
                 */
                wfree(wp->uploadTmp);
                printf("uploadDir %s  wp->uploadTmp is %s",uploadDir,wp->uploadTmp);
                if ((wp->uploadTmp = websTempFile(uploadDir, "tmp")) == 0) {
                    websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR,
                        "Cannot create upload temp file %s. Check upload temp dir %s", wp->uploadTmp, uploadDir);
                    return;
                }
                trace(5, "File upload of: %s stored as %s", wp->clientFilename, wp->uploadTmp);

                if ((wp->upfd = open(wp->uploadTmp, O_WRONLY | O_CREAT | O_TRUNC | O_BINARY, 0600)) < 0) {
                    websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Cannot open upload temp file %s", wp->uploadTmp);
                    return;
                }
                /*
                    Create the files[id]
                 */
                freeUploadFile(wp->currentFile);
                file = wp->currentFile = walloc(sizeof(WebsUpload));
                memset(file, 0, sizeof(WebsUpload));
                file->clientFilename = sclone(wp->clientFilename);
                file->filename = sclone(wp->uploadTmp);
            }
............
}


PUBLIC char *websTempFile(cchar *dir, cchar *prefix)
{
    static int count = 0;
    char   sep;

    sep = '/';
    if (!dir || *dir == '\0') {
#if WINCE
        dir = "/Temp";
        sep = '\\';
#elif ME_WIN_LIKE
        dir = getenv("TEMP");
        sep = '\\';
#elif VXWORKS
        dir = ".";
#else
        dir = "/tmp";
#endif
    }
    if (!prefix) {
        prefix = "tmp";
    }
    return sfmt("%s%c%s-%d.tmp", dir, sep, prefix, count++);
}

processContentData函数将读取到的内容写到临时文件中,writeToFile将数据写到文件中。

static bool processContentData(Webs *wp){
............
    if ((bp = getBoundary(wp, content->servp, size)) == 0) {
        trace(7, "uploadFilter: Got boundary filename %x", wp->clientFilename);
        if (wp->clientFilename) {
            /*
                No signature found yet. probably more data to come. Must handle split boundaries.
             */
            data = content->servp;
            nbytes = ((int) (content->endp - data)) - (wp->boundaryLen - 1);
            if (writeToFile(wp, content->servp, nbytes) < 0) {
                /* Proceed to handle error */
                return 1;
            }
            websConsumeInput(wp, nbytes);
            /* Get more data */
            return 0;
        }
    }
..............
}

//向文件中写内容
static int writeToFile(Webs *wp, char *data, ssize len)
{
    WebsUpload      *file;
    ssize           rc;

    file = wp->currentFile;

    if ((file->size + len) > ME_GOAHEAD_LIMIT_UPLOAD) {
        websError(wp, HTTP_CODE_REQUEST_TOO_LARGE, "Uploaded file exceeds maximum %d", (int) ME_GOAHEAD_LIMIT_UPLOAD);
        return -1;
    }
    if (len > 0) {
        /*
            File upload. Write the file data.
         */
        if ((rc = write(wp->upfd, data, (int) len)) != len) {
            websError(wp, HTTP_CODE_INTERNAL_SERVER_ERROR, "Cannot write to upload temp file %s, rc %d", wp->uploadTmp, rc);
            return -1;
        }
        file->size += len;
        trace(7, "uploadFilter: Wrote %d bytes to %s", len, wp->uploadTmp);
    }
    return 0;
}

清理文件是通过termWebs,中调用websFreeUpload实现的。具体可以看一下websFreeUpload和freeUploadFile代码

PUBLIC void websFreeUpload(Webs *wp)
{
    WebsUpload  *up;
    WebsKey     *s;

    if (wp->files >= 0) {
        for (s = hashFirst(wp->files); s; s = hashNext(wp->files, s)) {
            up = s->content.value.symbol;
            freeUploadFile(up);
            if (up == wp->currentFile) {
                wp->currentFile = 0;
            }
        }
        hashFree(wp->files);
    }
    if (wp->currentFile) {
        freeUploadFile(wp->currentFile);
        wp->currentFile = 0;
    }
    if (wp->upfd >= 0) {
        close(wp->upfd);
        wp->upfd = -1;
    }
}


static void freeUploadFile(WebsUpload *up)
{
    if (up) {
        if (up->filename) {
            unlink(up->filename);
            wfree(up->filename);
        }
        wfree(up->clientFilename);
        wfree(up->contentType);
        wfree(up);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值