├── pic ├── 1.png ├── 2.png └── 3.png ├── Makefile ├── htdocs ├── index.html ├── color.cgi └── easycgi.py ├── README.md └── httpd.c /pic/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterzhao/EasyHttp/HEAD/pic/1.png -------------------------------------------------------------------------------- /pic/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterzhao/EasyHttp/HEAD/pic/2.png -------------------------------------------------------------------------------- /pic/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hunterzhao/EasyHttp/HEAD/pic/3.png -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | httpd : httpd.o 2 | cc -o -pthread httpd httpd.o 3 | httpd.o: httpd.c 4 | cc -c httpd.c 5 | -------------------------------------------------------------------------------- /htdocs/index.html: -------------------------------------------------------------------------------- 1 | 2 | Index 3 | 4 |

welcome to EasyHttp

5 |

CGI demo 6 |
7 | First Name:
8 | 9 | Last Name: 10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /htdocs/color.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -Tw 2 | 3 | use strict; 4 | use CGI; 5 | 6 | my($cgi)=new CGI; 7 | 8 | print $cgi->header; 9 | 10 | my($color)="blue"; 11 | $color=$cgi->param('color') if defined $cgi->param('color'); 12 | 13 | print $cgi->start_html(-title => uc($color) 14 | -BGCOLOR =>$color); 15 | print $cgi->h1("This is $color"); 16 | print $cgi->end_html; 17 | 18 | -------------------------------------------------------------------------------- /htdocs/easycgi.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: UTF-8 -*- 3 | 4 | # CGI处理模块 5 | import cgi, cgitb 6 | 7 | # 创建 FieldStorage 的实例化 8 | form = cgi.FieldStorage() 9 | 10 | # 获取数据 11 | first_name = form.getvalue('first_name') 12 | last_name = form.getvalue('last_name') 13 | 14 | print "Content-type:text/html\r\n\r\n" 15 | print "" 16 | print "" 17 | print "Hello - Second CGI Program" 18 | print "" 19 | print "" 20 | print "

Hello %s %s

