HTTP запрос парсер на C


Я ввожу простой HTTP-сервер на C, и это первая часть. В основном, он принимает строку, содержащую "сырой" HTTP-запрос, и разобрать его в struct Requestв машиночитаемой форме.

Он состоит из 3 файлов: главная.с, Либ.ч, и Либ.гр.

главная.с

#include <stdio.h>

#include "lib.h"


int main(void) {
    char *raw_request = "GET / HTTP/1.1\r\n"
            "Host: localhost:8080\r\n"
            "Connection: keep-alive\r\n"
            "Upgrade-Insecure-Requests: 1\r\n"
            "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
            "User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6\r\n"
            "Accept-Language: en-us\r\n"
            "DNT: 1\r\n"
            "Accept-Encoding: gzip, deflate\r\n"
            "\r\n"
            "Usually GET requests don\'t have a body\r\n"
            "But I don\'t care in this case :)";
    struct Request *req = parse_request(raw_request);
    if (req) {
        printf("Method: %d\n", req->method);
        printf("Request-URI: %s\n", req->url);
        printf("HTTP-Version: %s\n", req->version);
        puts("Headers:");
        struct Header *h;
        for (h=req->headers; h; h=h->next) {
            printf("%32s: %s\n", h->name, h->value);
        }
        puts("message-body:");
        puts(req->body);
    }
    free_request(req);
    return 0;
}

Либ.ч

#ifndef C11CODEREVIEW_LIB_H
#define C11CODEREVIEW_LIB_H

typedef enum Method {UNSUPPORTED, GET, HEAD} Method;

typedef struct Header {
    char *name;
    char *value;
    struct Header *next;
} Header;

typedef struct Request {
    enum Method method;
    char *url;
    char *version;
    struct Header *headers;
    char *body;
} Request;


struct Request *parse_request(const char *raw);
void free_header(struct Header *h);
void free_request(struct Request *req);

#endif //C11CODEREVIEW_LIB_H

Либ.с

#include <stdlib.h>
#include <string.h>

#include "lib.h"

struct Request *parse_request(const char *raw) {
    struct Request *req = NULL;
    req = malloc(sizeof(struct Request));
    if (!req) {
        return NULL;
    }
    memset(req, 0, sizeof(struct Request));

    // Method
    size_t meth_len = strcspn(raw, " ");
    if (memcmp(raw, "GET", strlen("GET")) == 0) {
        req->method = GET;
    } else if (memcmp(raw, "HEAD", strlen("HEAD")) == 0) {
        req->method = HEAD;
    } else {
        req->method = UNSUPPORTED;
    }
    raw += meth_len + 1; // move past <SP>

    // Request-URI
    size_t url_len = strcspn(raw, " ");
    req->url = malloc(url_len + 1);
    if (!req->url) {
        free_request(req);
        return NULL;
    }
    memcpy(req->url, raw, url_len);
    req->url[url_len] = '\0';
    raw += url_len + 1; // move past <SP>

    // HTTP-Version
    size_t ver_len = strcspn(raw, "\r\n");
    req->version = malloc(ver_len + 1);
    if (!req->version) {
        free_request(req);
        return NULL;
    }
    memcpy(req->version, raw, ver_len);
    req->version[ver_len] = '\0';
    raw += ver_len + 2; // move past <CR><LF>

    struct Header *header = NULL, *last = NULL;
    while (raw[0]!='\r' || raw[1]!='\n') {
        last = header;
        header = malloc(sizeof(Header));
        if (!header) {
            free_request(req);
            return NULL;
        }

        // name
        size_t name_len = strcspn(raw, ":");
        header->name = malloc(name_len + 1);
        if (!header->name) {
            free_request(req);
            return NULL;
        }
        memcpy(header->name, raw, name_len);
        header->name[name_len] = '\0';
        raw += name_len + 1; // move past :
        while (*raw == ' ') {
            raw++;
        }

        // value
        size_t value_len = strcspn(raw, "\r\n");
        header->value = malloc(value_len + 1);
        if (!header->value) {
            free_request(req);
            return NULL;
        }
        memcpy(header->value, raw, value_len);
        header->value[value_len] = '\0';
        raw += value_len + 2; // move past <CR><LF>

        // next
        header->next = last;
    }
    req->headers = header;
    raw += 2; // move past <CR><LF>

