Tinyhttpd

#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>
#include <strings.h>
#include <string.h>
#include <sys/stat.h>
#include <pthread.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <stdint.h>
#include <assert.h>

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

#define SERVER_STRING "Server: xxrlhttpd/0.1.0
"
#define STDIN	0
#define STDOUT	1
#define STDERR	2
#define String  const char *

const int BUF_SIZE = 1024;
const int CONTENT_SIZE = 255;

// 头信息
struct HeaderInfo
{
    int length;
    String *head_str;
};

void accept_request(void *);
void headers(int, struct HeaderInfo *);
void cat(int, FILE *);
void error_die(String);
void execute_cgi(int, String, String , String);
int get_line(int, char *, int);
void serve_file(int, String);
int startup(u_short *);

struct HeaderInfo accpetInfo, notfoundInfo, badrequestInfo, unimplementedInfo, cannotexecuteInfo;

/**
 * 初始化头信息
*/
void init_headers() {
    accpetInfo.head_str = (String*)malloc(sizeof(String) * 4);
    accpetInfo.head_str[0] = "HTTP/1.0 200 OK
";
    accpetInfo.head_str[1] = SERVER_STRING;
    accpetInfo.head_str[2] = "Content-Type: text/html
";
    accpetInfo.head_str[3] = "
";
    accpetInfo.length = 4;

    notfoundInfo.head_str = (String *)malloc(sizeof(String) * 9);
    notfoundInfo.head_str[0] = "HTTP/1.0 404 NOT FOUND
";
    notfoundInfo.head_str[1] = SERVER_STRING;
    notfoundInfo.head_str[2] = "Content-Type: text/html
";
    notfoundInfo.head_str[3] = "
";
    notfoundInfo.head_str[4] = "<HTML><TITLE>Not Found</TITLE>
";
    notfoundInfo.head_str[5] = "<BODY><P>The server could not fulfill
";
    notfoundInfo.head_str[6] = "your request because the resorce specified
";
    notfoundInfo.head_str[7] = "is unavailable or nonexistent.</P>
";
    notfoundInfo.head_str[8] = "</BODY></HTML>
";
    notfoundInfo.length = 9;

    badrequestInfo.head_str = (String *)malloc(sizeof(String) * 6);
    badrequestInfo.head_str[0] = "HTTP/1.0 400 BAD REQUEST
";
    badrequestInfo.head_str[1] = SERVER_STRING;
    badrequestInfo.head_str[2] = "Content-type: text/html
";
    badrequestInfo.head_str[3] = "
";
    badrequestInfo.head_str[4] = "<P>Your browser send a bad request, ";
    badrequestInfo.head_str[5] = "such as a POST without a Content-Length.</P>
";
    badrequestInfo.length = 6;

    unimplementedInfo.head_str = (String *)malloc(sizeof(String) * 6);
    unimplementedInfo.head_str[0] = "HTTP/1.0 501 Method Not Implemented
";
    unimplementedInfo.head_str[1] = SERVER_STRING;
    unimplementedInfo.head_str[2] = "Content-Type: text/html
";
    unimplementedInfo.head_str[3] = "
";
    unimplementedInfo.head_str[4] = "<HTML><HEAD><TITLE>Method Not Implemented
";
    unimplementedInfo.head_str[5] = "</BODY></HTML>
";
    unimplementedInfo.length = 6;

    cannotexecuteInfo.head_str = (String *)malloc(sizeof(String) * 4);
    cannotexecuteInfo.head_str[0] = "HTTP/1.0 500 Internal Server Error
";
    cannotexecuteInfo.head_str[1] = "Content-Type: text/html
";
    cannotexecuteInfo.head_str[2] = "
";
    cannotexecuteInfo.head_str[3] = "<P>Error prohibited CGI execution.</P>
";
    cannotexecuteInfo.length = 4;
}

// 日志文件
FILE *logFile;

