英文:
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
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->conn_fd, file_buffer, result, 0) < 0)
{
fclose(fp);
err_n_die("Error while sending");
}
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->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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论