Getting image rendering errors while sending it through C code using fread and send functions over tcp socket in a http response

huangapple go评论89阅读模式
英文:

Getting image rendering errors while sending it through C code using fread and send functions over tcp socket in a http response

问题

I see you've provided code snippets and a description of the issue you're facing with serving images in your web server project. If you have any specific questions or need assistance with a particular aspect of the code or the problem, please let me know, and I'll be happy to help.

英文:

I am writing a simple web server as a personal project in c. The server can take in a static files path and then serve those static files. The approach I am using is working well for any text/* content-type but I am getting errors in the browser saying that the image contains errors.

Here is a snippet of the handler function that is responsible to send the response. The else-if block is responsible for sending static files.

// 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);
    }

Here is the screenshot of the network tab in firefox v112.0

Getting image rendering errors while sending it through C code using fread and send functions over tcp socket in a http response

I've tried checking that the image is not corrupted, I am sending the necessary headers, the content-type is right, and it's working well for any text/* content type. I've tried it on the other browsers

Let me know if something is not clear in the question. The full source code can be found at: https://github.com/rahulgpt/kraken

Modified code snippet according to @Steffen Ullrich answer.

// 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);

答案1

得分: 0

if (send(client_server->conn_fd, file_buffer, result, 0) < 0)
{
fclose(fp);
err_n_die("发送时发生错误");
}

bytes_read += result;

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

英文:
        if (send(client_server-&gt;conn_fd, file_buffer, result, 0) &lt; 0)
{
fclose(fp);
err_n_die(&quot;Error while sending&quot;);
}
bytes_read += result;

This logic is wrong. send might send less then result bytes. How many bytes are actually sent is returned by send. While this might not be a problem with small data like HTML it will be a problem with larger data like images, which no longer fit fully into the sockets send buffer. In this case parts of the data will not be actually sent, resulting in corrupted data.

答案2

得分: 0

The problem was I had three CRLF sequences between the last header and the body instead of two.

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);
}

According to this, the last header will have a trailing CRLF.

 // 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);

But I was adding another two CRLF sequences. Removing one of those fixed the problem.

英文:

The problem was I had three CRLF sequences between the last header and the body instead of two.

while (owl_hashmap_iter(http_res-&gt;headers, &amp;iter, &amp;item))
{
    const header_t *header = item;
    headers_len += snprintf(headers + headers_len, sizeof(headers) - headers_len,
                                    &quot;%s: %s\r\n&quot;, header-&gt;name, header-&gt;value);
}

According to this, the last header will have a trailing CRLF.

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

But I was adding another two CRLF sequences. Removing one of those fixed the problem.

huangapple
  • 本文由 发表于 2023年4月17日 01:28:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76029313.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定