/**
 * 处理一次请求
 * 
*/
void accept_request(void *arg)
{
	int client = (intptr_t)arg;
	char buf[BUF_SIZE];
	size_t numchars;
    char method[CONTENT_SIZE];
    char url[CONTENT_SIZE];
    char path[512];
	size_t i, j;
	struct stat st;
	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))
	{
		method[i] = buf[i];
		i++;
	}
	j = i;
	method[i] = '';

	if (strcasecmp(method, "GET") && strcasecmp(method, "POST"))
	{
		headers(client, &unimplementedInfo);
		return;
	}

	if (strcasecmp(method, "POST") == 0)
		cgi = 1;

	i = 0;
	while (ISspace(buf[j]) && (j < numchars))
		j++;
	while (!ISspace(buf[j]) && (i < sizeof(url) - 1) && (j < numchars))
	{
		url[i] = buf[j];
		i++, j++;
	}
	url[i] = '';

	if (strcasecmp(method, "GET") == 0)
	{
		query_string = url;
		while ((*query_string != '?') && (*query_string != ''))
			query_string++;
		if (*query_string == '?')
		{
			cgi = 1;
			*query_string = '';
			query_string++;
		}
	}

	sprintf(path, "htpdocs%s", url);
	if (path[strlen(path) - 1] == '/')
		strcat(path, "index.html");
	if (stat(path, &st) == -1) {
		while ((numchars > 0) && strcmp("
", buf))
			numchars = get_line(client, buf, sizeof(buf));
		headers(client, &notfoundInfo);
	}
	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);
	}

    fprintf(logFile, "Accept a request, path: %s	 query:%s
", path, query_string);
    fflush(logFile);

    close(client);
}

