【Unix/Linux编程实践】DIY简单web服务器
2015-12-19 15:55
731 查看
一.服务器的设计问题:DIY or 代理
1.DIY——服务器接收请求,自己处理工作——用于快速简单的任务;2.代理——服务器接收请求,然后创建一个新进程来处理工作——用于慢速的更加复杂的任务。
二.web服务器功能
常用的3种用户操作:1.列举目录信息;
2.cat文件;
3.运行程序
请求处理: 目录:显示目录列表
文件:显示内容
.cgi文件:运行
不存在:错误消息
三.设计web服务器
1.建立服务器2.接收请求
3.读取请求
4.处理请求
5.发送应答
四.HTTP基本结构
1.客户发送请求GET filename HTTP/version
可选参数
空行
2.服务器发送应答
HTTP/version status-code status-message
附加信息
空行
内容
五.编写Web服务器
为简单起见,我们的Web服务器做以下约定:1.服务器只支持GET命令,只接收请求航,跳过其余参数,然后处理请求和发送应答,另外,我们忽略出错检查。
2.服务器为每一个请求创建一个新的进程来处理,子进程将请求分割成命令和参数。如果命令不是GET,服务器应答HTTP返回码表示未实现的命令。如果命令是GET,服务器将期望得到目录名,一个以.cgi结尾的可执行程序或文件名。如果没有该目录或指定的文件,服务器报错。如果存在,服务器决定执行的操作:ls,exec或cat。
代码:
/* * socklib.c * * This file contains functions used lots when writing internet * client/server programs. The two main functions here are: * * make_server_socket( portnum ) returns a server socket * or -1 if error * make_server_socket_q(portnum,backlog) * * connect_to_server(char *hostname, int portnum) * returns a connected socket * or -1 if error */ #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> #include <time.h> #include <strings.h> #define HOSTLEN 256 #define BACKLOG 1 int make_server_socket_q(int , int ); int make_server_socket(int portnum) { return make_server_socket_q(portnum, BACKLOG); } int make_server_socket_q(int portnum, int backlog) { struct sockaddr_in saddr; /* build our address here */ struct hostent *hp; /* this is part of our */ char hostname[HOSTLEN]; /* address */ int sock_id; /* the socket */ sock_id = socket(PF_INET, SOCK_STREAM, 0); /* get a socket */ if ( sock_id == -1 ) return -1; /** build address and bind it to socket **/ bzero((void *)&saddr, sizeof(saddr)); /* clear out struct */ gethostname(hostname, HOSTLEN); /* where am I ? */ hp = gethostbyname(hostname); /* get info about host */ /* fill in host part */ bcopy( (void *)hp->h_addr, (void *)&saddr.sin_addr, hp->h_length); saddr.sin_port = htons(portnum); /* fill in socket port */ saddr.sin_family = AF_INET ; /* fill in addr family */ if ( bind(sock_id, (struct sockaddr *)&saddr, sizeof(saddr)) != 0 ) return -1; /** arrange for incoming calls **/ if ( listen(sock_id, backlog) != 0 ) return -1; return sock_id; } int connect_to_server(char *host, int portnum) { int sock; struct sockaddr_in servadd; /* the number to call */ struct hostent *hp; /* used to get number */ /** Step 1: Get a socket **/ sock = socket( AF_INET, SOCK_STREAM, 0 ); /* get a line */ if ( sock == -1 ) return -1; /** Step 2: connect to server **/ bzero( &servadd, sizeof(servadd) ); /* zero the address */ hp = gethostbyname( host ); /* lookup host's ip # */ if (hp == NULL) return -1; bcopy(hp->h_addr, (struct sockaddr *)&servadd.sin_addr, hp->h_length); servadd.sin_port = htons(portnum); /* fill in port number */ servadd.sin_family = AF_INET ; /* fill in socket type */ if ( connect(sock,(struct sockaddr *)&servadd, sizeof(servadd)) !=0) return -1; return sock; }
/* webserv.c - a minimal web server (version 0.2) * usage: ws portnumber * features: supports the GET command only * runs in the current directory * forks a new child to handle each request * has MAJOR security holes, for demo purposes only * has many other weaknesses, but is a good start * build: cc webserv.c socklib.c -o webserv */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <string.h> main(int ac, char *av[]) { int sock, fd; FILE *fpin; char request[BUFSIZ]; if ( ac == 1 ){ fprintf(stderr,"usage: ws portnum\n"); exit(1); } sock = make_server_socket( atoi(av[1]) ); if ( sock == -1 ) exit(2); /* main loop here */ while(1){ /* take a call and buffer it */ fd = accept( sock, NULL, NULL ); fpin = fdopen(fd, "r" ); /* read request */ fgets(request,BUFSIZ,fpin); printf("got a call: request = %s", request); read_til_crnl(fpin); /* do what client asks */ process_rq(request, fd); fclose(fpin); } } /* ------------------------------------------------------ * read_til_crnl(FILE *) skip over all request info until a CRNL is seen ------------------------------------------------------ */ read_til_crnl(FILE *fp) { char buf[BUFSIZ]; while( fgets(buf,BUFSIZ,fp) != NULL && strcmp(buf,"\r\n") != 0 ) ; } /* ------------------------------------------------------ * process_rq( char *rq, int fd ) do what the request asks for and write reply to fd handles request in a new process rq is HTTP command: GET /foo/bar.html HTTP/1.0 ------------------------------------------------------ */ process_rq( char *rq, int fd ) { char cmd[BUFSIZ], arg[BUFSIZ]; /* create a new process and return if not the child */ if ( fork() != 0 ) { while (waitpid(-1, NULL, 0) > 0); /*wait for all children*/ return; } /*avoid zombie*/ if ( fork() != 0) exit(0); strcpy(arg, "./"); /* precede args with ./ */ if ( sscanf(rq, "%s%s", cmd, arg+2) != 2 ) return; if ( strcmp(cmd,"GET") != 0 ) cannot_do(fd); else if ( not_exist( arg ) ) do_404(arg, fd ); else if ( isadir( arg ) ) do_ls( arg, fd ); else if ( ends_in_cgi( arg ) ) do_exec( arg, fd ); else do_cat( arg, fd ); } /* ------------------------------------------------------ * the reply header thing: all functions need one if content_type is NULL then don't send content type ------------------------------------------------------ */ header( FILE *fp, char *content_type ) { fprintf(fp, "HTTP/1.0 200 OK\r\n"); if ( content_type ) fprintf(fp, "Content-type: %s\r\n", content_type ); } /* ------------------------------------------------------ * simple functions first: cannot_do(fd) unimplemented HTTP command and do_404(item,fd) no such object ------------------------------------------------------ */ cannot_do(int fd) { FILE *fp = fdopen(fd,"w"); fprintf(fp, "HTTP/1.0 501 Not Implemented\r\n"); fprintf(fp, "Content-type: text/plain\r\n"); fprintf(fp, "\r\n"); fprintf(fp, "That command is not yet implemented\r\n"); fclose(fp); } do_404(char *item, int fd) { FILE *fp = fdopen(fd,"w"); fprintf(fp, "HTTP/1.0 404 Not Found\r\n"); fprintf(fp, "Content-type: text/plain\r\n"); fprintf(fp, "\r\n"); fprintf(fp, "The item you requested: %s\r\nis not found\r\n", item); fclose(fp); } /* ------------------------------------------------------ * the directory listing section isadir() uses stat, not_exist() uses stat do_ls runs ls. It should not ------------------------------------------------------ */ isadir(char *f) { struct stat info; return ( stat(f, &info) != -1 && S_ISDIR(info.st_mode) ); } not_exist(char *f) { struct stat info; return( stat(f,&info) == -1 ); } do_ls(char *dir, int fd) { FILE *fp ; fp = fdopen(fd,"w"); header(fp, "text/plain"); fprintf(fp,"\r\n"); fflush(fp); dup2(fd,1); dup2(fd,2); close(fd); execlp("ls","ls","-l",dir,NULL); perror(dir); exit(1); } /* ------------------------------------------------------ * the cgi stuff. function to check extension and one to run the program. ------------------------------------------------------ */ char * file_type(char *f) /* returns 'extension' of file */ { char *cp; if ( (cp = strrchr(f, '.' )) != NULL ) return cp+1; return ""; } ends_in_cgi(char *f) { return ( strcmp( file_type(f), "cgi" ) == 0 ); } do_exec( char *prog, int fd ) { FILE *fp ; fp = fdopen(fd,"w"); header(fp, NULL); fflush(fp); dup2(fd, 1); dup2(fd, 2); close(fd); execl(prog,prog,NULL); perror(prog); } /* ------------------------------------------------------ * do_cat(filename,fd) sends back contents after a header ------------------------------------------------------ */ do_cat(char *f, int fd) { char *extension = file_type(f); char *content = "text/plain"; FILE *fpsock, *fpfile; int c; if ( strcmp(extension,"html") == 0 ) content = "text/html"; else if ( strcmp(extension, "gif") == 0 ) content = "image/gif"; else if ( strcmp(extension, "jpg") == 0 ) content = "image/jpeg"; else if ( strcmp(extension, "jpeg") == 0 ) content = "image/jpeg"; fpsock = fdopen(fd, "w"); fpfile = fopen( f , "r"); if ( fpsock != NULL && fpfile != NULL ) { header( fpsock, content ); fprintf(fpsock, "\r\n"); while( (c = getc(fpfile) ) != EOF ) putc(c, fpsock); fclose(fpfile); fclose(fpsock); } exit(0); }
六.运行web服务器
编译运行:$ cc webserv.c socklib.c -o webserv $ ./webserv 12345
访问文件:
我们将html文件放到webserv同一目录下,并且用http://yourhostname:12345/filename.html来打开它。
执行程序:
我们创建以下脚本(命名为hello.cgi,权限为755):
#!/bin/sh #hello.cgi printf "Content-type:text/plain\n\nhello\n";
打开http://yourhostname:12345/hello.cgi即可执行它。
查看目录:
新建一个目录hello,访问http://yourhostname:12345/hello即可。
七.改进建议
1.缓存溢出保护;2.CGI程序需要设计一些环境变量;
3.HTTP头部可以包含更多的信息;
……
相关文章推荐
- centos system info
- CentOS 添加常用 yum 源
- CentOS6.5菜鸟之旅:安装VirtualBox4.3
- Linux sed命令常用方法
- Linux 创建线程注意点
- linux基本概念及操作
- linux下一些有趣的命令
- LinuxWindowSDK开发日记
- Centos7配置iscsi多路径
- linux中编译安装MySQL
- Linux命令:磁盘和文件系统管理
- vim常用指令
- Java 程序员应会的 Linux 命令
- linux压缩率较高的工具xz
- linux 除了某个文件或某个目录以外所有删除
- linux 创建、删除文件和文件夹 命令
- linux下安装cmake和mysql遇到的问题总结
- Linux 0.12内核与现代内核在内存管理上的区别
- centos7配置JDK环境变量
- Linux简单驱动框架