Web 服务器未收到所有请求
Web server not receiving all requests
我有一个用 C 语言编写的简单 Web 服务器,在向浏览器提供带有一些图像的小 HTML 文件时工作正常。当我尝试为一个包含更多具有不同内容类型的对象(例如 css 和 js 文件)的更复杂的网站提供服务时,我发现我没有收到对正确加载所需的许多对象的请求 index.html - 浏览器无限期地等待主机。如果我刷新页面几次,最终所有内容都会正确加载并且我能够访问超链接。我注意到的另一件事是,通常是相同的文件没有被发送回浏览器。
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdbool.h>
#include <pthread.h>
bool writeDataToClient(int sckt, const void *data, int datalen)
{
const char *pdata = (const char*) data;
while (datalen > 0){
int numSent = send(sckt, pdata, datalen, 0);
if (numSent <= 0){
if (numSent == 0){
printf("The client was not written to: disconnected\n");
} else {
perror("The client was not written to");
}
return false;
}
pdata += numSent;
datalen -= numSent;
}
return true;
}
bool writeStrToClient(int sckt, const char *str)
{
return writeDataToClient(sckt, str, strlen(str));
}
int get_filename_and_method(char *str, char **buf1, char **buf2)
{
char *request = str;
char *status_line;
char *url;
char *token = strtok(request, "\r\n");
status_line = token;
*buf1 = strtok(status_line, " ");
if (strcasecmp(*buf1, "GET") != 0) return -1;
url = strtok(NULL, " ");
if (strncmp(url, "/", strlen("/")) != 0) return -1;
if (strlen(url) == 1) strcat(url, "index.html");
if (url[strlen(url) - 1] == '/') strcat(url, "index.html");
char *tmp = strdup(url);
strcpy(url, "web");
strcat(url, tmp);
*buf2 = url;
free(tmp);
return 0;
}
int get_connection_type(char *str, char **buf)
{
char *req = str;
char *token = strtok(req, "\r\n");
char *connection;
while (token != NULL)
{
if (strncmp(token, "Connection:", 11) == 0)
{
connection = token;
strtok(connection, " ");
if (strcasecmp(strtok(NULL, " "), "Keep-Alive") == 0)
{
*buf = "Connection: keep-alive\r\n\r\n";
return 0;
}
}
token = strtok(NULL, "\r\n");
}
*buf = "Connection: close\r\n\r\n";
return 0;
}
void *connection_handler (void *sockfd)
{
// Connection handler
int sock = *(int*)sockfd;
char *buffer, *method, *filename, *connection_type, *content_type;
int bufsize = 2048;
const char *HTTP_404_CONTENT = "<html><head><title>404 Not "
"Found</title></head><body><h1>404 Not Found</h1>The requested "
"resource could not be found but may be available again in the "
"future."</body></html>";
const char *HTTP_501_CONTENT = "<html><head><title>501 Not "
"Implemented</title></head><body><h1>501 Not Implemented</h1>The "
"server either does not recognise the request method, or it lacks "
"the ability to fulfill the request.</body></html>";
buffer = (char*) malloc(bufsize);
if (!buffer){
printf("The receive buffer was not allocated\n");
exit(1);
}
while (1)
{
int numRead = recv(sock, buffer, bufsize, 0);
if (numRead < 1){
if (numRead == 0){
printf("The client was not read from: disconnected\n");
break;
} else {
perror("The client was not read from");
break;
}
close(sock);
continue;
}
printf("%.*s\n", numRead, buffer);
// Extract info from request header
get_connection_type(buffer, &connection_type);
if (get_filename_and_method(buffer, &method, &filename) == -1)
{
char clen[40];
writeStrToClient(sock, "HTTP/1.1 501 Not Implemented\r\n");
sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_501_CONTENT));
writeStrToClient(sock, clen);
writeStrToClient(sock, "Content-Type: text/html\r\n");
writeStrToClient(sock, connection_type);
writeStrToClient(sock, HTTP_501_CONTENT);
}
else
{
// Open and read file
long fsize;
FILE *fp = fopen(filename, "rb");
if (!fp){
perror("The file was not opened");
char clen[40];
writeStrToClient(sock, "HTTP/1.1 404 Not Found\r\n");
sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_404_CONTENT));
writeStrToClient(sock, clen);
writeStrToClient(sock, "Content-Type: text/html\r\n");
writeStrToClient(sock, connection_type);
writeStrToClient(sock, HTTP_404_CONTENT);
if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
break;
continue;
}
printf("The file was opened\n");
if (fseek(fp, 0, SEEK_END) == -1){
perror("The file was not seeked");
exit(1);
}
fsize = ftell(fp);
if (fsize == -1) {
perror("The file size was not retrieved");
exit(1);
}
rewind(fp);
char *msg = (char*) malloc(fsize);
if (!msg){
perror("The file buffer was not allocated\n");
exit(1);
}
if (fread(msg, fsize, 1, fp) != 1){
perror("The file was not read\n");
exit(1);
}
fclose(fp);
// Get extension of filename
char *ext = strrchr(filename, '.');
if (ext != NULL)
ext++;
if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0)
content_type = "Content-Type: text/html\r\n";
else if (strcmp(ext, "css") == 0)
content_type = "Content-Type: text/css\r\n";
else if (strcmp(ext, "jpg") == 0)
content_type = "Content-Type: image/jpeg\r\n";
else if (strcmp(ext, "png") == 0)
content_type = "Content-Type: image/png\r\n";
else if (strcmp(ext, "gif") == 0)
content_type = "Content-Type: image/gif\r\n";
else
content_type = "Content-Type: text/plain\r\n";
if (!writeStrToClient(sock, "HTTP/1.1 200 OK\r\n")){
close(sock);
continue;
}
char clen[40];
sprintf(clen, "Content-length: %ld\r\n", fsize);
if (!writeStrToClient(sock, clen)){
printf("Cannot write content length\n");
close(sock);
continue;
}
if (!writeStrToClient(sock, content_type)){
close(sock);
continue;
}
if (!writeStrToClient(sock, connection_type) == -1){
close(sock);
continue;
}
if (!writeDataToClient(sock, msg, fsize)){
close(sock);
continue;
}
printf("The file was sent successfully\n");
}
if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
break;
}
close(sock);
pthread_exit(0);
}
int main(int argc, char *argv[]){
int create_socket, new_socket;
struct sockaddr_in address;
socklen_t addrlen;
char *ptr;
if (argc != 2)
{
printf("Usage: %s <port number>\n", argv[0]);
exit(0);
}
create_socket = socket(AF_INET, SOCK_STREAM, 0);
if (create_socket == -1){
perror("The socket was not created");
exit(1);
}
printf("The socket was created\n");
const unsigned short port = (unsigned short) strtol(argv[1], &ptr, 10);
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == -1){
printf("The socket was not bound because that port is not available\n");
exit(1);
}
printf("The socket is bound\n");
if (listen(create_socket, 10) == -1){
perror("The socket was not opened for listening");
exit(1);
}
printf("The socket is listening\n");
while (1) {
addrlen = sizeof(address);
pthread_t tid;
new_socket = accept(create_socket, (struct sockaddr *) &address, &addrlen);
if (new_socket == -1) {
perror("A client was not accepted");
exit(1);
}
printf("A client is connected from %s:%hu...\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
if (pthread_create(&tid, NULL, connection_handler, (void *)&new_socket) < 0)
{
perror("Could not create thread");
return 1;
}
pthread_join(tid, NULL);
}
if (new_socket < 0)
{
perror("accept failed");
return 1;
}
close(create_socket);
printf("Socket was closed\n");
return 0;
}
此外,关闭浏览器(断开与服务器的连接)会导致接受另一个连接,该连接发送浏览器请求但未收到的第一个文件,然后服务器程序结束且没有任何错误消息。
更新:删除 pthread_join 允许页面正确加载。正如一位用户提到的,浏览器并行执行多个连接,所以我认为发生的情况是所有请求都是通过多个连接发送的(查看我的程序的输出,似乎有 5 个连接到服务器)。由于 pthread_join 等待一个线程(连接)完成,一次只处理一个连接,这就是我没有收到所有请求的原因。
HTTP might be more complex than you think. Have you read its specification in full (RFC 7230 for HTTP 1.1) , or some book about HTTP ? Did you consider using some HTTP server library, such as libonion or libhttp (or libmicrohttpd or others)? The size of these libraries tells something about the complexity of HTTP! And these libraries are free software, so you can study their source code and take inspiration from them. (Your teacher should be delighted if you tell him honestly that you studied the source code of e.g. libonion
and have read RFC 7230).
顺便说一句,现代浏览器(最近的 Firefox 或 Chrome 等...)倾向于使用 多个 并行连接来显示一个页面。现代浏览器能够向您显示实际的 HTTP 流量和网络协议。
我的建议是使用一些现有的库。我很高兴使用 libonion
,即使它有一些限制。
最后,阅读how to debug small programs. Enable all warnings and debug info (so compile using gcc -Wall -Wextra -g
with a recent GCC, e.g. GCC 8 in end of 2018). Learn how to debug with GDB (and use also a recent one, GDB 8.2 in end of 2018). Use also valgrind and perhaps clang-analyzer。
I have a simple web server written in C
这是一种自相矛盾的说法。 Web 服务器要么不能简单,要么不能实现所有 HTTP。
您使用 sprintf
很危险(有 buffer overflow). I strongly recommend using snprintf 的风险。而且您的 404 处理看起来很糟糕。
另一个问题(我不知道它的答案)是,如果在您的特定情况下,对于某些特定的浏览器客户端,您可能会为您的特定实现一小部分 HTTP(对您的浏览器和客户端来说足够了)案例.
我有一个用 C 语言编写的简单 Web 服务器,在向浏览器提供带有一些图像的小 HTML 文件时工作正常。当我尝试为一个包含更多具有不同内容类型的对象(例如 css 和 js 文件)的更复杂的网站提供服务时,我发现我没有收到对正确加载所需的许多对象的请求 index.html - 浏览器无限期地等待主机。如果我刷新页面几次,最终所有内容都会正确加载并且我能够访问超链接。我注意到的另一件事是,通常是相同的文件没有被发送回浏览器。
#include <sys/socket.h>
#include <sys/sendfile.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <stdbool.h>
#include <pthread.h>
bool writeDataToClient(int sckt, const void *data, int datalen)
{
const char *pdata = (const char*) data;
while (datalen > 0){
int numSent = send(sckt, pdata, datalen, 0);
if (numSent <= 0){
if (numSent == 0){
printf("The client was not written to: disconnected\n");
} else {
perror("The client was not written to");
}
return false;
}
pdata += numSent;
datalen -= numSent;
}
return true;
}
bool writeStrToClient(int sckt, const char *str)
{
return writeDataToClient(sckt, str, strlen(str));
}
int get_filename_and_method(char *str, char **buf1, char **buf2)
{
char *request = str;
char *status_line;
char *url;
char *token = strtok(request, "\r\n");
status_line = token;
*buf1 = strtok(status_line, " ");
if (strcasecmp(*buf1, "GET") != 0) return -1;
url = strtok(NULL, " ");
if (strncmp(url, "/", strlen("/")) != 0) return -1;
if (strlen(url) == 1) strcat(url, "index.html");
if (url[strlen(url) - 1] == '/') strcat(url, "index.html");
char *tmp = strdup(url);
strcpy(url, "web");
strcat(url, tmp);
*buf2 = url;
free(tmp);
return 0;
}
int get_connection_type(char *str, char **buf)
{
char *req = str;
char *token = strtok(req, "\r\n");
char *connection;
while (token != NULL)
{
if (strncmp(token, "Connection:", 11) == 0)
{
connection = token;
strtok(connection, " ");
if (strcasecmp(strtok(NULL, " "), "Keep-Alive") == 0)
{
*buf = "Connection: keep-alive\r\n\r\n";
return 0;
}
}
token = strtok(NULL, "\r\n");
}
*buf = "Connection: close\r\n\r\n";
return 0;
}
void *connection_handler (void *sockfd)
{
// Connection handler
int sock = *(int*)sockfd;
char *buffer, *method, *filename, *connection_type, *content_type;
int bufsize = 2048;
const char *HTTP_404_CONTENT = "<html><head><title>404 Not "
"Found</title></head><body><h1>404 Not Found</h1>The requested "
"resource could not be found but may be available again in the "
"future."</body></html>";
const char *HTTP_501_CONTENT = "<html><head><title>501 Not "
"Implemented</title></head><body><h1>501 Not Implemented</h1>The "
"server either does not recognise the request method, or it lacks "
"the ability to fulfill the request.</body></html>";
buffer = (char*) malloc(bufsize);
if (!buffer){
printf("The receive buffer was not allocated\n");
exit(1);
}
while (1)
{
int numRead = recv(sock, buffer, bufsize, 0);
if (numRead < 1){
if (numRead == 0){
printf("The client was not read from: disconnected\n");
break;
} else {
perror("The client was not read from");
break;
}
close(sock);
continue;
}
printf("%.*s\n", numRead, buffer);
// Extract info from request header
get_connection_type(buffer, &connection_type);
if (get_filename_and_method(buffer, &method, &filename) == -1)
{
char clen[40];
writeStrToClient(sock, "HTTP/1.1 501 Not Implemented\r\n");
sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_501_CONTENT));
writeStrToClient(sock, clen);
writeStrToClient(sock, "Content-Type: text/html\r\n");
writeStrToClient(sock, connection_type);
writeStrToClient(sock, HTTP_501_CONTENT);
}
else
{
// Open and read file
long fsize;
FILE *fp = fopen(filename, "rb");
if (!fp){
perror("The file was not opened");
char clen[40];
writeStrToClient(sock, "HTTP/1.1 404 Not Found\r\n");
sprintf(clen, "Content-length: %zu\r\n", strlen(HTTP_404_CONTENT));
writeStrToClient(sock, clen);
writeStrToClient(sock, "Content-Type: text/html\r\n");
writeStrToClient(sock, connection_type);
writeStrToClient(sock, HTTP_404_CONTENT);
if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
break;
continue;
}
printf("The file was opened\n");
if (fseek(fp, 0, SEEK_END) == -1){
perror("The file was not seeked");
exit(1);
}
fsize = ftell(fp);
if (fsize == -1) {
perror("The file size was not retrieved");
exit(1);
}
rewind(fp);
char *msg = (char*) malloc(fsize);
if (!msg){
perror("The file buffer was not allocated\n");
exit(1);
}
if (fread(msg, fsize, 1, fp) != 1){
perror("The file was not read\n");
exit(1);
}
fclose(fp);
// Get extension of filename
char *ext = strrchr(filename, '.');
if (ext != NULL)
ext++;
if (strcmp(ext, "html") == 0 || strcmp(ext, "htm") == 0)
content_type = "Content-Type: text/html\r\n";
else if (strcmp(ext, "css") == 0)
content_type = "Content-Type: text/css\r\n";
else if (strcmp(ext, "jpg") == 0)
content_type = "Content-Type: image/jpeg\r\n";
else if (strcmp(ext, "png") == 0)
content_type = "Content-Type: image/png\r\n";
else if (strcmp(ext, "gif") == 0)
content_type = "Content-Type: image/gif\r\n";
else
content_type = "Content-Type: text/plain\r\n";
if (!writeStrToClient(sock, "HTTP/1.1 200 OK\r\n")){
close(sock);
continue;
}
char clen[40];
sprintf(clen, "Content-length: %ld\r\n", fsize);
if (!writeStrToClient(sock, clen)){
printf("Cannot write content length\n");
close(sock);
continue;
}
if (!writeStrToClient(sock, content_type)){
close(sock);
continue;
}
if (!writeStrToClient(sock, connection_type) == -1){
close(sock);
continue;
}
if (!writeDataToClient(sock, msg, fsize)){
close(sock);
continue;
}
printf("The file was sent successfully\n");
}
if (strcmp(connection_type, "Connection: close\r\n\r\n") == 0)
break;
}
close(sock);
pthread_exit(0);
}
int main(int argc, char *argv[]){
int create_socket, new_socket;
struct sockaddr_in address;
socklen_t addrlen;
char *ptr;
if (argc != 2)
{
printf("Usage: %s <port number>\n", argv[0]);
exit(0);
}
create_socket = socket(AF_INET, SOCK_STREAM, 0);
if (create_socket == -1){
perror("The socket was not created");
exit(1);
}
printf("The socket was created\n");
const unsigned short port = (unsigned short) strtol(argv[1], &ptr, 10);
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
if (bind(create_socket, (struct sockaddr *) &address, sizeof(address)) == -1){
printf("The socket was not bound because that port is not available\n");
exit(1);
}
printf("The socket is bound\n");
if (listen(create_socket, 10) == -1){
perror("The socket was not opened for listening");
exit(1);
}
printf("The socket is listening\n");
while (1) {
addrlen = sizeof(address);
pthread_t tid;
new_socket = accept(create_socket, (struct sockaddr *) &address, &addrlen);
if (new_socket == -1) {
perror("A client was not accepted");
exit(1);
}
printf("A client is connected from %s:%hu...\n", inet_ntoa(address.sin_addr), ntohs(address.sin_port));
if (pthread_create(&tid, NULL, connection_handler, (void *)&new_socket) < 0)
{
perror("Could not create thread");
return 1;
}
pthread_join(tid, NULL);
}
if (new_socket < 0)
{
perror("accept failed");
return 1;
}
close(create_socket);
printf("Socket was closed\n");
return 0;
}
此外,关闭浏览器(断开与服务器的连接)会导致接受另一个连接,该连接发送浏览器请求但未收到的第一个文件,然后服务器程序结束且没有任何错误消息。
更新:删除 pthread_join 允许页面正确加载。正如一位用户提到的,浏览器并行执行多个连接,所以我认为发生的情况是所有请求都是通过多个连接发送的(查看我的程序的输出,似乎有 5 个连接到服务器)。由于 pthread_join 等待一个线程(连接)完成,一次只处理一个连接,这就是我没有收到所有请求的原因。
HTTP might be more complex than you think. Have you read its specification in full (RFC 7230 for HTTP 1.1) , or some book about HTTP ? Did you consider using some HTTP server library, such as libonion or libhttp (or libmicrohttpd or others)? The size of these libraries tells something about the complexity of HTTP! And these libraries are free software, so you can study their source code and take inspiration from them. (Your teacher should be delighted if you tell him honestly that you studied the source code of e.g. libonion
and have read RFC 7230).
顺便说一句,现代浏览器(最近的 Firefox 或 Chrome 等...)倾向于使用 多个 并行连接来显示一个页面。现代浏览器能够向您显示实际的 HTTP 流量和网络协议。
我的建议是使用一些现有的库。我很高兴使用 libonion
,即使它有一些限制。
最后,阅读how to debug small programs. Enable all warnings and debug info (so compile using gcc -Wall -Wextra -g
with a recent GCC, e.g. GCC 8 in end of 2018). Learn how to debug with GDB (and use also a recent one, GDB 8.2 in end of 2018). Use also valgrind and perhaps clang-analyzer。
I have a simple web server written in C
这是一种自相矛盾的说法。 Web 服务器要么不能简单,要么不能实现所有 HTTP。
您使用 sprintf
很危险(有 buffer overflow). I strongly recommend using snprintf 的风险。而且您的 404 处理看起来很糟糕。
另一个问题(我不知道它的答案)是,如果在您的特定情况下,对于某些特定的浏览器客户端,您可能会为您的特定实现一小部分 HTTP(对您的浏览器和客户端来说足够了)案例.