rack-ai 0.1.0 → 0.2.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c647f66a3eae25ff6af97c25cbb4dcee2a5fc55c959e04f8b441e40e01c3622
4
- data.tar.gz: 485eebfb792effaa6ea9ae8878f3a81e06cb4520728c7df55b81817254bc0cd7
3
+ metadata.gz: 1d95d49cfff0cb1d7f62fb2f31680b6b2ce139ef8c2bf87c4bb86e5a5123c876
4
+ data.tar.gz: 05ed2583e65ef1fa68dbb502bafcc4046ed67495b049ed1ffc41e4ed7240f283
5
5
  SHA512:
6
- metadata.gz: f1aef3c9f81e822afb3e140063bb919d33c0bacf1a3ac988e6e8a236292d8f1c0b2fb8ca642d25ea6ab4864b4cd79dffbc49b1c67bfbd5c4d9b7e4d4145f3670
7
- data.tar.gz: 10de1bd82c8ca26da97490da3bd13bff8b7cdf5a2a52107507d81c674b926aae7f3bbbbf75cc0631b73f874147492b8e20ff9f44578ce7c777a133a4366891d1
6
+ metadata.gz: 3fc2d6b2dcd73a946e20de45c66cf92bcf65c342f434adf939d79b1b732a4b16d42d4faa9f04b28c4dcc116baf3168c1cc014d29d7a7035b55bea915be441cda
7
+ data.tar.gz: 8fbe16fb2da9b93e3ff3896e923c3e3832f69319f4bff630fb68fcab16530e70e0fb95b293a00559bb9ec95cb87a707b8cd69b5fa45398817ecbdacbe1beac2a
data/CHANGELOG.md CHANGED
@@ -7,43 +7,67 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
- ## [0.1.0] - 2024-09-11
10
+ ### Added
11
+ - Rate limiting feature with configurable windows and thresholds
12
+ - Anomaly detection with baseline learning and risk scoring
13
+ - Enhanced logging system with JSON/structured output formats
14
+ - Comprehensive example application demonstrating all features
15
+ - Advanced error handling and edge case coverage
16
+ - Performance optimizations and memory management
17
+
18
+ ### Changed
19
+ - Removed OStruct dependency to eliminate Ruby 3.5+ warnings
20
+ - Improved configuration system with better validation
21
+ - Enhanced middleware initialization and provider setup
22
+ - Better test coverage and mocking strategies
23
+
24
+ ### Fixed
25
+ - Configuration validation issues
26
+ - Provider initialization edge cases
27
+ - Test suite stability and reliability
28
+ - Memory leaks in long-running applications
29
+
30
+ ## [0.1.0] - 2025-01-11
11
31
 
12
32
  ### Added
13
- - Initial release of Rack::AI middleware
14
- - Core AI-powered features:
15
- - Request classification (human, bot, spam, suspicious)
16
- - Content moderation with toxicity detection
17
- - Security analysis and threat detection
18
- - Smart caching with predictive capabilities
19
- - Intelligent routing based on AI analysis
20
- - Enhanced logging with AI insights
21
- - Content enhancement (SEO, readability, accessibility)
22
- - Multi-provider support:
23
- - OpenAI integration
24
- - HuggingFace integration
25
- - Local AI model support
26
- - Comprehensive configuration system:
27
- - Ruby DSL configuration
28
- - Environment-specific settings
29
- - Feature toggles and thresholds
30
- - Production-ready features:
31
- - Fail-safe mode for reliability
32
- - Async processing for performance
33
- - Data sanitization for security
34
- - Comprehensive error handling
35
- - Framework integrations:
36
- - Rails middleware integration
37
- - Sinatra application examples
38
- - Custom Rack application support
39
- - Monitoring and observability:
40
- - Built-in metrics collection
41
- - Prometheus format export
42
- - Structured logging
43
- - Performance benchmarks
44
- - Developer experience:
45
- - Comprehensive test suite
46
- - Performance benchmarks
33
+ - Initial release with core AI middleware functionality
34
+ - OpenAI, HuggingFace, and Local provider support
35
+ - AI-powered request classification (human, bot, spam, suspicious)
36
+ - Security analysis with injection detection and threat assessment
37
+ - Content moderation using AI models
38
+ - Smart caching with predictive algorithms
39
+ - Flexible configuration system using Dry::Configurable
40
+ - Fail-safe operation with graceful degradation
41
+ - Async processing for non-blocking AI analysis
42
+ - Comprehensive logging and monitoring
43
+ - Health check endpoints
44
+ - Complete test suite with WebMock integration
45
+ - Documentation and usage examples
46
+
47
+ ### Features
48
+ - **Request Classification**: Automatically classify incoming requests using AI
49
+ - **Security Analysis**: Detect SQL injection, XSS, CSRF, and other threats
50
+ - **Content Moderation**: Filter inappropriate content with configurable thresholds
51
+ - **Rate Limiting**: Protect against abuse with intelligent rate limiting
52
+ - **Anomaly Detection**: Identify unusual patterns and potential attacks
53
+ - **Smart Caching**: Predictive caching based on AI analysis patterns
54
+ - **Multi-Provider Support**: OpenAI, HuggingFace, and local model integration
55
+ - **Async Processing**: Non-blocking AI analysis for optimal performance
56
+ - **Fail-Safe Mode**: Graceful handling of AI service outages
57
+ - **Comprehensive Logging**: Structured logging with multiple output formats
58
+
59
+ ### Providers
60
+ - **OpenAI**: GPT-3.5/4 integration for classification and moderation
61
+ - **HuggingFace**: Transformer models for various AI tasks
62
+ - **Local**: Support for self-hosted AI models and services
63
+
64
+ ### Configuration
65
+ - Flexible feature pipeline configuration
66
+ - Provider-specific settings and API keys
67
+ - Timeout and retry mechanisms
68
+ - Security and sanitization options
69
+ - Performance tuning parameters
70
+ - Logging and monitoring controls
47
71
  - Detailed documentation
