在HTML响应中通过TCP套接字使用fread和send函数通过C代码发送图像时获取图像错误

wydwbb8l  于 2023-04-19  发布在  其他
关注(0)|答案(1)|浏览(105)

我正在用c编写一个简单的web服务器作为个人项目。服务器可以接受一个静态文件路径,然后为这些静态文件提供服务。我使用的方法适用于任何text/*内容类型,但我在浏览器中遇到错误,说图像包含错误。
下面是负责发送响应的handler函数的片段。else-if块负责发送静态文件。

// thread function
void *client_handler(void *arg)
{
    client_server_t *client_server = arg;
    uint8_t recv_line[MAX_LINE + 1];
    uint8_t buff[MAX_LINE + 1];
    uint8_t req_string[3000];
    int n;
    size_t req_len = 0;

    memset(recv_line, 0, MAX_LINE);

    // print the request
    while ((n = recv(client_server->conn_fd, recv_line, MAX_LINE - 1, 0)) > 0)
    {
        fprintf(stdout, "\n%s\n\n%s", bin2hex(recv_line, n), recv_line);

        memcpy(req_string + req_len, recv_line, n);
        req_len += n;

        if (recv_line[n - 1] == '\n') break;

        memset(recv_line, 0, MAX_LINE);
    }

    if (n == 0)
        owl_println("Connection closed by client");
    else if (n < 0)
        err_n_die("read error");

    // get the http headers
    http_req_t *http_req = http_req_init((char *)req_string);
    owl_hashmap_t *routes_map = client_server->server->routes;
    owl_hashmap_t *http_status_map = client_server->server->http_status_map;

    http_res_t *http_res = http_res_init();

    http_route_t *route = owl_hashmap_get(routes_map, &(http_route_t){.uri = http_req->uri});

    // Date header indicates the data and time the response was generated
    char date_str[100];
    time_t now = time(NULL);
    struct tm tm = *gmtime(&now);
    strftime(date_str, sizeof(date_str), "%a, %d %b %Y %H:%M:%S GMT", &tm);

    if (route)
    {
        // After calling the handler, "http_res" will be populated
        char *res = route->handler(http_req, http_res);
        size_t res_len = strlen(res);

        // get the reason string from the status map
        status_code_with_reason_t *scr = owl_hashmap_get(http_status_map,
                                                         &(status_code_with_reason_t){.code = http_res->status_code});
        if (!scr) err_n_die("%d http status is not supported by the server.", http_res->status_code);
        http_res->reason = scr->reason;

        // format headers to send in the response
        char headers[MAX_HEADER_LEN] = "";
        size_t headers_len = 0;
        void *item;
        size_t iter = 0;
        while (owl_hashmap_iter(http_res->headers, &iter, &item))
        {
            const header_t *header = item;
            headers_len += snprintf(headers + headers_len, sizeof(headers) - headers_len,
                                    "%s: %s\r\n", header->name, header->value);
        }
        // format the response string
        char chunk[BUFF_SIZE];
        size_t bytes_sent = 0;
        size_t bytes_remaining = res_len;
        snprintf((char *)buff, sizeof(buff),
                 "HTTP/1.1 %d %s\r\nContent-Type: %s\r\nContent-Length: %zu\r\nDate: %s\r\n%s\r\n\r\n",
                 http_res->status_code, http_res->reason, http_res->content_type, res_len, date_str, headers);
        headers_len = strlen((char *)buff);
        if (send(client_server->conn_fd, (char *)buff, headers_len, 0) < 0)
            err_n_die("Error while sending headers");

        while (bytes_remaining > 0)
        {
            size_t bytes_to_send = MIN(BUFF_SIZE, bytes_remaining);
            memcpy(chunk, res + bytes_sent, bytes_to_send);
            if (send(client_server->conn_fd, chunk, bytes_to_send, 0) < 0)
            {
                err_n_die("Error while sending response chunk");
            }
            bytes_sent += bytes_to_send;
            bytes_remaining -= bytes_to_send;
        }

        // header_t *header = owl_hashmap_get(http_req->headers, &(header_t){.name = "Connection"});
    }
    else if (server->num_registered_file_paths > 0)
    {
        char filepath[MAX_PATH_LEN];
        int found = 0;
        for (int i = 0; i < server->num_registered_file_paths; i++)
        {
            snprintf(filepath, MAX_PATH_LEN, "%s%s", client_server->server->static_files_path[i], http_req->uri);

            if (access(filepath, F_OK) == 0)
            {
                owl_println("filepath: %s", filepath);
                found = 1;
                break;
            }
        }

        if (!found) goto send404;

        FILE *fp = fopen(filepath, "rb");
        if (!fp) goto send404;

        // get the file size
        fseek(fp, 0, SEEK_END);
        long fsize = ftell(fp);
        fseek(fp, 0, SEEK_SET);

        char *content_type = "text/plain";
        char *ext = strrchr(filepath, '.');
        if (ext)
        {
            if (strcmp(ext, ".html") == 0)
                content_type = "text/html";
            else if (strcmp(ext, ".css") == 0)
                content_type = "text/css";
            else if (strcmp(ext, ".js") == 0)
                content_type = "text/javascript";
            else if (strcmp(ext, ".png") == 0)
                content_type = "image/png";
            else if (strcmp(ext, ".jpg") == 0 || strcmp(ext, ".jpeg") == 0)
                content_type = "image/jpeg";
            else if (strcmp(ext, ".gif") == 0)
                content_type = "image/gif";
            else if (strcmp(ext, ".svg") == 0)
                content_type = "image/svg+xml";
            else if (strcmp(ext, ".ico") == 0)
                content_type = "image/x-icon";
            else if (strcmp(ext, ".avif") == 0)
                content_type = "image/avif";
        }

        // format headers to send in the response
        char headers[MAX_HEADER_LEN] = "";
        size_t headers_len = 0;
        void *item;
        size_t iter = 0;
        while (owl_hashmap_iter(http_res->headers, &iter, &item))
        {
            const header_t *header = item;
            headers_len += snprintf(headers + headers_len, sizeof(headers) - headers_len,
                                    "%s: %s\r\n", header->name, header->value);
        }

        // format the response string for other content types
        snprintf((char *)buff, sizeof(buff),
                 "HTTP/1.1 200 OK\r\nDate: %s\r\nContent-Type: %s\r\nContent-Length: %ld\r\n%s\r\n\r\n", date_str, content_type, fsize, headers);

        if (send(client_server->conn_fd, (char *)buff, strlen((char *)buff), 0) < 0)
        {
            fclose(fp);
            err_n_die("Error while sending");
        }

        // send the file in chunks
        char file_buffer[BUFF_SIZE];
        size_t bytes_read = 0;
        while (bytes_read < fsize)
        {
            size_t bytes_to_read = fsize - bytes_read;
            if (bytes_to_read > BUFF_SIZE)
                bytes_to_read = BUFF_SIZE;

            size_t result = fread(file_buffer, 1, bytes_to_read, fp);
            if (result == 0)
                break;

            if (send(client_server->conn_fd, file_buffer, result, 0) < 0)
            {
                fclose(fp);
                err_n_die("Error while sending");
            }

            bytes_read += result;
        }

        fclose(fp);
    }

下面是firefox v112.0中网络标签的截图

我已经试过检查图像没有损坏,我发送必要的头,内容类型是正确的,它的工作以及任何text/*内容类型。
如果问题中有什么不清楚的地方,请告诉我。完整的源代码可以在以下位置找到:https://github.com/rahulgpt/kraken
根据@Steffen Ullrich答案修改代码片段。

// send file in chunks
char file_buffer[BUFF_SIZE];
size_t bytes_read = 0;
while (bytes_read < fsize)
{
    size_t bytes_to_read = fsize - bytes_read;
    if (bytes_to_read > BUFF_SIZE)
        bytes_to_read = BUFF_SIZE;

    size_t result = fread(file_buffer, 1, bytes_to_read, fp);
    if (result == 0)
        break;

    size_t bytes_sent = 0;
    while (bytes_sent < result)
    {
        ssize_t sent = send(client_server->conn_fd, file_buffer + bytes_sent, result - bytes_sent, 0);
        if (sent == -1)
        {
            fclose(fp);
            err_n_die("Error while sending");
        }
        bytes_sent += sent;
    }

    bytes_read += result;
}

fclose(fp);
6jjcrrmo

6jjcrrmo1#

if (send(client_server->conn_fd, file_buffer, result, 0) < 0)
        {
            fclose(fp);
            err_n_die("Error while sending");
        }

        bytes_read += result;

这种逻辑是错误的。send可能发送的字节数少于result。实际发送的字节数由send返回。虽然这对于HTML等小数据可能不是问题,但对于图像等较大数据可能会有问题,这些数据不再完全适合套接字发送缓冲区。在这种情况下,部分数据将不会实际发送,导致数据损坏。

相关问题