Ребята, поделитесь пожалуйста примером многопоточного 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, но нужно именно в такой архитектуре сделать.