Веб-сервер не получает все запросы - PullRequest
0 голосов
/ 02 декабря 2018

У меня есть простой веб-сервер, написанный на C, который отлично работает при подаче небольшого 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 голосов
/ 02 декабря 2018

HTTP может быть более сложным , чем вы думаете. Вы полностью прочитали его спецификацию ( RFC 7230 для HTTP 1.1)или какая-то книга о HTTP?Рассматривали ли вы использование некоторой библиотеки HTTP-сервера, такой как libonion или libhttp (или libmicrohttpd или другие)?Размер этих библиотек говорит о сложности HTTP!И эти библиотеки бесплатное программное обеспечение , так что вы можете изучить их исходный код и черпать вдохновение из них.(Ваш учитель будет рад, если вы честно скажете ему, что вы изучили исходный код, например, libonion и прочитали RFC 7230 ).

Кстати, современные браузеры (последние Firefox или Chromeи т. д.) имеют тенденцию использовать несколько соединений параллельно для отображения одной страницы.А современные браузеры способны отображать фактический HTTP-трафик и сетевые протоколы.

Я рекомендую использовать некоторые существующие библиотеки.Я очень рад использовать libonion, даже если у него есть некоторые ограничения.

Наконец, read , как отлаживать небольшие программы .Включите все предупреждения и отладочную информацию (поэтому скомпилируйте, используя gcc -Wall -Wextra -g с недавним GCC , например, GCC 8 в конце 2018 года).Узнайте, как отладить с помощью GDB (и использовать также недавнюю, GDB 8.2 в конце 2018 года).Используйте также valgrind и, возможно, clang-analyzer .

У меня есть простой веб-сервер, написанный на C

Этопротиворечие в терминах.Веб-сервер либо не может быть простым, либо не поддерживает весь HTTP.

Использование sprintf опасно (риск переполнение буфера ).Я настоятельно рекомендую использовать snprintf вместо этого.И ваша обработка 404 выглядит действительно плохо.


Другой вопрос (и я не знаю его ответа) заключается в том, что если в вашем конкретном случае с некоторыми конкретными браузерными клиентами вы могли бы реализовать небольшую долюHTTP (достаточно для ваших браузеров и клиентов) для вашего конкретного случая.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...