17

Как реализовать простой GET/POST запрос без использования сторонних библиотек?

Нашел много примеров, но там либо cpp-netlib, либо curl, а мне бы хотелось самому все это написать, но с сетью в С++ не работал, только через Qt. Было бы очень здорово увидеть маленький работающий пример :)

Abyx
  • 31,143
shotInLeg
  • 849
  • 4
    На чистом C++? Не смешите меня. Чистый C++ даже не имеет средств работы с консолью (keypressed и т. п.), потому что «а вдруг программа будет писаться под систему, на которой нету клавиатуры», так что уж на доступ к сети без библиотек не надейтесь. Пользуйтесь boost::asio, и будет вам счастье. – VladD Dec 25 '15 at 22:43
  • я знаю, что можно это реализовать через winsocket – shotInLeg Dec 25 '15 at 23:04
  • 3
    winsocket — это не чистый C++, это WinAPI (системная библиотека Windows). – VladD Dec 25 '15 at 23:10
  • Если даже socket API не доступно, то в баше достаточно специальное имя файла использовать /dev/tcp/$host/80 – jfs Dec 26 '15 at 00:03
  • @jfs: Но это POSIX-специфично, так что не пойдёт на Windows. – VladD Dec 26 '15 at 00:13
  • 2
    @VladD: как я уже явно упомянул это bash-специфично. Мне кажется, что просто POSIX не поддерживает это. Я не вижу упоминания Windows. – jfs Dec 26 '15 at 00:17

5 Answers5

11

Я решил проблему так:

#include <cstring>
#include <stdlib.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
#include <vector>
#include <string.h>
#include <assert.h>
#include <stdio.h>
#include <sstream>

char http[]={"http://"};
char host[]={"localhost"};
char parm[]={"/api/method.php"};


std::string getProtocol( std::string url )
{
    std::string protocol = "";

    int i = 0;

    for(i = 0; i < url.size(); i++)
    {
        if( url[i] != '/' || url[i+1] != '/'  )
        {
            protocol += url[i];
        }
        else
        {
            protocol += "//";
            break;
        }
    }

    return protocol;
}

std::string getHost( std::string url )
{
    std::string host = "";

    url.replace(0, getProtocol(url).size(), "");

    int i = 0;
    for(i = 0; i < url.size(); i++)
    {

        if( url[i] != '/' )
        {
            host += url[i];
        }
        else
        {
            break;
        }

    }

    return host;
}

std::string getAction( std::string url )
{
    std::string parm = "";

    url.replace(0, getProtocol(url).size()+getHost(url).size(), "");

    int i = 0;
    for(i = 0; i < url.size(); i++)
    {

        if( url[i] != '?' && url[i] != '#' )
        {
            parm += url[i];
        }
        else
        {
            break;
        }

    }

    return parm;
}

std::string getParams( std::vector< std::pair< std::string, std::string> > requestData )
{
    std::string parm = "";

    std::vector< std::pair< std::string, std::string> >::iterator itr = requestData.begin();

    for( ; itr != requestData.end(); ++itr )
    {
        if( parm.size() < 1 )
        {
            parm += "";
        }
        else
        {
            parm += "&";
        }
        parm += itr->first + "=" + itr->second;
    }

    return parm;
}


std::string GET( std::string url, std::vector< std::pair< std::string, std::string> > requestData )
{
    std::string http = getProtocol(url);
    std::string host = getHost(url);
    std::string script = getAction(url);
    std::string parm = getParams( requestData );

    char buf[1024];

    std::string header = "";

    header += "GET ";
    header += http + host + script + "?" + parm;
    header += (std::string)" HTTP/1.1" + "\r\n";
    header += (std::string)"Host: " + http + host + "/" + "\r\n";
    header += (std::string)"User-Agent: Mozilla/5.0" + "\r\n";
    //header += (std::string)"Accept: text/html" + "\r\n";
    header += (std::string)"Accept-Language: ru,en-us;q=0.7,en;q=0.3" + "\r\n";
    header += (std::string)"Accept-Charset: windows-1251,utf-8;q=0.7,*;q=0.7" + "\r\n";
    header += (std::string)"Connection: keep-alive " + "\r\n";
    header += "\r\n";



    int sock;
    struct sockaddr_in addr;
    struct hostent* raw_host;
    raw_host = gethostbyname( host.c_str() );
    if (raw_host == NULL)
    {
        std::cout<<"ERROR, no such host";
        exit(0);
    }

    sock = socket(AF_INET, SOCK_STREAM, 0);

    addr.sin_family = AF_INET;
    addr.sin_port = htons(80);

    bcopy( (char*)raw_host->h_addr, (char*)&addr.sin_addr, raw_host->h_length );

    if( connect( sock, (struct sockaddr *)&addr, sizeof(addr) ) < 0)
    {
        std::cerr<<"connect error"<<std::endl;
        exit(2);
    }


    char * message = new char[ header.size() ];
    for(int i = 0; i < header.size(); i++)
    {
        message[i] = header[i];
    }

    send(sock, message, header.size(), 0);
    recv(sock, buf, sizeof(buf), 0);

    std::string answer = "";

    for(int j = 0; j < 1024; j++)
    {
            answer += buf[j];
    }

    return answer;

}
Sergey Rufanov
  • 8,500
  • 25
  • 37
shotInLeg
  • 849
  • 4
    Кто же строки по значению передает, это же растрата! Инициализация строк кавычками - тоже ужас. Игнорирование итераторов - фактическая замена C++ на "C с потоками". И до кучи, приведение типов в стиле C. – gbg Jan 27 '16 at 06:26
  • 2
    Я вам советую использовать boost.asio как показано в примере Abyx – Мстислав Павлов Jan 27 '16 at 16:32
  • std::string - замедляет работу, из-за работы с кучей. Лучше было бы использовать char*, а ваше header += заменить на wsprintf либо на strcat. Из концептуальных ошибок - 1. у вас константное число параметров header, а в реальности нужно переменное число, т.к. могут быть кукисы, авторизация, и много другого. 2. Не обработан ответ. Мусор в ответе. Но... думаю пример сгодится, как для начинающего. – nick_n_a Jun 26 '19 at 06:47
  • Однако здесь используется масса сторонних библиотек. – user7860670 Feb 28 '24 at 17:50
10

На "чистом C++" много чего нельзя сделать, потому что стандартная библиотека языка слишком миниатюрная, она не включает достаточно функционала, чтобы работать с HTTP.

В какой-то мере "полустандартной" библиотекой является boost (какие-то его библиотеки позже становятся частью стандартной библиотеки), поэтому можно воспользоваться boost::asio. Однако учтите, что это всё равно достаточно низкоуровневая библиотека. Простой HTTP-запрос вы сможете написать в пару строчек, но вы будете вручную разруливать все сотни современных возможностей HTTP: безопасное соединение, сжатие траффика, кэширование и так далее и тому подобное. Я уж молчу про новую версию стандарта. И если вы можете сказать серверу "я не поддерживаю сжатие, давай данные как есть", то проигнорировать HTTPS вы ну никак не сможете.

Вы изобретёте велосипед, который никому не будет нужен. Вы станете лучше понимать потроха HTTP, конечно, но кроме как для обучения смысла писать такой код нет.

Если вам нужны маленькие и работающие примеры, то посмотрите Debunking Stroustrup's debunking of the myth “C++ is for large, complicated, programs only” — они там есть на всех языках, в том числе несколько на C++, а один из примеров написан самим Страуструпом.