" % (first_name, last_name) 21 | print "" 22 | print "" 23 | 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #EasyHttp 最简单的http服务器 2 | 3 | 4 | ##说明: 5 | 这是一个C语言编写的http服务器简单实现,使用python语言作为cgi程序处理用户的表单输入。通过这个项目有利于理解http服务流程,以及多线程调试等诸多知识点。 6 | 7 | 8 | ##使用方法: 9 | 1. 平台:centos7 编译器:gcc 4.8.4 10 | 2. 下载项目,修改htdocs目录下easycgi.py 的运行权限 chmod 11 | 3. cd EasyHttp && make && ./httpd 12 | 4. 可以通过修改 index.html 表格中的请求方式来测试get post方法 13 | 5. 建议初学者通过逐步调试的方式学习代码,每个函数都有详细的注释哦~ 14 | 15 | ##流程: 16 | - 代理发送过来请求,服务器建立client_sock套接字,创建进程处理请求 accept_request 17 | - 逐行从套接字中读取http请求报文,判断请求类型 GET \ POST 判断 url 根据url上的参数读取服务器上的文件或者准备执行cgi文件 18 | - 根据具体的方法 将参数存储到 meth_env (putenv)中 19 | - 创建子进程执行cgi程序,父进程通过管道向子进程传入数据,从管道读取子进程输出的数据 父子进程通信示意图如下 20 | ![父子进程通信](https://github.com/hunterzhao/EasyHttp/blob/master/pic/3.png?raw=true) 21 | - 该服务器采用python编写cgi程序 22 | 23 | 24 | ##注意: 25 | 1. strlen 与 sizeof的区别 如果send中使用 sizeof 很有可能由于缓冲区被占满导致 send 被阻塞 26 | 2. 用户的代理(浏览器)会检查http的响应报文格式,所以http服务器返回的报文一定要严格按照要求编写 27 | 28 | 29 | 30 | ##测试: 31 | 1. 浏览器第一次请求到页面 32 | 33 | ![浏览器第一次请求到页面](https://github.com/hunterzhao/EasyHttp/blob/master/pic/1.png?raw=true) 34 | 35 | 2. 浏览器提交用户的输入,并返回执行结果 36 | 37 | ![浏览器提交用户的输入,并返回执行结果](https://github.com/hunterzhao/EasyHttp/blob/master/pic/2.png?raw=true) 38 | 39 | ##感谢: 40 | **谢谢您的查看,水平有限献丑了,如果对您有帮助请给我点star哦 :)** 41 | 42 | ##参考: 43 | 1. [http 请求报文 与 响应报文][1] 44 | 2. [GDB 调试多进程方法][2] 45 | 3. [python cgi编写方法][3] 46 | 47 | ##后续: 48 | 1. 支持图片 49 | 2. https 50 | 3. 大量的并发请求 51 | 52 | [1]: http://network.chinabyte.com/401/13238901.shtml 53 | [2]: http://blog.csdn.net/pbymw8iwm/article/details/7876797 54 | [3]: http://www.runoob.com/python/python-cgi.html -------------------------------------------------------------------------------- /httpd.c: -------------------------------------------------------------------------------- 1 | /* hunter's webserver 2 | * this is a simple webserver 3 | * created April 4th 2016 by hunter zhao 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #define ISspace(x) isspace((int)(x)) 20 | #define SERVER_STRING "Server: httpd/0.1.0\r\n" 21 | 22 | void accept_request(int); 23 | void bad_request(int); 24 | void cat(int ,FILE *); 25 | void cannot_execute(int); 26 | void error_die(const char*); 27 | int get_line(int ,char *,int); 28 | void header(int,const char*); 29 | void not_found(int); 30 | void server_file(int ,const char*); 31 | int startup(u_short *); 32 | void unimplemented(int); 33 | 34 | /* 35 | * * receive a call to accept 36 | * return void 37 | * parameters: the socket connected to the client 38 | */ 39 | void accept_request(int client){ 40 | char buf[1024]; 41 | int numchars; 42 | char method[255]; 43 | char url[255]; 44 | char path[512]; 45 | size_t i,j; 46 | struct stat st; 47 | int cgi = 0; 48 | char *query_string=NULL; 49 | 50 | //read from the socket return the len of content 51 | numchars = get_line(client,buf,sizeof(buf)); 52 | i=0;j=0; 53 | //get method 54 | while(!ISspace(buf[j]) && (i 0) && strcmp("\n",buf)) 110 | numchars=get_line(client,buf,sizeof(buf)); 111 | not_found(client); 112 | } 113 | //成功获取文件的属性 114 | else{ 115 | //如果路径为文件夹 则默认访问文件夹中的html文件 116 | if((st.st_mode & S_IFMT) == S_IFDIR) 117 | strcat(path,"/index.html"); 118 | // 119 | if((st.st_mode & S_IXUSR)|| //文件可读 120 | (st.st_mode & S_IXGRP)|| //文件可写 121 | (st.st_mode & S_IXOTH) ) //文件其他用户可执行 122 | cgi =1; 123 | if(!cgi) 124 | server_file(client,path); 125 | else 126 | execute_cgi(client,path,method,query_string); 127 | } 128 | //close the connection to client 129 | //close(client); 130 | } 131 | /* 132 | * infom the client that a request has made a mistake 133 | */ 134 | void bad_request(int client) 135 | { 136 | char buf[1024]; 137 | 138 | sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n"); 139 | send(client,buf,strlen(buf),0); 140 | sprintf(buf,"Content-type:text/html\r\n"); 141 | send(client,buf,strlen(buf),0); 142 | sprintf(buf,"\r\n"); 143 | send(client,buf,strlen(buf),0); 144 | sprintf(buf,"

your browser send a bad request, "); 145 | send(client,buf,strlen(buf),0); 146 | sprintf(buf,"such as a POST without a content-Length.\r\n"); 147 | send(client,buf,strlen(buf),0); 148 | } 149 | 150 | /*put the entire content of a file out on a socket 151 | * parameters: the client socket descriper 152 | * FILE pointer for the file to cat 153 | * FILE pointer for the file to cat 154 | */ 155 | 156 | void cat(int client ,FILE *resource) 157 | { 158 | char buf[1024]; 159 | 160 | //read the content of file into socket 161 | fgets(buf,sizeof(buf),resource); 162 | while(!feof(resource)) 163 | { 164 | send(client,buf,strlen(buf),0); 165 | fgets(buf,sizeof(buf),resource); 166 | } 167 | printf("send index.html\n"); 168 | } 169 | 170 | /* 171 | * inform the client cgi could not run 172 | */ 173 | 174 | void cannot_execute(int client) 175 | { 176 | char buf[1024]; 177 | 178 | sprintf(buf,"HTTP/1.0 500 Internal Server Error\r\n"); 179 | send(client,buf,strlen(buf),0); 180 | sprintf(buf,"Content-type: text/html\r\n"); 181 | send(client,buf,strlen(buf),0); 182 | sprintf(buf,"\r\n"); 183 | send(client,buf,strlen(buf),0); 184 | sprintf(buf,"

Error prohibited CGI execution

\r\n"); 185 | send(client,buf,strlen(buf),0); 186 | } 187 | 188 | /* 189 | * print out the error message whith perror() 190 | */ 191 | 192 | void error_die(const char *sc) 193 | { 194 | perror(sc); 195 | exit(1); 196 | } 197 | 198 | /* 199 | *execute the CGI script will need ro set environment variable as appropriate 200 | Parameters:client socket descriptor 201 | path to the CGI script 202 | */ 203 | void execute_cgi(int client,const char *path,const char *method,const char *query_string) 204 | { 205 | char buf[1024]; 206 | int cgi_output[2]; 207 | int cgi_input[2]; 208 | // int cgi_pip[2]; 209 | pid_t pid; 210 | int status; 211 | int i; 212 | char c; 213 | int numchars=1; 214 | int content_length=-1; 215 | 216 | buf[0]='A';buf[1]='\0'; 217 | if(strcasecmp(method,"GET")==0) 218 | //将所有的http header 读取并丢弃 219 | while((numchars>0) && strcmp("\n",buf)){ 220 | //memset( buf, 0, strlen( buf ) ); 221 | numchars=get_line(client,buf,strlen(buf)); 222 | } 223 | else 224 | { 225 | //post 找出content-length 226 | numchars=get_line(client,buf,strlen(buf)); 227 | while((numchars >0) && strcmp("\n",buf)) 228 | { 229 | //利用\0进行分割 获取15个字符长度的字符串 230 | buf[15]='\0'; 231 | if(strcasecmp(buf,"Content-Length:")==0){ 232 | content_length=atoi(&buf[16]); 233 | } 234 | numchars =get_line(client,buf,strlen(buf)); 235 | } 236 | //没有找到content_length 237 | if(content_length==-1){ 238 | bad_request(client); 239 | return; 240 | } 241 | } 242 | 243 | //correct 244 | sprintf(buf,"HTTP/1.0 200 OK\r\n");//已经清空了buf的内容 245 | send(client,buf,strlen(buf),0); 246 | // sprintf(buf,"Content-type: text/html\r\n"); 247 | // send(client,buf,strlen(buf),0); 248 | // sprintf(buf,"\r\n"); 249 | // send(client,buf,strlen(buf),0); 250 | 251 | //传送系统的缓冲区空间不够保存需传送的数据,除非套接口处于非阻塞I/O方式,否则send()将阻塞 所以导致一直处于pending的状态 252 | 253 | //close(client); 关闭了连接 导致发出reset信号 254 | //建立管道 255 | if(pipe(cgi_output)<0){ 256 | cannot_execute(client); 257 | return; 258 | } 259 | 260 | if(pipe(cgi_input)<0){ 261 | cannot_execute(client); 262 | return; 263 | } 264 | 265 | if((pid=fork())<0){ 266 | cannot_execute(client); 267 | return; 268 | } 269 | 270 | if(pid==0) //子进程 271 | { 272 | char meth_env[255]; 273 | char query_env[255]; 274 | char length_env[255]; 275 | 276 | dup2(cgi_output[1],1);//stdout 重定向到cgi_out的写入端 277 | dup2(cgi_input[0],0);//stdin 重定向到cgi_input的读取端 278 | close(cgi_output[0]); 279 | close(cgi_input[1]); 280 | sprintf(meth_env,"REQUEST_METHOD=%s",method); 281 | putenv(meth_env); 282 | if(strcasecmp(method,"GET")==0){ 283 | snprintf(query_env,"QUERY_STRING=%s",query_string); 284 | putenv(query_env); 285 | } 286 | else{ //post 287 | sprintf(length_env,"CONTENT_LENGTH=%d",content_length); 288 | } 289 | if(execl(path,path,NULL)<0) 290 | { 291 | printf("execl error\n"); 292 | } 293 | printf("

come on cgi

\n"); 294 | exit(0); 295 | }else{ //父进程 296 | // close(cgi_output[1]); 297 | // close(cgi_input[0]); 298 | if(strcasecmp(method,"POST")==0) 299 | //接受post过来的数据 300 | for(i=0;i0) //从子进程的标准输出中读取数据 306 | send(client,&c,1,0); 307 | close(cgi_output[0]); 308 | close(cgi_input[1]); 309 | //暂停目前的进程执行,直到有信号来到或子进程结束 310 | waitpid(pid,&status,0); 311 | } 312 | } 313 | 314 | void not_found(int client) 315 | { 316 | char buf[1024]; 317 | 318 | /* 404 页面 */ 319 | sprintf(buf, "HTTP/1.0 404 NOT FOUND\r\n"); 320 | send(client, buf, strlen(buf), 0); 321 | /*服务器信息*/ 322 | sprintf(buf, SERVER_STRING); 323 | send(client, buf, strlen(buf), 0); 324 | sprintf(buf, "Content-Type: text/html\r\n"); 325 | send(client, buf, strlen(buf), 0); 326 | sprintf(buf, "\r\n"); 327 | send(client, buf, strlen(buf), 0); 328 | sprintf(buf, "Not Found\r\n"); 329 | send(client, buf, strlen(buf), 0); 330 | sprintf(buf, "

The server could not fulfill\r\n"); 331 | send(client, buf, strlen(buf), 0); 332 | sprintf(buf, "your request because the resource specified\r\n"); 333 | send(client, buf, strlen(buf), 0); 334 | sprintf(buf, "is unavailable or nonexistent.\r\n"); 335 | send(client, buf, strlen(buf), 0); 336 | sprintf(buf, "\r\n"); 337 | send(client, buf, strlen(buf), 0); 338 | } 339 | 340 | int get_line(int sock, char *buf, int size) 341 | { 342 | int i = 0; 343 | char c = '\0'; 344 | int n; 345 | 346 | while ((i < size - 1) && (c != '\n')) 347 | { 348 | n = recv(sock, &c, 1, 0); 349 | /* DEBUG printf("%02X\n", c); */ 350 | if (n > 0) 351 | { 352 | if (c == '\r') 353 | { 354 | n = recv(sock, &c, 1, MSG_PEEK); 355 | /* DEBUG printf("%02X\n", c); */ 356 | if ((n > 0) && (c == '\n')) 357 | recv(sock, &c, 1, 0); 358 | else 359 | c = '\n'; 360 | } 361 | buf[i] = c; 362 | i++; 363 | } 364 | else 365 | c = '\n'; 366 | } 367 | buf[i] = '\0'; 368 | 369 | return(i); 370 | } 371 | 372 | void header(int client,const char *filename) 373 | { 374 | char buf[1024]; 375 | (void)filename; 376 | 377 | strcpy(buf,"HTTP/1.0 200 OK\r\n"); 378 | send(client,buf,strlen(buf),0); 379 | //服务器信息 380 | strcpy(buf,SERVER_STRING); 381 | send(client,buf,strlen(buf),0); 382 | strcpy(buf,"Content-Type: text/html\r\n"); 383 | send(client,buf,strlen(buf),0); 384 | strcpy(buf,"\r\n"); 385 | send(client,buf,strlen(buf),0); 386 | printf("send head\n"); 387 | } 388 | 389 | void server_file(int client,const char *filename) 390 | { 391 | FILE *resource =NULL; 392 | int numchars= 1; 393 | char buf[1024]; 394 | 395 | //丢弃header 396 | buf[0]='A'; buf[1]='\0'; 397 | while((numchars >0) && strcmp("\n", buf) ) 398 | numchars=get_line(client,buf,sizeof(buf)); 399 | //打开server的文件 400 | resource = fopen(filename,"r"); 401 | if(resource ==NULL) 402 | not_found(client); 403 | else 404 | { 405 | header(client,filename); 406 | cat(client,resource); 407 | 408 | } 409 | fclose(resource); 410 | } 411 | 412 | int startup(u_short *port) 413 | { 414 | int httpd = 0; 415 | struct sockaddr_in name; 416 | 417 | httpd=socket(PF_INET,SOCK_STREAM,0); 418 | if(httpd== -1) 419 | error_die("socket"); 420 | memset(&name,0,sizeof(name)); 421 | name.sin_family =AF_INET; 422 | name.sin_port = htons(*port); 423 | name.sin_addr.s_addr=htonl(INADDR_ANY); 424 | if(bind(httpd,(struct sockaddr *)&name,sizeof(name))<0) 425 | error_die("bind"); 426 | 427 | if(*port ==0 ) 428 | { 429 | int namelen=sizeof(name); 430 | if(getsockname(httpd,(struct sockaddr*)& name , &namelen)==-1) 431 | error_die("getsockname"); 432 | *port=ntohs(name.sin_port); 433 | } 434 | 435 | if(listen(httpd,5)<0) 436 | error_die("listen"); 437 | return(httpd); 438 | } 439 | 440 | void unimplemented(int client) 441 | { 442 | char buf[1024]; 443 | sprintf(buf,"HTTP/1.0 501 Method Not Implemented\r\n"); 444 | send(client,buf,strlen(buf),0); 445 | 446 | sprintf(buf,SERVER_STRING); 447 | send(client,buf,strlen(buf),0); 448 | sprintf(buf,"Content-Type: Text/html\r\n"); 449 | send(client,buf,strlen(buf),0); 450 | sprintf(buf,"\r\n"); 451 | send(client,buf,strlen(buf),0); 452 | sprintf(buf,"Method Not Implemented\r\n"); 453 | send(client,buf,strlen(buf),0); 454 | sprintf(buf,"\r\n"); 455 | send(client,buf,strlen(buf),0); 456 | sprintf(buf,"

HTTP request method not supported.\r\n"); 457 | send(client,buf,strlen(buf),0); 458 | sprintf(buf,"\r\n"); 459 | send(client,buf,strlen(buf),0); 460 | } 461 | 462 | int main(void) 463 | { 464 | int server_sock =-1; 465 | u_short port=0; 466 | int client_sock=-1; 467 | struct sockaddr_in client_name; 468 | int client_name_len=sizeof(client_name); 469 | pthread_t newthread; 470 | 471 | server_sock =startup(&port); 472 | printf("http running on port %d\n",port); 473 | 474 | while(1) 475 | { 476 | client_sock =accept(server_sock,(struct sockaddr *)&client_name,&client_name_len); 477 | if(client_sock ==-1) 478 | error_die("accept"); 479 | // accept_request(client_sock); 480 | if(pthread_create(&newthread,NULL,accept_request,client_sock)!=0) 481 | perror("pthread_create"); 482 | //close(client_sock); 483 | } 484 | 485 | close(server_sock); 486 | return(0); 487 | } 488 | --------------------------------------------------------------------------------