Web服务器未收到所有请求

时间:2018-12-02 18:12:49

标签: c sockets network-programming webserver

我有一个用C编写的简单Web服务器,当向浏览器提供带有一些图像的小型HTML文件时,该服务器可以正常工作。当我尝试为更复杂的网站提供更多具有不同内容类型的对象(如css和js文件)的网站时,我发现我没有收到正确加载index.html所需的许多对象的请求-浏览器一直在等待无限期地为主机服务。如果我刷新页面两次,最终所有内容都可以正确加载,并且可以跟踪超链接。我注意到的另一件事是,通常它们是相同的文件,不会发送回浏览器。

#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdbool.h>
#include <pthread.h>

bool writeDataToClient(int sckt, const void *data, int datalen)
{
    const char *pdata = (const char*) data;

    while (datalen > 0){
        int numSent = send(sckt, pdata, datalen, 0);
        if (numSent <= 0){
            if (numSent == 0){
                printf("The client was not written to: disconnected\n");
            } else {
                perror("The client was not written to");
            }
            return false;
        }
        pdata += numSent;
        datalen -= numSent;
    }

    return true;
}

bool writeStrToClient(int sckt, const char *str)
{
    return writeDataToClient(sckt, str, strlen(str));
}

int get_filename_and_method(char *str, char **buf1, char **buf2)
{   
    char *request = str;
    char *status_line;
    char *url;
    char *token = strtok(request, "\r\n");
    status_line = token;

    *buf1 = strtok(status_line, " ");
    if (strcasecmp(*buf1, "GET") != 0) return -1;

    url = strtok(NULL, " ");
    if (strncmp(url, "/", strlen("/")) != 0) return -1;

    if (strlen(url) == 1) strcat(url, "index.html");
    if (url[strlen(url) - 1] == '/') strcat(url, "index.html");

    char *tmp = strdup(url);
    strcpy(url, "web");
    strcat(url, tmp);
    *buf2 = url;

    free(tmp);

    return 0;
}

int get_connection_type(char *str, char **buf)
{   
    char *req = str;
    char *token = strtok(req, "\r\n");
    char *connection;

    while (token != NULL)
    {   

        if (strncmp(token, "Connection:", 11) == 0)
        {   
            connection = token;
            strtok(connection, " ");
            if (strcasecmp(strtok(NULL, " "), "Keep-Alive") == 0)
            {   
                *buf = "Connection: keep-alive\r\n\r\n";
                return 0;
            }
        }

        token = strtok(NULL, "\r\n");
    }

    *buf = "Connection: close\r\n\r\n";
    return 0;
}

void *connection_handler (void *sockfd)
{
    // Connection handler
    int sock = *(int*)sockfd;
    char *buffer, *method, *filename, *connection_type, *content_type;
    int bufsize = 2048;

    const char *HTTP_404_CONTENT = "<html><head><title>404 Not "
    "Found</title></head><body><h1>404 Not Found</h1>The requested "
    "resource could not be found but may be available again in the "
    "future."</body></html>";

    const char *HTTP_501_CONTENT = "<html><head><title>501 Not "
    "Implemented</title></head><body><h1>501 Not Implemented</h1>The "
    "server either does not recognise the request method, or it lacks "
    "the ability to fulfill the request.</body></html>";

    buffer = (char*) malloc(bufsize);    
    if (!buffer){
        printf("The receive buffer was not allocated\n");
        exit(1);    
    }

    while (1)
    {
        int numRead = recv(sock, buffer, bufsize, 0);
        if (numRead < 1){
            if (numRead == 0){
                printf("The client was not read from: disconnected\n");
                break;
            } else {
                perror("The client was not read from");
                break;
            }
            close(sock);
            continue;
        }
        printf("%.*s\n", numRead, buffer);

        // Extract info from request header
        get_connection_type(buffer, &connection_type);
        if (get_filename_and_method(buffer, &method, &filename) == -1)
        {
            char clen[40];
            writeStrToClient(sock, "HTTP/1.1 501 Not Implemented\r\n");
            sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_501_CONTENT));
            writeStrToClient(sock, clen);
            writeStrToClient(sock, "Content-Type: text/html\r\n");
            writeStrToClient(sock, connection_type);
            writeStrToClient(sock, HTTP_501_CONTENT);
        }
        else
        {

            // Open and read file
            long fsize;
            FILE *fp = fopen(filename, "rb");
            if (!fp){
                perror("The file was not opened");
                char clen[40];
                writeStrToClient(sock, "HTTP/1.1 404 Not Found\r\n");
                sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_404_CONTENT));
                writeStrToClient(sock, clen);
                writeStrToClient(sock, "Content-Type: text/html\r\n");
                writeStrToClient(sock, connection_type);
                writeStrToClient(sock, HTTP_404_CONTENT);

                if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
                    break;

                continue;    
            }

            printf("The file was opened\n");

            if (fseek(fp, 0, SEEK_END) == -1){
                perror("The file was not seeked");
                exit(1);
            }

            fsize = ftell(fp);
            if (fsize == -1) {
                perror("The file size was not retrieved");
                exit(1);
            }
            rewind(fp);

            char *msg = (char*) malloc(fsize);
            if (!msg){
                perror("The file buffer was not allocated\n");
                exit(1);
            }

            if (fread(msg, fsize, 1, fp) != 1){
                perror("The file was not read\n");
                exit(1);
            }
            fclose(fp);

            // Get extension of filename
            char *ext = strrchr(filename, '.');
            if (ext != NULL)
                ext++;
            if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0)
                content_type = "Content-Type: text/html\r\n";
            else if (strcmp(ext, "css") == 0)
                content_type = "Content-Type: text/css\r\n";
            else if (strcmp(ext, "jpg") == 0)
                content_type = "Content-Type: image/jpeg\r\n";
            else if (strcmp(ext, "png") == 0)
                content_type = "Content-Type: image/png\r\n";
            else if (strcmp(ext, "gif") == 0)
                content_type = "Content-Type: image/gif\r\n";
            else
                content_type = "Content-Type: text/plain\r\n";


            if (!writeStrToClient(sock, "HTTP/1.1 200 OK\r\n")){
                close(sock);
                continue;
            }
            char clen[40];

            sprintf(clen, "Content-length: %ld\r\n", fsize);

            if (!writeStrToClient(sock, clen)){
                printf("Cannot write content length\n");
                close(sock);
                continue;
            }


            if (!writeStrToClient(sock, content_type)){
                close(sock);
                continue;
            }



            if (!writeStrToClient(sock, connection_type) == -1){
                close(sock);
                continue;
            }



            if (!writeDataToClient(sock, msg, fsize)){
                close(sock);
                continue;
            }

            printf("The file was sent successfully\n");
        }

        if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
            break;
    }

    close(sock);
    pthread_exit(0);
}

