强烈建议从头开始看,思路会比较顺畅。
先来看websOpen函数的源码:
PUBLIC int websOpen(cchar *documents, cchar *routeFile)
{
WebsMime *mt;
webs = NULL;
websMax = 0;
websOsOpen();
websRuntimeOpen();
websTimeOpen();
websFsOpen();
logOpen();
setFileLimits();
socketOpen();
if (setLocalHost() < 0) {
return -1;
}
#if ME_COM_SSL
if (sslOpen() < 0) {
return -1;
}
#endif
if ((sessions = hashCreate(-1)) < 0) {
return -1;
}
if (!websDebug) {
pruneId = websStartEvent(WEBS_SESSION_PRUNE, (WebsEventProc) pruneSessions, 0);
}
if (documents) {
websSetDocuments(documents);
}
if (websOpenRoute() < 0) {
return -1;
}
#if ME_GOAHEAD_CGI
websCgiOpen();
#endif
websOptionsOpen();
websActionOpen();
websFileOpen();
#if ME_GOAHEAD_UPLOAD
websUploadOpen();
#endif
#if ME_GOAHEAD_JAVASCRIPT
websJstOpen();
#endif
#if ME_GOAHEAD_AUTH
if (websOpenAuth(0) < 0) {
return -1;
}
#endif
if (routeFile && websLoad(routeFile) < 0) {
return -1;
}
/*
Create a mime type lookup table for quickly determining the content type
*/
websMime = hashCreate(WEBS_HASH_INIT * 4);
assert(websMime >= 0);
for (mt = websMimeList; mt->type; mt++) {
hashEnter(websMime, mt->ext, valueString(mt->type, 0), 0);
}
#if ME_GOAHEAD_ACCESS_LOG && !ME_ROM
if ((accessFd = open(accessLog, O_CREAT | O_TRUNC | O_APPEND | O_WRONLY, 0666)) < 0) {
error("Cannot open access log %s", accessLog);
return -1;
}
/* Some platforms don't implement O_APPEND (VXWORKS) */
lseek(accessFd, 0, SEEK_END);
#endif
return 0;
}
然后我们一步一步来解释。
PUBLIC int websOsOpen(void)
{
#if SOLARIS
openlog(ME_NAME, LOG_LOCAL0);
#elif ME_UNIX_LIKE
openlog(ME_NAME, 0, LOG_LOCAL0);
#endif
#if WINDOWS || VXWORKS || TIDSP
rand();
#else
random();
#endif
return 0;
}
该函数整体的解释为打开操作系统。(OS应该是理解成操作系统吧)
openlog()打开一个程序的系统记录器的连接。
参数一
idents指向的字符串可以是想要打出的任意字符,它所表示的字符串将固定地加在每行日志的前面以标识这个日志,该标志通常设置为程序的名称。
参数二
option参数所指定的标志用来控制openlog()操作和syslog()的后续调用。
LOG_CONS的含义是直接写入系统控制台,如果有一个错误,同时发送到系统日志记录。
参数三
facility参数是用来指定记录消息程序的类型。它让指定的配置文件,将以不同的方式来处理来自不同方式的消息。
LOG_LOCAL0含义是保留供本地使用。
然后就是后面为什么要生成一个随机数,我非常费解,主要是他也没有去获取这个生成的随机数,搞不懂。
PUBLIC int websRuntimeOpen(void)
{
symMax = 0;
sym = 0;
srand((uint) time(NULL));
return 0;
}
srand 函数是随机数发生器的初始化函数。
为了防止随机数每次重复,常常使用系统时间来初始化。计算机并不能产生真正的随机数,而是已经编写好的一些无规则排列的数字存储在电脑里,把这些数字划分为若干相等的N份,并为每份加上一个编号用srand()函数获取这个编号,然后rand()就按顺序获取这些数字,当srand()的参数值固定的时候,rand()获得的数也是固定的,所以一般srand的参数用time(NULL),因为系统的时间一直在变,所以rand()获得的数,也就一直在变,相当于是随机数了。
这个函数可以跟下面的创建定时事件的函数关联起来,关于定时器事件的创建,回调这些具体详情可以参考这个帖主的文章。
https://blog.csdn.net/xiyuan255/article/details/105836249/
PUBLIC int websTimeOpen(void)
{
TimeToken *tt;
timeTokens = hashCreate(59);
for (tt = days; tt->name; tt++) {
hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
}
for (tt = fullDays; tt->name; tt++) {
hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
}
for (tt = months; tt->name; tt++) {
hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
}
for (tt = fullMonths; tt->name; tt++) {
hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
}
for (tt = ampm; tt->name; tt++) {
hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
}
for (tt = zones; tt->name; tt++) {
hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
}
for (tt = offsets; tt->name; tt++) {
hashEnter(timeTokens, tt->name, valueSymbol(tt), 0);
}
return 0;
}
该函数整体作用是打开解析时间的模块。
这里是将描述时间的各个量存入哈希表,其中包括月份全称,简称,星期全称,简称,上午下午,时间格式,以及昨天明天之类的时间描述词,就是下面这些。
static TimeToken days[] = {
{ "sun", 0, TOKEN_DAY },
{ "mon", 1, TOKEN_DAY },
{ "tue", 2, TOKEN_DAY },
{ "wed", 3, TOKEN_DAY },
{ "thu", 4, TOKEN_DAY },
{ "fri", 5, TOKEN_DAY },
{ "sat", 6, TOKEN_DAY },
{ 0, 0 },
};
static TimeToken fullDays[] = {
{ "sunday", 0, TOKEN_DAY },
{ "monday", 1, TOKEN_DAY },
{ "tuesday", 2, TOKEN_DAY },
{ "wednesday", 3, TOKEN_DAY },
{ "thursday", 4, TOKEN_DAY },
{ "friday", 5, TOKEN_DAY },
{ "saturday", 6, TOKEN_DAY },
{ 0, 0 },
};
/*
Make origin 1 to correspond to user date entries 10/28/2014
*/
static TimeToken months[] = {
{ "jan", 1, TOKEN_MONTH },
{ "feb", 2, TOKEN_MONTH },
{ "mar", 3, TOKEN_MONTH },
{ "apr", 4, TOKEN_MONTH },
{ "may", 5, TOKEN_MONTH },
{ "jun", 6, TOKEN_MONTH },
{ "jul", 7, TOKEN_MONTH },
{ "aug", 8, TOKEN_MONTH },
{ "sep", 9, TOKEN_MONTH },
{ "oct", 10, TOKEN_MONTH },
{ "nov", 11, TOKEN_MONTH },
{ "dec", 12, TOKEN_MONTH },
{ 0, 0 },
};
static TimeToken fullMonths[] = {
{ "january", 1, TOKEN_MONTH },
{ "february", 2, TOKEN_MONTH },
{ "march", 3, TOKEN_MONTH },
{ "april", 4, TOKEN_MONTH },
{ "may", 5, TOKEN_MONTH },
{ "june", 6, TOKEN_MONTH },
{ "july", 7, TOKEN_MONTH },
{ "august", 8, TOKEN_MONTH },
{ "september", 9, TOKEN_MONTH },
{ "october", 10, TOKEN_MONTH },
{ "november", 11, TOKEN_MONTH },
{ "december", 12, TOKEN_MONTH },
{ 0, 0 }
};
static TimeToken ampm[] = {
{ "am", 0, TOKEN_OFFSET },
{ "pm", (12 * 3600), TOKEN_OFFSET },
{ 0, 0 },
};
static TimeToken zones[] = {
{ "ut", 0, TOKEN_ZONE },
{ "utc", 0, TOKEN_ZONE },
{ "gmt", 0, TOKEN_ZONE },
{ "edt", -240, TOKEN_ZONE },
{ "est", -300, TOKEN_ZONE },
{ "cdt", -300, TOKEN_ZONE },
{ "cst", -360, TOKEN_ZONE },
{ "mdt", -360, TOKEN_ZONE },
{ "mst", -420, TOKEN_ZONE },
{ "pdt", -420, TOKEN_ZONE },
{ "pst", -480, TOKEN_ZONE },
{ 0, 0 },
};
static TimeToken offsets[] = {
{ "tomorrow", 86400, TOKEN_OFFSET },
{ "yesterday", -86400, TOKEN_OFFSET },
{ "next week", (86400 * 7), TOKEN_OFFSET },
{ "last week", -(86400 * 7), TOKEN_OFFSET },
{ 0, 0 },
};
PUBLIC int websFsOpen(void)
{
#if ME_ROM
WebsRomIndex *wip;
char name[ME_GOAHEAD_LIMIT_FILENAME];
ssize len;
romFs = hashCreate(WEBS_HASH_INIT);
for (wip = websRomIndex; wip->path; wip++) {
strncpy(name, wip->path, ME_GOAHEAD_LIMIT_FILENAME);
len = strlen(name) - 1;
if (len > 0 && (name[len] == '/' || name[len] == '\\')) {
name[len] = '\0';
}
hashEnter(romFs, name, valueSymbol(wip), 0);
}
#endif
return 0;
}
该函数整体是打开文件系统模块的作用。但是函数内部这个从ROM编译运行的宏是关闭的,我暂时使用也没打开,就不讲了。
接下来一个函数就是关于日志打印的,影响不大,跳过。
之后
static void setFileLimits(void)
{
#if ME_UNIX_LIKE
struct rlimit r;
int limit;
limit = ME_GOAHEAD_LIMIT_FILES;
if (limit) {
r.rlim_cur = r.rlim_max = limit;
if (setrlimit(RLIMIT_NOFILE, &r) < 0) {
error("Cannot set file limit to %d", limit);
}
}
getrlimit(RLIMIT_NOFILE, &r);
trace(6, "Max files soft %d, max %d", r.rlim_cur, r.rlim_max);
#endif
}
getrlimit()/setrlimit()函数
获取或设置资源使用限制,
RLIMIT_NOFILE指定比进程可打开的最大文件描述词大一的值,超出此值,将会产生EMFILE错误。
不过该函数中最大文件数量默认是0,也就是不设置最大数量。
下一个函数
PUBLIC int socketOpen(void)
{
Socket fd;
if (++socketOpenCount > 1) {
return 0;
}
#if ME_WIN_LIKE
{
WSADATA wsaData;
if (WSAStartup(MAKEWORD(1,1), &wsaData) != 0) {
return -1;
}
if (wsaData.wVersion != MAKEWORD(1,1)) {
WSACleanup();
return -1;
}
}
#endif
socketList = NULL;
socketMax = 0;
socketHighestFd = -1;
if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) != -1) {
hasIPv6 = 1;
closesocket(fd);
} else {
trace(1, "This system does not have IPv6 support");
}
return 0;
}
这个函数在创建套接字成功后就关闭了,所以作用应该只是socket列表初始化,以及检测当前系统是否支持IPv6,是否能正常创建套接字。
下一个函数
static int setLocalHost(void)
{
struct in_addr intaddr;
char host[128], *ipaddr;
if (gethostname(host, sizeof(host)) < 0) {
error("Cannot get hostname: errno %d", errno);
return -1;
}
#if VXWORKS
intaddr.s_addr = (ulong) hostGetByName(host);
ipaddr = inet_ntoa(intaddr);
websSetIpAddr(ipaddr);
websSetHost(ipaddr);
#if _WRS_VXWORKS_MAJOR < 6
free(ipaddr);
#endif
#elif ECOS
ipaddr = inet_ntoa(eth0_bootp_data.bp_yiaddr);
websSetIpAddr(ipaddr);
websSetHost(ipaddr);
#elif TIDSP
{
struct hostent *hp;
if ((hp = gethostbyname(host)) == NULL) {
error("Cannot get host address for host %s: errno %d", host, errno);
return -1;
}
memcpy((char*) &intaddr, (char *) hp->h_addr[0], (size_t) hp->h_length);
ipaddr = inet_ntoa(intaddr);
websSetIpAddr(ipaddr);
websSetHost(ipaddr);
}
#elif MACOSX
{
struct hostent *hp;
if ((hp = gethostbyname(host)) == NULL) {
if ((hp = gethostbyname(sfmt("%s.local", host))) == NULL) {
error("Cannot get host address for host %s: errno %d", host, errno);
return -1;
}
}
memcpy((char*) &intaddr, (char *) hp->h_addr_list[0], (size_t) hp->h_length);
ipaddr = inet_ntoa(intaddr);
websSetIpAddr(ipaddr);
websSetHost(ipaddr);
}
#else
{
struct hostent *hp;
if ((hp = gethostbyname(host)) == NULL) {
error("Cannot get host address for host %s: errno %d", host, errno);
return -1;
}
memcpy((char*) &intaddr, (char *) hp->h_addr_list[0], (size_t) hp->h_length);
ipaddr = inet_ntoa(intaddr);
websSetIpAddr(ipaddr);
websSetHost(ipaddr);
}
#endif
return 0;
}
PUBLIC void websSetHost(cchar *host)
{
scopy(websHost, sizeof(websHost), host);
}
这里就是获取本地的主机ip,然后将其赋值到下面两个静态全局变量中
static char websHost[ME_MAX_IP]; /* Host name for the server */
static char websIpAddr[ME_MAX_IP]; /* IP address for the server */
作为服务器的ip和主机名。
if ((sessions = hashCreate(-1)) < 0) {
return -1;
}
if (!websDebug) {
pruneId = websStartEvent(WEBS_SESSION_PRUNE, (WebsEventProc) pruneSessions, 0);
}
websStartEvent是开启了一个定时器任务。(定时器任务跟我上面说的websRuntimeOpen是对应的)
#define WEBS_SESSION_PRUNE (60*1000) /* Prune sessions every minute */
static void pruneSessions(void)
{
WebsSession *sp;
WebsTime when;
WebsKey *sym, *next;
int oldCount;
if (sessions >= 0) {
oldCount = sessionCount;
when = time(0);
for (sym = hashFirst(sessions); sym; sym = next) {
next = hashNext(sessions, sym);
sp = (WebsSession*) sym->content.value.symbol;
if (sp->expires <= when) {
hashDelete(sessions, sp->id);
sessionCount--;
freeSession(sp);
}
}
if (oldCount != sessionCount || sessionCount) {
trace(4, "Prune %d sessions. Remaining: %d", oldCount - sessionCount, sessionCount);
}
}
websRestartEvent(pruneId, WEBS_SESSION_PRUNE);
}
时间周期是1分钟,执行函数内部就是遍历所有当前的webSessions,判断是否有失效的,将失效的结点删除。
if (documents) {
websSetDocuments(documents);
}
该函数的作用就是设置web相关文件夹的路径。
PUBLIC int websOpenRoute(void)
{
if ((handlers = hashCreate(-1)) < 0) {
return -1;
}
websDefineHandler("continue", continueHandler, 0, 0, 0);
websDefineHandler("redirect", redirectHandler, 0, 0, 0);
return 0;
}
该函数是定义continue和redirect的处理函数。
/*
Handler to just continue matching other routes
*/
static bool continueHandler(Webs *wp)
{
return 0;
}
由此也可以确定第一篇中写的continue只是伪处理程序,实际啥也没做。
/*
Handler to redirect to the default (code zero) URI
*/
static bool redirectHandler(Webs *wp)
{
return websRedirectByStatus(wp, 0) == 0;
}
重定向的处理函数就是根据界面返回状态进行重定向。
websOptionsOpen();
websActionOpen();
websFileOpen();
这三个分别是打开options,action和file的处理函数。
我用到最多的是action,对此进行一下细致讲解。
PUBLIC void websActionOpen(void)
{
actionTable = hashCreate(WEBS_HASH_INIT);
websDefineHandler("action", 0, actionHandler, closeAction, 0);
}
将所有action的名字和对应的处理函数存储在哈希表中。
static bool actionHandler(Webs *wp)
{
WebsKey *sp;
char actionBuf[ME_GOAHEAD_LIMIT_URI + 1];
char *cp, *actionName;
WebsAction fn;
assert(websValid(wp));
assert(actionTable >= 0);
/*
Extract the action name
*/
scopy(actionBuf, sizeof(actionBuf), wp->path);
if ((actionName = strchr(&actionBuf[1], '/')) == NULL) {
websError(wp, HTTP_CODE_NOT_FOUND, "Missing action name");
return 1;
}
actionName++;
if ((cp = strchr(actionName, '/')) != NULL) {
*cp = '\0';
}
/*
Lookup the C action function first and then try tcl (no javascript support yet).
*/
sp = hashLookup(actionTable, actionName);
if (sp == NULL) {
websError(wp, HTTP_CODE_NOT_FOUND, "Action %s is not defined", actionName);
} else {
fn = (WebsAction) sp->content.value.symbol;
assert(fn);
if (fn) {
#if ME_GOAHEAD_LEGACY
(*((WebsProc) fn))((void*) wp, actionName, wp->query);
#else
(*fn)((void*) wp);
#endif
}
}
return 1;
}
收到action的时候在哈希表中查找对应的名字,然后再去执行对应的函数。
还有值得一提的是,在websFileOpen函数中还有一个设置初始化页面的语句。
PUBLIC void websFileOpen(void)
{
websIndex = sclone("index.html");
websDefineHandler("file", 0, fileHandler, fileClose, 0);
}
如果需要在MAIN函数中自定义新的初始界面,需要注意自定义设置应该在该函数执行之后,不然会被覆盖。
#if ME_GOAHEAD_UPLOAD
websUploadOpen();
#endif
#if ME_GOAHEAD_JAVASCRIPT
websJstOpen();
#endif
#if ME_GOAHEAD_AUTH
if (websOpenAuth(0) < 0) {
return -1;
}
#endif
这三个是需要用到文件上传,界面动态加载,用户登录验证功能时,将对应的宏打开即可。(默认好像就是开的)
因为我用到了用户登录功能,所以多提一嘴websOpenAuth函数。
PUBLIC int websOpenAuth(int minimal)
{
char sbuf[64];
assert(minimal == 0 || minimal == 1);
if ((users = hashCreate(-1)) < 0) {
return -1;
}
if ((roles = hashCreate(-1)) < 0) {
return -1;
}
if (!minimal) {
fmt(sbuf, sizeof(sbuf), "%x:%x", rand(), time(0));
masterSecret = websMD5(sbuf);
#if ME_GOAHEAD_JAVASCRIPT && FUTURE
websJsDefine("can", jsCan);
#endif
websDefineAction("login", loginServiceProc);
websDefineAction("logout", logoutServiceProc);
}
if (smatch(ME_GOAHEAD_AUTH_STORE, "file")) {
verifyPassword = websVerifyPasswordFromFile;
#if ME_COMPILER_HAS_PAM
} else if (smatch(ME_GOAHEAD_AUTH_STORE, "pam")) {
verifyPassword = websVerifyPasswordFromPam;
#endif
}
return 0;
}
这里创建哈希表,用于存储所有用户的账号密码,然后定义了登录和登出两个action。
/*
Load route and authentication configuration files
*/
PUBLIC int websLoad(cchar *path)
{
WebsRoute *route;
WebsHash abilities, extensions, methods, redirects;
char *buf, *line, *kind, *next, *auth, *dir, *handler, *protocol, *uri, *option, *key, *value, *status;
char *redirectUri, *token;
int rc;
assert(path && *path);
rc = 0;
if ((buf = websReadWholeFile(path)) == 0) {
error("Cannot open config file %s", path);
return -1;
}
for (line = stok(buf, "\r\n", &token); line; line = stok(NULL, "\r\n", &token)) {
kind = stok(line, " \t", &next);
if (kind == 0 || *kind == '\0' || *kind == '#') {
continue;
}
if (smatch(kind, "route")) {
auth = dir = handler = protocol = uri = 0;
abilities = extensions = methods = redirects = -1;
while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
key = stok(option, "=", &value);
if (smatch(key, "abilities")) {
addOption(&abilities, value, 0);
} else if (smatch(key, "auth")) {
auth = value;
} else if (smatch(key, "dir")) {
dir = value;
} else if (smatch(key, "extensions")) {
addOption(&extensions, value, 0);
} else if (smatch(key, "handler")) {
handler = value;
} else if (smatch(key, "methods")) {
addOption(&methods, value, 0);
} else if (smatch(key, "redirect")) {
if (strchr(value, '@')) {
status = stok(value, "@", &redirectUri);
if (smatch(status, "*")) {
status = "0";
}
} else {
status = "0";
redirectUri = value;
}
if (smatch(redirectUri, "https")) redirectUri = "https://";
if (smatch(redirectUri, "http")) redirectUri = "http://";
addOption(&redirects, status, redirectUri);
} else if (smatch(key, "protocol")) {
protocol = value;
} else if (smatch(key, "uri")) {
uri = value;
} else {
error("Bad route keyword %s", key);
continue;
}
}
if ((route = websAddRoute(uri, handler, -1)) == 0) {
rc = -1;
break;
}
websSetRouteMatch(route, dir, protocol, methods, extensions, abilities, redirects);
#if ME_GOAHEAD_AUTH
if (auth && websSetRouteAuth(route, auth) < 0) {
rc = -1;
break;
}
} else if (smatch(kind, "user")) {
char *name, *password, *roles;
name = password = roles = 0;
while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
key = stok(option, "=", &value);
if (smatch(key, "name")) {
name = value;
} else if (smatch(key, "password")) {
password = value;
} else if (smatch(key, "roles")) {
roles = value;
} else {
error("Bad user keyword %s", key);
continue;
}
}
if (websAddUser(name, password, roles) == 0) {
rc = -1;
break;
}
} else if (smatch(kind, "role")) {
char *name;
name = 0;
abilities = -1;
while ((option = stok(NULL, " \t\r\n", &next)) != 0) {
key = stok(option, "=", &value);
if (smatch(key, "name")) {
name = value;
} else if (smatch(key, "abilities")) {
addOption(&abilities, value, 0);
}
}
if (websAddRole(name, abilities) == 0) {
rc = -1;
break;
}
#endif
} else {
error("Unknown route keyword %s", kind);
rc = -1;
break;
}
}
wfree(buf);
#if ME_GOAHEAD_AUTH
websComputeAllUserAbilities();
#endif
return rc;
}
这个函数就是读取了route.txt和auth.txt。上个步骤中创建了存储用户账号密码和权限的哈希表,这部分就是将数据插入哈希表。
/*
Create a mime type lookup table for quickly determining the content type
*/
websMime = hashCreate(WEBS_HASH_INIT * 4);
assert(websMime >= 0);
for (mt = websMimeList; mt->type; mt++) {
hashEnter(websMime, mt->ext, valueString(mt->type, 0), 0);
}
这里创建了一个哈希表,里面插入的是网络请求的各种文件类型及其后缀。
static WebsMime websMimeList[] = {
{ "application/java", ".class" },
{ "application/java", ".jar" },
{ "text/html", ".asp" },
{ "text/html", ".htm" },
{ "text/html", ".html" },
{ "text/xml", ".xml" },
{ "image/gif", ".gif" },
{ "image/jpeg", ".jpg" },
{ "image/png", ".png" },
{ "image/svg+xml", ".svg" },
{ "image/vnd.microsoft.icon", ".ico" },
{ "text/css", ".css" },
{ "text/plain", ".txt" },
{ "application/x-javascript", ".js" },
{ "application/x-shockwave-flash", ".swf" },
{ "application/json", ".json" },
{ "application/binary", ".exe" },
{ "application/compress", ".z" },
{ "application/gzip", ".gz" },
{ "application/octet-stream", ".bin" },
{ "application/oda", ".oda" },
{ "application/pdf", ".pdf" },
{ "application/postscript", ".ai" },
{ "application/postscript", ".eps" },
{ "application/postscript", ".ps" },
{ "application/rtf", ".rtf" },
{ "application/x-bcpio", ".bcpio" },
{ "application/x-cpio", ".cpio" },
{ "application/x-csh", ".csh" },
{ "application/x-dvi", ".dvi" },
{ "application/x-gtar", ".gtar" },
{ "application/x-hdf", ".hdf" },
{ "application/x-latex", ".latex" },
{ "application/x-mif", ".mif" },
{ "application/x-netcdf", ".nc" },
{ "application/x-netcdf", ".cdf" },
{ "application/x-ns-proxy-autoconfig", ".pac" },
{ "application/x-patch", ".patch" },
{ "application/x-sh", ".sh" },
{ "application/x-shar", ".shar" },
{ "application/x-sv4cpio", ".sv4cpio" },
{ "application/x-sv4crc", ".sv4crc" },
{ "application/x-tar", ".tar" },
{ "application/x-tgz", ".tgz" },
{ "application/x-tcl", ".tcl" },
{ "application/x-tex", ".tex" },
{ "application/x-texinfo", ".texinfo" },
{ "application/x-texinfo", ".texi" },
{ "application/x-troff", ".t" },
{ "application/x-troff", ".tr" },
{ "application/x-troff", ".roff" },
{ "application/x-troff-man", ".man" },
{ "application/x-troff-me", ".me" },
{ "application/x-troff-ms", ".ms" },
{ "application/x-ustar", ".ustar" },
{ "application/x-wais-source", ".src" },
{ "application/zip", ".zip" },
{ "audio/basic", ".au snd" },
{ "audio/x-aiff", ".aif" },
{ "audio/x-aiff", ".aiff" },
{ "audio/x-aiff", ".aifc" },
{ "audio/x-wav", ".wav" },
{ "audio/x-wav", ".ram" },
{ "image/ief", ".ief" },
{ "image/jpeg", ".jpeg" },
{ "image/jpeg", ".jpe" },
{ "image/tiff", ".tiff" },
{ "image/tiff", ".tif" },
{ "image/x-cmu-raster", ".ras" },
{ "image/x-portable-anymap", ".pnm" },
{ "image/x-portable-bitmap", ".pbm" },
{ "image/x-portable-graymap", ".pgm" },
{ "image/x-portable-pixmap", ".ppm" },
{ "image/x-rgb", ".rgb" },
{ "image/x-xbitmap", ".xbm" },
{ "image/x-xpixmap", ".xpm" },
{ "image/x-xwindowdump", ".xwd" },
{ "text/html", ".cfm" },
{ "text/html", ".shtm" },
{ "text/html", ".shtml" },
{ "text/richtext", ".rtx" },
{ "text/tab-separated-values", ".tsv" },
{ "text/x-setext", ".etx" },
{ "video/mpeg", ".mpeg" },
{ "video/mpeg", ".mpg" },
{ "video/mpeg", ".mpe" },
{ "video/quicktime", ".qt" },
{ "video/quicktime", ".mov" },
{ "video/mp4", ".mp4" },
{ "video/x-msvideo", ".avi" },
{ "video/x-sgi-movie", ".movie" },
{ NULL, NULL},
};
居然有这么多。
后面那个宏还是ROM编译运行的那个,为0,我用不到,就不讲了。
至此websOpen函数就介绍完毕了。