Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Сети > POST запрос


Автор: rudolfninja 6.12.2015, 12:09
Ребята, приветствую.

Т.к. в теме сетевого программирования не совсем силен, то вопросы могут показаться глупыми.
В общем, проблема такая: мне надо из приложения отправлять post запрос на www.kinopoisk.ru с названием фильма, информацию о котором мне надо найти. А в ответ получить картинку фильма, год выпуска, режиссера, актеров и жанр.

Собственно, в чем пока что вопросы:
1) Как сформулировать этот запрос? Куда именно подставлять название фильма?
2) На кинопоиске если ввести название фильма (например Психо), то он покажет целый список результатов. Как в этом случае получить фильм, который первый в списке?

Спасибо.

Автор: volatile 6.12.2015, 12:39
Вообще это чистый  smile конечно smile 
Но я чуток в теме, поэтому отвечу (а модераторы пусть переносят  smile )

Цитата(rudolfninja @  6.12.2015,  12:09 Найти цитируемый пост)
POST запрос
там не нужен POST, сойдет обычный GET

Цитата(rudolfninja @  6.12.2015,  12:09 Найти цитируемый пост)
1) Как сформулировать этот запрос? Куда именно подставлять название фильма?

Код

"http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=" + urlencode(query);

Цитата(rudolfninja @  6.12.2015,  12:09 Найти цитируемый пост)
(например Психо
Код

http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE


Цитата(rudolfninja @  6.12.2015,  12:09 Найти цитируемый пост)
2) На кинопоиске если ввести название фильма (например Психо), то он покажет целый список результатов. Как в этом случае получить фильм, который первый в списке?

Ну собстенно, вышеприведенный запрос так и делает.



Автор: rudolfninja 6.12.2015, 12:59
Код

const char QUERY[] = "GET http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE";


Если отправить такой запрос, то вы итоге зависаю на получении ответа. Если без слова "GET", то получаю вот такой ответ:
Цитата

HTTP/1.1 400 Bad Request
Server: nginx/1.6.2
Date: Sun, 06 Dec 2015 09:58:37 GMT
Content-Type: text/html
Content-Length: 172
Connection: close


Переделал запрос так:
Код

const char HEAD_REQUEST_PART1[] =
{
    "GET http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE HTTP/1.1\r\n"            
    "Host: "                        
};
const char HEAD_REQUEST_PART2[] =
{
    "\r\n"                        
    "Connection: close\r\n"        
    "\r\n"                            
};


Потом отправляю его:

Код

// Отправляем запрос part 1
        if (send(hSocket, HEAD_REQUEST_PART1, sizeof(HEAD_REQUEST_PART1)-1, 0) == SOCKET_ERROR)
            throw HeadReqException("failed to send data.");

        // Отправляем имя хоста
        if (send(hSocket, pServername, strlen(pServername), 0) == SOCKET_ERROR)
            throw HeadReqException("failed to send data.");

        // Отправляем запрос part 2
        if (send(hSocket, HEAD_REQUEST_PART2, sizeof(HEAD_REQUEST_PART2)-1, 0) == SOCKET_ERROR)
            throw HeadReqException("failed to send data.");


В итоге такой ответ:
Цитата

HTTP/1.1 302 Found
Server: nginx/1.6.2
Date: Sun, 06 Dec 2015 10:07:56 GMT
Content-Type: text/html; charset=windows-1251
Content-Length: 0
Connection: close
Set-Cookie: PHPSESSID=f1043ff5ad06e75836f034c4cf673106; path=/; domain=kinopoisk
.ru; HttpOnly
Expires: 0
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Last-Modified: Sun, 06 Dec 2015 10:07:55 GMT
Set-Cookie: last_visit=2015-12-06+13%3A07%3A56; expires=Wed, 20-Jan-2016 10:07:5
6 GMT; path=/; domain=.kinopoisk.ru
Location: http://error.kinopoisk.ru/?ht=4

