2012-06-26 3 views
26

Я хочу написать программу на C, чтобы сгенерировать запрос Get без использования каких-либо внешних библиотек. Возможно ли использование только библиотек C с использованием сокетов? Я собираюсь создать http-пакет (используя правильное форматирование) и отправить его на сервер. Это единственный возможный путь или есть лучший способ?HTTP get Request using C WITHOUT libCurl

+0

Нет, вы должны научиться BSD-сокетов API, а затем вручную упаковать вместе все исходные данные. –

ответ

22

Использование сокетов BSD или, если вы несколько ограничены, скажем, что у вас есть несколько RTOS, более простой стек TCP, например lwIP, вы можете сформировать запрос GET/POST.

Существует множество версий с открытым исходным кодом. См. «Happyhttp» в качестве образца (http://scumways.com/happyhttp/happyhttp.html). Я знаю, это C++, а не C, но единственное, что является «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); 
    }   
} 
+0

Спасибо! Это сделало то, что мне было нужно! – asudhak

+3

@asudhak - Это отлично работает, пока этот код не должен запускаться в корпоративной рабочей среде, где доступ через Интернет осуществляется через прокси-сервер. Протокол для получения URL-адреса через HTTP-прокси немного отличается от прямого TCP. – selbie

+0

@selbie - Конечно, ответы HTTP с кодом 300 (перенаправления) и прокси-файлы - это именно то, что затрудняет HTTP. Таким образом, tayloring libCurl, чтобы исключить разные криптосвязанные вещи, может быть способом, а не обработанным вручную HTTP-запросом. –

3

«Без каких-либо внешних библиотек», строго говоря, исключает Libc, так что вы бы нужно писать все syscalls самостоятельно. Я сомневаюсь, что вы имеете в виду это строго. Если вы не хотите ссылаться на другую библиотеку и не хотите копировать исходный код из другой библиотеки в ваше приложение, то ваш лучший подход к прямому взаимодействию с потоком TCP с помощью API сокетов.

Создание запроса HTTP и отправка его по адресу TCP socket connection легко, как читает ответ. Он анализирует ответ, который будет действительно сложным, особенно если вы намерены поддерживать достаточно значительную часть стандарта. Такие вещи, как страницы ошибок, переадресации, согласование контента и т. Д., Могут усложнить нашу жизнь, если вы разговариваете с произвольными веб-серверами. Если, с другой стороны, сервер, как известно, хорошо себя ведет, а простое сообщение об ошибке подходит для любого неожиданного ответа сервера, то это также достаточно просто.

7

POSIX 7 минимален работоспособный пример

#define _XOPEN_SOURCE 700 

#include <assert.h> 
#include <stdbool.h> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#include <arpa/inet.h> 
#include <netdb.h> /* getprotobyname */ 
#include <netinet/in.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. 
    * 
    * The second read hangs for a few seconds, until the server times out. 
    * 
    * Either server or client has to close the connection. 
    * 
    * We are not doing it, and neither is the server, likely to make serving the page faster 
    * to allow fetching HTML, CSS, Javascript and images in a single connection. 
    * 
    * The solution is to parse Content-Length to see if the HTTP response is over, 
    * and close it then. 
    * 
    * http://stackoverflow.com/a/25586633/895245 says that if Content-Length 
    * is not sent, the server can just close to determine length. 
    **/ 
    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); 
} 

Использование

Compile:

gcc -o wget wget.c 

Получить http://example.com и выход на стандартный вывод:

./wget example.com 

IP:

./wget 104.16.118.182 

Эта команда зависании для большинства серверов, пока тайм-аут, и что ожидается:

  • либо сервер или клиент должен закрыть соединение
  • большинство серверов HTTP оставить соединение открывать до истечения времени ожидания, ожидая дальнейших запросов, напримерJavaScript, CSS и изображения после HTML страницы
  • мы могли разобрать ответ, и закрываются, когда Content-Length байт для чтения, но мы не сделали для простоты

Испытано на Ubuntu 15.10.

стороне сервера Пример по адресу: Send and Receive a file in socket programming in Linux with C/C++ (GCC/G++)

GitHub вверх по течению: https://github.com/cirosantilli/cpp-cheat/blob/88d0c30681114647cce456c2e17aa2c5b31abcd0/posix/socket/wget.c

+0

Код висит на 'read (socket_file_descriptor, buffer, BUFSIZ)'. – CroCo

+0

@CroCo см. Комментарий к источнику: «второе чтение зависает в течение нескольких секунд. [...]». Любой сервер или клиент должен закрыть соединение. Мы не закрываем, поэтому ни один сервер. Это, вероятно, оптимизирует несколько HTTP-запросов, выполненных в одном соединении, что является обычным случаем (получить HTML, получить CSS, получить изображения). Клиенты обычно должны разбирать выходные данные и проверять, что ответ завершен и закрыт с помощью 'Content-Length:' в случае HTTP, но я не хотел анализировать HTTP в этом простом примере. –