    size_t body_len = strlen(raw);
    req->body = malloc(body_len + 1);
    if (!req->body) {
        free_request(req);
        return NULL;
    }
    memcpy(req->body, raw, body_len);
    req->body[body_len] = '\0';


    return req;
}


void free_header(struct Header *h) {
    if (h) {
        free(h->name);
        free(h->value);
        free_header(h->next);
        free(h);
    }
}


void free_request(struct Request *req) {
    free(req->url);
    free(req->version);
    free_header(req->headers);
    free(req->body);
    free(req);
}

Выход:

Method: 1
Request-URI: /
HTTP-Version: HTTP/1.1
Headers:
                 Accept-Encoding: gzip, deflate
                             DNT: 1
                 Accept-Language: en-us
                      User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_3) AppleWebKit/604.5.6 (KHTML, like Gecko) Version/11.0.3 Safari/604.5.6
                          Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
       Upgrade-Insecure-Requests: 1
                      Connection: keep-alive
                            Host: localhost:8080
message-body:
Usually GET requests don't have a body
But I don't care in this case :)

Process finished with exit code 0

Вопрос:

В

if (!foo) {
    free_request(req);
    return NULL;
}

структуры некрасиво! Как я могу улучшить его?



4283
3
задан 26 февраля 2018 в 03:02 Источник Поделиться
Комментарии
2 ответа


