对于我的班级,我们正在用 C 编写一个服务器。
此服务器必须有一些可用的网站,并接收和存储它通过 POST 接收的任何文件。我们已经有了网站的 GET 部分,但是当涉及到存储任何传入文件时,我们遇到了更多麻烦,我创建了一个解析器来解析请求。但是当请求的主体不是纯文本(而是图像或 pdf 文档)然后将其存储在请求结构的主体中(然后存储在具有相应的新文件中)时,我无法保存请求的主体扩展。
这是我的解析器的代码:
// Analiza la línea de solicitud HTTP para determinar el método, la ruta y el host
void parse_request_line(char *buffer, http_request *request)
{
char *method_end = strchr(buffer, ' ');
if (method_end == NULL)
{
request->method = UNSUPPORTED;
return;
}
size_t method_len = method_end - buffer;
if (strncmp(buffer, "GET", method_len) == 0)
{
request->method = GET;
}
else if (strncmp(buffer, "POST", method_len) == 0)
{
request->method = POST;
}
else if (strncmp(buffer, "PUT", method_len) == 0)
{
request->method = PUT;
}
else if (strncmp(buffer, "DELETE", method_len) == 0)
{
request->method = DELETE;
}
else
{
request->method = UNSUPPORTED;
}
printf("--> Método HTTP: %d\n", request->method);
char *uri_start = method_end + 1;
char *uri_end = strchr(uri_start, ' ');
if (uri_end == NULL)
{
request->method = UNSUPPORTED;
return;
}
size_t uri_len = uri_end - uri_start;
char *http_version_start = uri_end + 1;
if (http_version_start[0] != 'H' || http_version_start[1] != 'T' || http_version_start[2] != 'T' || http_version_start[3] != 'P' || http_version_start[4] != '/')
{
request->method = UNSUPPORTED;
printf("-->http_version_start no comenza con HTTP/. ");
return;
}
if (http_version_start[5] == '0')
{
strcpy(request->version, "HTTP/1.0");
printf("-->HTTP version 1.0 detectado. \n");
}
else if (http_version_start[5] == '1')
{
strcpy(request->version, "HTTP/1.1 ");
printf("-->HTTP version 1.1 detectado. \n");
}
else
{
request->method = UNSUPPORTED;
printf("--> No se puede detectar la version de HTTP o no esta soportado. \n");
return;
}
request->version[7] = '\0';
char *uri = strndup(uri_start, uri_len);
char *host_start = strstr(uri, "://");
if (host_start)
{
host_start += 3;
char *path_start = strchr(host_start, '/');
if (path_start)
{
*path_start = '\0';
path_start++;
request->host = strdup(host_start);
request->path = strdup(path_start);
}
else
{
request->host = strdup(host_start);
request->path = strdup("");
}
}
else
{
// Trata de obtener host desde antes
char *host_start = strstr(method_end, "Host: ");
if (host_start)
{
host_start += 6;
char *host_end = strstr(host_start, "\r\n");
size_t host_len = host_end - host_start;
request->host = strndup(host_start, host_len);
}
else
{
request->host = strdup("");
}
request->path = strdup(uri);
}
printf("->> Host: %s\n", request->host);
printf("->> Ruta: %s\n", request->path);
free(uri);
if (request->method == POST)
{
char *content_type_start = strstr(method_end, "Content-Type: ");
if (content_type_start != NULL)
{
content_type_start += 14;
char *content_type_end = strstr(content_type_start, "\r\n");
size_t content_type_len = content_type_end - content_type_start;
request->content_type = strndup(content_type_start, content_type_len);
// Detectar el tipo de archivo y asignar la extensión correspondiente
if (strncmp(request->content_type, "text/plain", 10) == 0) {
request->file_ext = strdup(".txt");
} else if (strncmp(request->content_type, "image/png", 9) == 0) {
request->file_ext = strdup(".png");
} else if (strncmp(request->content_type, "image/jpeg", 9) == 0) {
request->file_ext = strdup(".jpeg");
} else if (strncmp(request->content_type, "image/gif", 15) == 0) {
request->file_ext = strdup(".gif");
} else if (strncmp(request->content_type, "application/pdf", 15) == 0) {
request->file_ext = strdup(".pdf");
} else if (strncmp(request->content_type, "application/octet-stream", 24) == 0) {
request->file_ext = strdup(".exe");
} else {
request->file_ext = strdup(".dat");
}
}
char *content_length_start = strstr(method_end, "Content-Length: ");
if (content_length_start != NULL)
{
content_length_start += 16; // saltar los caracteres de "Content-Length:"
char *content_length_end = strstr(content_length_start, "\r\n");
size_t content_length_len = content_length_end - content_length_start;
char content_length_str[content_length_len + 1];
strncpy(content_length_str, content_length_start, content_length_len);
content_length_str[content_length_len] = '\0';
request->content_len = strtol(content_length_str, NULL, 10);
}
char *body_start = strstr(buffer, "\r\n\r\n");
if (body_start != NULL)
{
body_start += 4; // saltar los caracteres de separación
size_t buffer_len = strlen(buffer);
size_t body_len = buffer + buffer_len - body_start - 4;
request->body = (char*) malloc(body_len + 1); // incluir espacio adicional para NULL
memcpy(request->body, body_start, body_len);
request->body[body_len] = '\0'; // agregar byte NULL
// Generar el nombre del archivo a partir de la fecha y hora
time_t t = time(NULL);
struct tm tm = *localtime(&t);
char filename[100];
sprintf(filename, "%04d-%02d-%02d_%02d-%02d-%02d%s", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, request->file_ext);
// Guardar el archivo en disco
int fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
write(fd, request->body, request->content_len);
//write(fd, request->body, body_len);
close(fd);
}
printf("->> Body: %s\n", request->body);
}
printf("->> THE BUFFER: %s\n", buffer);
}
最重要的是身体状况之后的部分:
if (request->method == POST)
{
char *content_type_start = strstr(method_end, "Content-Type: ");
if (content_type_start != NULL)
{
content_type_start += 14;
char *content_type_end = strstr(content_type_start, "\r\n");
size_t content_type_len = content_type_end - content_type_start;
request->content_type = strndup(content_type_start, content_type_len);
// Detectar el tipo de archivo y asignar la extensión correspondiente
if (strncmp(request->content_type, "text/plain", 10) == 0) {
request->file_ext = strdup(".txt");
} else if (strncmp(request->content_type, "image/png", 9) == 0) {
request->file_ext = strdup(".png");
} else if (strncmp(request->content_type, "image/jpeg", 9) == 0) {
request->file_ext = strdup(".jpeg");
} else if (strncmp(request->content_type, "image/gif", 15) == 0) {
request->file_ext = strdup(".gif");
} else if (strncmp(request->content_type, "application/pdf", 15) == 0) {
request->file_ext = strdup(".pdf");
} else if (strncmp(request->content_type, "application/octet-stream", 24) == 0) {
request->file_ext = strdup(".exe");
} else {
request->file_ext = strdup(".dat");
}
}
char *content_length_start = strstr(method_end, "Content-Length: ");
if (content_length_start != NULL)
{
content_length_start += 16; // saltar los caracteres de "Content-Length:"
char *content_length_end = strstr(content_length_start, "\r\n");
size_t content_length_len = content_length_end - content_length_start;
char content_length_str[content_length_len + 1];
strncpy(content_length_str, content_length_start, content_length_len);
content_length_str[content_length_len] = '\0';
request->content_len = strtol(content_length_str, NULL, 10);
}
char *body_start = strstr(buffer, "\r\n\r\n");
if (body_start != NULL)
{
body_start += 4; // saltar los caracteres de separación
size_t buffer_len = strlen(buffer);
size_t body_len = buffer + buffer_len - body_start - 4;
request->body = (char*) malloc(body_len + 1); // incluir espacio adicional para NULL
memcpy(request->body, body_start, body_len);
request->body[body_len] = '\0'; // agregar byte NULL
// Generar el nombre del archivo a partir de la fecha y hora
time_t t = time(NULL);
struct tm tm = *localtime(&t);
char filename[100];
sprintf(filename, "%04d-%02d-%02d_%02d-%02d-%02d%s", tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, request->file_ext);
// Guardar el archivo en disco
int fd = open(filename, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR);
write(fd, request->body, request->content_len);
//write(fd, request->body, body_len);
close(fd);
}
printf("->> Body: %s\n", request->body);
}
我已经测试过,并且 http_request 结构被保存了,除了请求的主体。
当我尝试向服务器发送一个简单的 png 图片时,在控制台上可以看到:
pc@pc server % ./server
Usando assets de ./logs
Servidor iniciado en el puerto 8080...
--> Método HTTP: 1
-->HTTP version 1.1 detectado.
->> Host: 127.0.0.1:8080
->> Ruta: /test2
->> Body: �PNG
->> THE BUFFER: POST /test2 HTTP/1.1
User-Agent: PostmanRuntime/7.32.2
Accept: */*
Postman-Token: 13ebab0b-b17c-4a89-916c-d0889bacd9f9
Host: 127.0.0.1:8080
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Content-Length: 86054
Content-Type: image/png
�PNG
--Method: 1
--Host: 127.0.0.1:8080
--Path: /test2
--Body: �PNG
--Content-Length: 86054
--Content-Type: image/png
正如我们在以“--”开头的最后几行中看到的那样,除了请求的正文之外,数据被正确解析,我如何正确保存或解析请求的正文以便能够保存传入的文件正确吗?
我们可以对比POSTMAN请求和存储的数据,Content-Length和Type似乎是正确的,路径和方法也是可以的。这只是我难以管理的身体。我们还尝试使用 CURL 来保存二进制文件,但对于我们需要的东西来说它似乎太乱了。
我们只需要将人们发送给我们的任何文本/图像/pdf 存储在 POST 请求的正文中。
对不起任何拼写错误,我有点睡眠不足,但非常感谢任何帮助!!
总结:我们尝试将数据存储在请求的主体中,并使用 write 将其保存到新文件中,但是当请求是二进制文件而不是纯文本时,它没有正确保存。