savostin
@savostin
Еще один программист

Есть ли пример многопоточного HTTP клиента на OpenSSL?

Ребята, поделитесь пожалуйста примером многопоточного HTTP клиента на OpenSSL.
Интересует:
1. OpenSSL 1.1+ (там изменился API для многопоточности)
2. HTTP 1.1 (в частности Keep-alive)
3. Какие handle (ctx, ssl, bio) можно хранить глобально, а какие нужны для каждого thread?
4. Что и когда нужно лочить (CRYPTO_THREAD_write_lock) в thread? Сейчас лочу все, может можно лочить только момент connect?
5. Как определить, что клиент закрыл соединение и переконнектиться? Желательно до того, как писать в него.

Проверку сертификатов и пр. секьюрность для простоты опустим. В примере каждый thread коннектится к одному и тому же хосту, в действительности к разным. В пределах одного thread, естественно, соединение и хост постоянные.

Сейчас примерно так (для Windows, но только в плане создания thread):
CRYPTO_RWLOCK *lock;
SSL_CTX* ctx;
SSL* ssl; // может быть static?

static void init() {
SSL_library_init();
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();
lock = CRYPTO_THREAD_lock_new();
ctx = SSL_CTX_new(TLS_method());
SSL_CTX_set_options(ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3);
}

static void finish() {
CRYPTO_THREAD_lock_free(lock);
SSL_free(ssl);
SSL_CTX_free(ctx);
}


#define host "example.com"

DWORD WINAPI ThreadProc( LPVOID lpParameter) {
if(CRYPTO_THREAD_write_lock(lock)) { // лочим ctx
BIO* bio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(bio, &ssl); // сдается мне, что ssl должен быть свой для каждого connect, нет?
if (ssl) {
   SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);
   BIO_set_conn_hostname(bio, host);
   BIO_set_conn_port(bio, "443");
   SSL_set_tlsext_host_name(ssl, host);
   if (BIO_do_connect(bio) == ) { // коннектимся к хосту
      for(int i=0; i<5; ++i) { // для примера отправим последовательно 5 одинаковых запросов в одно соединение
         const char* buffer = "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: keep-alive\r\nAccept: */*\r\n\r\n";
         int r = 0;
         do { // отправляем запрос
            r = BIO_write(bio, buffer, strlen(buffer));
            if (r < 0) {
               if (!BIO_should_retry(bio)) {
                  CRYPTO_THREAD_unlock(lock);
                  return EXIT_FAILURE;
                 }
              }
          } while (r > 0 || BIO_should_retry(bio));

         http_parser parser; // парсер chunked ответа

         int r = 0, rall = 0;
         char buffer[4096];
         do { // читаем ответ
            r = BIO_read(bio, buffer, sizeof(buffer));
            if (r > 0) {
               rall += parser.Parse(buffer, r);
               }
            else if (r < 0) {
               if (!BIO_should_retry(bio))
                  CRYPTO_THREAD_unlock(lock);
                  return EXIT_FAILURE;
                 }
               }
            } while ((r > 0 || BIO_should_retry(bio)) && !parser.IsError() && !parser.IsFinished());
         }
      }
   CRYPTO_THREAD_unlock(lock); // может можно разлочить сразу после connect?
   }
   BIO_ssl_shutdown(bio); // закрываем соединение
   SSL_free(bio);
   }
return EXIT_SUCCESS;
}

#define THREADS 10


int main() {
init(); // иннициализация библиотеки
for( int i=0; i<THREADS; i++ ) { // создаем 10 потоков
     hThreadArray[i] = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
     }
WaitForMultipleObjects(THREADS, hThreadArray, TRUE, INFINITE); // ждем-с
for(int i=0; i<THREADS; i++) { // закрываем
        CloseHandle(hThreadArray[i]);
        }
finish(); // освобождаем ресурсы библиотеки
return EXIT_SUCCESS;
}

Может есть какие неточности в коде - это псевдокод.
P.S. знаю, что можно и лучше через неблокирующий I/O, но нужно именно в такой архитектуре сделать.
  • Вопрос задан
  • 193 просмотра
Пригласить эксперта
Ответы на вопрос 1
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы