Я хочу написать C-программу для создания запроса Get без использования каких-либо внешних библиотек. Возможно ли использование только библиотек C с использованием сокетов? Я собираюсь создать http-пакет (используя правильное форматирование) и отправить его на сервер. Это единственный возможный путь или есть лучший способ?
Как сделать HTTP-запрос получения в C без libcurl?
Ответ 1
Используя сокеты BSD или, если вы несколько ограничены, скажите, что у вас есть RTOS, какой-то более простой стек TCP, например lwIP, вы можете сформировать запрос GET/POST.
Существует несколько версий с открытым исходным кодом. См. "Happyhttp" в качестве образца (http://scumways.com/happyhttp/happyhttp.html). Я знаю, это С++, а не C, но единственное, что является "С++-зависимым", - это управление строкой/массивом, поэтому его легко переносить в чистый C.
Остерегайтесь, нет "пакетов", поскольку HTTP обычно передается по TCP-соединению, поэтому технически в RFC-формате есть только поток символов. Поскольку HTTP-запросы обычно выполняются с помощью метода connect-send-disconnect, на самом деле можно назвать это "пакетом".
В принципе, если у вас есть открытый сокет (sockfd), "все", что вам нужно сделать, это что-то вроде
char sendline[MAXLINE + 1], recvline[MAXLINE + 1];
char* ptr;
size_t n;
/// Form request
snprintf(sendline, MAXSUB,
"GET %s HTTP/1.0\r\n" // POST or GET, both tested and works. Both HTTP 1.0 HTTP 1.1 works, but sometimes
"Host: %s\r\n" // but sometimes HTTP 1.0 works better in localhost type
"Content-type: application/x-www-form-urlencoded\r\n"
"Content-length: %d\r\n\r\n"
"%s\r\n", page, host, (unsigned int)strlen(poststr), poststr);
/// Write the request
if (write(sockfd, sendline, strlen(sendline))>= 0)
{
/// Read the response
while ((n = read(sockfd, recvline, MAXLINE)) > 0)
{
recvline[n] = '\0';
if(fputs(recvline,stdout) == EOF) { cout << ("fputs erros"); }
/// Remove the trailing chars
ptr = strstr(recvline, "\r\n\r\n");
// check len for OutResponse here ?
snprintf(OutResponse, MAXRESPONSE,"%s", ptr);
}
}
Ответ 2
POSIX 7 минимальный исполняемый пример
Пусть принесет http://example.com.
wget.c
#define _XOPEN_SOURCE 700
#include <arpa/inet.h>
#include <assert.h>
#include <netdb.h> /* getprotobyname */
#include <netinet/in.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
int main(int argc, char** argv) {
char buffer[BUFSIZ];
enum CONSTEXPR { MAX_REQUEST_LEN = 1024};
char request[MAX_REQUEST_LEN];
char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\n\r\n";
struct protoent *protoent;
char *hostname = "example.com";
in_addr_t in_addr;
int request_len;
int socket_file_descriptor;
ssize_t nbytes_total, nbytes_last;
struct hostent *hostent;
struct sockaddr_in sockaddr_in;
unsigned short server_port = 80;
if (argc > 1)
hostname = argv[1];
if (argc > 2)
server_port = strtoul(argv[2], NULL, 10);
request_len = snprintf(request, MAX_REQUEST_LEN, request_template, hostname);
if (request_len >= MAX_REQUEST_LEN) {
fprintf(stderr, "request length large: %d\n", request_len);
exit(EXIT_FAILURE);
}
/* Build the socket. */
protoent = getprotobyname("tcp");
if (protoent == NULL) {
perror("getprotobyname");
exit(EXIT_FAILURE);
}
socket_file_descriptor = socket(AF_INET, SOCK_STREAM, protoent->p_proto);
if (socket_file_descriptor == -1) {
perror("socket");
exit(EXIT_FAILURE);
}
/* Build the address. */
hostent = gethostbyname(hostname);
if (hostent == NULL) {
fprintf(stderr, "error: gethostbyname(\"%s\")\n", hostname);
exit(EXIT_FAILURE);
}
in_addr = inet_addr(inet_ntoa(*(struct in_addr*)*(hostent->h_addr_list)));
if (in_addr == (in_addr_t)-1) {
fprintf(stderr, "error: inet_addr(\"%s\")\n", *(hostent->h_addr_list));
exit(EXIT_FAILURE);
}
sockaddr_in.sin_addr.s_addr = in_addr;
sockaddr_in.sin_family = AF_INET;
sockaddr_in.sin_port = htons(server_port);
/* Actually connect. */
if (connect(socket_file_descriptor, (struct sockaddr*)&sockaddr_in, sizeof(sockaddr_in)) == -1) {
perror("connect");
exit(EXIT_FAILURE);
}
/* Send HTTP request. */
nbytes_total = 0;
while (nbytes_total < request_len) {
nbytes_last = write(socket_file_descriptor, request + nbytes_total, request_len - nbytes_total);
if (nbytes_last == -1) {
perror("write");
exit(EXIT_FAILURE);
}
nbytes_total += nbytes_last;
}
/* Read the response. */
fprintf(stderr, "debug: before first read\n");
while ((nbytes_total = read(socket_file_descriptor, buffer, BUFSIZ)) > 0) {
fprintf(stderr, "debug: after a read\n");
write(STDOUT_FILENO, buffer, nbytes_total);
}
fprintf(stderr, "debug: after last read\n");
if (nbytes_total == -1) {
perror("read");
exit(EXIT_FAILURE);
}
close(socket_file_descriptor);
exit(EXIT_SUCCESS);
}
Обобщение:
gcc -ggdb3 -std=c99 -Wall -Wextra -o wget wget.c
Получить http://example.com и вывести на стандартный вывод:
./wget example.com
Эта команда зависает для большинства серверов до истечения времени ожидания, и это ожидается:
- либо сервер, либо клиент должны закрыть соединение
- мы (клиент) этого не делаем
- большинство HTTP-серверов оставляют соединение открытым до истечения времени ожидания дальнейших запросов, например, JavaScript, CSS и изображения после HTML-страницы
- мы могли бы проанализировать ответ и закрыть, когда прочитаны байты Content-Length, но мы не сделали этого для простоты. Какие заголовки ответа HTTP требуются говорит, что если
Content-Length
не отправлено, сервер может просто закрыть, чтобы определить длину.
Однако мы могли бы закрыть хост, передав на сервер стандартный заголовок HTTP 1.1 Connection: close
:
char request_template[] = "GET / HTTP/1.1\r\nHost: %s\r\nConnection: close\r\n\r\n";
Часть соединения также работает с IP:
host example.com
дает:
example.com has address 93.184.216.34
example.com has IPv6 address 2606:2800:220:1:248:1893:25c8:1946
и так мы делаем:
./wget 93.184.216.34
однако ответ является ошибкой, поскольку мы неправильно устанавливаем Host:
в нашей программе, а это требуется в HTTP 1.1.
Проверено на Ubuntu 18.04.
Примеры серверов
- минимальный пример POSIX C: отправка и получение файла при программировании сокетов в Linux с C/C++ (GCC/G++)
- минимальный пример Android Java: как создать сокет-соединение в Android?
Ответ 3
Строго говоря, "без каких-либо внешних библиотек" исключает также libc, поэтому вам придется писать все системные вызовы самостоятельно. Я сомневаюсь, что ты имеешь в виду это так строго. Если вы не хотите ссылаться на другую библиотеку и не хотите копировать исходный код из другой библиотеки в свое приложение, тогда лучшим решением будет непосредственная работа с потоком TCP с использованием API сокетов.
Создать HTTP запрос и отправить его через сокет TCP очень просто, так же как и чтение ответа. Он анализирует ответ, который будет очень сложным, особенно если вы стремитесь поддерживать достаточно большую часть стандарта. Такие вещи, как страницы с ошибками, перенаправления, согласование содержимого и т.д., Могут сделать нашу жизнь довольно сложной, если вы говорите с произвольными веб-серверами. Если, с другой стороны, известно, что сервер ведет себя хорошо, и простое сообщение об ошибке подходит для любого неожиданного ответа сервера, то это также достаточно просто.