Автор: volatile 6.12.2015, 13:08
Цитата(rudolfninja @  6.12.2015,  12:59 Найти цитируемый пост)
const char QUERY[] = "GET http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE";
Если отправить такой запрос, то вы итоге зависаю на получении ответа. Если без слова "GET", 

rudolfninja, GET запрос, это просто ссылка
Вот http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE
Открывается у вас в браузере?

Если да, то тогда в чем проблема?
Ваша программа может открывать ссылки?? 

Автор: rudolfninja 6.12.2015, 13:45
Цитата(volatile @  6.12.2015,  13:08 Найти цитируемый пост)
Открывается у вас в браузере?

Открывается, но когда отправляю запрос программно, то вылетает ошибка, типа сильно много запросов с моего ip.

Моя программа получает ответ на запрос.

Автор: feodorv 6.12.2015, 14:14
Цитата(rudolfninja @  6.12.2015,  12:59 Найти цитируемый пост)
        // Отправляем имя хоста

Интересно, какое именно имя хоста Вы туда отправляете  smile 

Автор: volatile 6.12.2015, 14:18
Цитата(rudolfninja @  6.12.2015,  12:59 Найти цитируемый пост)
{
    "GET http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE HTTP/1.1\r\n"            
    "Host: "                        
};

ну там же не так, а как-то так
Код

    "GET /index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE HTTP/1.1\r\n"            
    "Host: www.kinopoisk.ru\r\n"




Автор: rudolfninja 6.12.2015, 16:31
Цитата(volatile @  6.12.2015,  14:18 Найти цитируемый пост)
ну там же не так, а как-то так

Ну так я потом исправился.

Автор: feodorv 6.12.2015, 17:30
Цитата(rudolfninja @  6.12.2015,  16:31 Найти цитируемый пост)
Ну так я потом исправился. 

Так каков в итоге HTTP-запрос?

Автор: rudolfninja 6.12.2015, 17:51
Код

const char HEAD_REQUEST_PART1[] =
{
    "GET http://www.kinopoisk.ru/index.php?first=yes&what=&kp_query=%D0%9F%D1%81%D0%B8%D1%85%D0%BE HTTP/1.1\r\n"            
    "Host: www.kinopoisk.ru\r\nConnection: close\r\n\r\n"                        
};

Автор: feodorv 6.12.2015, 19:10
Значит, на сайте стоит какая-то защита. Стер все свои куки. Браузер отправил такой запрос:
Цитата

GET /index.php?first=no&what=&kp_query=%EC%E8%F1%F1%E8%FF HTTP/1.1
Host: www.kinopoisk.ru
User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:42.0) Gecko/20100101 Firefox/42.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Referer: http://www.kinopoisk.ru/index.php?first=no&what=&kp_query=1%2B1
Connection: keep-alive


Получил такой ответ:
Цитата

HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Sun, 06 Dec 2015 15:56:11 GMT
Content-Type: text/html; charset=windows-1251
Content-Length: 16914
Connection: keep-alive
Keep-Alive: timeout=60
Set-Cookie: PHPSESSID=4cc547ca883bb80a31e7b4c1075b52fe; path=/; domain=kinopoisk.ru; HttpOnly
Expires: 0
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Last-Modified: Sun, 06 Dec 2015 15:56:10 GMT
Set-Cookie: last_visit=2015-12-06+18%3A56%3A10; expires=Wed, 20-Jan-2016 15:56:10 GMT; path=/; domain=.kinopoisk.ru
Set-Cookie: tc=1; expires=Sun, 06-Dec-2015 18:22:10 GMT; path=/; domain=.kinopoisk.ru
Set-Cookie: user_country=ru; expires=Sun, 06-Dec-2015 16:56:10 GMT; path=/; domain=.kinopoisk.ru
Set-Cookie: my_perpages=%5B%5D; expires=Mon, 05-Dec-2016 15:56:10 GMT; path=/; domain=.kinopoisk.ru
Content-Encoding: gzip


Далее шла загэзипленная страница. Сильное подозрение, что сайт работает только с зазипованным html текстом.

