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);
}
}