小型HTTP服务器

#include "httpd.h"  

void usage(const char *proc)  
{  
    printf("Usage : %s [PORT]\n", proc);  
}  

static void not_found(int client)  
{  
}  
void print_debug(const char * msg)  
{  
#ifdef _DEBUG_  
    printf("%s\n", msg);  
#endif  
}  

static void bad_request(int client)  
{  
    print_debug("enter our fault...\n");  
    char buf[1024];  
    sprintf(buf, "HTTP/1.0 400 BAD REQUEST\r\n");  
    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></br><p>your enter message is a bad request</p></br></html>\r\n");  
    send(client, buf, strlen(buf), 0);  
}  


void print_log(const char *fun, int line, int err_no,  const char *err_str)  
{  
    printf("[%s: %d] [%d] [%s]\n", fun, line, err_no, err_str);  
}  

void clear_header(int client)  
{  
    char buf[1024];  
    memset(buf, '\0', sizeof(buf));  
    int ret = 0;  
    do{  
        ret = get_line(client, buf, sizeof(buf));  
    }while(ret > 0 && strcmp(buf, "\n") != 0 );  
}  

//return num char, success  
//return <= 0, failed  
int get_line(int sock, char *buf, int max_len)  
{  
    if( !buf || max_len < 0){  
        return -1;  
    }  
    int i = 0;  
    int n = 0;  
    char c = '\0';  
    while( i < max_len-1 && c != '\n' ){  
        n = recv(sock, &c, 1, 0);  
        if(n > 0){//success  
            if( c == '\r' ){  
                n = recv(sock, &c, 1,MSG_PEEK);  
                if( n > 0 && c == '\n' ){//windows  
                    recv(sock, &c, 1, 0);//delete  
                }else{  
                    c = '\n';  
                }  
            }  
            buf[i++] = c;  
        }else{//failed  
            c = '\n';  
        }  
    }  
    buf[i] = '\0';  
    return i;  
}  

void echo_error_to_client(int client, int error_code)  
{  
    switch(error_code){  
        case 400://request error  
            bad_request(client);  
            break;  
        case 404://not found  
            not_found(client);  
            break;  
        case 500://server error  
    //      server_error(client);  
            break;  
        case 503://server unavailable  
//          server_unavailable(client);  
            break;  
        //.....................  
        default:  
//          default_error(client);  
            break;  
    }  
}  

void echo_html(int client, const char *path, unsigned int file_size)  
{  
    if( !path ){  
        return;  
    }  
    int in_fd = open(path, O_RDONLY);//以只读的形式打开  
    if(in_fd < 0){  
        print_debug("open index.html error");  
        //echo_error_to_client();  
        return;  
    }  
    print_debug("open index.html success");  
    char echo_line[1024];  
    memset(echo_line, '\0', sizeof(echo_line));  
    strncpy(echo_line, HTTP_VERSION, strlen(HTTP_VERSION)+1);  
    strcat(echo_line, " 200 OK");  
    strcat(echo_line, "\r\n\r\n");  
    send(client, echo_line,strlen(echo_line), 0);  
    print_debug("send echo head success");  
    if( sendfile(client, in_fd, NULL, file_size) < 0 ){  
        print_debug("send_file error");  
        //echo_error_to_client();  
        close(in_fd);  
        return;  
    }  
    print_debug("sendfile success");  
    close(in_fd);  
}  

