C实现的小型的httpserver

本文介绍了一个简易HTTP服务器的实现过程,包括初始化套接字、处理客户端请求、发送响应头及内容等关键步骤。服务器能够解析HTTP请求,支持GET和POST方法,并能根据不同文件类型返回相应的Content-Type。
/*******************************************************
    > File Name: test.c
    > Author: hsz
    > Mail:  
    > Created Time: Fri 11 Oct 2019 09:42:44 PM CST
 ******************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <error.h>
#include <stdint.h>
#include <stdbool.h>
#include <unistd.h> // the fellowing is linux header
#include <pthread.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define DEBUG
#define SERVER_STRING "Server: hszhttpd/1.0\r\n"
#define ROOT "/home/hsz/www/html"
#define isSpace(character) ((character == ' ') ? 1 : 0)

#ifdef DEBUG
#define ALOGD(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define ALOGD(fmt, ...) \
	do{} while (0)
#endif
#define ALOGI(fmt, ...) printf(fmt, ##__VA_ARGS__)

int initSocket(uint16_t port);
void header(const int client, const char *type, const size_t length);
void notFoundHeader(int client, const char *type, const size_t length);
int getLine(const char *buf, char *dst, const uint16_t dstSize);
void getFileType(const char *src, char *fileType, uint32_t fileTypeBufSize);
void getContentType(const char *src, char *contentType, uint32_t contentTypeBufSize);
void process(int client);
void acceptRequest(void *val);
void doResponse(const int client, const char *recvBuf, const char *method, const char *url);
bool strCaseCmp(const char *str1, const char *str2);
void upper(char *str);
void sendHtml(const int client, const char *filePath);

int main()
{
    uint16_t port = 80;
    int httpd = initSocket(port);
    if (httpd < 0)
    {
        return 1;
    }
    int client = 0;
    struct sockaddr_in addr;
    socklen_t addrSize = sizeof(struct sockaddr_in);
    ALOGI("waiting for client...\n");
    while (1)
    {
        client = accept(httpd, (struct sockaddr *)&addr, &addrSize);
        if (client > 0)
        {
            ALOGI("[%s:%u] connected\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
            process(client);
        }
    }

    return 0;
}

void acceptRequest(void *val)
{
    int client = (int)(intptr_t)val;
    char recvBuf[1024];
    int recvBufSize = sizeof(recvBuf);
    while (1)
    {
        memset(recvBuf, 0, recvBufSize);
        int length = recv(client, recvBuf, recvBufSize, 0);
        if (length == 0)
        {
            ALOGI("client disconnected...\n");
            break;
        }
        if (length < 0)
        {
            ALOGD("recv error: %s\n", strerror(errno));
            break;
        }
        char line[512] = {0};
        char url[256] = {0};
        char method[16] = {0};
        getLine(recvBuf, line, sizeof(line) - 1);
        int i = 0, j = 0;
        int lineSize = strlen(line);
        for (; i < lineSize; ++i)
        {
            if (isSpace(line[i]))
            {
                method[i] = '\0';
                break;
            }
            method[i] = line[i];
        }
        ++i;
        strcpy(url, ROOT);
        j += strlen(ROOT);
        for (; i < lineSize; ++i, ++j)
        {
            if (isSpace(line[i]))
            {
                url[j] = '\0';
                break;
            }
            url[j] = line[i];
        }
        ALOGD("method: %s, url: \"%s\"\n", method, url);
        doResponse(client, recvBuf, method, url);
    }
    ALOGD("--------------------------------------\n");
}

void doResponse(const int client, const char *recvBuf, const char *method, const char *url)
{
    if (strCaseCmp(method, "POST"))
    {
        char *dataStartPos = strstr(recvBuf, "\r\n\r\n");
        dataStartPos += strlen("\r\n\r\n") + 1;
        ALOGD("%s() data: %s\n", __func__, dataStartPos);
        // 处理数据
        if (strstr(url, "login"))
        {
        }
    }
    if (strCaseCmp(method, "GET"))
    {
        if (strlen(url) < 20) // only /home/hsz/www/html/
        {
            sendHtml(client, "/home/hsz/www/html/login.html");
            return;
        }
        sendHtml(client, url);
    }
}

void sendHtml(const int client, const char *filePath)
{
    char buf[1024];
    char contentType[128] = {0};
    char fileType[32] = {0};
    getFileType(filePath, fileType, sizeof(fileType));
    getContentType(fileType, contentType, sizeof(contentType));

    FILE *fp = fopen(filePath, "r");
    if(fp == NULL)
    {
        perror("fopen error");
        FILE *fp404 = fopen("/home/hsz/www/html/404.html", "r");
        if(fp404 == NULL)
        {
            ALOGD("connot found 404.html\n");
            char *notFoundPage = "<html>"
                "<TITLE>Not Found</TITLE>"
                "<body>"
                "<P>The server could not fulfill your request because the resource is unavailable.</P>"
                "</body></html>";
            notFoundHeader(client, "text/html", strlen(notFoundPage));
            send(client, notFoundPage, strlen(notFoundPage), 0);
            return;
        }
        fseek(fp404, 0, SEEK_END);
        size_t length = ftell(fp404);
        fseek(fp404, 0, SEEK_SET);
        notFoundHeader(client, "text/html", length);
        while (!feof(fp404))
        {
            memset(buf, 0, sizeof(buf));
            size_t readSize = fread(buf, sizeof(buf[0]), sizeof(buf), fp404);
            send(client, buf, readSize, 0);
        }
        fclose(fp404);
        return;
    }
    fseek(fp, 0, SEEK_END);
    size_t length = ftell(fp);
    fseek(fp, 0, SEEK_SET);
    
    header(client, contentType, length);

    while (!feof(fp))
    {
        memset(buf, 0, sizeof(buf));
        size_t readSize = fread(buf, sizeof(buf[0]), sizeof(buf), fp);
        send(client, buf, readSize, 0);
    }
    fclose(fp);
}

void getFileType(const char *src, char *fileType, uint32_t fileTypeBufSize)
{
    size_t size = strlen(src);
    size_t index = size;
    for(size_t i = size - 1; i > 0; --i)
    {
        if(src[i] == '.')
        {
            index = i + 1;
            break;
        }
    }
    if(strlen(src + index) < fileTypeBufSize)
    {
        strcpy(fileType, src + index);
    }
}
void getContentType(const char *src, char *contentType, uint32_t contentTypeBufSize)
{
    if(strCaseCmp(src, "html"))
    {
        snprintf(contentType, contentTypeBufSize, "text/html");
        return;
    }
    if(strCaseCmp(src, "css"))
    {
        snprintf(contentType, contentTypeBufSize, "text/css");
        return;
    }
    if(strCaseCmp(src, "js"))
    {
        snprintf(contentType, contentTypeBufSize, "application/x-javascript");
        return;
    }
    if(strCaseCmp(src, "ico"))
    {
        snprintf(contentType, contentTypeBufSize, "image/x-icon");
        return;
    }
    if(strCaseCmp(src, "jpg") || strCaseCmp(src, "jpeg"))
    {
        snprintf(contentType, contentTypeBufSize, "image/jpeg");
        return;
    }
    if(strCaseCmp(src, "png"))
    {
        snprintf(contentType, contentTypeBufSize, "image/png");
        return;
    }
    ALOGI("unknown type \"%s\"\n", src);
}

bool strCaseCmp(const char *str1, const char *str2)
{
    size_t size1 = strlen(str1);
    size_t size2 = strlen(str2);
    if (size1 != size2)
    {
        return false;
    }
    char *tmp1 = (char *)malloc(size1 + 1);
    char *tmp2 = (char *)malloc(size1 + 1);

    strcpy(tmp1, str1);
    strcpy(tmp2, str2);

    upper(tmp1);
    upper(tmp2);
    bool index = true;
    for (size_t i = 0; i < size1; ++i)
    {
        if (tmp1[i] != tmp2[i])
        {
            index = false;
            break;
        }
    }
    free(tmp1);
    free(tmp2);
    return index;
}

void upper(char *str)
{
    size_t size = strlen(str);
    for (size_t i = 0; i < size; ++i)
    {
        if ('a' <= str[i] && str[i] <= 'z')
        {
            str[i] = str[i] - 'a' + 'A';
        }
    }
}

void process(int client)
{
    pthread_t pid;
    pthread_attr_t attrDetach;
    pthread_attr_init(&attrDetach);
    pthread_attr_setdetachstate(&attrDetach, PTHREAD_CREATE_DETACHED);
    pthread_create(&pid, &attrDetach, (void *)acceptRequest, (void *)(intptr_t)client);
}

int initSocket(uint16_t port)
{
    int httpd = 0;
    struct sockaddr_in srvAddr;

    httpd = socket(AF_INET, SOCK_STREAM, 0);
    if (httpd < 0)
    {
        perror("socket error");
        return -1;
    }
    memset(&srvAddr, 0, sizeof(srvAddr));
    srvAddr.sin_family = AF_INET;
    srvAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    srvAddr.sin_port = htons(port);

    int value = 1;

    if (setsockopt(httpd, SOL_SOCKET, SO_REUSEADDR, (const void *)&value, sizeof(int)) < 0)
    {
        perror("setsockopt error");
        return -1;
    }

    if (bind(httpd, (struct sockaddr *)&srvAddr, sizeof(srvAddr)) < 0)
    {
        perror("bind error");
        return -1;
    }

    if (listen(httpd, 16) < 0)
    {
        perror("listen error");
        return -1;
    }
    return httpd;
}
void header(const int client, const char *type, const size_t length)
{
    if (client <= 0 || !type || length <= 0)
    {
        ALOGD("check your param\n");
        return;
    }
    char buf[1024] = {0};
    sprintf(buf, "HTTP/1.1 200 OK\r\n"
                 "%s"
                 "Content-Length: %zu\r\n"
                 "Content-Type: %s\r\n\r\n",
            SERVER_STRING, length, type);
    send(client, buf, strlen(buf), 0);
}

void notFoundHeader(int client, const char *type, const size_t length)
{
    if (client <= 0 || !type || length <= 0)
    {
        ALOGD("check your param\n");
        return;
    }
    char buf[1024] = {0};
    sprintf(buf, "HTTP/1.1 404 NOT FOUND\r\n"
                 "%s"
                 "Content-Length: %zu\r\n"
                 "Content-Type: %s\r\n\r\n",
            SERVER_STRING, length, type);
    send(client, buf, strlen(buf), 0);
}

int getLine(const char *buf, char *dst, const uint16_t dstSize)
{
    if (!buf || !dst || dstSize <= 0)
    {
        ALOGD("check your param\n");
        return -1;
    }
    int bufSize = strlen(buf);
    int i;
    for (i = 0; i < bufSize && i < dstSize; ++i)
    {
        if (buf[i] == '\r' && buf[i + 1] == '\n')
        {
            dst[i] = '\0';
            break;
        }
        dst[i] = buf[i];
    }
    return i;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值