HTTPRuby 1.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fd11c3b0f3a918f0a03b5598ce7f4dd6c2e4dfe6c17c8a400bddd2730898248b
4
+ data.tar.gz: 93ecdbcd668d9c1122f53246654e04251bb3cc9bfe7258b4602b04510befa47c
5
+ SHA512:
6
+ metadata.gz: b387fdf611455d8823831e82bbbb765b43f0e1d299e4a1f3b1cc1a7c4bb385d62a6c15eec4ca70d44bbf55c539371f09ebd93216e789a8faceaad2c4e5eeef05
7
+ data.tar.gz: 2daf5df235ad5ea20156f83a1e0f267107c724534e2010c93be16e2f11ab81486d95ed9cdb0e5bd59d3c7b3e0094c33180e855a6f05b51bd9c4a79a09058d4aa
data/README.md ADDED
@@ -0,0 +1,100 @@
1
+ # Own HTTP Server Framework
2
+
3
+ This project is a lightweight and customizable HTTP server framework built in Ruby, designed to work seamlessly with Puma. It provides robust tools for creating and managing web APIs, offering features such as route handling, dynamic parameters, query validation, typed responses, and JSON body parsing, all while processing requests asynchronously.
4
+
5
+ ## Purpose
6
+
7
+ The goal of this project is to build an HTTP server framework similar to FastAPI but for Ruby, enabling developers to create web applications quickly and effectively.
8
+
9
+ ## Technologies and Libraries
10
+
11
+ - **Ruby**: Core programming language for the framework.
12
+ - **Puma**: High-performance web server used to handle requests.
13
+ - **Rack**: Interface between web servers and web applications.
14
+ - **JSON**: Parsing and generating JSON responses.
15
+
16
+ ## Implementation Overview
17
+
18
+ This framework consists of the following main components:
19
+
20
+ ### `App`
21
+ The main class responsible for managing routers and handling requests asynchronously.
22
+
23
+ #### Key Features:
24
+ - Adds routers to the application.
25
+ - Handles incoming requests and delegates them to appropriate routers.
26
+ - Parses JSON bodies and query parameters.
27
+ - Generates JSON responses.
28
+ - Asynchronous request handling.
29
+
30
+ ```ruby
31
+ class Router
32
+ def initialize
33
+ @routes = { GET: {}, POST: {}, PUT: {}, DELETE: {} }
34
+ end
35
+
36
+ def add_route(method, path, accepted_queries: nil, accepted_body: nil, accepted_response: nil, &block)
37
+ @routes[method][path] = {
38
+ accepted_queries: accepted_queries,
39
+ accepted_body: accepted_body,
40
+ accepted_response: accepted_response,
41
+ handler: block
42
+ }
43
+ end
44
+
45
+ def get(path, accepted_queries: nil, accepted_body: nil, accepted_response: nil, &block)
46
+ add_route(:GET, path, accepted_queries: accepted_queries, accepted_body: accepted_body, accepted_response: accepted_response, &block)
47
+ end
48
+
49
+ def post(path, accepted_queries: nil, accepted_body: nil, accepted_response: nil, &block)
50
+ add_route(:POST, path, accepted_queries: accepted_queries, accepted_body: accepted_body, accepted_response: accepted_response, &block)
51
+ end
52
+ ```
53
+
54
+ ### `Router`
55
+ Handles route definitions and request matching. Supports dynamic routes, query validation, and request body validation.
56
+
57
+ #### Key Features:
58
+ - Define routes for `GET`, `POST`, `PUT`, and `DELETE` methods.
59
+ - Validate query parameters and request bodies.
60
+ - Support for dynamic route parameters with type constraints.
61
+
62
+ ```ruby
63
+ class Router
64
+ def initialize
65
+ @routes = { GET: {}, POST: {}, PUT: {}, DELETE: {} }
66
+ end
67
+
68
+ def get(path, accepted_queries: nil, accepted_body: nil, accepted_response: nil, &block)
69
+ add_route(:GET, path, accepted_queries: accepted_queries, accepted_body: accepted_body, accepted_response: accepted_response, &block)
70
+ end
71
+ end
72
+ ```
73
+
74
+ ### `HTTPResponse`
75
+ Encapsulates HTTP responses with status codes, headers, and JSON bodies.
76
+
77
+ #### Key Features:
78
+ - Generates structured responses for the client.
79
+ - Based on Rack return standard
80
+
81
+ ```ruby
82
+ class HTTPResponse
83
+ def initialize(status, json)
84
+ @status = status
85
+ @headers = { 'Content-Type' => 'application/json' }
86
+ @body = [JSON.generate(json)]
87
+ end
88
+
89
+ def to_a
90
+ [@status, @headers, @body]
91
+ end
92
+ end
93
+ ```
94
+
95
+ ## Usage
96
+
97
+ ## Conclusion
98
+
99
+ This framework offers a clean, Ruby-based solution for building HTTP APIs, complete with dynamic routing, type validation, and asynchronous processing. By leveraging the power of Puma and Rack, it provides a high-performance and scalable foundation for your web applications.
100
+
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+ require 'puma'
3
+ require 'rack'
4
+ require 'json'
5
+
6
+ require_relative 'router'
7
+
8
+ class App
9
+ def initialize
10
+ @routers = {}
11
+ end
12
+
13
+ def add_router(path, router)
14
+ @routers[path] = router
15
+ end
16
+
17
+ def call(env)
18
+ # Use a Thread to handle the request asynchronously
19
+ result = nil
20
+ thread = Thread.new do
21
+ result = handle_request(env)
22
+ end
23
+ thread.join # Ensure the thread completes before returning
24
+ result # Return the result from the thread
25
+ end
26
+
27
+
28
+ def handle_request(env)
29
+ begin
30
+ if env["CONTENT_TYPE"] == "application/json"
31
+ request_body = JSON.parse(env["rack.input"].read)
32
+ env["parsed_body"] = request_body
33
+ end
34
+
35
+ query_params = Rack::Utils.parse_query(env["QUERY_STRING"])
36
+ env["query_params"] = query_params
37
+
38
+ full_path = env["PATH_INFO"]
39
+ method = env["REQUEST_METHOD"].to_sym
40
+
41
+ @routers.each do |base_path, router|
42
+ if full_path.start_with?(base_path)
43
+ sub_path = full_path.sub(base_path, "")
44
+ return router.call(sub_path, method, env, full_path)
45
+ end
46
+ end
47
+
48
+ json_response(404, { error: "Not Found" })
49
+ rescue => e
50
+ puts "[ERROR] #{e.message}"
51
+ puts e.backtrace.join("\n")
52
+ json_response(500, { error: "Internal Server Error" })
53
+ end
54
+ end
55
+
56
+
57
+ def run
58
+ puts "Starting Puma Server..."
59
+ server = Puma::Server.new(self)
60
+
61
+ server.add_tcp_listener('127.0.0.1', 3000) # Port setup
62
+
63
+ trap(:INT) do
64
+ puts "\nStopping the server..."
65
+ server.stop
66
+ end
67
+
68
+ puts "Server successfully started."
69
+ server.run
70
+ server.thread.join # Ensure server blocks the main thread
71
+ end
72
+
73
+ private
74
+
75
+ def json_response(status, data)
76
+ [
77
+ status,
78
+ { 'Content-Type' => 'application/json' },
79
+ [JSON.generate(data)]
80
+ ]
81
+ end
82
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ class HTTPResponse
4
+
5
+ attr_accessor :status, :headers, :body
6
+
7
+ def initialize(status, json)
8
+ @status = status
9
+ @headers = { 'Content-Type' => 'application/json' }
10
+ @body = [JSON.generate(json)]
11
+ end
12
+
13
+ def to_a
14
+ [@status, @headers, @body]
15
+ end
16
+ end
17
+
@@ -0,0 +1,166 @@
1
+ # frozen_string_literal: true
2
+ require 'json'
3
+
4
+ class Router
5
+ def initialize
6
+ @routes = { GET: {}, POST: {}, PUT: {}, DELETE: {} }
7
+ end
8
+
9
+ def add_route(method, path, accepted_queries: nil, accepted_body: nil, accepted_response: nil, &block)
10
+ @routes[method][path] = {
11
+ accepted_queries: accepted_queries,
12
+ accepted_body: accepted_body,
13
+ accepted_response: accepted_response,
14
+ handler: block
15
+ }
16
+ end
17
+
18
+ def get(path, accepted_queries: nil, accepted_body: nil, accepted_response: nil, &block)
19
+ add_route(:GET, path, accepted_queries: accepted_queries, accepted_body: accepted_body, accepted_response: accepted_response, &block)
20
+ end
21
+
22
+ def post(path, accepted_queries: nil, accepted_body: nil, accepted_response: nil, &block)
23
+ add_route(:POST, path, accepted_queries: accepted_queries, accepted_body: accepted_body, accepted_response: accepted_response, &block)
24
+ end
25
+
26
+ def call(path, method, env, full_path)
27
+ log_request(method, full_path)
28
+
29
+ unless @routes.key?(method)
30
+ log_response(405, "Method Not Allowed")
31
+ return json_response(405, { error: "Method Not Allowed" })
32
+ end
33
+
34
+ @routes[method].each do |route_path, route|
35
+ match = match_path(route_path, path)
36
+
37
+ if match
38
+ # If there's a match, extract URL parameters (if any)
39
+ if match.is_a?(MatchData)
40
+ env["url_params"] = match.named_captures || {}
41
+ else
42
+ env["url_params"] = {}
43
+ end
44
+
45
+ # Validate query parameters
46
+ if route[:accepted_queries] && !validate_queries(route[:accepted_queries], env["query_params"])
47
+ log_response(400, "Invalid Query Parameters")
48
+ return json_response(400, { error: "Invalid Query Parameters" })
49
+ end
50
+
51
+ # Validate body parameters
52
+ if route[:accepted_body] && !validate_body(route[:accepted_body], env["parsed_body"])
53
+ log_response(400, "Invalid Body Parameters")
54
+ return json_response(400, { error: "Invalid Body Parameters" })
55
+ end
56
+
57
+ # Call the handler block
58
+ result = route[:handler].call(env)
59
+
60
+ # Ensure result is an HTTPResponse and validate it
61
+ if result.is_a?(HTTPResponse)
62
+ if route[:accepted_response] && !validate_accepted_response(route[:accepted_response], result.body)
63
+ log_response(500, "Invalid Response Structure")
64
+ return json_response(500, { error: "Invalid Response Structure" })
65
+ end
66
+
67
+ log_response(result.status, result.body[0] || result.body[0])
68
+
69
+ return result.to_a # Convert HTTPResponse to Rack-compatible response
70
+ else
71
+ log_response(500, "Handler did not return an HTTPResponse")
72
+ return json_response(500, { error: "Handler must return an HTTPResponse" })
73
+ end
74
+ end
75
+ end
76
+
77
+ # If no match is found, return 404
78
+ log_response(404, "Not Found")
79
+ json_response(404, { error: "Not Found" })
80
+ end
81
+
82
+ private
83
+
84
+ def json_response(status, data)
85
+ [
86
+ status,
87
+ { 'Content-Type' => 'application/json' },
88
+ [JSON.generate(data)]
89
+ ]
90
+ end
91
+
92
+ def log_request(method, path)
93
+ method_color = case method
94
+ when :GET then "\e[32m" # Green for GET
95
+ when :POST then "\e[34m" # Blue for POST
96
+ when :PUT then "\e[33m" # Yellow for PUT
97
+ when :DELETE then "\e[31m" # Red for DELETE
98
+ else "\e[37m" # White for others
99
+ end
100
+ reset = "\e[0m"
101
+ puts "#{method_color}[REQUEST] Method: #{method} | Path: #{path}#{reset}"
102
+ end
103
+
104
+ def log_response(status, reason)
105
+ status_color = case status
106
+ when 200 then "\e[32m" # Green for success
107
+ when 400 then "\e[33m" # Yellow for client errors
108
+ when 404 then "\e[31m" # Red for not found
109
+ when 405 then "\e[31m" # Red for method not allowed
110
+ when 500 then "\e[35m" # Magenta for server errors
111
+ else "\e[37m" # White for other statuses
112
+ end
113
+ reset = "\e[0m"
114
+ puts "#{status_color}[RESPONSE] Status: #{status} | Reason: #{reason}#{reset}"
115
+ end
116
+
117
+ def match_path(route_path, request_path)
118
+ regex_path = route_path.gsub(/\$\{(\w+)(?::(\w+))?\}/) do |_match|
119
+ param_name = Regexp.last_match(1)
120
+ param_type = Regexp.last_match(2)
121
+
122
+ # Handle different types
123
+ case param_type
124
+ when "int"
125
+ "(?<#{param_name}>\\d+)" # Only digits
126
+ when "string", nil
127
+ "(?<#{param_name}>[^/]+)" # Default to any non-slash characters
128
+ else
129
+ raise "Unsupported type: #{param_type}"
130
+ end
131
+ end
132
+
133
+ Regexp.new("^#{regex_path}$").match(request_path)
134
+ end
135
+
136
+
137
+ def validate_accepted_response(accepted_response_class, response_body)
138
+ return false unless response_body.is_a?(Array) && response_body.first.is_a?(String)
139
+
140
+ parsed_json = JSON.parse(response_body.first, symbolize_names: true)
141
+
142
+ instance = accepted_response_class.new
143
+
144
+ parsed_json.each do |key, value|
145
+ return false unless instance.respond_to?("#{key}=")
146
+ end
147
+
148
+ true
149
+ end
150
+
151
+
152
+
153
+
154
+ def validate_queries(accepted_queries, query_params)
155
+ # Ensure all required queries are present in the query_params
156
+ accepted_queries.all? { |query| query_params.key?(query) }
157
+ end
158
+
159
+
160
+ def validate_body(accepted_body, parsed_body)
161
+
162
+ accepted_body.instance_variables.all? do |var|
163
+ parsed_body.key?(var.to_s.sub('@', ''))
164
+ end
165
+ end
166
+ end
data/lib/TeleRuby.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "TeleRuby/app"
2
+ require "TeleRuby/router"
3
+ require "TeleRuby/http_response"
4
+ # frozen_string_literal: true
5
+
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: HTTPRuby
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Bryan Sigaran
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-12-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: puma
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '6.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '6.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rack
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '2.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '2.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: json
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '2.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '2.0'
55
+ description: An HTTP server framework similar to FastAPI, featuring async processing,
56
+ dynamic routing, and type validation.
57
+ email:
58
+ - bryanohss@gmail.com
59
+ executables: []
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - README.md
64
+ - lib/TeleRuby.rb
65
+ - lib/TeleRuby/app.rb
66
+ - lib/TeleRuby/http_response.rb
67
+ - lib/TeleRuby/router.rb
68
+ homepage: https://github.com/artisenpaiii/TeleRuby
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubygems_version: 3.4.20
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: A lightweight HTTP server framework for Ruby
91
+ test_files: []