В if (!foo) { ... структуры некрасиво! Как я могу улучшить его?

Какой код это на самом деле хорошо, еще чуть сократил код, давайте free_request() вернуться NULL;

if (!req->version) {
return free_request(req);
}

Код может прокатить void *SQ_malloc(void **, size_t) вспомогательную функцию в качестве альтернативы для немного больше кода гольф.

// req->url = malloc(url_len + 1);
// if (!req->url) {
// free_request(req);
// return NULL;
// }
if (SQ_malloc(&req->url, url_len + 1) == NULL) {
return free_request(req);
}

Или предварительно известково потребности на число членов и вернуться true на SQ2_malloc() отказ:

if (SQ2_malloc(&req->url, url_len + 1) || //
SQ2_malloc(&req->this_member, this_len + 1) || //
SQ2_malloc(&req->that_member, that_len + 1)) {
return free_request(req);
}


Другие

Во избежание неопределенного поведения

raw не известно, что допустимое время доступа не менее 3.

// memcmp(raw, "GET", strlen("GET"))
strncmp(raw, "GET", strlen("GET"))
// or better
#define S_GET "GET"
strncmp(raw, S_GET, sizeof S_GET - 1);

Избежать конца строки предположение

raw += ver_len + 2; // move past <CR><LF> предполагается, что строки закончились "\r\n". Это могло закончиться только одним из них. Надежный код будет считать, что.

Испытания нуль-Несс

В free_header(struct Header *h)код делает if (h) { .... Я рекомендую делать то же самое с free_request(struct Request *req)добавить if (req) { .... Это позволяет более терпимо использования и содержания приемлемости free(NULL).

Использовать размер де-ссылается переменная

Вместо того, чтобы использовать размер определенной структуры, использовать размер де-ссылается указатель. Проще код правильно, пересматривать и поддерживать.

// header = malloc(sizeof(Header));
header = malloc(sizeof *header));

1
ответ дан 26 февраля 2018 в 10:02 Источник Поделиться

Когда заголовки сканирования. Если вы не отменить заявку, но не список уже созданных заголовков.

    if (!header) {
req->headers = last; // Make sure you clean up the headers.
free_request(req);
return NULL;
}

// STUFF
if (!header->name) {
free(header); // Clean up the current header
req->headers = last; // Clean up the list of good headers
free_request(req);
return NULL;
}

// STUFF
if (!header->value) {
free(header); // Clean up the current header
req->headers = last; // Clean up the list of good headers
free_request(req);
return NULL;
}

Протокол HTTP

Комментарии о HTTP.

URL-адрес

Вы не храните URL-адрес в ваш запрос.

typedef struct Request {
enum Method method;
char *url; // This is not a URL
char *version;
struct Header *headers;
char *body;
} Request;

Это является компонентом пути URL-адреса. HTTP запрос шпагат хоста и путь до принятия запроса. Чтобы получить URL-адрес, вы должны объединить "хозяин" глава параметра с пути.

Нет, если ваш веб-сервер для поддержки нескольких сайтов, используя те же машины (одинаковые IP-адреса) стоит поставить "узел" в тело запроса. Как первое, что вы хотите сделать, это решить, какой сайт вы собираетесь служить.

Заголовки

Заголовок не должен быть уникальным. Там может быть несколько "примет" заголовки например. Чтобы сделать это легко для вашего кода для поддержки Вашего объекта заголовок должен разрешить несколько значений для каждого заголовка.

Длина Тела

Когда прошло тело. Стандартный протокол HTTP требует, чтобы длина тела передается в заголовке.


4.4 Длина Сообщения
Передачи-длина сообщения длина сообщения-тело, как оно отображается в сообщении; то есть, после любой передачи-коды были применены. Когда тело сообщения включается в сообщение, передача-длина этого тела определяется одним из следующих (в порядке приоритета):

1.Любой ответ сообщение, что "не должны" содержать тело сообщения (например, 1хх, 204, 304 и все ответы и все ответы на запрос Head) всегда завершается первой пустой строкой после полей заголовка, независимо от сущности-поля заголовка в сообщении.

2.Если передача-кодирование поля заголовка (статья 14.41) и имеет значение, отличное от "идентичности", то перевод-длина определяется использование "фрагментарности" кодирования передачи данных (п. 3.6), кроме случаев, когда сообщение прекращается путем закрытия соединения.

3.Если контент-длина заголовка (статья 14.13) присутствует, его десятичное значение октета представляет обе сущности-длина и передачи-длина. Контент-длина заголовка не должна быть отправлена, если эти две длины отличаются (т. е. если передача-кодирование

header field is present). If a message is received with both a
Transfer-Encoding header field and a Content-Length header field,
the latter MUST be ignored.


4.Если сообщение использует тип носителя "составные/byteranges", и передачи-длина не указано иное, то это я - отграничение тип носителя определяет передачу-длина. Этот тип носителя не должна использоваться, если отправитель знает, что получатель может анализировать его; наличие в запросе заголовка Range с несколькими байт - диапазонах от 1.1 клиента подразумевает, что клиент может анализировать составные/ответы byteranges.

  A range header might be forwarded by a 1.0 proxy that does not
understand multipart/byteranges; in this case the server MUST
delimit the message using methods defined in items 1,3 or 5 of
this section.


5.By сервер закрывает соединение. (Закрытии подключение не может быть использовано для обозначения конца текста запроса, поскольку это оставит никакой возможности для отправки ответа).

Для обеспечения совместимости с приложениями по протоколу HTTP/1.0 и HTTP/1.1 запросы, содержащий тело сообщения должно содержать действительный контент-длина заголовка, если сервер известен как HTTP уступчивый/1.1. Если запрос содержит тело сообщения и контент-длина не дали, сервер должен ответить С 400 (неверный запрос), если он не может определить длину сообщения, или с 411 (необходимой длины), если он хочет настаивать на получении действительного содержания-длина.

Все HTTP/1.1 приложения, которые получают хозяйствующие субъекты должны принять "фрагментарности" кодирования передачи данных (раздел 3.6), так что этот механизм будет использоваться для сообщений, когда длина сообщения не может быть определена заранее.

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

Когда контент-длина приведен в сообщение, где разрешено тело сообщения, ее значение поля должно точно соответствовать количеству октетов в тело сообщения. Протокол HTTP/1.1 агенты пользователей должны уведомить пользователей, когда недопустимая длина получено и обнаружил.

В противном случае веб-сервер не знаю, где просить следующего протокола HTTP. Это особенно важно, когда в заголовке Connection: keep-alive передается как соединение от клиента не близко, и следующий запрос будет записан в том же соединении.

1
ответ дан 2 марта 2018 в 08:03 Источник Поделиться