Kyubey
  • 32,103
  • С сокетов уж точно не стоит начинать. Слишком низкоуровнево, это уровень TCP, а не HTTP. Сперва же лучше освоить HTTP (начать как минимум с WinInet - тоже часть WinAPI, но лучше с curl и прочего подобного, идеально - C# и .NET Framework [и снова я евангелист M$] а углубиться уже потом, с опытом), и освоить HTTP-сниффер хотя бы. Это и практичнее, чем голый TCP. Ну а "самый простейший" пример GET-запроса - это WinAPI-функция urldownloadtofile (скачиванием файла всегда есть GET-запрос) – Simus Jan 27 '16 at 03:10
  • Вот кто вообще такие ответы плюсует? Вопрос был, как самому сделать, для каких целей, вас не должно волновать. Скорее всего для обучения, и для этой цели как раз самому и надо написать код, кстати он не такой уж и сложный. А вы начинаете отговаривать человека, ну давайте тогда скажем что программировать вообще тяжко, и можно взять готовый сервер nginx например, ну или пойти работать блогером, а сложными вещами не забивать голову.Кстати сжатие и https добавляется довольно быстро, какой-нибудь крохотный wolfssl подключается запросто. Понятно что это будет проект только для себя. – F10PPY Nov 16 '20 at 19:43
  • @F10PPY Цели автора вопроса не имеют значения, этот вопрос в гугле будут находить все по запросу "запрос GET C++". Именно поэтому важно объяснение, почему, что и как делать не надо. – Kyubey Nov 16 '20 at 23:10
  • @Kyubey Не согласен. Человек ищет как ему сделать get на с++, приходит сюда, и вместо ответа, его просто отговаривают это делать. Лично я долго искал нормальные примеры реализации, в том числе, сервера на epoll, просто потому что почти никто это не делает. Может у человека дипломный проект или ещё что. Я считаю что нужно в первую очередь отвечать на вопросы, а советы давать уже в дополнение, вы вот дали ссылку, но там опять же с помощью библиотеки реализация. Ниже уже добавили код под линукс, вот пример под виндовс - https://stackoverflow.com/a/28369887/4739686 , не знаю почему это так сложно. – F10PPY Nov 17 '20 at 16:10
  • @F10PPY Человека не отговаривают сделать, человека отговаривают городить велосипед, потому что, приступая к его написанию, сложно оценить масштаб проблемы. Практическая часть ответа — "возьми любую нормальную библиотеку HTTP". Возможно, это надо как-то выделить жирным, а не подразумевать неявно. Последний параграф — самопиар и лулзы, что чести мне не делает, но преступлением не является. – Kyubey Nov 17 '20 at 20:33
8

На данный момент в стандарте С++ нет средств для работы с сетью.

При помощи библиотеки Boost.Asio (которую собираются включить в стандарт) это можно сделать буквально тремя строчками кода:

#define _WIN32_WINNT 0x0A00
#define BOOST_DATE_TIME_NO_LIB
#define BOOST_REGEX_NO_LIB

#include <boost/asio.hpp>
#include <iostream>

int main() {
  boost::asio::ip::tcp::iostream stream("httpbin.org", "http");
  stream << "GET /ip HTTP/1.1\r\nHost: httpbin.org\r\nConnection: close\r\n\r\n";
  std::cout << stream.rdbuf();
}

С использованием Boost.Beast:

#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <iostream>

int main() {
  boost::asio::io_service io_service;

  boost::asio::ip::tcp::resolver resolver(io_service);
  boost::asio::ip::tcp::resolver::query query("httpbin.org", "http");

  boost::asio::ip::tcp::socket socket(io_service);
  boost::asio::connect(socket, resolver.resolve(query));

  boost::beast::http::request<boost::beast::http::string_body> req(
      boost::beast::http::verb::get, "/headers", 11);
  req.set(boost::beast::http::field::host, "httpbin.org");
  req.set(boost::beast::http::field::user_agent, "MyAgent/1");
  req.set(boost::beast::http::field::connection, "Close");
  write(socket, req);

  boost::beast::flat_buffer buffer;
  boost::beast::http::response<boost::beast::http::string_body> reply;
  boost::beast::http::read(socket, buffer, reply);
  std::cout << reply.body();
}
Abyx
  • 31,143
6

Средствами чистого сиплюсплюса можно написать HTTP запрос, используя только iostream. А программу запускать, присоединив streams к netcat.

unix-way в чистом виде, если что.

gbg
  • 22,253
4

Можно на чистом с++ это сделать стандартными библиотеками #include <sys/socket.h> или # include <winsock2.h>. Конечно не ручаюсь, насколько это работает сейчас, но раньше на сокетах как раз можно было написать вполне вменяемы TCP_IP запросы. По первой либе здесь можно посмотреть пример

  • 6
    Упомянутые заголовочные файлы системозависимы и не являются стандартными. Их описания нет в стандартах C и C++. – αλεχολυτ Dec 26 '15 at 07:38
  • И что с того, все названия функций стандартные, для линя можно заголовки передефайнить.... – ilw Jun 15 '20 at 19:42