JS文件和css文件连接后面带的参数是什么意思

本文解释了在网站开发中使用CSS版本号进行强制缓存刷新的原因,以确保浏览器加载最新的样式代码,避免因缓存旧样式导致的显示问题。
http://pic.hao123.com/static/public/css/common.css?v=1349923377


版本号 用来做强制缓存刷新的 ,不然管理员修改css代码后,浏览器还可能用以前存储在你的ie缓存中的旧样式。
<think>我们正在用C语言写一个Web服务器,需要正确返回前端页面及其资源(如HTML、CSSJS、图片等)。根据用户提供的目录结构,前端文件位于当前目录(或者指定目录)下,例如Index.html、css/style.css、images/body.png等。 目标:当浏览器请求不同资源时,服务器能够正确返回对应的文件。 步骤: 1. 解析HTTP请求,获取请求的路径(例如:GET / 或 GET /css/style.css)。 2. 将请求的路径映射到本地文件路径。 3. 根据文件扩展名设置正确的MIME类型(Content-Type)。 4. 读取文件内容并发送给客户端,如果文件不存在则返回404。 注意:用户的前端目录结构如下(注意,用户提供的目录树中,根目录下有Index.html,以及css、images、img、js等目录)。 根据之前的讨论,我们已经有了一个基本的框架,现在需要完善它,特别是路径映射MIME类型识别。 具体实现: 1. 映射URL路径到本地文件路径: - 如果请求的是根路径"/",则返回"Index.html"(注意大小写,在Linux下是大小写敏感的)。 - 否则,将URL路径拼接到前端根目录(例如:"./frontend")后面。但是注意,用户的前端文件就在当前目录(或者与服务器程序同一目录)?还是需要指定一个目录? 根据用户提供的目录树,假设服务器程序(webserv_linux)前端文件在同一个父目录下,那么前端文件就在当前目录。但是,为了安全,我们通常会将前端文件放在一个单独的目录(比如"frontend")中,但用户并没有这样的结构。因此,我们可以将当前目录(或指定目录)作为前端根目录。 2. 处理请求路径中的目录问题: - 注意URL中的路径分隔符是'/',而本地文件系统可能是'/'(Linux)或'\'(Windows),但我们的服务器是Linux的,所以直接使用'/'。 3. MIME类型:根据文件扩展名设置。 4. 读取文件并发送,注意二进制文件(如图片)必须用二进制模式打开。 5. 错误处理:文件不存在时返回404。 根据之前的代码,我们修改如下: 首先,我们定义一个函数将URL路径映射到本地文件路径。假设前端根目录是当前目录(即程序运行的目录),或者我们可以指定一个目录(比如程序同目录下的某个文件夹)。但是用户的前端文件就在程序同一目录,所以我们可以直接使用。 但是,为了安全,我们最好将前端文件放在一个子目录(比如"www")中,但用户没有这样。因此,我们可以这样处理: 我们设定一个基础路径,比如: char base_path[1024] = "."; // 当前目录 然后,对于请求的路径,我们拼接在base_path后面。但是要注意,如果请求路径是"/",我们则返回base_path下的"Index.html"(注意用户目录中是大写的I)。 但是,用户有多个HTML文件:Index.html、About.html、Contact.html、Work.html。所以,当请求"/"时,我们返回Index.html;当请求"/About.html"时,返回About.html。 另外,注意路径中可能包含子目录,比如"/css/style.css",那么本地路径就是"./css/style.css"。 但是,有一个问题:用户的前端目录树中,图片分别放在imagesimg两个目录下。所以,请求可能是"/images/body.png"或"/img/1.jpg"。 因此,我们不需要做特殊处理,直接拼接即可。 但是,要防止路径遍历攻击(例如请求"/../server.c"),所以我们需要检查最终路径是否在base_path下。我们可以使用realpath函数来获取绝对路径,然后检查是否以base_path的绝对路径开头。 下面,我们修改之前的代码: 步骤: a. 获取请求的路径(例如:path变量)。 b. 构造完整路径:base_path + path。 c. 使用realpath得到规范化的绝对路径,并检查是否在base_path的目录下。 由于我们之前已经写了一个map_url_to_path函数,现在需要增强安全性。 另外,注意用户目录中的文件有些是大写开头(如Index.html),而Linux文件系统是大小写敏感的,所以请求的URL必须与文件名大小写一致?但浏览器通常不区分URL的大小写,但服务器文件系统区分。所以,我们最好将文件命名统一为小写,或者让服务器对文件名大小写不敏感?但这样效率低。另一种做法是,在保存文件时使用小写,或者要求前端开发使用小写文件名。 不过,用户的前端文件有的使用大写,比如Index.html,所以我们在映射时,如果请求的是"/",我们映射到"Index.html",但如果是其他文件,我们按照请求的路径来。 但是,如果请求的是"/index.html"呢?我们并没有这个文件(有Index.html)。所以,我们可能需要做一些大小写转换?或者使用一个函数来检查文件是否存在(忽略大小写)?但这样会增加复杂度。 为了简单,我们假设服务器部署在Linux上,且文件名大小写必须与请求一致。因此,前端在写链接时,必须注意大小写(比如链接到"/About.html"而不是"/about.html")。 另一种做法:在服务器端,当文件不存在时,尝试将路径中的文件名转换为首字母大写再查找?但这样效率低,且可能造成混淆。所以,我们暂时按照请求的路径直接映射。 现在,我们修改代码: 1. 在map_url_to_path函数中,我们使用base_path(比如当前目录".")作为基础目录。 2. 对于根路径,我们返回Index.html的完整路径。 3. 对于其他路径,直接拼接。 但是,我们还需要注意:如果请求的路径以'/'开头,拼接时不要重复加'/'。我们可以这样处理: 例如:base_path = ".",请求路径为"/css/style.css",则拼接为"./css/style.css"。 修改map_url_to_path函数: ```c char* map_url_to_path(const char* url) { const char* base_path = "."; // 当前目录作为前端根目录 char full_path[2048]; // 处理根路径 if (strcmp(url, "/") == 0) { snprintf(full_path, sizeof(full_path), "%s/Index.html", base_path); } else { // 注意:url以'/'开头,所以直接拼接 snprintf(full_path, sizeof(full_path), "%s%s", base_path, url); } // 返回动态分配的字符串副本 return strdup(full_path); } ``` 但是,这样拼接后,如果base_path是".",那么根路径就会变成"./Index.html",而其他路径变成"./css/style.css",这是正确的。 4. 安全性:防止路径遍历攻击(如请求"/../server.c")。我们可以使用realpath函数来解析绝对路径,并确保它在base_path下。 我们可以在send_file函数中,在打开文件之前,检查解析后的绝对路径是否在base_path的目录下。 假设我们定义base_path的绝对路径,然后检查解析后的路径是否以base_path的绝对路径开头。 修改send_file函数: ```c void send_file(int client_socket, const char* requested_path) { // 先解析请求路径的绝对路径 char* resolved_path = realpath(requested_path, NULL); if (resolved_path == NULL) { // 文件不存在 send_404(client_socket); return; } // 获取base_path的绝对路径 char base_abs[PATH_MAX]; realpath(".", base_abs); // 当前目录的绝对路径 // 检查解析后的路径是否在base_path下 if (strstr(resolved_path, base_abs) != resolved_path) { // 不在,说明是路径遍历攻击 free(resolved_path); send_404(client_socket); return; } // 现在可以安全地使用requested_path打开文件 FILE* file = fopen(requested_path, "rb"); if (!file) { free(resolved_path); send_404(client_socket); return; } // ... 读取文件并发送 ... free(resolved_path); fclose(file); } ``` 注意:上面的代码中,我们使用realpath来解析请求路径的绝对路径,然后检查这个绝对路径是否以base_path的绝对路径开头。如果是,则说明安全。 但是,注意:realpath函数在文件不存在时会返回NULL,所以我们在调用realpath时,如果返回NULL,就说明文件不存在(或者路径无效)。因此,我们直接返回404。 5. MIME类型:根据文件扩展名设置。 我们之前已经有一个get_mime_type函数,现在需要扩展它,支持更多的类型,特别是图片类型。 修改get_mime_type函数: ```c const char* get_mime_type(const char* path) { const char* ext = strrchr(path, '.'); if (!ext) return "application/octet-stream"; if (strcmp(ext, ".html") == 0) return "text/html"; if (strcmp(ext, ".htm") == 0) return "text/html"; if (strcmp(ext, ".css") == 0) return "text/css"; if (strcmp(ext, ".js") == 0) return "application/javascript"; if (strcmp(ext, ".json") == 0) return "application/json"; if (strcmp(ext, ".png") == 0) return "image/png"; if (strcmp(ext, ".jpg") == 0) return "image/jpeg"; if (strcmp(ext, ".jpeg") == 0) return "image/jpeg"; if (strcmp(ext, ".gif") == 0) return "image/gif"; if (strcmp(ext, ".ico") == 0) return "image/x-icon"; // 可以继续添加其他类型 return "text/plain"; } ``` 6. 发送文件:注意二进制模式打开(特别是图片等非文本文件)。 我们已经用"rb"模式打开文件,这样没问题。 7. 主请求处理流程: 在handle_request函数中,我们解析请求行,获取methodpath,然后调用map_url_to_path得到文件路径,再调用send_file发送。 8. 错误处理:404页面。 9. 缓存控制:根据引用[2][^2][^3],对于html文件,我们应该设置不缓存,而对于jscss、图片等可以设置缓存。但这不是必须的,可以根据需要添加。 我们可以在发送文件时,根据文件扩展名设置Cache-Control头。 例如,在send_file函数中,发送HTTP头之前,添加: ```c // 根据文件扩展名设置缓存 const char* cache_control = ""; const char* ext = strrchr(file_path, '.'); if (ext) { if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) { cache_control = "Cache-Control: no-store, no-cache\r\n"; } else { // 其他资源缓存7天 cache_control = "Cache-Control: public, max-age=604800\r\n"; // 7天=7*24*3600=604800秒 } } ``` 然后在HTTP响应头中加入cache_control。 但是,注意:如果浏览器在请求时已经缓存了资源,设置缓存头可以优化性能。但也可以不设置,根据需求。 现在,我们修改send_file函数中的响应头: ```c // 发送HTTP头 char header[2048]; // 增大header大小,以防不够 const char* mime_type = get_mime_type(file_path); snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "%s" // 这里插入Cache-Control "Content-Length: %ld\r\n\r\n", mime_type, cache_control, file_size); ``` 注意:上面的cache_control字符串可能为空,所以没问题。 但是,我们也可以选择不发送缓存头,这样浏览器会自己处理缓存(可能每次都会请求)。所以,这一步是可选的。 10. 处理目录请求:如果请求的是一个目录(比如请求路径以'/'结尾),我们应该返回该目录下的默认文件(如index.html)。但用户的前端目录中,每个HTML文件都是直接放在根目录下的,没有子目录的index.html。所以,我们暂时不处理目录请求,而是返回404。 我们可以在map_url_to_path函数中,如果发现请求的路径是一个目录(在拼接后),则自动追加"index.html"(如果存在的话)。但用户没有这样的结构,所以我们暂时不处理。 但是,如果请求的是目录,我们也可以生成目录列表?但通常不需要,所以返回403或404。 为了安全,我们不会列出目录,所以如果请求的路径对应的是目录,我们返回403(禁止访问)或者尝试返回该目录下的index.html(如果存在)。我们修改send_file函数,在打开文件之前,先检查路径是文件还是目录。 我们可以在用fopen打开之前,用stat函数检查: ```c #include <sys/stat.h> struct stat path_stat; if (stat(requested_path, &path_stat) == -1) { // 文件不存在 send_404(client_socket); return; } if (S_ISDIR(path_stat.st_mode)) { // 是目录,尝试在该目录下寻找index.html char index_path[2048]; snprintf(index_path, sizeof(index_path), "%s/index.html", requested_path); // 检查index.html是否存在 if (stat(index_path, &path_stat) == -1 || S_ISDIR(path_stat.st_mode)) { // 不存在index.html,返回403或者404 send_403(client_socket); return; } else { // 使用index_path作为文件路径 // 注意:这里需要重新设置file_path,然后继续 // 但是,我们的函数参数是const,所以我们可以递归调用send_file?或者修改函数设计 // 为了避免递归,我们修改requested_path为index_path,然后重新开始 // 但是注意,这样可能会循环?不会,因为目录下又有目录的情况我们做了检查。 // 但是,我们这里简单处理:关闭之前的resolved_path(如果有),然后重新设置requested_path为index_path,再重新进行安全检查? // 但是,我们已经做了安全检查(路径在base_path下),而index_path是在requested_path下的,所以安全。 // 因此,我们可以直接调用send_file(client_socket, index_path); 但是这样会递归,而且我们还没有打开文件。 // 所以,我们直接设置requested_path = index_path,然后重新执行一次?不行,因为递归深度可能很大。 // 因此,我们重构代码:将安全检查后的文件打开部分单独写一个函数,然后这里如果是目录,就构造index.html路径,再调用一次这个函数?或者我们在这里重新设置requested_path,然后继续执行(用goto?不推荐)。 // 为了简单,我们直接返回403,因为用户的前端没有目录结构。 send_403(client_socket); return; } } ``` 但是,用户的前端没有目录结构,所以我们暂时不处理目录请求,直接返回403。 11. 实现send_403函数: ```c void send_403(int client_socket) { const char* response = "HTTP/1.1 403 Forbidden\r\n" "Content-Type: text/html\r\n\r\n" "<html><body><h1>403 Forbidden</h1></body></html>"; send(client_socket, response, strlen(response), 0); } ``` 由于时间关系,我们暂时不处理目录请求,所以先不添加这段代码。 综上所述,我们修改后的代码包括: - 增强安全性的路径解析(防路径遍历) - 支持多种MIME类型 - 可选的缓存控制(根据文件类型) 下面,我们将这些修改整合到之前的代码中。 注意:由于我们使用了realpath,需要在编译时定义特性宏(_XOPEN_SOURCE 500)或者在包含头文件之前定义,以保证realpath可用。 在代码开头添加: #define _XOPEN_SOURCE 500 或者使用: #define _POSIX_C_SOURCE 200809L 另外,需要包含头文件: #include <limits.h> // PATH_MAX #include <stdlib.h> // realpath 但是,PATH_MAX可能没有定义,我们可以用4096代替。 修改:不使用PATH_MAX,而是动态分配内存?或者使用固定大小的缓冲区。realpath函数如果第一个参数为NULL,第二个参数为NULL,则会动态分配内存。我们可以这样: char* resolved_path = realpath(requested_path, NULL); if (resolved_path == NULL) { ... } 然后记得free(resolved_path)。 我们之前已经这样做了。 最后,注意内存泄漏:在send_file函数中,如果文件打开失败,我们释放了resolved_path,但其他情况也要注意释放。 由于我们修改的地方较多,下面给出完整的send_file函数(包括缓存控制,但缓存控制是可选的,如果不需要可以去掉): 注意:缓存控制部分,我们根据文件扩展名设置不同的Cache-Control头。但是,注意,如果请求的是目录,我们还没有处理,所以暂时不考虑。 完整send_file函数(缓存控制): ```c void send_file(int client_socket, const char* requested_path) { // 使用realpath获取绝对路径并检查安全性 char* resolved_path = realpath(requested_path, NULL); if (resolved_path == NULL) { send_404(client_socket); return; } // 获取base_path(当前目录)的绝对路径 char base_abs[4096]; if (realpath(".", base_abs) == NULL) { free(resolved_path); send_500(client_socket); // 服务器内部错误 return; } // 检查解析后的路径是否在base_abs下 if (strncmp(resolved_path, base_abs, strlen(base_abs)) != 0) { free(resolved_path); send_404(client_socket); // 也可以返回403,这里返回404避免暴露信息 return; } free(resolved_path); // 检查是文件还是目录 struct stat path_stat; if (stat(requested_path, &path_stat) == -1) { send_404(client_socket); return; } if (S_ISDIR(path_stat.st_mode)) { // 是目录,我们暂时不处理,返回403 send_403(client_socket); return; } // 打开文件 FILE* file = fopen(requested_path, "rb"); if (!file) { send_404(client_socket); return; } // 获取文件大小 fseek(file, 0, SEEK_END); long file_size = ftell(file); rewind(file); // 设置缓存控制头 const char* cache_control = ""; const char* ext = strrchr(requested_path, '.'); if (ext) { if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) { cache_control = "Cache-Control: no-store, no-cache\r\n"; } else { cache_control = "Cache-Control: public, max-age=604800\r\n"; } } // 发送HTTP头 char header[2048]; const char* mime_type = get_mime_type(requested_path); snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "%s" "Content-Length: %ld\r\n\r\n", mime_type, cache_control, file_size); send(client_socket, header, strlen(header), 0); // 发送文件内容 char buffer[4096]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { send(client_socket, buffer, bytes_read, 0); } fclose(file); } ``` 注意:我们添加了send_500send_403函数,需要实现它们。 由于我们修改了代码结构,现在需要包含的头文件有: #include <sys/stat.h> // stat #include <unistd.h> // fstat, etc. 最后,我们还需要考虑性能,特别是大文件,可以使用sendfile系统调用(Linux特有)或者mmap,但为了可移植性,我们暂时使用循环读取发送。 以上代码已经能够处理用户的前端文件了。 测试: - 启动服务器,监听某个端口(比如8080)。 - 在浏览器中访问:http://localhost:8080/ 应该返回Index.html。 - 在Index.html中引用的CSS文件(如/css/style.css)应该能正确加载。 - 图片(如/images/body.png)也应该能正确加载。 如果遇到问题,可以检查路径是否正确,以及文件权限。 注意:当前目录(base_path)是服务器程序运行的目录,所以确保前端文件在程序运行的目录下(或者使用chdir改变工作目录到前端目录)。 另外,我们可以通过命令行参数指定前端目录,这样更灵活。 修改:在main函数中,我们可以接受一个参数作为前端根目录,如果没有指定,则使用当前目录。 将base_path改为全局变量,然后在map_url_to_path中使用这个全局变量。 由于时间关系,这里不再展开。 希望这个解答能够帮助你配置服务器以正确返回前端页面及其资源。 如果遇到具体问题,可以再调整。 §§相关问题§§ 1. 如何在C语言Web服务器中处理POST请求(如表单提交)? 2. 如何使用多线程或epoll提高C语言Web服务器的并发性能? 3. 如何实现C语言Web服务器的CGI支持? 4. 在C语言Web服务器中,如何处理HTTP请求头中的Host字段? 5. 如何配置C语言Web服务器支持HTTPS?</think>### 配置C语言Web服务器返回前端资源 根据您的目录结构需求,以下是配置C语言Web服务器正确返回`index.html`及相关资源文件的完整方案: #### 核心配置步骤 1. **路径映射机制**(处理前端资源请求) ```c #define FRONTEND_ROOT "./" // 前端文件根目录 char* map_url_to_path(const char* url) { char full_path[2048]; // 处理根路径请求 if (strcmp(url, "/") == 0) { snprintf(full_path, sizeof(full_path), "%sIndex.html", FRONTEND_ROOT); } // 处理HTML文件请求 else if (strstr(url, ".html")) { snprintf(full_path, sizeof(full_path), "%s%s", FRONTEND_ROOT, url + 1); } // 处理其他资源请求 else { snprintf(full_path, sizeof(full_path), "%s%s", FRONTEND_ROOT, url); } return strdup(full_path); } ``` 2. **MIME类型扩展**(支持所有前端资源) ```c const char* get_mime_type(const char* path) { const char* ext = strrchr(path, '.'); if (!ext) return "application/octet-stream"; if (strcmp(ext, ".html") == 0) return "text/html"; if (strcmp(ext, ".css") == 0) return "text/css"; if (strcmp(ext, ".js") == 0) return "application/javascript"; if (strcmp(ext, ".json") == 0) return "application/json"; if (strcmp(ext, ".png") == 0) return "image/png"; if (strcmp(ext, ".jpg") == 0) return "image/jpeg"; if (strcmp(ext, ".jpeg") == 0) return "image/jpeg"; return "text/plain"; } ``` 3. **缓存控制策略**(参考引用[2][^2][^3]) ```c const char* get_cache_control(const char* path) { const char* ext = strrchr(path, '.'); if (!ext) return "Cache-Control: no-cache\r\n"; // HTML文件不缓存 if (strcmp(ext, ".html") == 0 || strcmp(ext, ".htm") == 0) { return "Cache-Control: no-store, no-cache\r\n"; } // 静态资源缓存7天 else if (strcmp(ext, ".css") == 0 || strcmp(ext, ".js") == 0 || strcmp(ext, ".png") == 0 || strcmp(ext, ".jpg") == 0) { return "Cache-Control: public, max-age=604800\r\n"; // 7天 } return ""; } ``` 4. **文件发送函数**(集成缓存控制) ```c void send_file(int client_socket, const char* file_path) { FILE* file = fopen(file_path, "rb"); if (!file) { send_404(client_socket); return; } // 获取文件大小 fseek(file, 0, SEEK_END); long file_size = ftell(file); rewind(file); // 准备HTTP头 char header[2048]; const char* mime_type = get_mime_type(file_path); const char* cache_policy = get_cache_control(file_path); snprintf(header, sizeof(header), "HTTP/1.1 200 OK\r\n" "Content-Type: %s\r\n" "%s" // 缓存策略 "Content-Length: %ld\r\n\r\n", mime_type, cache_policy, file_size); send(client_socket, header, strlen(header), 0); // 发送文件内容 char buffer[4096]; size_t bytes_read; while ((bytes_read = fread(buffer, 1, sizeof(buffer), file)) > 0) { send(client_socket, buffer, bytes_read, 0); } fclose(file); } ``` #### 目录结构适配方案 ```mermaid graph TD A[客户端请求] --> B{请求类型} B -->|/ 或 /Index.html| C[返回 ./Index.html] B -->|/About.html| D[返回 ./About.html] B -->|/css/style.css| E[返回 ./css/style.css] B -->|/images/body.png| F[返回 ./images/body.png] B -->|/img/1.jpg| G[返回 ./img/1.jpg] B -->|其他路径| H[返回404] ``` #### 关键安全措施 1. **路径遍历防护**: ```c // 在打开文件前添加安全检查 if (strstr(requested_path, "..") != NULL) { send_403(client_socket); // 禁止访问 return; } ``` 2. **文件类型白名单**: ```c // 只允许特定扩展名 const char* allowed_ext[] = {".html", ".css", ".js", ".png", ".jpg", ".jpeg", ".json"}; bool valid = false; for (int i = 0; i < sizeof(allowed_ext)/sizeof(char*); i++) { if (strstr(requested_path, allowed_ext[i])) { valid = true; break; } } if (!valid) { send_403(client_socket); return; } ``` #### 部署测试方案 1. **启动服务器**: ```bash gcc webserv_linux.c -o webserver ./webserver 8080 ``` 2. **测试路径**: - `http://localhost:8080/` → 返回Index.html - `http://localhost:8080/css/style.css` → 返回CSS文件 - `http://localhost:8080/images/body.png` → 返回PNG图片 - `http://localhost:8080/data/contact.json` → 返回JSON数据 3. **验证缓存策略**: ```http # HTML文件响应头 Cache-Control: no-store, no-cache # CSS文件响应头 Cache-Control: public, max-age=604800 ``` #### 性能优化建议 1. **内存映射传输**: ```c // 替代fread/send循环 int fd = fileno(file); void* map = mmap(0, file_size, PROT_READ, MAP_PRIVATE, fd, 0); send(client_socket, map, file_size, 0); munmap(map, file_size); ``` 2. **连接复用**: ```c // HTTP头添加 "Connection: keep-alive\r\n" "Keep-Alive: timeout=5, max=100\r\n" ``` > 完整实现需要处理HTTP协议细节,包括请求头解析、状态码处理连接管理[^1][^3] --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值