我正在用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);
1条答案
按热度按时间6jjcrrmo1#
这种逻辑是错误的。
send
可能发送的字节数少于result
。实际发送的字节数由send
返回。虽然这对于HTML等小数据可能不是问题,但对于图像等较大数据可能会有问题,这些数据不再完全适合套接字发送缓冲区。在这种情况下,部分数据将不会实际发送,导致数据损坏。