tinyhttpd ------ C 语言实现最简单的 HTTP 服务器
Posted Elaine
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了tinyhttpd ------ C 语言实现最简单的 HTTP 服务器相关的知识,希望对你有一定的参考价值。
工作流程:
1>服务器启动,在指定端口或随机选取端口绑定httpd服务。
2>收到一个http请求时(其实就是listen端口accept的时候),派生一个线程运行accept_request函数。
3>取出http请求中method(get或post)和url,对于get方法,如果有携带参数,则query_string指针指向url中?后面的get参数。
4>格式化url到path数组,表示浏览器请求的文件路径,在tinyhttpd中服务器文件是在htdocs文件夹下。当url以/结尾,或者url是个目录,则默认在path中加上index.thml,表示访问主页。
5>如果文件路径合法,对于无参数的get请求,直接输出服务器文件到浏览器,即用http格式写到套接字上,跳到(10)。其他情况(带参数get,post方法,url为科执行文件),则调用execute_cgi函数执行cgi脚本。
6>读取整个http请求并丢弃,如果是post则找出content-length,把http状态码200写到套接字里面。
7>建立两个管道,cgi_input和cgi_output,并fork一个子进程。
8>在子进程中,把stdout重定向到cgi_output的写入端,把stdin重定向到cgi_input的读取端,关闭cgi_input的写入端和cgi_output的读取端,是指request_method的环境变量,get的话设置query_string的环境变量,post的话设置content-length的环境变量,这些环境变量都是为了给cgi脚本调用,接着用execl运行cgi程序。
9>在父进程中,关闭cgi_input的读取端和cgi_output的写入端,如果post的话,把post数据写入到cgo_input,已被重定向到stdin读取cgi_output的管道输出到客户端,等待子进程结束。
10>关闭与浏览器的链接,完成一次http请求与回应,因为http是无连接的。
1 /* J. David‘s webserver */ 2 /* This is a simple webserver. 3 * Created November 1999 by J. David Blackstone. 4 * CSE 4344 (Network concepts), Prof. Zeigler 5 * University of Texas at Arlington 6 */ 7 /* This program compiles for Sparc Solaris 2.6. 8 * To compile for Linux: 9 * 1) Comment out the #include <pthread.h> line. 10 * 2) Comment out the line that defines the variable newthread. 11 * 3) Comment out the two lines that run pthread_create(). 12 * 4) Uncomment the line that runs accept_request(). 13 * 5) Remove -lsocket from the Makefile. 14 */ 15 #include <stdio.h> 16 #include <sys/socket.h> 17 #include <sys/types.h> 18 #include <netinet/in.h> 19 #include <arpa/inet.h> 20 #include <unistd.h> 21 #include <ctype.h> 22 #include <strings.h> 23 #include <string.h> 24 #include <sys/stat.h> 25 #include <pthread.h> 26 #include <sys/wait.h> 27 #include <stdlib.h> 28 29 #define ISspace(x) isspace((int)(x)) 30 31 #define SERVER_STRING "Server: jdbhttpd/0.1.0\r\n" 32 33 void accept_request(int); 34 //处理从套接字上监听到的一个HTTP请求,在这里可以很大一部分的体现服务器处理请求的流程 35 void bad_request(int); 36 //返回给客户端这是个错误请求,HTTP状态码是400 BAD REQUEST 37 void cat(int, FILE *); 38 //读取服务器上某个文件写到socket套接字 39 void cannot_execute(int); 40 //主要执行在处理cgi程序的处理,也是个主要函数 41 void error_die(const char *); 42 //把错误信息写到perror并退出 43 void execute_cgi(int, const char *, const char *, const char *); 44 //运行cgi程序的处理,也是哥主函数 45 int get_line(int, char *, int); 46 //读取套接字的一行,把回车换行等情况都统一为换行符结束 47 void headers(int, const char *); 48 //把HTTP相应头写到套接字 49 void not_found(int); 50 //主要处理找不到请求的文件时的情况 51 void serve_file(int, const char *); 52 //调用cat把服务器文件返回给浏览器 53 int startup(u_short *); 54 //初始化httpd服务,包括建立套接字,绑定端口,进行监听等 55 void unimplemented(int); 56 //返回给浏览器表示接收到的http请求所用的method不被支持 57 58 /**********************************************************************/ 59 /* A request has caused a call to accept() on the server port to 60 * return. Process the request appropriately. 61 * Parameters: the socket connected to the client */ 62 /**********************************************************************/ 63 void accept_request(int client) 64 { 65 66 char buf[1024]; 67 int numchars; 68 char method[255]; 69 char url[255]; 70 char path[512]; 71 size_t i, j; 72 struct stat st; 73 int cgi = 0; /* becomes true if server decides this is a CGI 74 * program */ 75 char *query_string = NULL; 76 77 numchars = get_line(client, buf, sizeof(buf)); 78 //读取 client端发送的数据并且 返回的参数是 numchars 79 i = 0; 80 j = 0; 81 while (!ISspace(buf[j]) && (i < sizeof(method) - 1)) 82 { 83 method[i] = buf[j]; 84 i++; 85 j++; 86 } 87 //得到传递的参数是post 还是get方法 还是其他 88 method[i] = ‘\0‘; 89 90 if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) 91 { 92 //回给浏览器表明收到的 HTTP 请求所用的 method 不被支持 93 unimplemented(client); 94 return; 95 } 96 97 if (strcasecmp(method, "POST") == 0) 98 cgi = 1; 99 100 i = 0; 101 //http请求行格式是 method urI http-version 102 while (ISspace(buf[j]) && (j < sizeof(buf))) 103 j++; 104 while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < sizeof(buf))) 105 { 106 url[i] = buf[j]; //包含请求行中的URI 107 i++; 108 j++; 109 } 110 url[i] = ‘\0‘; 111 ///获取发送数据中的URI 112 if (strcasecmp(method, "GET") == 0) 113 { 114 //get方法 是将参数传递在URI中的?后面如果元素多用&进行链接 115 query_string = url; 116 while ((*query_string != ‘?‘) && (*query_string != ‘\0‘)) 117 query_string++; 118 // get 请求? 后面为参数 119 if (*query_string == ‘?‘) 120 { 121 cgi = 1; 122 *query_string = ‘\0‘; 123 query_string++; 124 } 125 } 126 //格式url存储在path数组 并且html存储在 htdocs文件中 127 sprintf(path, "htdocs%s", url); 128 if (path[strlen(path) - 1] == ‘/‘) 129 strcat(path, "index.html"); //字符串进行衔接 + index.html 130 //stat函数 根据path路径 获取文件内容 存储在 st 结构体中 成功返回0 错误返回-1 131 if (stat(path, &st) == -1) 132 { 133 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 134 numchars = get_line(client, buf, sizeof(buf)); 135 not_found(client); 136 } 137 else 138 { 139 if ((st.st_mode & S_IFMT) == S_IFDIR) 140 strcat(path, "/index.html"); 141 //文件的权限 属主 属组 其它 三种任一个拥有执行权 142 if ((st.st_mode & S_IXUSR) || 143 (st.st_mode & S_IXGRP) || 144 (st.st_mode & S_IXOTH) ) 145 cgi = 1; 146 //调用cat 把服务器文件返回给浏览器 post方法或者拥有执行权限 147 if (!cgi) 148 serve_file(client, path); 149 else 150 execute_cgi(client, path, method, query_string) 151 //运行cgi程序的处理,也是个主要函数 152 } 153 154 close(client); 155 } 156 157 /**********************************************************************/ 158 /* Inform the client that a request it has made has a problem. 159 * Parameters: client socket */ 160 /**********************************************************************/ 161 void bad_request(int client) 162 { 163 char buf[1024]; 164 165 sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n"); 166 send(client, buf, sizeof(buf), 0); 167 sprintf(buf, "Content-type: text/html\r\n"); 168 send(client, buf, sizeof(buf), 0); 169 sprintf(buf, "\r\n"); 170 send(client, buf, sizeof(buf), 0); 171 sprintf(buf, "<P>Your browser sent a bad request, "); 172 send(client, buf, sizeof(buf), 0); 173 sprintf(buf, "such as a POST without a Content-Length.\r\n"); 174 send(client, buf, sizeof(buf), 0); 175 } 176 177 /**********************************************************************/ 178 /* Put the entire contents of a file out on a socket. This function 179 * is named after the UNIX "cat" command, because it might have been 180 * easier just to do something like pipe, fork, and exec("cat"). 181 * Parameters: the client socket descriptor 182 * FILE pointer for the file to cat */ 183 /**********************************************************************/ 184 //读取服务器上的某个文件 写到socket套接字上 185 void cat(int client, FILE *resource) 186 { 187 char buf[1024]; 188 189 fgets(buf, sizeof(buf), resource); 190 //检测流上的文件结束符 如果文件结束,则返回非0值,否则返回0,文件结束符只能被clearerr()清除。 191 while (!feof(resource)) 192 { 193 send(client, buf, strlen(buf), 0); 194 fgets(buf, sizeof(buf), resource); 195 } 196 } 197 198 /**********************************************************************/ 199 /* Inform the client that a CGI script could not be executed. 200 * Parameter: the client socket descriptor. */ 201 /**********************************************************************/ 202 void cannot_execute(int client) 203 { 204 char buf[1024]; 205 206 sprintf(buf, "HTTP/1.0 500 Internal Server Error\r\n"); 207 send(client, buf, strlen(buf), 0); 208 sprintf(buf, "Content-type: text/html\r\n"); 209 send(client, buf, strlen(buf), 0); 210 sprintf(buf, "\r\n"); 211 send(client, buf, strlen(buf), 0); 212 sprintf(buf, "<P>Error prohibited CGI execution.\r\n"); 213 send(client, buf, strlen(buf), 0); 214 } 215 216 /**********************************************************************/ 217 /* Print out an error message with perror() (for system errors; based 218 * on value of errno, which indicates system call errors) and exit the 219 * program indicating an error. */ 220 /**********************************************************************/ 221 void error_die(const char *sc) 222 { 223 perror(sc); 224 exit(1); 225 } 226 227 /**********************************************************************/ 228 /* Execute a CGI script. Will need to set environment variables as 229 * appropriate. 230 * Parameters: client socket descriptor 231 * path to the CGI script */ 232 /**********************************************************************/ 233 234 //运行cgi程序 也是个主函数 235 void execute_cgi(int client, const char *path, 236 const char *method, const char *query_string) 237 { 238 //在父进程中,关闭cgi_input的读入端和cgi_output的写入端,如果post的话 239 //把post数据写入到cgi_input,已被重定向到stdin,读取cgi_output的管道 240 //输出到客户端,该管道输入是stdout,接着关闭所有管道,等待子进程结束。 241 242 char buf[1024]; 243 int cgi_output[2]; 244 int cgi_input[2]; 245 //cgi_output[1] cgi_input[1] 为输入端 246 //cgi_input[0] cgi_output[0] 为输出端 247 pid_t pid; 248 int status; 249 int i; 250 char c; 251 int numchars = 1; 252 int content_length = -1; 253 254 buf[0] = ‘A‘; 255 buf[1] = ‘\0‘; 256 //读入请求头 257 if (strcasecmp(method, "GET") == 0) 258 while ((numchars > 0) && strcmp("\n", buf)) /* read & discard headers */ 259 numchars = get_line(client, buf, sizeof(buf)); 260 else /* POST */ 261 { 262 numchars = get_line(client, buf, sizeof(buf)); 263 while ((numchars > 0) && strcmp("\n", buf)) 264 { 265 buf[15] = ‘\0‘; 266 if (strcasecmp(buf, "Content-Length:") == 0) 267 content_length = atoi(&(buf[16])); 268 numchars = get_line(client, buf, sizeof(buf)); 269 } 270 if (content_length == -1) 271 { 272 bad_request(client); 273 return; 274 } 275 } 276 277 sprintf(buf, "HTTP/1.0 200 OK\r\n"); 278 send(client, buf, strlen(buf), 0); 279 280 if (pipe(cgi_output) < 0) 281 { 282 cannot_execute(client); 283 return; 284 } 285 if (pipe(cgi_input) < 0) 286 { 287 cannot_execute(client); 288 return; 289 } 290 291 if ( (pid = fork()) < 0 ) 292 { 293 cannot_execute(client); 294 return; 295 } 296 if (pid == 0) /* child: CGI script */ 297 { 298 char meth_env[255]; 299 char query_env[255]; 300 char length_env[255]; 301 //把stdout重定向到cgi_output的写入端 302 dup2(cgi_output[1], 1); 303 //把stdin重定向到cgi_input的读入端 304 dup2(cgi_input[0], 0); 305 //关闭cgi_output的读入端 和 cgi_input的 写入端 306 close(cgi_output[0]); 307 close(cgi_input[1]); 308 //设置request_method的环境变量 309 sprintf(meth_env, "REQUEST_METHOD=%s", method); 310 putenv(meth_env); 311 if (strcasecmp(method, "GET") == 0) 312 { 313 //设置query_string的环境变量 314 sprintf(query_env, "QUERY_STRING=%s", query_string); 315 putenv(query_env); 316 } 317 else /* POST */ 318 { 319 //设置content_length的环境变量 320 sprintf(length_env, "CONTENT_LENGTH=%d", content_length); 321 putenv(length_env); 322 } 323 //用execl运行cgi程序 324 execl(path, path, NULL); 325 exit(0); 326 } 327 else /* parent */ 328 { 329 close(cgi_output[1]); 330 close(cgi_input[0]); 331 //关闭cgi_output的 写入端 和 cgi_input的读取端 332 if (strcasecmp(method, "POST") == 0) 333 //接受post的数据 334 for (i = 0; i < content_length; i++) 335 { 336 recv(client, &c, 1, 0); 337 write(cgi_input[1], &c, 1); 338 //讲post数据写入cgi_input,并且重定向到stdin中 339 } 340 //读取cgi_output的管道输出到客户端,该管道输入时stdout 341 while (read(cgi_output[0], &c, 1) > 0) 342 send(client, &c, 1, 0); 343 //关闭管道 344 close(cgi_output[0]); 345 close(cgi_input[1]); 346 //等待子进程 347 waitpid(pid, &status, 0); 348 } 349 } 350 351 /**********************************************************************/ 352 /* Get a line from a socket, whether the line ends in a newline, 353 * carriage return, or a CRLF combination. Terminates the string read 354 * with a null character. If no newline indicator is found before the 355 * end of the buffer, the string is terminated with a null. If any of 356 * the above three line terminators is read, the last character of the 357 * string will be a linefeed and the string will be terminated with a 358 * null character. 359 * Parameters: the socket descriptor 360 * the buffer to save the data in 361 * the size of the buffer 362 * Returns: the number of bytes stored (excluding null) */ 363 /**********************************************************************/ 364 int get_line(int sock, char *buf, int size) 365 { 366 int i = 以上是关于tinyhttpd ------ C 语言实现最简单的 HTTP 服务器的主要内容,如果未能解决你的问题,请参考以下文章