void exe_cgi(int sock_client, const char *path, const char *method,const char *query_string)  
{  
    print_debug("enter cgi\n");  
    char buf[_COMM_SIZE_];  
    int numchars = 0;  
    int content_length = -1;  
    //pipe  
    int cgi_input[2] = {0, 0};  
    int cgi_output[2] = {0, 0};  
    //child proc  
    pid_t id;  

    print_debug(method);  

    if(strcasecmp(method, "GET") == 0){//GET  
        clear_header(sock_client);  
    }else{//POST  
        do{  
            memset(buf, '\0', sizeof(buf));  
            numchars = get_line(sock_client, buf, sizeof(buf));  
            if(strncasecmp(buf, "Content-Length:", strlen("Content-Length:")) == 0){  
                //函数定义:int strncasecmp(const char *s1, const char *s2, size_t n)  
                //函数说明:strncasecmp()用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异。  
                content_length = atoi(&buf[16]);  
            }  
        }while(numchars > 0 && strcmp(buf, "\n") != 0);//一行一行的比较直到寻找content_length  
        if( content_length == -1 ){  
            //echo_error_to_client();  
            return;  
        }  
    }  


    memset(buf, '\0', sizeof(buf));  
    strcpy(buf, HTTP_VERSION);  
    strcat(buf, " 200 OK\r\n\r\n");  
    send(sock_client, buf, strlen(buf), 0);  

    if( pipe(cgi_input) == -1 ){//pipe error  
        //echo_error_to_client();  
        return;  
    }  
    if( pipe(cgi_output) == -1 ){  
        close(cgi_input[0]);  
        close(cgi_input[1]);  
        //echo_error_to_client();  
        return;  
    }  

    if( (id = fork()) < 0){//fork error  
        close(cgi_input[0]);  
        close(cgi_input[1]);  
        close(cgi_output[0]);  
        close(cgi_output[1]);  
        //echo_error_to_client();  
        return;  
    }else if( id == 0 ){//child  
        char query_env[_COMM_SIZE_/10];  
        char method_env[_COMM_SIZE_];  
        char content_len_env[_COMM_SIZE_];  
        memset(method_env, '\0', sizeof(method_env));  
        memset(query_env, '\0', sizeof(query_env));  
        memset(content_len_env, '\0', sizeof(content_len_env));  

        close(cgi_input[1]);  
        close(cgi_output[0]);  
        //redir  
        dup2(cgi_input[0], 0);  
        dup2(cgi_output[1], 1);  

        sprintf(method_env, "REQUEST_METHOD=%s", method);  
        putenv(method_env);  
        if(strcasecmp("GET", method) == 0){//POST  
            sprintf(query_env, "QUERY_STRING=%s", query_string);  
            putenv(query_env);  
        }else{//POST  
            sprintf(content_len_env, "CONTENT_LENGTH=%d", content_length);  
            putenv(content_len_env);  
        }  

        execl(path, path, NULL);//exec函数族  
        exit(1);  
    }else{//father  
        close(cgi_input[0]);  
        close(cgi_output[1]);  
        int i = 0;  
        char c = '\0';  
        if(strcasecmp("POST", method) == 0){  
            for(; i < content_length; i++ ){  
                recv(sock_client, &c, 1, 0);  
                write(cgi_input[1], &c, 1);  
            }  
        }  
        while( read(cgi_output[0], &c, 1) > 0 ){  
            send(sock_client, &c, 1, 0);  
        }  
        close(cgi_input[1]);  
        close(cgi_output[0]);  

        waitpid(id, NULL, 0);  
    }  
}  