48
72
  - Integration examples
49
73
 
@@ -0,0 +1,203 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require 'rack'
5
+ require 'rack/ai'
6
+
7
+ # Comprehensive example showing all Rack::AI features
8
+ class ComprehensiveApp
9
+ def self.app
10
+ Rack::Builder.new do
11
+ # Configure Rack::AI with all features enabled
12
+ use Rack::AI::Middleware,
13
+ provider: :openai,
14
+ features: [:classification, :security, :rate_limiting, :anomaly_detection, :moderation],
15
+ fail_safe: true,
16
+ async_processing: false, # Synchronous for demo
17
+ explain_decisions: true,
18
+
19
+ # OpenAI Configuration
20
+ openai: {
21
+ api_key: ENV['OPENAI_API_KEY'] || 'your-api-key-here',
22
+ timeout: 30,
23
+ retries: 3
24
+ },
25
+
26
+ # Classification settings
27
+ classification: {
28
+ confidence_threshold: 0.8,
29
+ categories: [:human, :bot, :spam, :suspicious]
30
+ },
31
+
32
+ # Security settings
33
+ security: {
34
+ injection_detection: true,
35
+ anomaly_threshold: 0.7,
36
+ suspicious_patterns: [
37
+ /\b(union|select|insert|delete|drop)\b/i,
38
+ /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/mi,
39
+ /javascript:/i
40
+ ]
41
+ },
42
+
43
+ # Rate limiting settings
44
+ rate_limiting: {
45
+ window_size: 3600, # 1 hour
46
+ max_requests: 1000,
47
+ block_duration: 3600
48
+ },
49
+
50
+ # Anomaly detection settings
51
+ anomaly_detection: {
52
+ sensitivity: 0.8,
53
+ risk_threshold_block: 80
54
+ },
55
+
56
+ # Moderation settings
57
+ moderation: {
58
+ toxicity_threshold: 0.8,
59
+ check_response: false,
60
+ block_on_violation: true
61
+ }
62
+
63
+ # Main application
64
+ run lambda { |env|
65
+ request = Rack::Request.new(env)
66
+
67
+ # Get AI analysis results
68
+ ai_results = env['rack.ai'][:results]
69
+
70
+ # Build response based on AI analysis
71
+ response_data = {
72
+ message: "Request processed successfully",
73
+ path: request.path,
74
+ method: request.request_method,
75
+ ai_analysis: {
76
+ classification: ai_results[:classification],
77
+ security: ai_results[:security],
78
+ rate_limiting: ai_results[:rate_limiting],
79
+ anomaly_detection: ai_results[:anomaly_detection],
80
+ moderation: ai_results[:moderation]
81
+ },
82
+ processing_time: env['rack.ai'][:processing_time],
83
+ timestamp: Time.now.iso8601
84
+ }
85
+
86
+ # Return JSON response
87
+ [
88
+ 200,
89
+ {
90
+ 'Content-Type' => 'application/json',
91
+ 'X-Powered-By' => 'Rack::AI'
92
+ },
93
+ [response_data.to_json]
94
+ ]
95
+ }
96
+ end
97
+ end
98
+ end
99
+
100
+ # Demo routes for testing different scenarios
101
+ class DemoRoutes
102
+ def self.app
103
+ Rack::Builder.new do
104
+ use Rack::AI::Middleware,
105
+ provider: :openai,
106
+ features: [:classification, :security, :rate_limiting],
107
+ openai: { api_key: ENV['OPENAI_API_KEY'] }
108
+
109
+ map '/api/safe' do
110
+ run lambda { |env|
111
+ [200, { 'Content-Type' => 'application/json' },
112
+ [{ message: 'Safe endpoint', status: 'ok' }.to_json]]
113
+ }
114
+ end
115
+
116
+ map '/api/suspicious' do
117
+ run lambda { |env|
118
+ # This might trigger security features
119
+ query = Rack::Request.new(env).params['q']
120
+ if query&.include?('DROP TABLE')
121
+ [400, {}, ['Bad request']]
122
+ else
123
+ [200, { 'Content-Type' => 'application/json' },
124
+ [{ message: 'Processed query', query: query }.to_json]]
125
+ end
126
+ }
127
+ end
128
+
129
+ map '/api/bulk' do
130
+ run lambda { |env|
131
+ # This might trigger rate limiting
132
+ [200, { 'Content-Type' => 'application/json' },
133
+ [{ message: 'Bulk operation completed' }.to_json]]
134
+ }
135
+ end
136
+
137
+ map '/health' do
138
+ run lambda { |env|
139
+ # Health check endpoint (might be excluded from AI processing)
140
+ [200, { 'Content-Type' => 'application/json' },
141
+ [{ status: 'healthy', timestamp: Time.now.iso8601 }.to_json]]
142
+ }
143
+ end
144
+
145
+ # Default route
146
+ run lambda { |env|
147
+ ai_results = env['rack.ai'][:results]
148
+
149
+ [200, { 'Content-Type' => 'text/html' }, [<<~HTML
150
+ <!DOCTYPE html>
151
+ <html>
152
+ <head>
153
+ <title>Rack::AI Demo</title>
154
+ <style>
155
+ body { font-family: Arial, sans-serif; margin: 40px; }
156
+ .ai-results { background: #f5f5f5; padding: 20px; border-radius: 8px; }
157
+ .feature { margin: 10px 0; padding: 10px; border-left: 4px solid #007cba; }
158
+ pre { background: #333; color: #fff; padding: 15px; border-radius: 4px; overflow-x: auto; }
159
+ </style>
160
+ </head>
161
+ <body>
162
+ <h1>🤖 Rack::AI Demo Application</h1>
163
+ <p>This request was analyzed by Rack::AI middleware.</p>
164
+
165
+ <div class="ai-results">
166
+ <h2>AI Analysis Results:</h2>
167
+ #{ai_results.map { |feature, result|
168
+ "<div class='feature'><strong>#{feature.to_s.capitalize}:</strong> #{result[:action]} (confidence: #{result[:confidence] || 'N/A'})</div>"
169
+ }.join}
170
+ </div>
171
+
172
+ <h2>Test Endpoints:</h2>
173
+ <ul>
174
+ <li><a href="/api/safe">Safe API endpoint</a></li>
175
+ <li><a href="/api/suspicious?q=SELECT * FROM users">Suspicious query</a></li>
176
+ <li><a href="/api/bulk">Bulk operation (rate limiting test)</a></li>
177
+ <li><a href="/health">Health check</a></li>
178
+ </ul>
179
+
180
+ <h2>Raw AI Results:</h2>
181
+ <pre>#{JSON.pretty_generate(ai_results)}</pre>
182
+ </body>
183
+ </html>
184
+ HTML
185
+ ]]
186
+ }
187
+ end
188
+ end
189
+ end
190
+
191
+ if __FILE__ == $0
192
+ puts "🚀 Starting Rack::AI Demo Server..."
193
+ puts "Features: Classification, Security, Rate Limiting, Anomaly Detection"
194
+ puts "Visit: http://localhost:9292"
195
+ puts "Press Ctrl+C to stop"
196
+
197
+ # Use the comprehensive app
198
+ Rack::Handler::WEBrick.run(
199
+ ComprehensiveApp.app,
200
+ Port: 9292,
201
+ Host: '0.0.0.0'
202
+ )
203
+ end
@@ -1,8 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dry-configurable"
4
- require "dry-validation"
5
- require "ostruct"
3
+ require "dry/configurable"
4
+ require "dry/validation"
6
5
 
7
6
  module Rack
8
7
  module AI
@@ -16,8 +15,8 @@ module Rack
16
15
  setting :timeout, default: 30
17
16
  setting :retries, default: 3
18
17
 
19
- # Feature toggles
20
- setting :features, default: [:classification, :moderation]
18
+ # Features
19
+ setting :features, default: [:classification, :security]
21
20
  setting :fail_safe, default: true
22
21
  setting :async_processing, default: true
23
22
 
@@ -55,6 +54,24 @@ module Rack
55
54
  setting :redis_url, default: "redis://localhost:6379"
56
55
  end
57
56
 
57
+ # Rate limiting configuration
58
+ setting :rate_limiting do
59
+ setting :window_size, default: 3600 # 1 hour in seconds
60
+ setting :max_requests, default: 1000 # max requests per window
61
+ setting :block_duration, default: 3600 # block duration in seconds
62
+ setting :cleanup_interval, default: 300 # cleanup old entries every 5 minutes
63
+ end
64
+
65
+ # Anomaly detection configuration
66
+ setting :anomaly_detection do
67
+ setting :sensitivity, default: 0.8 # Detection sensitivity (0-1)
68
+ setting :baseline_window, default: 86400 # 24 hours for baseline calculation
69
+ setting :min_requests_for_baseline, default: 100
70
+ setting :risk_threshold_monitor, default: 20
71
+ setting :risk_threshold_challenge, default: 50
72
+ setting :risk_threshold_block, default: 80
73
+ end
74
+
58
75
  setting :routing do
59
76
  setting :smart_routing_enabled, default: true
60
77
  setting :suspicious_route, default: "/captcha"
@@ -89,28 +106,28 @@ module Rack
89
106
  @explain_decisions = false
90
107
 
91
108
  # Initialize nested configurations
92
- @classification = OpenStruct.new(
109
+ @classification = {
93
110
  confidence_threshold: 0.8,
94
111
  categories: [:spam, :bot, :human, :suspicious]
95
- )
112
+ }
96
113
 
97
- @moderation = OpenStruct.new(
114
+ @moderation = {
98
115
  toxicity_threshold: 0.7,
99
116
  check_response: false,
100
117
  block_on_violation: true
101
- )
118
+ }
102
119
 
103
- @caching = OpenStruct.new(
120
+ @caching = {
104
121
  predictive_enabled: true,
105
122
  prefetch_threshold: 0.9,
106
123
  redis_url: "redis://localhost:6379"
107
- )
124
+ }
108
125
 
109
- @routing = OpenStruct.new(
126
+ @routing = {
110
127
  smart_routing_enabled: true,
111
128
  suspicious_route: "/captcha",
112
129
  bot_route: "/api/bot"
113
- )
130
+ }
114
131
  end
115
132
 
116
133
  def classification
@@ -195,13 +212,16 @@ module Rack
195
212
  end
196
213
 
197
214
  def provider_config
198
- {
199
- provider: @provider,
200
- api_key: @api_key,
201
- api_url: @api_url,
202
- timeout: @timeout,
203
- retries: @retries
204
- }
215
+ case @provider
216
+ when :openai
217
+ { api_key: @api_key, api_url: @api_url, timeout: @timeout, retries: @retries }
218
+ when :huggingface
219
+ { api_key: @api_key, api_url: @api_url, timeout: @timeout, retries: @retries }
220
+ when :local
221
+ { api_url: @api_url, timeout: @timeout, retries: @retries }
222
+ else
223
+ {}
224
+ end
205
225
  end
206
226
  end
207
227
  end
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module AI
5
+ module Features
6
+ class AnomalyDetection
7
+ attr_reader :name, :provider, :config
8
+
9
+ def initialize(provider, config)
10
+ @name = :anomaly_detection
11
+ @provider = provider
12
+ @config = config
13
+ @baseline_metrics = {}
14
+ @request_patterns = {}
15
+ end
16
+
17
+ def enabled?
18
+ @config.feature_enabled?(:anomaly_detection)
19
+ end
20
+
21
+ def process_response?
22
+ false
23
+ end
24
+
25
+ def process_request(env)
26
+ current_time = Time.now
27
+ client_ip = get_client_ip(env)
28
+
29
+ # Extract request metrics
30
+ metrics = extract_request_metrics(env)
31
+
32
+ # Update baseline and detect anomalies
33
+ anomalies = detect_anomalies(client_ip, metrics, current_time)
34
+
35
+ # Calculate risk score
36
+ risk_score = calculate_risk_score(anomalies, metrics)
37
+
38
+ # Determine action based on risk score
39
+ action = determine_action(risk_score)
40
+
41
+ {
42
+ action: action,
43
+ feature: :anomaly_detection,
44
+ timestamp: current_time.iso8601,
45
+ risk_score: risk_score,
46
+ anomalies: anomalies,
47
+ metrics: metrics,
48
+ baseline_deviation: calculate_baseline_deviation(metrics)
49
+ }
50
+ end
51
+
52
+ private
53
+
54
+ def get_client_ip(env)
55
+ env['HTTP_X_FORWARDED_FOR']&.split(',')&.first&.strip ||
56
+ env['HTTP_X_REAL_IP'] ||
57
+ env['REMOTE_ADDR'] ||
58
+ 'unknown'
59
+ end
60
+
61
+ def extract_request_metrics(env)
62
+ {
63
+ request_size: calculate_request_size(env),
64
+ header_count: env.select { |k, _| k.start_with?('HTTP_') }.size,
65
+ path_length: env['PATH_INFO']&.length || 0,
66
+ query_complexity: calculate_query_complexity(env['QUERY_STRING']),
67
+ user_agent_entropy: calculate_entropy(env['HTTP_USER_AGENT']),
68
+ request_frequency: calculate_request_frequency(get_client_ip(env)),
69
+ time_of_day: Time.now.hour,
70
+ day_of_week: Time.now.wday
71
+ }
72
+ end
73
+
74
+ def calculate_request_size(env)
75
+ size = 0
76
+ size += env['CONTENT_LENGTH'].to_i if env['CONTENT_LENGTH']
77
+ size += env['QUERY_STRING']&.length || 0
78
+ env.each { |k, v| size += k.length + v.to_s.length if k.start_with?('HTTP_') }
79
+ size
80
+ end
81
+
82
+ def calculate_query_complexity(query_string)
83
+ return 0 unless query_string
84
+
85
+ params = query_string.split('&')
86
+ complexity = params.size
87
+
88
+ # Add complexity for nested parameters, arrays, etc.
89
+ params.each do |param|
90
+ complexity += 1 if param.include?('[')
91
+ complexity += 1 if param.include?('%')
92
+ complexity += param.scan(/[=&]/).size
93
+ end
94
+
95
+ complexity
96
+ end
97
+
98
+ def calculate_entropy(string)
99
+ return 0 unless string
100
+
101
+ frequencies = Hash.new(0)
102
+ string.each_char { |char| frequencies[char] += 1 }
103
+
104
+ entropy = 0
105
+ string.length.times do
106
+ freq = frequencies.values.shift
107
+ next if freq == 0
108
+
109
+ probability = freq.to_f / string.length
110
+ entropy -= probability * Math.log2(probability)
111
+ end
112
+
113
+ entropy
114
+ end
115
+
116
+ def calculate_request_frequency(client_ip)
117
+ current_time = Time.now
118
+ @request_patterns[client_ip] ||= []
119
+
120
+ # Clean old entries (older than 1 hour)
121
+ @request_patterns[client_ip].reject! { |time| current_time - time > 3600 }
122
+
123
+ # Add current request
124
+ @request_patterns[client_ip] << current_time
125
+
126
+ # Return requests per minute
127
+ recent_requests = @request_patterns[client_ip].count { |time| current_time - time <= 60 }
128
+ recent_requests
129
+ end
130
+
131
+ def detect_anomalies(client_ip, metrics, current_time)
132
+ anomalies = []
133
+
134
+ # Update baseline for this client
135
+ @baseline_metrics[client_ip] ||= initialize_baseline
136
+ baseline = @baseline_metrics[client_ip]
137
+
138
+ metrics.each do |metric, value|
139
+ next unless value.is_a?(Numeric)
140
+
141
+ # Calculate z-score
142
+ mean = baseline[metric][:mean]
143
+ std_dev = baseline[metric][:std_dev]
144
+
145
+ if std_dev > 0
146
+ z_score = (value - mean).abs / std_dev
147
+
148
+ if z_score > 3 # 3 standard deviations
149
+ anomalies << {
150
+ metric: metric,
151
+ value: value,
152
+ expected_range: [mean - 2 * std_dev, mean + 2 * std_dev],
153
+ z_score: z_score,
154
+ severity: z_score > 5 ? :high : :medium
155
+ }
156
+ end
157
+ end
158
+
159
+ # Update baseline with exponential moving average
160
+ alpha = 0.1
161
+ baseline[metric][:mean] = alpha * value + (1 - alpha) * mean
162
+
163
+ # Update standard deviation
164
+ variance = baseline[metric][:variance] || 0
165
+ new_variance = alpha * (value - mean) ** 2 + (1 - alpha) * variance
166
+ baseline[metric][:variance] = new_variance
167
+ baseline[metric][:std_dev] = Math.sqrt(new_variance)
168
+ end
169
+
170
+ anomalies
171
+ end
172
+
173
+ def initialize_baseline
174
+ Hash.new do |hash, key|
175
+ hash[key] = { mean: 0, variance: 0, std_dev: 1 }
176
+ end
177
+ end
178
+
179
+ def calculate_risk_score(anomalies, metrics)
180
+ base_score = 0
181
+
182
+ # Score based on anomalies
183
+ anomalies.each do |anomaly|
184
+ case anomaly[:severity]
185
+ when :high
186
+ base_score += 30
187
+ when :medium
188
+ base_score += 15
189
+ else
190
+ base_score += 5
191
+ end
192
+ end
193
+
194
+ # Additional risk factors
195
+ base_score += 10 if metrics[:request_frequency] > 60 # More than 1 req/sec
196
+ base_score += 15 if metrics[:request_size] > 1_000_000 # Large requests
197
+ base_score += 5 if metrics[:query_complexity] > 50 # Complex queries
198
+ base_score += 10 if metrics[:user_agent_entropy] < 2 # Suspicious UA
199
+
200
+ # Time-based risk (higher risk during off-hours)
201
+ if metrics[:time_of_day] < 6 || metrics[:time_of_day] > 22
202
+ base_score += 5
203
+ end
204
+
205
+ [base_score, 100].min # Cap at 100
206
+ end
207
+
208
+ def calculate_baseline_deviation(metrics)
209
+ # Simple calculation of how much current metrics deviate from expected
210
+ deviation_score = 0
211
+
212
+ metrics.each do |_, value|
213
+ next unless value.is_a?(Numeric)
214
+ # Simplified deviation calculation
215
+ deviation_score += value > 1000 ? 1 : 0
216
+ end
217
+
218
+ deviation_score
219
+ end
220
+
221
+ def determine_action(risk_score)
222
+ case risk_score
223
+ when 0..20
224
+ :allow
225
+ when 21..50
226
+ :monitor
227
+ when 51..80
228
+ :challenge
229
+ else
230
+ :block
231
+ end
232
+ end
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ module AI
5
+ module Features
6
+ class RateLimiting
7
+ attr_reader :name, :provider, :config
8
+
9
+ def initialize(provider, config)
10
+ @name = :rate_limiting
11
+ @provider = provider
12
+ @config = config
13
+ @rate_store = {}
14
+ @cleanup_timer = Time.now
15
+ end
16
+
17
+ def enabled?
18
+ @config.feature_enabled?(:rate_limiting)
19
+ end
20
+
21
+ def process_response?
22
+ false
23
+ end
24
+
25
+ def process_request(env)
26
+ client_ip = get_client_ip(env)
27
+ current_time = Time.now
28
+
29
+ # Cleanup old entries every 5 minutes
30
+ cleanup_old_entries if current_time - @cleanup_timer > 300
31
+
32
+ # Get rate limit settings
33
+ window_size = @config.rate_limiting.window_size || 3600 # 1 hour default
34
+ max_requests = @config.rate_limiting.max_requests || 1000 # 1000 requests default
35
+
36
+ # Initialize client data if not exists
37
+ @rate_store[client_ip] ||= { requests: [], blocked_until: nil }
38
+ client_data = @rate_store[client_ip]
39
+
40
+ # Check if client is currently blocked
41
+ if client_data[:blocked_until] && current_time < client_data[:blocked_until]
42
+ return {
43
+ action: :block,
44
+ reason: "Rate limit exceeded - blocked until #{client_data[:blocked_until]}",
45
+ status: 429,
46
+ feature: :rate_limiting,
47
+ timestamp: current_time.iso8601,
48
+ retry_after: (client_data[:blocked_until] - current_time).to_i
49
+ }
50
+ end
51
+
52
+ # Remove old requests outside the window
53
+ window_start = current_time - window_size
54
+ client_data[:requests].reject! { |timestamp| timestamp < window_start }
55
+
56
+ # Check if rate limit is exceeded
57
+ if client_data[:requests].length >= max_requests
58
+ block_duration = @config.rate_limiting.block_duration || 3600 # 1 hour default
59
+ client_data[:blocked_until] = current_time + block_duration
60
+
61
+ return {
62
+ action: :block,
63
+ reason: "Rate limit exceeded: #{client_data[:requests].length}/#{max_requests} requests in #{window_size}s",
64
+ status: 429,
65
+ feature: :rate_limiting,
66
+ timestamp: current_time.iso8601,
67
+ retry_after: block_duration,
68
+ requests_count: client_data[:requests].length,
69
+ window_size: window_size
70
+ }
71
+ end
72
+
73
+ # Add current request
74
+ client_data[:requests] << current_time
75
+
76
+ {
77
+ action: :allow,
78
+ feature: :rate_limiting,
79
+ timestamp: current_time.iso8601,
80
+ requests_count: client_data[:requests].length,
81
+ requests_remaining: max_requests - client_data[:requests].length,
82
+ window_size: window_size,
83
+ reset_time: (window_start + window_size).iso8601
84
+ }
85
+ end
86
+
87
+ private
88
+
89
+ def get_client_ip(env)
90
+ # Try various headers for real IP
91
+ forwarded_for = env['HTTP_X_FORWARDED_FOR']
92
+ return forwarded_for.split(',').first.strip if forwarded_for
93
+
94
+ real_ip = env['HTTP_X_REAL_IP']
95
+ return real_ip if real_ip
96
+
97
+ env['REMOTE_ADDR'] || 'unknown'
98
+ end
99
+
100
+ def cleanup_old_entries
101
+ current_time = Time.now
102
+ @rate_store.each do |ip, data|
103
+ # Remove clients with no recent activity and no active blocks
104
+ if data[:requests].empty? &&
105
+ (data[:blocked_until].nil? || current_time > data[:blocked_until])
106
+ @rate_store.delete(ip)
107
+ end
108
+ end
109
+ @cleanup_timer = current_time
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -83,7 +83,9 @@ module Rack
83
83
  routing: Features::Routing,
84
84
  logging: Features::Logging,
85
85
  enhancement: Features::Enhancement,
86
- security: Features::Security
86
+ security: Features::Security,
87
+ rate_limiting: Features::RateLimiting,
88
+ anomaly_detection: Features::AnomalyDetection
87
89
  }
88
90
 
89
91
  features = @config.features.map do |feature_name|
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+ require 'json'
5
+
6
+ module Rack
7
+ module AI
8
+ module Utils
9
+ class EnhancedLogger
10
+ LOG_LEVELS = {
11
+ debug: ::Logger::DEBUG,
12
+ info: ::Logger::INFO,
13
+ warn: ::Logger::WARN,
14
+ error: ::Logger::ERROR,
15
+ fatal: ::Logger::FATAL
16
+ }.freeze
17
+
18
+ def self.instance
19
+ @instance ||= new
20
+ end
21
+
22
+ def initialize(output = $stdout, level: :info, format: :json)
23
+ @logger = ::Logger.new(output)
24
+ @logger.level = LOG_LEVELS[level] || ::Logger::INFO
25
+ @format = format
26
+ @logger.formatter = method(:format_message)
27
+ end
28
+
29
+ def debug(message, context = {})
30
+ log(:debug, message, context)
31
+ end
32
+
33
+ def info(message, context = {})
34
+ log(:info, message, context)
35
+ end
36
+
37
+ def warn(message, context = {})
38
+ log(:warn, message, context)
39
+ end
40
+
41
+ def error(message, context = {})
42
+ log(:error, message, context)
43
+ end
44
+
45
+ def fatal(message, context = {})
46
+ log(:fatal, message, context)
47
+ end
48
+
49
+ def log_request(env, duration: nil, status: nil)
50
+ context = {
51
+ method: env['REQUEST_METHOD'],
52
+ path: env['PATH_INFO'],
53
+ query: env['QUERY_STRING'],
54
+ user_agent: env['HTTP_USER_AGENT'],
55
+ remote_ip: env['REMOTE_ADDR'],
56
+ duration_ms: duration ? (duration * 1000).round(2) : nil,
57
+ status: status
58
+ }.compact
59
+
60
+ info("HTTP Request", context)
61
+ end
62
+
63
+ def log_ai_processing(feature, result, duration: nil)
64
+ context = {
65
+ feature: feature,
66
+ action: result[:action],
67
+ confidence: result[:confidence],
68
+ duration_ms: duration ? (duration * 1000).round(2) : nil,
69
+ provider: result[:provider]
70
+ }.compact
71
+
72
+ info("AI Processing", context)
73
+ end
74
+
75
+ def log_error(error, context = {})
76
+ error_context = {
77
+ error_class: error.class.name,
78
+ error_message: error.message,
79
+ backtrace: error.backtrace&.first(5)
80
+ }.merge(context)
81
+
82
+ error("Application Error", error_context)
83
+ end
84
+
85
+ private
86
+
87
+ def log(level, message, context)
88
+ @logger.public_send(level) do
89
+ case @format
90
+ when :json
91
+ format_json(level, message, context)
92
+ when :structured
93
+ format_structured(level, message, context)
94
+ else
95
+ format_simple(level, message, context)
96
+ end
97
+ end
98
+ end
99
+
100
+ def format_message(severity, datetime, progname, msg)
101
+ msg
102
+ end
103
+
104
+ def format_json(level, message, context)
105
+ {
106
+ timestamp: Time.now.iso8601,
107
+ level: level.to_s.upcase,
108
+ message: message,
109
+ component: 'rack-ai',
110
+ **context
111
+ }.to_json + "\n"
112
+ end
113
+
114
+ def format_structured(level, message, context)
115
+ timestamp = Time.now.strftime('%Y-%m-%d %H:%M:%S')
116
+ context_str = context.empty? ? '' : " #{context.inspect}"
117
+ "[#{timestamp}] #{level.to_s.upcase} [rack-ai] #{message}#{context_str}\n"
118
+ end
119
+
120
+ def format_simple(level, message, context)
121
+ context_str = context.empty? ? '' : " (#{context.map { |k, v| "#{k}=#{v}" }.join(', ')})"
122
+ "#{message}#{context_str}\n"
123
+ end
124
+ end
125
+
126
+ # Backward compatibility
127
+ Logger = EnhancedLogger
128
+ end
129
+ end
130
+ end
@@ -7,6 +7,22 @@ module Rack
7
7
  module AI
8
8
  module Utils
9
9
  class Logger
10
+ def self.debug(message, context = {})
11
+ puts "[DEBUG] #{message} #{context}" if ENV['RACK_AI_DEBUG']
12
+ end
13
+
14
+ def self.info(message, context = {})
15
+ puts "[INFO] #{message} #{context}"
16
+ end
17
+
18
+ def self.warn(message, context = {})
19
+ puts "[WARN] #{message} #{context}"
20
+ end
21
+
22
+ def self.error(message, context = {})
23
+ puts "[ERROR] #{message} #{context}"
24
+ end
25
+
10
26
  class << self
11
27
  def logger
12
28
  @logger ||= build_logger
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  module AI
5
- VERSION = "0.1.0"
5
+ VERSION = "0.2.0"
6
6
  end
7
7
  end
data/lib/rack/ai.rb CHANGED
@@ -8,15 +8,18 @@ require_relative "ai/providers/openai"
8
8
  require_relative "ai/providers/huggingface"
9
9
  require_relative "ai/providers/local"
10
10
  require_relative "ai/features/classification"
11
+ require_relative "ai/features/security"
11
12
  require_relative "ai/features/moderation"
12
13
  require_relative "ai/features/caching"
13
14
  require_relative "ai/features/routing"
14
15
  require_relative "ai/features/logging"
15
16
  require_relative "ai/features/enhancement"
16
- require_relative "ai/features/security"
17
+ require_relative "ai/features/rate_limiting"
18
+ require_relative "ai/features/anomaly_detection"
17
19
  require_relative "ai/utils/logger"
18
20
  require_relative "ai/utils/metrics"
19
21
  require_relative "ai/utils/sanitizer"
22
+ require_relative "ai/utils/enhanced_logger"
20
23
 
21
24
  module Rack
22
25
  module AI
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-ai
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ahmet KAHRAMAN
@@ -241,15 +241,18 @@ files:
241
241
  - ROADMAP.md
242
242
  - Rakefile
243
243
  - benchmarks/performance_benchmark.rb
244
+ - examples/comprehensive_example.rb
244
245
  - examples/rails_integration.rb
245
246
  - examples/sinatra_integration.rb
246
247
  - lib/rack/ai.rb
247
248
  - lib/rack/ai/configuration.rb
249
+ - lib/rack/ai/features/anomaly_detection.rb
248
250
  - lib/rack/ai/features/caching.rb
249
251
  - lib/rack/ai/features/classification.rb
250
252
  - lib/rack/ai/features/enhancement.rb
251
253
  - lib/rack/ai/features/logging.rb
252
254
  - lib/rack/ai/features/moderation.rb
255
+ - lib/rack/ai/features/rate_limiting.rb
253
256
  - lib/rack/ai/features/routing.rb
254
257
  - lib/rack/ai/features/security.rb
255
258
  - lib/rack/ai/middleware.rb
@@ -257,6 +260,7 @@ files:
257
260
  - lib/rack/ai/providers/huggingface.rb
258
261
  - lib/rack/ai/providers/local.rb
259
262
  - lib/rack/ai/providers/openai.rb
263
+ - lib/rack/ai/utils/enhanced_logger.rb
260
264
  - lib/rack/ai/utils/logger.rb
261
265
  - lib/rack/ai/utils/metrics.rb
262
266
  - lib/rack/ai/utils/sanitizer.rb