robust_client_socket 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/README.md ADDED
@@ -0,0 +1,732 @@
1
+ # RobustClientSocket
2
+
3
+ HTTP-клиент для защищённых межсервисных коммуникаций с автоматической генерацией токенов авторизации.
4
+
5
+ ## 📋 Содержание
6
+
7
+ - [Функции безопасности](#функции-безопасности)
8
+ - [Установка](#установка)
9
+ - [Конфигурация](#конфигурация)
10
+ - [Использование](#использование)
11
+ - [Методы HTTP](#методы-http)
12
+ - [Обработка ошибок](#обработка-ошибок)
13
+ - [SSL/TLS настройки](#ssltls-настройки)
14
+ - [Рекомендации по использованию](#рекомендации-по-использованию)
15
+ - [Безопасность в production](#безопасность-в-production)
16
+
17
+ ## 🔒 Функции безопасности
18
+
19
+ RobustClientSocket обеспечивает защищённую коммуникацию между микросервисами:
20
+
21
+ ### 1. Автоматическая токенизация
22
+ - **Генерация токенов на лету**: Каждый запрос автоматически получает новый токен
23
+ - **Временные метки**: Токены содержат UTC timestamp для защиты от replay attacks
24
+ - **Одноразовые токены**: Каждый запрос использует уникальный токен
25
+
26
+ ### 2. RSA шифрование
27
+ - **RSA-2048 минимум**: Автоматическая валидация размера ключа
28
+ - **PKCS1_OAEP_PADDING**: Безопасный padding для защиты от атак
29
+ - **Валидация ключей**: Проверка корректности публичных ключей при конфигурации
30
+
31
+ ### 3. TLS/SSL защита
32
+ - **TLS 1.2+**: Современные протоколы шифрования
33
+ - **Проверка сертификатов**: VERIFY_PEER режим для production
34
+ - **Настраиваемые cipher suites**: Поддержка ECDHE для forward secrecy
35
+ - **HTTPS enforcement**: Обязательное использование HTTPS в production
36
+
37
+ ### 4. Защита заголовков
38
+ - **Кастомные заголовки**: Настраиваемое имя заголовка для токена
39
+ - **User-Agent идентификация**: Версионирование клиента
40
+ - **Content-Type контроль**: Автоматические заголовки для JSON
41
+
42
+ ### 5. Защита от timeout атак
43
+ - **Connection timeout**: Настраиваемый таймаут подключения (default: 5s)
44
+ - **Request timeout**: Настраиваемый таймаут запроса (default: 10s)
45
+ - **Защита от зависания**: Автоматическое прерывание долгих запросов
46
+
47
+ ### 6. Мультисервисная архитектура
48
+ - **Keychain управление**: Отдельные ключи для каждого сервиса
49
+ - **Автоматическая генерация клиентов**: Динамическое создание классов для сервисов
50
+ - **Изоляция конфигураций**: Независимые настройки для каждого сервиса
51
+
52
+ ## 📦 Установка
53
+
54
+ Добавьте в Gemfile:
55
+
56
+ ```ruby
57
+ gem 'robust_client_socket'
58
+ ```
59
+
60
+ Затем выполните:
61
+
62
+ ```bash
63
+ bundle install
64
+ ```
65
+
66
+ ## ⚙️ Конфигурация
67
+
68
+ Создайте файл `config/initializers/robust_client_socket.rb`:
69
+
70
+ ```ruby
71
+ RobustClientSocket.configure do |c|
72
+ # ОБЯЗАТЕЛЬНО: Имя текущего сервиса (клиента)
73
+ # Должно совпадать с allowed_services на сервере
74
+ c.client_name = 'core'
75
+
76
+ # ОПЦИОНАЛЬНО: Имя заголовка для токена (default: 'Secure-Token')
77
+ c.header_name = 'Secure-Token'
78
+
79
+ # KEYCHAIN: Конфигурация для каждого целевого сервиса
80
+
81
+ # Базовая конфигурация (без SSL валидации)
82
+ c.payments = {
83
+ base_uri: 'http://localhost:3001',
84
+ public_key: ENV['PAYMENTS_PUBLIC_KEY']
85
+ }
86
+
87
+ # Production конфигурация с SSL
88
+ c.notifications = {
89
+ base_uri: 'https://notifications.example.com',
90
+ public_key: ENV['NOTIFICATIONS_PUBLIC_KEY'],
91
+ ssl_verify: true, # Включить проверку SSL сертификатов
92
+ timeout: 15, # Таймаут запроса (секунды)
93
+ open_timeout: 5 # Таймаут подключения (секунды)
94
+ }
95
+
96
+ # Конфигурация с кастомными cipher suites
97
+ c.core = {
98
+ base_uri: 'https://core.example.com',
99
+ public_key: ENV['CORE_PUBLIC_KEY'],
100
+ ssl_verify: true,
101
+ ciphers: %w[
102
+ ECDHE-RSA-AES128-GCM-SHA256
103
+ ECDHE-RSA-AES256-GCM-SHA384
104
+ ECDHE-ECDSA-AES128-GCM-SHA256
105
+ ECDHE-ECDSA-AES256-GCM-SHA384
106
+ ]
107
+ }
108
+ end
109
+
110
+ # Загрузка конфигурации и создание клиентов
111
+ RobustClientSocket.load!
112
+ ```
113
+
114
+ ### Опции конфигурации сервиса
115
+
116
+ | Параметр | Тип | Обязательный | Default | Описание |
117
+ |----------|-----|--------------|---------|----------|
118
+ | `base_uri` | String | ✅ | - | URL целевого сервиса |
119
+ | `public_key` | String | ✅ | - | Публичный RSA ключ сервиса |
120
+ | `ssl_verify` | Boolean | ❌ | false | Включить проверку SSL сертификатов |
121
+ | `timeout` | Integer | ❌ | 10 | Таймаут запроса (секунды) |
122
+ | `open_timeout` | Integer | ❌ | 5 | Таймаут подключения (секунды) |
123
+ | `ciphers` | Array/String | ❌ | См. ниже | TLS cipher suites |
124
+
125
+ **Default cipher suites:**
126
+ ```
127
+ ECDHE-RSA-AES128-GCM-SHA256
128
+ ECDHE-RSA-AES256-GCM-SHA384
129
+ ECDHE-ECDSA-AES128-GCM-SHA256
130
+ ECDHE-ECDSA-AES256-GCM-SHA384
131
+ ```
132
+
133
+ ## 🚀 Использование
134
+
135
+ ### Автоматическая генерация клиентов
136
+
137
+ После `RobustClientSocket.load!` автоматически создаются классы для каждого сервиса в keychain:
138
+
139
+ ```ruby
140
+ # Конфигурация
141
+ c.payments = { base_uri: '...', public_key: '...' }
142
+ c.notifications = { base_uri: '...', public_key: '...' }
143
+ c.user_management = { base_uri: '...', public_key: '...' }
144
+
145
+ # После load! доступны классы:
146
+ RobustClientSocket::Payments # payments -> Payments
147
+ RobustClientSocket::Notifications # notifications -> Notifications
148
+ RobustClientSocket::UserManagement # user_management -> UserManagement
149
+ ```
150
+
151
+ ### Базовое использование
152
+
153
+ ```ruby
154
+ # GET запрос
155
+ response = RobustClientSocket::Payments.get('/api/v1/transactions')
156
+
157
+ if response.success?
158
+ transactions = response.parsed_response
159
+ puts "Получено транзакций: #{transactions.count}"
160
+ else
161
+ puts "Ошибка: #{response.code} - #{response.message}"
162
+ end
163
+
164
+ # POST запрос с телом
165
+ response = RobustClientSocket::Notifications.post(
166
+ '/api/v1/send',
167
+ body: {
168
+ user_id: 123,
169
+ message: 'Hello World',
170
+ type: 'email'
171
+ }.to_json
172
+ )
173
+
174
+ # PUT запрос
175
+ response = RobustClientSocket::UserManagement.put(
176
+ '/api/v1/users/123',
177
+ body: { name: 'John Doe' }.to_json
178
+ )
179
+
180
+ # DELETE запрос
181
+ response = RobustClientSocket::Payments.delete('/api/v1/transactions/456')
182
+
183
+ # PATCH запрос
184
+ response = RobustClientSocket::UserManagement.patch(
185
+ '/api/v1/users/123',
186
+ body: { status: 'active' }.to_json
187
+ )
188
+ ```
189
+
190
+ ### Query параметры
191
+
192
+ ```ruby
193
+ # GET с query параметрами
194
+ response = RobustClientSocket::Payments.get(
195
+ '/api/v1/transactions',
196
+ query: {
197
+ status: 'completed',
198
+ date_from: '2024-01-01',
199
+ limit: 100
200
+ }
201
+ )
202
+
203
+ # Автоматически преобразуется в:
204
+ # /api/v1/transactions?status=completed&date_from=2024-01-01&limit=100
205
+ ```
206
+
207
+ ### Кастомные заголовки
208
+
209
+ ```ruby
210
+ # Добавление дополнительных заголовков
211
+ response = RobustClientSocket::Payments.get(
212
+ '/api/v1/transactions',
213
+ headers: {
214
+ 'X-Request-ID' => SecureRandom.uuid,
215
+ 'X-User-ID' => current_user.id.to_s
216
+ }
217
+ )
218
+
219
+ # Secure-Token заголовок добавляется автоматически!
220
+ ```
221
+
222
+ ### Обработка ответов
223
+
224
+ ```ruby
225
+ response = RobustClientSocket::Payments.get('/api/v1/balance')
226
+
227
+ # Проверка успешности
228
+ if response.success?
229
+ # 2xx статус коды
230
+ data = response.parsed_response
231
+ puts "Balance: #{data['amount']}"
232
+ end
233
+
234
+ # Доступ к деталям
235
+ response.code # HTTP статус код (Integer)
236
+ response.message # HTTP статус сообщение (String)
237
+ response.body # Сырое тело ответа (String)
238
+ response.parsed_response # Распарсенный JSON (Hash)
239
+ response.headers # Заголовки ответа (Hash)
240
+
241
+ # Проверка конкретных статусов
242
+ if response.code == 200
243
+ # OK
244
+ elsif response.code == 404
245
+ # Not Found
246
+ elsif response.code == 401
247
+ # Unauthorized - возможно истёк токен
248
+ elsif response.code == 429
249
+ # Rate limit exceeded
250
+ end
251
+ ```
252
+
253
+ ## 🌐 Методы HTTP
254
+
255
+ Все стандартные HTTP методы поддерживаются:
256
+
257
+ ### GET
258
+ ```ruby
259
+ RobustClientSocket::ServiceName.get(path, options = {})
260
+ ```
261
+
262
+ ### POST
263
+ ```ruby
264
+ RobustClientSocket::ServiceName.post(path, options = {})
265
+ ```
266
+
267
+ ### PUT
268
+ ```ruby
269
+ RobustClientSocket::ServiceName.put(path, options = {})
270
+ ```
271
+
272
+ ### DELETE
273
+ ```ruby
274
+ RobustClientSocket::ServiceName.delete(path, options = {})
275
+ ```
276
+
277
+ ### PATCH
278
+ ```ruby
279
+ RobustClientSocket::ServiceName.patch(path, options = {})
280
+ ```
281
+
282
+ ### HEAD
283
+ ```ruby
284
+ RobustClientSocket::ServiceName.head(path, options = {})
285
+ ```
286
+
287
+ ### OPTIONS
288
+ ```ruby
289
+ RobustClientSocket::ServiceName.options(path, options = {})
290
+ ```
291
+
292
+ ### Опции запроса
293
+
294
+ ```ruby
295
+ {
296
+ body: '{"key": "value"}', # Тело запроса (String или Hash)
297
+ query: { param: 'value' }, # Query параметры (Hash)
298
+ headers: { 'X-Custom': 'value' }, # Дополнительные заголовки (Hash)
299
+ timeout: 30, # Переопределить таймаут запроса
300
+ open_timeout: 10 # Переопределить таймаут подключения
301
+ }
302
+ ```
303
+
304
+ ## ❌ Обработка ошибок
305
+
306
+ ### Типы исключений
307
+
308
+ | Исключение | Причина | Действие |
309
+ |-----------|---------|----------|
310
+ | `InsecureConnectionError` | HTTP используется в production с `ssl_verify: true` | Используйте HTTPS |
311
+ | `InvalidCredentialsError` | Отсутствуют `base_uri` или `public_key` | Проверьте конфигурацию |
312
+ | `SecurityError` | Ключ меньше 2048 бит или невалиден | Используйте корректный RSA-2048+ ключ |
313
+ | `OpenSSL::PKey::RSAError` | Ошибка шифрования | Проверьте формат публичного ключа |
314
+ | `Timeout::Error` | Превышен таймаут | Увеличьте timeout или проверьте сервис |
315
+ | `SocketError` | Сервис недоступен | Проверьте base_uri и сеть |
316
+
317
+ ### Обработка ошибок
318
+
319
+ ```ruby
320
+ begin
321
+ response = RobustClientSocket::Payments.post(
322
+ '/api/v1/charge',
323
+ body: { amount: 1000 }.to_json
324
+ )
325
+
326
+ if response.success?
327
+ # Успешно
328
+ elsif response.code == 401
329
+ # Проблема авторизации
330
+ Rails.logger.error("Auth failed: token may be rejected by server")
331
+ elsif response.code == 422
332
+ # Validation error
333
+ errors = response.parsed_response['errors']
334
+ elsif response.code >= 500
335
+ # Server error
336
+ Rails.logger.error("Server error: #{response.code}")
337
+ end
338
+
339
+ rescue RobustClientSocket::HTTP::Client::InsecureConnectionError => e
340
+ Rails.logger.error("Insecure connection: #{e.message}")
341
+ # Используйте HTTPS в production
342
+
343
+ rescue Timeout::Error => e
344
+ Rails.logger.error("Request timeout: #{e.message}")
345
+ # Retry или обработка таймаута
346
+
347
+ rescue SocketError => e
348
+ Rails.logger.error("Service unavailable: #{e.message}")
349
+ # Сервис недоступен
350
+
351
+ rescue SecurityError => e
352
+ Rails.logger.error("Security error: #{e.message}")
353
+ # Проблема с ключами или шифрованием
354
+
355
+ rescue StandardError => e
356
+ Rails.logger.error("Unexpected error: #{e.class} - #{e.message}")
357
+ end
358
+ ```
359
+
360
+ ### Retry стратегия
361
+
362
+ ```ruby
363
+ def call_with_retry(max_attempts: 3, backoff: 2)
364
+ attempts = 0
365
+
366
+ begin
367
+ attempts += 1
368
+ response = RobustClientSocket::Payments.get('/api/v1/status')
369
+
370
+ return response if response.success?
371
+
372
+ # Retry на 5xx и таймаутах
373
+ if response.code >= 500 && attempts < max_attempts
374
+ sleep(backoff ** attempts)
375
+ retry
376
+ end
377
+
378
+ response
379
+
380
+ rescue Timeout::Error, SocketError => e
381
+ if attempts < max_attempts
382
+ sleep(backoff ** attempts)
383
+ retry
384
+ else
385
+ raise
386
+ end
387
+ end
388
+ end
389
+ ```
390
+
391
+ ## 🔐 SSL/TLS настройки
392
+
393
+ ### Production конфигурация
394
+
395
+ ```ruby
396
+ RobustClientSocket.configure do |c|
397
+ c.client_name = 'core'
398
+
399
+ c.payments = {
400
+ base_uri: 'https://payments.example.com',
401
+ public_key: ENV['PAYMENTS_PUBLIC_KEY'],
402
+
403
+ # Включить SSL валидацию
404
+ ssl_verify: true,
405
+
406
+ # TLS 1.2+ с безопасными ciphers
407
+ ciphers: %w[
408
+ ECDHE-RSA-AES128-GCM-SHA256
409
+ ECDHE-RSA-AES256-GCM-SHA384
410
+ ]
411
+ }
412
+ end
413
+ ```
414
+
415
+ ### Рекомендуемые cipher suites
416
+
417
+ **High Security (рекомендуется):**
418
+ ```ruby
419
+ ciphers: %w[
420
+ ECDHE-RSA-AES128-GCM-SHA256
421
+ ECDHE-RSA-AES256-GCM-SHA384
422
+ ECDHE-ECDSA-AES128-GCM-SHA256
423
+ ECDHE-ECDSA-AES256-GCM-SHA384
424
+ ]
425
+ ```
426
+
427
+ **Balanced (совместимость + безопасность):**
428
+ ```ruby
429
+ ciphers: %w[
430
+ ECDHE-RSA-AES128-GCM-SHA256
431
+ ECDHE-RSA-AES256-GCM-SHA384
432
+ AES128-GCM-SHA256
433
+ AES256-GCM-SHA384
434
+ ]
435
+ ```
436
+
437
+ ### Проверка SSL в разных окружениях
438
+
439
+ ```ruby
440
+ c.payments = {
441
+ base_uri: ENV['PAYMENTS_URL'],
442
+ public_key: ENV['PAYMENTS_PUBLIC_KEY'],
443
+ # Включать SSL только в production
444
+ ssl_verify: Rails.env.production?
445
+ }
446
+ ```
447
+
448
+ ## 💡 Рекомендации по использованию
449
+
450
+ ### 1. Управление ключами
451
+
452
+ **✅ DO:**
453
+ ```ruby
454
+ # Храните ключи в переменных окружения
455
+ c.payments = {
456
+ base_uri: ENV['PAYMENTS_URL'],
457
+ public_key: ENV['PAYMENTS_PUBLIC_KEY']
458
+ }
459
+
460
+ # Используйте secrets management
461
+ c.payments = {
462
+ base_uri: 'https://payments.example.com',
463
+ public_key: Rails.application.credentials.dig(:payments, :public_key)
464
+ }
465
+
466
+ # Один файл для всех публичных ключей
467
+ # config/public_keys/payments.pem
468
+ c.payments = {
469
+ base_uri: ENV['PAYMENTS_URL'],
470
+ public_key: File.read(Rails.root.join('config/public_keys/payments.pem'))
471
+ }
472
+ ```
473
+
474
+ **❌ DON'T:**
475
+ ```ruby
476
+ # НЕ коммитьте ключи в git
477
+ c.payments = {
478
+ public_key: "-----BEGIN PUBLIC KEY-----\nMII..."
479
+ }
480
+
481
+ # НЕ используйте один ключ для всех сервисов
482
+ ```
483
+
484
+ ### 2. Настройка таймаутов
485
+
486
+ **Рекомендации по таймаутам:**
487
+
488
+ ```ruby
489
+ # Быстрые операции (чтение)
490
+ c.cache_service = {
491
+ base_uri: 'https://cache.example.com',
492
+ public_key: ENV['CACHE_PUBLIC_KEY'],
493
+ timeout: 3, # 3 секунды
494
+ open_timeout: 1 # 1 секунда
495
+ }
496
+
497
+ # Стандартные операции
498
+ c.api_service = {
499
+ base_uri: 'https://api.example.com',
500
+ public_key: ENV['API_PUBLIC_KEY'],
501
+ timeout: 10, # 10 секунд (default)
502
+ open_timeout: 5 # 5 секунд (default)
503
+ }
504
+
505
+ # Долгие операции (обработка, экспорт)
506
+ c.processor = {
507
+ base_uri: 'https://processor.example.com',
508
+ public_key: ENV['PROCESSOR_PUBLIC_KEY'],
509
+ timeout: 60, # 60 секунд
510
+ open_timeout: 10 # 10 секунд
511
+ }
512
+ ```
513
+
514
+ ### 3. Логирование и мониторинг
515
+
516
+ ```ruby
517
+ # Wrapper для логирования всех запросов
518
+ module RobustClientSocketLogger
519
+ def self.call(service_name, method, path, options = {})
520
+ start_time = Time.now
521
+
522
+ response = RobustClientSocket.const_get(service_name).send(method, path, options)
523
+
524
+ duration = ((Time.now - start_time) * 1000).round(2)
525
+
526
+ Rails.logger.info(
527
+ "RobustClientSocket Request: " \
528
+ "service=#{service_name} method=#{method} path=#{path} " \
529
+ "status=#{response.code} duration=#{duration}ms"
530
+ )
531
+
532
+ # Метрики
533
+ Metrics.timing("robust_client.#{service_name}.#{method}", duration)
534
+ Metrics.increment("robust_client.#{service_name}.status.#{response.code}")
535
+
536
+ response
537
+ rescue StandardError => e
538
+ Rails.logger.error(
539
+ "RobustClientSocket Error: " \
540
+ "service=#{service_name} method=#{method} path=#{path} " \
541
+ "error=#{e.class} message=#{e.message}"
542
+ )
543
+
544
+ Metrics.increment("robust_client.#{service_name}.error.#{e.class.name}")
545
+ raise
546
+ end
547
+ end
548
+
549
+ # Использование
550
+ response = RobustClientSocketLogger.call(
551
+ 'Payments',
552
+ :post,
553
+ '/api/v1/charge',
554
+ body: { amount: 1000 }.to_json
555
+ )
556
+ ```
557
+
558
+ ### 4. Синхронизация с сервером
559
+
560
+ ```ruby
561
+ # Клиент
562
+ RobustClientSocket.configure do |c|
563
+ c.client_name = 'core' # ← Важно: имя текущего сервиса
564
+
565
+ c.payments = {
566
+ base_uri: 'https://payments.example.com',
567
+ public_key: '-----BEGIN PUBLIC KEY-----...' # Публичный ключ PAYMENTS сервиса
568
+ }
569
+ end
570
+
571
+ # Сервер (payments)
572
+ RobustServerSocket.configure do |c|
573
+ c.allowed_services = %w[core] # ← Должно содержать 'core'
574
+ c.private_key = '-----BEGIN PRIVATE KEY-----...' # Приватная пара к публичному ключу выше
575
+ end
576
+ ```
577
+
578
+ ### 5. Тестирование
579
+
580
+ ```ruby
581
+ # spec/support/robust_client_socket.rb
582
+ RSpec.configure do |config|
583
+ config.before(:suite) do
584
+ RobustClientSocket.configure do |c|
585
+ c.client_name = 'test_service'
586
+
587
+ c.payments = {
588
+ base_uri: 'http://localhost:3001',
589
+ public_key: File.read(Rails.root.join('spec/fixtures/keys/payments_public.pem'))
590
+ }
591
+ end
592
+
593
+ RobustClientSocket.load!
594
+ end
595
+ end
596
+
597
+ # Тест с WebMock
598
+ require 'webmock/rspec'
599
+
600
+ RSpec.describe 'Payments integration' do
601
+ before do
602
+ stub_request(:post, "http://localhost:3001/api/v1/charge")
603
+ .to_return(status: 200, body: { success: true }.to_json)
604
+ end
605
+
606
+ it 'creates charge' do
607
+ response = RobustClientSocket::Payments.post(
608
+ '/api/v1/charge',
609
+ body: { amount: 1000 }.to_json
610
+ )
611
+
612
+ expect(response.success?).to be true
613
+ expect(response.parsed_response['success']).to be true
614
+ end
615
+ end
616
+ ```
617
+
618
+ ## 🔐 Безопасность в Production
619
+
620
+ ### Чеклист безопасности
621
+
622
+ - [ ] **Публичные ключи хранятся в secrets manager**
623
+ - [ ] **Использованы RSA-2048 или выше ключи**
624
+ - [ ] **ssl_verify: true для production**
625
+ - [ ] **HTTPS используется для всех сервисов**
626
+ - [ ] **client_name синхронизирован с allowed_services серверов**
627
+ - [ ] **Настроены разумные таймауты**
628
+ - [ ] **Безопасные cipher suites настроены**
629
+ - [ ] **Логирование всех запросов включено**
630
+ - [ ] **Метрики собираются**
631
+ - [ ] **Retry логика реализована для критичных запросов**
632
+
633
+ ### Мониторинг
634
+
635
+ ```ruby
636
+ # Метрики для отслеживания
637
+ # - Количество запросов по сервисам
638
+ # - Время ответа (percentiles: p50, p95, p99)
639
+ # - Количество ошибок по типам
640
+ # - Количество таймаутов
641
+ # - Количество retry попыток
642
+
643
+ class RobustClientMetrics
644
+ def self.track(service, method, path)
645
+ start = Time.now
646
+ response = yield
647
+ duration = ((Time.now - start) * 1000).round(2)
648
+
649
+ # StatsD/Prometheus metrics
650
+ Metrics.timing("robust_client.request.duration", duration, tags: [
651
+ "service:#{service}",
652
+ "method:#{method}",
653
+ "status:#{response.code}"
654
+ ])
655
+
656
+ Metrics.increment("robust_client.request.count", tags: [
657
+ "service:#{service}",
658
+ "method:#{method}",
659
+ "status:#{response.code}"
660
+ ])
661
+
662
+ response
663
+ rescue StandardError => e
664
+ Metrics.increment("robust_client.error.count", tags: [
665
+ "service:#{service}",
666
+ "error:#{e.class.name}"
667
+ ])
668
+ raise
669
+ end
670
+ end
671
+ ```
672
+
673
+ ## 🤝 Интеграция с RobustServerSocket
674
+
675
+ ### Полный пример настройки
676
+
677
+ **Сервис A (client):**
678
+ ```ruby
679
+ # config/initializers/robust_client_socket.rb
680
+ RobustClientSocket.configure do |c|
681
+ c.client_name = 'service_a'
682
+
683
+ c.service_b = {
684
+ base_uri: ENV['SERVICE_B_URL'],
685
+ public_key: ENV['SERVICE_B_PUBLIC_KEY'],
686
+ ssl_verify: Rails.env.production?
687
+ }
688
+ end
689
+
690
+ RobustClientSocket.load!
691
+ ```
692
+
693
+ **Сервис B (server):**
694
+ ```ruby
695
+ # config/initializers/robust_server_socket.rb
696
+ RobustServerSocket.configure do |c|
697
+ c.allowed_services = %w[service_a] # Разрешить service_a
698
+ c.private_key = ENV['SERVICE_B_PRIVATE_KEY']
699
+ c.token_expiration_time = 3
700
+ c.redis_url = ENV['REDIS_URL']
701
+ c.redis_pass = ENV['REDIS_PASSWORD']
702
+ end
703
+
704
+ RobustServerSocket.load!
705
+ ```
706
+
707
+ **Генерация пары ключей:**
708
+ ```bash
709
+ # Генерация приватного ключа (для Service B)
710
+ openssl genrsa -out service_b_private.pem 2048
711
+
712
+ # Генерация публичного ключа (для Service A)
713
+ openssl rsa -in service_b_private.pem -pubout -out service_b_public.pem
714
+
715
+ # Добавить в переменные окружения
716
+ # Service A: SERVICE_B_PUBLIC_KEY=$(cat service_b_public.pem)
717
+ # Service B: SERVICE_B_PRIVATE_KEY=$(cat service_b_private.pem)
718
+ ```
719
+
720
+ ## 📚 Дополнительные ресурсы
721
+
722
+ - [RobustServerSocket documentation](../robust_server_socket/README.ru.md)
723
+ - [HTTParty documentation](https://github.com/jnunemaker/httparty)
724
+ - [OpenSSL Ruby documentation](https://ruby-doc.org/stdlib/libdoc/openssl/rdoc/OpenSSL.html)
725
+
726
+ ## 📝 Лицензия
727
+
728
+ См. файл [LICENSE.txt](LICENSE.txt)
729
+
730
+ ## 🐛 Баги и предложения
731
+
732
+ Сообщайте о проблемах через issue tracker вашего репозитория.