您的位置:首页 > 运维架构 > Linux

【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头部可以包含更多的信息;

……
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: