Tinyhttpd的实现和一些基本问题的解决

TInyhttpd是一个简单的http服务器的实现,代码总共有500多行,但是读下来对http的具体实现过程和linux网络编程的学习都很有好处。我重写了代码,然后进行了详细的注释。注释见详细代码。
具体的可以用图表示:
这里写图片描述
代码:

/*#!/usr/bin/env
* ******************************************************
* Last modified: 2018-05-06 16:27
* Filename     : tinyhttpd.c
* Description  :
* ********************************************************/
#include<stdio.h>
#include<sys/wait.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<sys/select.h>
#include<ctype.h>
#include<pthread.h>
#include<stdint.h>

#define ISspace(x)  isspace((int)(x))

#define SERVER_STRING "Server:jdbhttpd/0.1.0\r\n"

#define STDIN 0
#define STDOUT 1
#define STDERR 2


void execute_cgi(int ,const char *,const char *,const char *);
void bad_requests(int );
void accept_request(void *);
int startup(u_short *);
void error_die(const char *);
void unimplemented(int );
void not_found(int );
void serve_file(int,const char*);
void headers(int,const char*);
void cat(int,FILE*);
void cannot_execute(int);
int get_line(int,char*,int );

void error_die(const char *sc)
{
    perror(sc);
    exit(1);
}


void accept_request(void *arg)
{
    int client = (intptr_t)arg;
    char buf[1024];
    size_t numchars;
    //保存请求方式
    char method[255];
    //保存请求url
    char url[255];
   //保存请求文件路径
    char path[512];
    size_t i,j;
    struct stat st;
    //如果cgi=1  则执行cgi程序
    int cgi = 0;

    char *query_string =NULL;
    numchars = get_line(client,buf,sizeof(buf));
    i = 0,j = 0;
    while(!ISspace(buf[i])&&(i<sizeof(method)-1))
    {
        //请求报文第一行就会说明请求方式,是否为GET或者POST
        method[i] = buf[i];
        i++;
    }
    j = i;
    method[i] = '\0';
    //tinyhttpd只实现了GET和POST
    if(strcasecmp(method,"GET")&&strcasecmp(method,"POST"))
    {
        //tinyhttpd只实现了GET和POST
        unimplemented(client);
        return;
    }
    if(strcasecmp(method,"POST")==0)  cgi = 1;
    i = 0;
    //跨过空格
    while(ISspace(buf[j])&&(j<numchars))   j++;
    //得到请求的url
    while(!ISspace(buf[j])&&(i<sizeof(url)-1)&&(j<numchars))
    {
        url[i] = buf[j];
        i++;
        j++;
    }
    url[i] = '\0';
    //对于GET请求,如果有携带参数,则query_string指针指向url中?后面的GET参数
    if(strcasecmp(method,"GET")==0)
    {
        query_string = url;
        while((*query_string!='?')&&(*query_string!='\0'))
            query_string++;
        if(*query_string=='?')
        {
            cgi = 1;
            *query_string = '\0';
            query_string++;
        }
    }
    sprintf(path,"htdocs%s",url);
    //如果path是一个目录,默认设置为首页index.html
    if(path[strlen(path)-1]=='/')  strcat(path,"index.html");
    if(stat(path,&st)==-1)
    {
        while((numchars>0)&&strcmp("\n",buf))  numchars = get_line(client,buf,sizeof(buf));
        not_found(client);
    }
    else
    {
        //如果为目录
        if((st.st_mode&S_IFMT)==S_IFDIR) strcat(path,"/index.html");
        if((st.st_mode&S_IXUSR)||(st.st_mode&S_IXGRP)||(st.st_mode&S_IXOTH))  cgi = 1;
        //返回静态文件
        if(!cgi) serve_file(client,path);
        else
            execute_cgi(client,path,method,query_string);
    }
    close(client);
}

void execute_cgi(int client,const char *path,const char *method,const char *query_string)
{
    char buf[1024];
    int cgi_input[2];
    int cgi_output[2];
    pid_t pid;
    int status;
    int i;
    char c;
    int numchars = 1;
    int content_length = -1;
    buf[0] = 'A';
    buf[1] = '\0';
    //读取并且舍弃头部信息
    if(strcasecmp(method,"GET")==0)
    {
        while((numchars>0)&&strcmp("\n",buf))
            numchars = get_line(client,buf,sizeof(buf));
    }
    else
    {
        numchars = get_line(client,buf,sizeof(buf));
        //15是因为Content-Length长度为15
        while((numchars>0)&&strcmp("\n",buf))
        {
            buf[15] = '\0';
        //求得Content-content_length的长度
            if(strcasecmp(buf,"Content-Length:")==0)
                content_length = atoi(&(buf[16]));
            numchars = get_line(client,buf,sizeof(buf));
        }
        if(content_length==-1)
        {
            bad_requests(client);
            return;
        }
    }
    if(pipe(cgi_output)<0)
    {
        cannot_execute(client);
        return;
    }
    if(pipe(cgi_input)<0)
    {
        cannot_execute(client);
        return;
    }
    //fork一个子进程
    if((pid = fork())<0)
    {
        cannot_execute(client);
        return;
    }
    sprintf(buf,"HTTP/1.0 200 OK\r\n");
    send(client,buf,strlen(buf),0);
    //子进程执行cgi,并将输出传到cgi_output[1]
    if(pid==0)
    {
        char meth_env[255];
        char query_env[255];
        char length_env[255];
        //对标准输入和标准输出进行重定向
        dup2(cgi_output[1],STDOUT);
        dup2(cgi_input[0],STDIN);

        close(cgi_output[0]);
        close(cgi_input[1]);
        sprintf(meth_env,"REQUEST_METHOD=%s",method);
        putenv(meth_env);
        if(strcasecmp(method,"GET")==0)
        {
            sprintf(query_env,"QUERY_STRING=%s",query_string);
            putenv(query_env);
        }
        else
        {
            sprintf(length_env,"CONTENT_LENGTH=%d",content_length);
            putenv(length_env);
        }
        execl(path,path,NULL);
        exit(0);
    }
    //父进程传输数据和把数据发送到浏览器
    else
    {
        close(cgi_input[0]);
        close(cgi_output[1]);
        if(strcasecmp(method,"POST")==0)
        {
            for(i = 0;i<content_length;i++)
            {
                recv(client,&c,1,0);
                write(cgi_input[1],&c,1);
            }
        }
        while(read(cgi_output[0],&c,1)>0) send(client,&c,1,0);
        close(cgi_output[0]);
        close(cgi_input[1]);
        waitpid(pid,&status,0);
    }
}

void cannot_execute(int client)
{
    char buf[1024];
    sprintf(buf,"HTTP/1.0 500 Internal Server Error\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"Content-type: text/html\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"<P>Error prohibited CGI execution. \r\n ");
    send(client,buf,sizeof(buf),0);
}

void bad_requests(int client)
{
    char buf[1024];
    sprintf(buf,"HTTP/1.0 400 BAD REQUEST\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"Content-type: text/html\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"\r\n");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"<P>your browser senta bad request, ");
    send(client,buf,sizeof(buf),0);
    sprintf(buf,"such as a POST without a Content-Length.\r\n");
    send(client,buf,sizeof(buf),0);
}


void headers(int client,const char* filename)
{
    char buf[1024];
    (void)filename;

    strcpy(buf,"HTTP/1.0 200 OK\r\n");
    send(client,buf,strlen(buf),0);
    strcpy(buf,SERVER_STRING);
    send(client,buf,strlen(buf),0);
    sprintf(buf,"Content-Type: text/html\r\n");
    send(client,buf,strlen(buf),0);
    strcpy(buf,"\r\n");
    send(client,buf,strlen(buf),0);
}

void cat(int client,FILE *resource)
{
    char buf[1024];
    fgets(buf,sizeof(buf),resource);
    while(!feof(resource))
    {
        send(client,buf,strlen(buf),0);
        fgets(buf,sizeof(buf),resource);
    }
}


void serve_file(int client,const char *filename)
{
    FILE *resource = NULL;
    int numchars = 1;
    char buf[1024];

    buf[0] = 'A';
    buf[1] = '\0';
    while((numchars>0)&&strcmp("\n",buf))  numchars = get_line(client,buf,sizeof(buf));
    resource = fopen(filename,"r");
    if(resource==NULL)  not_found(client);
    else
    {
        //添加http头部
        headers(client,filename);
        //并发送文件内容
        cat(client,resource);
    }
    fclose(resource);
}

void not_found(int client)
{
    char buf[1024];
    sprintf(buf,"HTTP/1.0 404 NOT FOUND\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,SERVER_STRING);
    send(client,buf,strlen(buf),0);
    sprintf(buf,"Content-Type: text/html\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"<HTML><TITLE>Not Found</TITLE>\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"<BODY><P>The server could not fulfill\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"your request because the resource specified\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"is unavailable or nonexistent.\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"</BODY></HTML>\r\n");
    send(client,buf,strlen(buf),0);

}

void unimplemented(int client)
{
    char buf[1024];
    sprintf(buf,"HTTP/1.0 501 Method Not implemented\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,SERVER_STRING);
    send(client,buf,strlen(buf),0);
    sprintf(buf,"Content-Type: text/html\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"<HTML><HEAD><TITLE>Method Not Implemented\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"</TITLE></HEAD>\r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"<BODY><P>HTTP request method not supported. \r\n");
    send(client,buf,strlen(buf),0);
    sprintf(buf,"</BODY></HTML>\r\n");
    send(client,buf,strlen(buf),0);
}


//解析一行http报文
int get_line(int sock,char *buf,int size)
{
    int i = 0;
    char c = '\0';
    int n;

    while((i<size-1)&&(c!='\n'))
    {
        //先取一个字节
        n = recv(sock,&c,1,0);
        if(n>0)
        {
            //因为请求报文末尾是以\r\n结束的
            if(c=='\r')
            {
                //偷窥一个字节,判断是否为\n,是则结束
                n = recv(sock,&c,1,MSG_PEEK);
                if(n>0&&(c=='\n'))  recv(sock,&c,1,0);
                else c = '\n';
            }
            buf[i] = c;
            i++;
        }
        else c = '\n';
    }
    buf[i] = '\0';
    return i;
}

int startup(u_short *port)
{
    int httpd = 0;
    int on = 1;
    struct sockaddr_in name;
    httpd = socket(PF_INET,SOCK_STREAM,0);//新建一个服务器端socket
    if(httpd == -1) error_die("socket");
    memset(&name,0,sizeof(name));
    //对socket的基本属性进行赋值
    name.sin_family = AF_INET;
    name.sin_port = htons(*port); //htons从主机字节序转换为网络字节序
    name.sin_addr.s_addr = htonl(INADDR_ANY);
    //setsockopt原型  int setsockopt(int sockfd,int level,int optname,const void *optval,socklen_t optlen)
    if((setsockopt(httpd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on)))<0)//
    {
        error_die("setsockopt failed");
    }
    //对端口进行绑定
    if(bind(httpd,(struct sockaddr*)&name,sizeof(name))<0)  error_die("bind");
    //动态分配一个端口
    if(*port==0)
    {
        socklen_t namelen = sizeof(name);
        if(getsockname(httpd,(struct sockaddr*)&name,&namelen)==-1)
        {
            error_die("getsockname");
        }
        *port = ntohs(name.sin_port);
    }
    //对socket进行监听
    //5代表内核维护一个队列跟踪这些完成的连接但服务器还没有接受处理
    //或者正在进行的连接,代表大小的上限
    if(listen(httpd,5)<0)  error_die("listen");
    else printf("listen success\n");
    return httpd;
}


int main(void)
{
    int server_sock = -1;
    u_short port = 0;
    int client_sock = -1;
    struct sockaddr_in  client_name;
    socklen_t client_name_len = sizeof(client_name);
    pthread_t newthread;
    //对服务器端口号进行绑定
    server_sock = startup(&port);
    printf("httpd running on the port %d\n",port);
    while(1)
    {
        //接受来自客户端的连接
        client_sock =  accept(server_sock,(struct sockaddr *)&client_name,&client_name_len);
        if(client_sock==-1)  error_die("accept");
        if(pthread_create(&newthread,NULL,(void*)accept_request,(void*)(intptr_t)client_sock)!=0)  //每次收到请求,创建一个线程来处理接受到的请求,把client_sock转成地址参数
            //传入pthread_create
            perror("pthread_create");
    }
    //关掉服务器端socket
    close(server_sock);
    return 0;
}

然后写命令./tinyhttpd执行得到
这里写图片描述
然后输入紫色
这里写图片描述
当然在这个过程中有几个问题:
首先index.html的不能有执行权限,还有后缀为.cgi的程序必须有执行权限,可以使用chmod命令改变执行权限。

然后使用wireshark对本地回环端口进行监听,然后追踪tcp流。可以看到http的请求和应答报文。

POST /color.cgi HTTP/1.1
Host: 127.0.0.1:36299
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-GB,en;q=0.5
Accept-Encoding: gzip, deflate
Referer: http://127.0.0.1:36299/
Content-Type: application/x-www-form-urlencoded
Content-Length: 10
Connection: keep-alive
Upgrade-Insecure-Requests: 1

color=pink

HTTP/1.0 200 OK
HTTP/1.0 200 OK
--------------10
Content-Type: text/html; charset=ISO-8859-1

<!DOCTYPE html
    PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en-US" xml:lang="en-US">
<head>
<title>PINK</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
</head>
<body bgcolor="pink">
<h1>This is pink</h1>
</body>
</html>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值