/**
 * 处理cgi请求
 * @param:
 *  client: 客户端 描述符
 *  path: 请求的路径
 *  method: 请求的方法
 *  query_string: 参数字符串
*/
void execute_cgi(int client, String path, String method, String query_string)
{
    char buf[BUF_SIZE];
    int cgi_output[2];
    int cgi_input[2];
    pid_t pid;
    int status;
    int i;
    char c;
    int numchars = 1;
    int content_length = -1;

    buf[0] = 'A';
    buf[1] = '';
    if (strcasecmp(method, "GET") == 0)
        while ((numchars > 0) && strcmp("
", buf))
            numchars = get_line(client, buf, sizeof(buf));
    else if (strcasecmp(method, "POST") == 0)
    {
        numchars = get_line(client, buf, sizeof(buf));
        while ((numchars > 0) && strcmp("
", buf))
        {
            buf[15] = '';
            if (strcasecmp(buf, "Content-Length:") == 0)
                content_length = atoi(&(buf[16]));
            numchars = get_line(client, buf, sizeof(buf));
        }
        if (content_length == -1)
        {
            headers(client, &badrequestInfo);
            return;
        }
    }
    else
    {
        // other request
    }

    // 创建管道和子进程
    if (pipe(cgi_output) < 0 || pipe(cgi_input) < 0 || (pid = fork()) < 0)
    {
        headers(client, &cannotexecuteInfo);
        return;
    }

    sprintf(buf, "HTTP/1.0 200 OK
");
    send(client, buf, strlen(buf), 0);
    if (pid == 0) /*child: CGI script*/
    {
        char meth_env[CONTENT_SIZE];
        char query_env[CONTENT_SIZE];
        char length_env[CONTENT_SIZE];
        // 重定向标准输入输出, 子进程在output向父进程输出, 在input获取输入
        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, NULL);
        exit(0);
    }
    else
    { /* parent */
        // 父进程 input向子进程输出, 在output获取输入
        close(cgi_output[1]);
        close(cgi_input[0]);
        if (strcasecmp(method, "POST") == 0)
            // 如果是post, 就向子进程写入长度为 content_length的数据 
            for (i = 0; i < content_length; ++i)
            {
                recv(client, &c, 1, 0);
                write(cgi_input[1], &c, 1);
            }
        // 读取子进程的输出, 发送给socket
        while (read(cgi_output[0], &c, 1))
            send(client, &c, 1, 0);

        // io完毕关闭描述符,等待子进程退出
        close(cgi_output[0]);
        close(cgi_input[1]);
        waitpid(pid, &status, 0);
    }
}

/**
 * 响应文件
 * @param:
 *  client: 客户端 描述符
 *  filename: 文件路径
*/
void serve_file(int client, String filename)
{
    FILE *resource = NULL;
    int numchars = 1;
    char buf[BUF_SIZE];

    // 获取 client 的数据
    buf[0] = 'A';
    buf[1] = '';
    while ((numchars > 0) && strcmp("
", buf))
        numchars = get_line(client, buf, sizeof(buf));

    // 向流中写入 文件
    resource = fopen(filename, "r");
    if (resource == NULL)
        headers(client, &notfoundInfo);
    else
    {
        headers(client, &accpetInfo);
        cat(client, resource);
    }
    fclose(resource);
}

/**
 * 将文件中的数据传送到流中
 * @param:
 *  client: 客户端 描述符
 *  resource: 文件
*/
void cat(int client, FILE* resource)
{
    char buf[BUF_SIZE];

    fgets(buf, sizeof(buf), resource);
    while (!feof(resource))
    {
        send(client, buf, strlen(buf), 0);
        fgets(buf, sizeof(buf), resource);
    }
}

/**
 * 出错后失败退出
 * @param:
 *  sc: 错误信息
*/
void error_die(String sc)
{
    perror(sc);
    exit(1);
}

/**
 * sock 流中获取一行
 * @param:
 *  sock: sockfd 描述符
 *  buf: 缓冲流
 *  size: 最长读取长度
 * @return:
 *  读取到的字节数
*/
int get_line(int sock, char *buf, int size)
{
    int i = 0;
    char c = '';
    int n;

    while ((i < size - 1) && (c != '
'))
    {
        n = recv(sock, &c, 1, 0);
        if (n > 0)
        {
            // 
 or 
 => 

            if (c == '
')
            {
                n = recv(sock, &c, 1, MSG_PEEK);
                if ((n > 0) && (c == '
'))
                    recv(sock, &c, 1, 0);
                else
                    c = '
';
            }
            buf[i] = c;
            i++;
        }
        else
            break;
    }
    buf[i] = '';

    return i;
}

/**
 * 根据headerInfo 写入header信息
 * @param:
 *  client: 客户端 描述符
 *  headerInfo: 响应的头信息
 * 
*/
void headers(int client, struct HeaderInfo *headerInfo)
{
    char buf[BUF_SIZE];

    int i = 0;
    for (i = 0; i < headerInfo->length; ++i)
    {
        sprintf(buf, headerInfo->head_str[i]);
        send(client, buf, strlen(buf), 0);
    }
}

/**
 * 创建socket 绑定到指定端口
 * @param:
 *  port 端口号
 * @return:
 *  socket 描述符
*/
int startup(u_short *port)
{
    int httpd = 0;
    int on = 1;
    struct sockaddr_in name;

    httpd = socket(PF_INET, SOCK_STREAM, 0);
    if (httpd == -1)
        error_die("socket");
    memset(&name, 0, sizeof(name));
    name.sin_family = AF_INET;
    name.sin_port = htons(*port);
    name.sin_addr.s_addr = htonl(INADDR_ANY);
    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);
    }
    if (listen(httpd, 5) < 0)
        error_die("listen");
    return httpd;
}

int main(void)
{
    int server_sock = -1;
    u_short port = 4000;
    int client_sock = -1;
    struct sockaddr_in client_name;
    socklen_t client_name_len = sizeof(client_name);
    pthread_t newthread;

    //prepare to start
    init_headers();
    server_sock = startup(&port);
    printf("httpd running on port %d
", port);
    logFile = fopen("log", "w");

    // main loop
    while (1)
    {
        // accpet a new request
        client_sock = accept(server_sock,
            (struct sockaddr *)&client_name,
            &client_name_len);
        if (client_sock == -1)
            error_die("accept");
        // send to a new thread
        if (pthread_create(&newthread, NULL, (void *)accept_request, (void *)(intptr_t)client_sock) != 0)
            perror("pthread_create");
    }
    // will never reach
    fclose(logFile);
    close(server_sock);

    printf("Program exit.");
    return 0;
}

more detail in

原文地址:https://www.cnblogs.com/xxrlz/p/13550736.html