Автор: rudolfninja 6.12.2015, 20:30
Цитата(feodorv @  6.12.2015,  19:10 Найти цитируемый пост)
Далее шла загэзипленная страница

То есть, в ответе было содержимое этой страницы, которое можно как-то преобразовать в нормальный вид?

Сделал такой запрос:
Код

const char HEAD_REQUEST_PART1[] =
{
    "GET /index.php?first=no&what=&kp_query=%EC%E8%F1%F1%E8%FF HTTP/1.1\r\n" 
    "Host: www.kinopoisk.ru\r\n"
    "User-Agent: Mozilla/5.0 (Windows NT 5.1; rv:42.0) Gecko/20100101 Firefox/42.0\r\n"
    "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n"
    "Accept-Language: ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3\r\n"
    "Accept-Encoding: gzip, deflate\r\n"
    "Referer: http://www.kinopoisk.ru/index.php?first=no...;kp_query=1%2B1\r\n"
    "Connection: keep-alive\r\n"
};


В итоге повисаю на функции 
Код

retval = recv(hSocket, tempBuffer, sizeof(tempBuffer)-1, 0);

Может ли это быть потому, что данные слишком большие?

UPD:
Нашел в чем проблема была. Проблема была в том, что в конце HTTP запроса надо было вставить пустую строку.

Теперь ответ нормально выводится на экран (или в файл) только если я под отладчиком прохожу все итерации цикла приема сообщений. Без отладчика ничего не выводится (ни на экран, ни в файл). Как быть в этом случае? Создавать отдельный поток? 
И еще вопрос, что храниться в этом gzip сообщении и как его расшифровать?

Автор: rudolfninja 6.12.2015, 23:46
feodorv, а не подскажете еще, какими средствами вы узнали, что запрос будет именно такой?

Автор: feodorv 7.12.2015, 09:55
Цитата(rudolfninja @  6.12.2015,  23:46 Найти цитируемый пост)
feodorv, а не подскажете еще, какими средствами вы узнали, что запрос будет именно такой? 
С помощью https://www.wireshark.org/. Просто перехватил соответствующие пакеты smile 


Цитата(rudolfninja @  6.12.2015,  20:30 Найти цитируемый пост)
Без отладчика ничего не выводится (ни на экран, ни в файл). Как быть в этом случае? Создавать отдельный поток? 
Нужно смотреть код. Как-то странно это.


Цитата(rudolfninja @  6.12.2015,  20:30 Найти цитируемый пост)
Сделал такой запрос
Лучше сократить Referer до "http://www.kinopoisk.ru/" и попробовать сделать Connection "close" (с этим сайтом может и не сработать).


Цитата(rudolfninja @  6.12.2015,  20:30 Найти цитируемый пост)
что храниться в этом gzip сообщении и как его расшифровать?

Его надо распаковать, например, с помощью библиотеки http://www.zlib.net/.


Вообще, Вам нужен анализ принятых заголовков:
  • Первый заголовок должен давать код 200
  • Заголовок Content-Type должен давать "text/html" с кодировкой (она может быть и UTF-8).
  • Наличие заголовка Content-Encoding говорит о том, что пришедшие данные обработаны, как - указано в заголовке. В таком случае очень нужен заголовок Content-Length, который говорит о том, сколько, собственно, байт запакованных данных.

Автор: rudolfninja 7.12.2015, 10:51
Цитата(feodorv @  7.12.2015,  09:55 Найти цитируемый пост)
Нужно смотреть код

Вам нужен весь код или только того участка, где приосходит прием данных? Вечером, как буду дома, скину нужную часть кода (только сообщите, какая часть кода нужна =) )
По поводу всех остальных ваших замечаний понял. Иправлюсь.
Спасибо.

Автор: feodorv 7.12.2015, 15:45
Цитата(rudolfninja @  7.12.2015,  10:51 Найти цитируемый пост)
Вам нужен весь код или только того участка, где приосходит прием данных?

Хотелось бы весь код, работающий с сокетом smile 

Автор: rudolfninja 7.12.2015, 18:45
Код

// Создаем socket
        cout << "Creating socket... ";
        if ((hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
            throw HeadReqException("could not create socket.");
        cout << "created.\n";

        // Соединяемся с сервером
        cout << "Attempting to connect to " << inet_ntoa(sockAddr.sin_addr)
            << ":" << SERVER_PORT << "... ";
        if (connect(hSocket, reinterpret_cast<sockaddr*>(&sockAddr), sizeof(sockAddr)) != 0)
            throw HeadReqException("could not connect.");
        cout << "connected.\n";

        cout << "Sending request... ";

        // Отправляем запрос part 1
        if (send(hSocket, HEAD_REQUEST_PART1, sizeof(HEAD_REQUEST_PART1)-1, 0) == SOCKET_ERROR)
            throw HeadReqException("failed to send data.");

        cout << "request sent.\n";

        cout << "Dumping received data...\n\n";
        // Выводим все данные в цикле
        string sresult;
        int iResult = 0;
        u_long buffsize = 1024;
        char* buff = new char[buffsize];
        u_long arg;
        ofstream fout;
        fout.open("D:\\answer.txt");
        do
        {
            ioctlsocket(hSocket, FIONREAD, &arg);
            if (arg == 0)
                break;
            if (arg > buffsize)
                arg = buffsize;
            iResult = recv(hSocket, buff, arg, 0);
            if (iResult > 0)
            for (int i = 0; i < iResult; ++i)
                sresult.append(1, buff[i]);
            fout << sresult;
            sresult.clear();
        } while (iResult > 0);
        fout.close();
    }


Вот код отправки и получения данных. 
Еще заметил такую странность, что сегодня такой запрос не работает =) После
Код

ioctlsocket(hSocket, FIONREAD, &arg);

В arg 0.
Повторяется не стабильно. Где-то через раз.

Автор: feodorv 7.12.2015, 22:10
Поверхностный осмотр кода привел к таким выводам:
  • Сервер возвращает отнюдь не текст, а заголовки плюс поток байт. Поэтому приёмный файл нужно делать бинарным. От string отказываться, использовать fout.write().
  • Вызов ioctlsocket() может завершиться с ошибкой, этот случай в коде не реализован.
  • Для ожидания данных используется совсем другая схема. ioctlsocket() возвращает число байт, готовых для чтения из буфера приёма, но он может быть ещё пуст на момент проверки (данные не успели дойти, сервер тормозит с отправкой данных и т.д.). В вашем случае разумно использовать вызов http://www.opennet.ru/man.shtml?topic=select&category=2&russian=() (http://www.programmersforum.ru/showthread.php?t=65754). Кстати, какая у Вас ОС?

Автор: rudolfninja 7.12.2015, 22:44
feodorv, спасибо, вроде работает.
Гляньте, пожалуйста, код на наличие ошибок\недочетов.
Код

try
    {
        // Ищем имя хоста и заполняем структуру sockaddr_in:
        cout << "Looking up hostname " << pServername << "... ";
        FillSockAddr(&sockAddr, pServername, SERVER_PORT);
        cout << "found.\n";

        // Создаем socket
        cout << "Creating socket... ";
        if ((hSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET)
            throw HeadReqException("could not create socket.");
        cout << "created.\n";

        // Соединяемся с сервером
        cout << "Attempting to connect to " << inet_ntoa(sockAddr.sin_addr)
            << ":" << SERVER_PORT << "... ";
        if (connect(hSocket, reinterpret_cast<sockaddr*>(&sockAddr), sizeof(sockAddr)) != 0)
            throw HeadReqException("could not connect.");
        cout << "connected.\n";

        cout << "Sending request... ";

        // Отправляем запрос part 1
        if (send(hSocket, HEAD_REQUEST_PART1, sizeof(HEAD_REQUEST_PART1)-1, 0) == SOCKET_ERROR)
            throw HeadReqException("failed to send data.");

        cout << "request sent.\n";

        cout << "Dumping received data...\n\n";
        // Выводим все данные в цикле
        string sresult;
        timeval tv;  
        tv.tv_sec = 1; // устанавливаем время ожидания в 1 сек.
        tv.tv_usec = 0;
        int iResult = 0;
        fd_set rfds;        // проверяем наличие данных в сокете, установив файловый дескриптор сокета в множестве "на чтение"
        FD_ZERO(&rfds);    // очищаем множество "на чтение"
        FD_SET(hSocket, &rfds);    // помещаем дескриптор сокета в множество "на чтение"
        u_long buffsize = 1024;
        char* buff = new char[buffsize];
        u_long arg;
        ofstream fout;
        fout.open("D:\\answer.out", std::ios::binary);
        do
        {
            if (select(0, &rfds, 0, 0, &tv) > 0)
            {
                if (ioctlsocket(hSocket, FIONREAD, &arg) != SOCKET_ERROR)
                {

                    if (arg == 0)
                        break;
                    if (arg > buffsize)
                        arg = buffsize;
                    iResult = recv(hSocket, buff, arg, 0);
                    if (iResult > 0)
                        fout.write(buff, arg);
                    sresult.clear();
                }
            }
        } while (iResult > 0);
        fout.close();
        delete buff;
    }

Автор: feodorv 8.12.2015, 09:12
Цитата(rudolfninja @  7.12.2015,  22:44 Найти цитируемый пост)
Гляньте, пожалуйста, код на наличие ошибок\недочетов.
  • Сокет не закрывается.
  • Одной секунды таймаута явно мало, дайте хотя бы 30.
  • Вызов ioctlsocket(FIONREAD) вообще не нужен. Нужен recv и анализ возвращаемого значения.
  • Не понял, где фиксируется успех-неуспех (если неуспех, то файл надо даже удалить, если он создан).
  • Запрос не виден. Для Вас существенно будет, чтобы сервер закрывал соединение по окончании передачи страницы (поэтому нужен Connection как "close").
  • iResult - это число прочтенных из сокета байт, но записываете в файл Вы почему-то arg байт (могут не совпадать).
  • На всякий случай я бы сокету выставил SO_OOBINLINE.
  • Зачем для размера буфера тип u_long? Чем int не угодил?
  • Болтается ненужная переменная sresult.
Я бы сделал как бы так:
Код

bool success = true;
...
do
{
   int bytes;
   FD_ZERO( &rfds );
   FD_SET( hSocket, &rfds);
   int rv = select( 0, &rfds, 0, 0, &tv);

   if( rv == SOCKET_ERROR )
   {
      if( WSAGetLastError() == WSAEINTR ) continue;
      success = false; // ошибка вызова select()
   }
   else if( rv == 0 )
   {
      success = false; // таймаут
   }
   else
   {
      do
      {
          bytes = recv( hSocket, buff, buffsize, 0);
      } while( bytes == SOCKET_ERROR && WSAGetLastError() == WSAEINTR );

      if( bytes == SOCKET_ERROR )
      {
         success = false; // ошибка вызова recv
      }
      else if( bytes == 0 )
      {
         break; // сервер закрыл соединение, страница принята до конца
      }
      else
         fout.write( buff, bytes);
   }
} while( success );


Автор: feodorv 8.12.2015, 09:29
Если грузится библиотека WinSock второй версии, то от проверок EINTR можно отказаться.

Если таймаут не важен (но при этом сервер заставляем закрывать соединение), то всё ещё проще (можно и от select() отказаться):
Код

do
{
   int bytes = recv( hSocket, buff, buffsize, 0);

   if( bytes == SOCKET_ERROR )
   {
      success = false; // ошибка вызова recv
   }
   else if( bytes == 0 )
   {
      break; // сервер закрыл соединение, страница принята до конца
   }
   else
      fout.write( buff, bytes);
} while( success );


Добавлено через 14 минут и 15 секунд
А как Вы будете высвобождать память, выделенную под buff, если после возникнет исключение? Вообще, здесь смысла в выделении памяти мало, можно же просто
Код
char buff[1024];


Автор: rudolfninja 8.12.2015, 09:50
Спасибо за замечания. Исправлю.
Помечаю тему как решенную.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)