//GET && POST  
void *accept_request(void *arg)//这个arg实际上是由socket强转过来的那个参数  
{  

    print_debug("get a new connect...\n");  
    pthread_detach(pthread_self());//detach //let the thread free automaticaly  
    int sock_client = (int)arg;//将客户端套接字恢复回来  
    //for test  
    //echo_error_to_client(sock_client, 400);  
    //close(sock_client);  
    //return;  

    int  cgi = 0;  
    char *query_string = NULL;  
    char method[_COMM_SIZE_/10];  
    char url[_COMM_SIZE_];  
    char buffer[_COMM_SIZE_];  
    char path[_COMM_SIZE_];  
    memset(method, '\0', sizeof(method));  
    memset(url, '\0', sizeof(url));  
    memset(buffer, '\0', sizeof(buffer));  
    memset(path, '\0', sizeof(path));  

//#ifdef _DEBUG_  
//  //success  > 0  
//  //else <= 0  
//  while(get_line(sock_client, buffer, sizeof(buffer)) > 0){  
//      printf("%s", buffer);  
//      fflush(stdout);  
//  }  
//  printf("\n");  
//#endif  

    if(get_line(sock_client, buffer, sizeof(buffer)) < 0){//从客户端一行一行读取数据  
        //echo_error_to_client();  
        return NULL;  
    }  

    int i = 0;   
    int j = 0;//buffer line index  
    while( !isspace(buffer[j]) &&\//从消息中获取方法保存到method  
            i < sizeof(method)-1 &&\  
            j < sizeof(buffer)){  
        method[i] = buffer[j];  
        i++, j++;  
    }  

    if( strcasecmp(method, "GET") && strcasecmp(method, "POST")){  
        //echo_error_to_client();  
        return NULL;  
    }  

    //clear space point useful url start  
    while( isspace(buffer[j]) &&\//如果发现是空行就跳到下一行,指向url开始的地方  
            j < sizeof(buffer)){  
        j++;  
    }  

    //get url  
    i = 0;  
    while(!isspace(buffer[j]) &&\//获取url  
            i < sizeof(url)-1 &&\  
            j < sizeof(buffer)){  
        url[i] = buffer[j];  
        i++;j++;  
    }  

    print_debug(method);//打印方法和路径  
    print_debug(url);  

    if(strcasecmp(method, "POST") == 0){//如果是POST方法,就让cgi=1,是不是POST方法就直接可用呢  
        cgi = 1;  
    }  

    if(strcasecmp(method, "GET") == 0){//那我们就可以稍微处理一下啦  
        query_string = url;  
        while( *query_string != '?' && *query_string != '\0'){  
            query_string++;  
        }  

        if( *query_string == '?' ){//url = /XXX/XXX + arg  
            *query_string = '\0';  
            query_string++;  
            cgi = 1;  
        }  
    }  

    sprintf(path, "htdocs%s", url);//将url所在文件包装成路径保存在path中  

    if(path[strlen(path)-1] == '/'){  
        strcat(path, MAIN_PAGE);//最后在附上主页就好了  
    }  

    print_debug(path);  

    struct stat st;  
    if( stat(path, &st) < 0 ){ //failed, does not exist  // stat()用来将参数path 所指的文件状态, 复制到参数st所指的结构中  
        print_debug("miss cgi");  
        clear_header(sock_client);  
        //echo_error_to_client();  
    }else{//file exist!  
        if(S_ISDIR(st.st_mode)){//如果是个目录文件追加/  
            strcat(path, "/");  
            strcat(path, MAIN_PAGE);  
        }else if(st.st_mode & S_IXUSR ||\  
                 st.st_mode & S_IXGRP ||\  
                 st.st_mode & S_IXOTH){   //文件S_IRUSR (S_IREAD) 00400 文件所有者具可读取权限  
                                         //S_IWUSR (S_IWRITE)00200 文件所有者具可写入权限  
                                        //S_IXUSR (S_IEXEC) 00100 文件所有者具可执行权限  
            cgi = 1;  
        }else{  
            //do nothing  
        }  
        if(cgi){  
            exe_cgi(sock_client, path, method, query_string);  
        }else{  
            clear_header(sock_client);  
            print_debug("begin enter our echo_html");  
            echo_html(sock_client, path, st.st_size);  
        }  
    }  
    close(sock_client);  
    return NULL;  
}  

//if success return sock  
//else exit process  
int start(short port)  
{  
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);  
    if(listen_sock == -1){  
        print_log(__FUNCTION__, __LINE__, errno, strerror(errno));  
        exit(1);  
    }  

    //reuse port//定义一个地址空间  
    int flag = 1;  
    setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));  

    struct sockaddr_in local;  
    local.sin_family = AF_INET;  
    local.sin_port   = htons(port);//host -> net  
    local.sin_addr.s_addr   = htonl(INADDR_ANY);  
    socklen_t len = sizeof(local);  
//将地址空间和定义的套接字绑定在一起  
    if(bind(listen_sock, (struct sockaddr*)&local, len) == -1){  
        print_log(__FUNCTION__, __LINE__, errno, strerror(errno));  
        exit(2);  
    }  
//不断监听处理套接字的请求  
    if(listen(listen_sock, _BACK_LOG_) == -1){  
        print_log(__FUNCTION__, __LINE__, errno, strerror(errno));  
        exit(3);  
    }  
//监听成功后返回监听套接字  
    return listen_sock; //sucess  
}  

//./httpd port  
int main(int argc, char *argv[])  
{  
    if(argc != 2){  
        usage(argv[0]);  
        exit(1);  
    }  
    //daemon();  
    int port = atoi(argv[1]);  
    int sock = start(port);//listen socket  

    struct sockaddr_in client;  
    socklen_t len = 0;  
    while(1){  
        int new_sock = accept(sock, (struct sockaddr*)&client, &len);//这个返回的新的套接字称为链接套接字,不同客户端的请求返回的  
                                                                //新socket自然不一样  
        if( new_sock < 0 ){//accept error  
            print_log(__FUNCTION__, __LINE__, errno, strerror(errno));  
            continue;  
        }  
        pthread_t new_thread;//一旦有请求就定义一个新的线程去解决问题  
        pthread_create(&new_thread, NULL, accept_request, (void*)new_sock);//转成void*后变成参数传给accept_request  
    }  
    return 0;  
} 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值