int main(int argc, char *argv[]){
    int create_socket, new_socket;    
    struct sockaddr_in address;    
    socklen_t addrlen;    
    char *ptr;

    if (argc != 2)
    {
        printf("Usage: %s <port number>\n", argv[0]);
        exit(0);
    }

    create_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (create_socket == -1){    
        perror("The socket was not created");    
        exit(1);    
    }

    printf("The socket was created\n");

    const unsigned short port = (unsigned short) strtol(argv[1], &ptr, 10);

    memset(&address, 0, sizeof(address));    
    address.sin_family = AF_INET;    
    address.sin_addr.s_addr = INADDR_ANY;    
    address.sin_port = htons(port);    

    if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == -1){    
        printf("The socket was not bound because that port is not available\n");    
        exit(1);    
    }

    printf("The socket is bound\n");    

    if (listen(create_socket, 10) == -1){
        perror("The socket was not opened for listening");    
        exit(1);    
    }    

    printf("The socket is listening\n");

    while (1) {    

        addrlen = sizeof(address);
        pthread_t tid;
        new_socket = accept(create_socket, (struct sockaddr *) &address, &addrlen);

        if (new_socket == -1) {    
            perror("A client was not accepted");    
            exit(1);    
        }    

        printf("A client is connected from %s:%hu...\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));

        if (pthread_create(&tid, NULL, connection_handler, (void *)&new_socket) < 0)
        {
            perror("Could not create thread");
            return 1;
        }

        pthread_join(tid, NULL);
   }

   if (new_socket < 0)
   {
    perror("accept failed");
    return 1;
   }    

   close(create_socket);
   printf("Socket was closed\n");
   return 0;    
}

此外,关闭浏览器(与服务器断开连接)会导致另一个连接被接受,该连接将发送浏览器请求但未收到的第一个文件,然后服务器程序结束而没有任何错误消息。

更新:删除pthread_join允许页面正确加载。正如用户提到的那样,浏览器并行执行几个连接,所以我认为正在发生的是所有请求都通过多个连接发送(从我程序的输出看,到服务器有5个连接)。当pthread_join等待一个线程(连接)完成时,一次只处理一个连接,这就是为什么我没有收到所有请求的原因。

1 个答案:

答案 0 :(得分:0)

HTTP可能比您想象的还要复杂。您是否已完整阅读其规范(对于HTTP 1.1为RFC 7230)或{ {3}}关于HTTP?您是否考虑过使用某些HTTP服务器库,例如some booklibonion(或libhttp或其他)?这些库的大小说明了HTTP的复杂性!这些库是libmicrohttpd,因此您可以研究它们的源代码并从中获得启发。 (如果您诚实地告诉他,您学习过libonion的源代码并已阅读free software,您的老师应该会感到高兴。)

顺便说一句,现代浏览器(最近的Firefox或Chrome等)往往会同时使用几个连接来显示一个页面。现代浏览器能够向您显示实际的HTTP流量和网络协议。

我的建议是使用一些现有的库。我很高兴使用libonion,即使它有一些限制。

最后,阅读RFC 7230 。启用所有警告和调试信息(因此,请使用gcc -Wall -Wextra -g recent how to debug small programs进行编译,例如2018年底的GCC)。了解如何GCC 8(并在2018年底使用最新的debug with GDB)。还要使用GDB 8.2,也可以使用valgrind

  

我有一个用C语言编写的简单Web服务器

这是一个矛盾。 Web服务器要么不能简单,要么不能实现所有HTTP。

您使用sprintf是危险的(clang-analyzer有风险)。我强烈建议改用buffer overflow。而且您的404处理看起来真的很糟糕。


另一个问题(我不知道它的答案)是,如果在您的特定情况下,对于某些特定的浏览器客户端,您是否可以针对特定情况实现一小部分HTTP(对于您的浏览器和客户端来说足够了)。