unipod 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +79 -0
- data/exe/unipod +13 -0
- data/lib/unipod/command/install.rb +384 -0
- data/lib/unipod/command/push.rb +384 -0
- data/lib/unipod/command/server.rb +115 -0
- data/lib/unipod/command.rb +40 -0
- data/lib/unipod/config.rb +393 -0
- data/lib/unipod/server/cache_manager.rb +1106 -0
- data/lib/unipod/server/download_handler.rb +359 -0
- data/lib/unipod/server/index.html +460 -0
- data/lib/unipod/server/package_builder_queue.rb +255 -0
- data/lib/unipod/server/server.rb +393 -0
- data/lib/unipod/server/server_handler.rb +526 -0
- data/lib/unipod/ui.rb +173 -0
- data/lib/unipod/version.rb +5 -0
- data/lib/unipod.rb +33 -0
- metadata +226 -0
@@ -0,0 +1,255 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'thread'
|
4
|
+
require 'singleton'
|
5
|
+
|
6
|
+
module UniPod
|
7
|
+
module Server
|
8
|
+
# 管理包构建任务的队列,使用后台线程异步处理Git克隆和tarball构建
|
9
|
+
class PackageBuilderQueue
|
10
|
+
include Singleton
|
11
|
+
|
12
|
+
# 表示要构建的包任务
|
13
|
+
class BuildTask
|
14
|
+
attr_reader :package_name, :version, :git_url, :git_tag, :git_branch, :priority, :created_at, :callbacks
|
15
|
+
|
16
|
+
def initialize(package_name, version, git_url, git_tag = nil, git_branch = nil, priority = 0)
|
17
|
+
@package_name = package_name
|
18
|
+
@version = version
|
19
|
+
@git_url = git_url
|
20
|
+
@git_tag = git_tag || "v#{version}"
|
21
|
+
@git_branch = git_branch || 'main'
|
22
|
+
@priority = priority # 优先级:0=普通,1=高优先级(当前请求的包)
|
23
|
+
@created_at = Time.now
|
24
|
+
@callbacks = []
|
25
|
+
end
|
26
|
+
|
27
|
+
# 添加任务完成后的回调函数
|
28
|
+
def add_callback(&block)
|
29
|
+
@callbacks << block if block_given?
|
30
|
+
end
|
31
|
+
|
32
|
+
# 执行所有回调
|
33
|
+
def execute_callbacks(success, tarball_path = nil)
|
34
|
+
@callbacks.each do |callback|
|
35
|
+
begin
|
36
|
+
callback.call(success, tarball_path)
|
37
|
+
rescue => e
|
38
|
+
# 忽略回调中的错误,避免影响队列处理
|
39
|
+
UI.error "执行包构建回调时出错: #{e.message}" if verbose?
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# 生成任务的唯一键
|
45
|
+
def key
|
46
|
+
"#{@package_name}-#{@version}"
|
47
|
+
end
|
48
|
+
|
49
|
+
# 任务已等待时间(秒)
|
50
|
+
def wait_time
|
51
|
+
Time.now - @created_at
|
52
|
+
end
|
53
|
+
|
54
|
+
# 打印任务信息
|
55
|
+
def to_s
|
56
|
+
"BuildTask[#{@package_name}@#{@version}, git:#{@git_url}, tag:#{@git_tag}, priority:#{@priority}]"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def initialize
|
61
|
+
@queue = Queue.new
|
62
|
+
@running_tasks = {}
|
63
|
+
@tasks_mutex = Mutex.new
|
64
|
+
@worker_thread = nil
|
65
|
+
@shutdown = false
|
66
|
+
@cache_manager = nil
|
67
|
+
@verbose = false
|
68
|
+
end
|
69
|
+
|
70
|
+
# 启动工作线程
|
71
|
+
def start(cache_manager, options = {})
|
72
|
+
@cache_manager = cache_manager
|
73
|
+
@verbose = options[:verbose] || false
|
74
|
+
|
75
|
+
log_verbose "启动包构建队列工作线程..."
|
76
|
+
|
77
|
+
# 确保只启动一个工作线程
|
78
|
+
return if @worker_thread && @worker_thread.alive?
|
79
|
+
|
80
|
+
@shutdown = false
|
81
|
+
@worker_thread = Thread.new do
|
82
|
+
worker_loop
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# 停止工作线程
|
87
|
+
def stop
|
88
|
+
log_verbose "正在停止包构建队列..."
|
89
|
+
@shutdown = true
|
90
|
+
@queue.clear
|
91
|
+
|
92
|
+
# 向队列发送关闭信号(nil任务)
|
93
|
+
@queue << nil
|
94
|
+
|
95
|
+
# 等待工作线程结束
|
96
|
+
if @worker_thread
|
97
|
+
begin
|
98
|
+
@worker_thread.join(5) # 最多等待5秒
|
99
|
+
rescue => e
|
100
|
+
log_verbose "等待工作线程结束时出错: #{e.message}"
|
101
|
+
end
|
102
|
+
|
103
|
+
# 如果线程仍在运行,强制终止
|
104
|
+
if @worker_thread.alive?
|
105
|
+
log_verbose "工作线程未及时结束,强制终止"
|
106
|
+
@worker_thread.kill
|
107
|
+
end
|
108
|
+
|
109
|
+
@worker_thread = nil
|
110
|
+
end
|
111
|
+
|
112
|
+
log_verbose "包构建队列已停止"
|
113
|
+
end
|
114
|
+
|
115
|
+
# 添加包构建任务到队列
|
116
|
+
# 如果同一个包版本已有任务,则不重复添加
|
117
|
+
def enqueue(package_name, version, git_url, git_tag = nil, git_branch = nil, priority = 0, &callback)
|
118
|
+
task = BuildTask.new(package_name, version, git_url, git_tag, git_branch, priority)
|
119
|
+
task.add_callback(&callback) if block_given?
|
120
|
+
|
121
|
+
@tasks_mutex.synchronize do
|
122
|
+
# 检查是否已有相同的任务正在运行
|
123
|
+
if @running_tasks[task.key]
|
124
|
+
existing_task = @running_tasks[task.key]
|
125
|
+
existing_task.add_callback(&callback) if block_given?
|
126
|
+
log_verbose "跳过已在处理的任务: #{task}"
|
127
|
+
return false
|
128
|
+
end
|
129
|
+
|
130
|
+
# 检查队列中是否已有相同的任务
|
131
|
+
existing_task = nil
|
132
|
+
@queue.size.times do
|
133
|
+
t = @queue.pop
|
134
|
+
if t && t.key == task.key
|
135
|
+
existing_task = t
|
136
|
+
# 如果新任务优先级更高,使用新任务
|
137
|
+
if priority > t.priority
|
138
|
+
log_verbose "用更高优先级的任务替换队列中的任务: #{task}"
|
139
|
+
t.add_callback(&callback) if block_given?
|
140
|
+
@queue << task
|
141
|
+
else
|
142
|
+
t.add_callback(&callback) if block_given?
|
143
|
+
@queue << t
|
144
|
+
end
|
145
|
+
else
|
146
|
+
@queue << t if t # 放回其他任务
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# 如果队列中没有相同任务,添加新任务
|
151
|
+
if existing_task.nil?
|
152
|
+
log_verbose "添加新的包构建任务到队列: #{task}"
|
153
|
+
@queue << task
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
true
|
158
|
+
end
|
159
|
+
|
160
|
+
# 获取队列中的任务数量
|
161
|
+
def queue_size
|
162
|
+
@queue.size
|
163
|
+
end
|
164
|
+
|
165
|
+
# 获取当前正在处理的任务数量
|
166
|
+
def running_task_count
|
167
|
+
@tasks_mutex.synchronize { @running_tasks.size }
|
168
|
+
end
|
169
|
+
|
170
|
+
private
|
171
|
+
|
172
|
+
# 工作线程主循环
|
173
|
+
def worker_loop
|
174
|
+
log_verbose "包构建工作线程已启动"
|
175
|
+
|
176
|
+
while !@shutdown
|
177
|
+
begin
|
178
|
+
# 从队列获取任务
|
179
|
+
task = @queue.pop
|
180
|
+
break if @shutdown || task.nil?
|
181
|
+
|
182
|
+
# 更新正在运行的任务列表
|
183
|
+
@tasks_mutex.synchronize do
|
184
|
+
@running_tasks[task.key] = task
|
185
|
+
end
|
186
|
+
|
187
|
+
log_verbose "开始处理包构建任务: #{task}"
|
188
|
+
success = false
|
189
|
+
tarball_path = nil
|
190
|
+
|
191
|
+
begin
|
192
|
+
# 执行包构建
|
193
|
+
if @cache_manager
|
194
|
+
tarball_name = "#{task.package_name}-#{task.version}.tgz"
|
195
|
+
|
196
|
+
# 首先检查是否已存在tarball文件
|
197
|
+
existing_tarball = @cache_manager.get_package_tarball(task.package_name, tarball_name)
|
198
|
+
if existing_tarball && File.exist?(existing_tarball)
|
199
|
+
log_verbose "找到已存在的tarball文件: #{existing_tarball}"
|
200
|
+
success = true
|
201
|
+
tarball_path = existing_tarball
|
202
|
+
else
|
203
|
+
# 克隆并构建包
|
204
|
+
log_verbose "尝试克隆并构建包: #{task.package_name}@#{task.version} (#{task.git_url})"
|
205
|
+
if @cache_manager.clone_and_build_package(task.package_name, task.git_url, task.git_tag, task.git_branch)
|
206
|
+
# 检查构建结果
|
207
|
+
tarball_path = @cache_manager.get_package_tarball(task.package_name, tarball_name)
|
208
|
+
if tarball_path && File.exist?(tarball_path)
|
209
|
+
log_verbose "成功构建tarball: #{tarball_path}"
|
210
|
+
success = true
|
211
|
+
else
|
212
|
+
log_verbose "构建过程完成,但未找到tarball文件: #{tarball_name}"
|
213
|
+
end
|
214
|
+
else
|
215
|
+
log_verbose "包构建失败: #{task.package_name}@#{task.version}"
|
216
|
+
end
|
217
|
+
end
|
218
|
+
else
|
219
|
+
log_verbose "未设置缓存管理器,无法构建包: #{task.package_name}@#{task.version}"
|
220
|
+
end
|
221
|
+
rescue => e
|
222
|
+
log_verbose "处理包构建任务时出错: #{e.message}"
|
223
|
+
log_verbose e.backtrace.join("\n") if @verbose
|
224
|
+
end
|
225
|
+
|
226
|
+
# 执行回调
|
227
|
+
task.execute_callbacks(success, tarball_path)
|
228
|
+
|
229
|
+
# 更新正在运行的任务列表
|
230
|
+
@tasks_mutex.synchronize do
|
231
|
+
@running_tasks.delete(task.key)
|
232
|
+
end
|
233
|
+
|
234
|
+
log_verbose "包构建任务处理#{success ? '成功' : '失败'}: #{task}"
|
235
|
+
|
236
|
+
rescue => e
|
237
|
+
# 捕获循环中的任何错误,确保工作线程不会意外终止
|
238
|
+
log_verbose "工作线程出错: #{e.message}"
|
239
|
+
log_verbose e.backtrace.join("\n") if @verbose
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
log_verbose "包构建工作线程已退出"
|
244
|
+
end
|
245
|
+
|
246
|
+
def log_verbose(message)
|
247
|
+
UI.puts message if @verbose
|
248
|
+
end
|
249
|
+
|
250
|
+
def verbose?
|
251
|
+
@verbose
|
252
|
+
end
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
@@ -0,0 +1,393 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'webrick'
|
4
|
+
require 'json'
|
5
|
+
require 'fileutils'
|
6
|
+
require 'socket'
|
7
|
+
require 'rainbow'
|
8
|
+
require 'unipod/ui'
|
9
|
+
require_relative 'cache_manager'
|
10
|
+
require_relative 'server_handler'
|
11
|
+
require_relative 'download_handler'
|
12
|
+
require_relative 'package_builder_queue'
|
13
|
+
|
14
|
+
module UniPod
|
15
|
+
module Server
|
16
|
+
class VerdaccioServer
|
17
|
+
attr_reader :server, :port, :host
|
18
|
+
|
19
|
+
DEFAULT_PORT = 7748
|
20
|
+
DEFAULT_HOST = '0.0.0.0' # 绑定到所有IP地址
|
21
|
+
|
22
|
+
#-----------------------------------------------
|
23
|
+
# 初始化和服务器生命周期管理
|
24
|
+
#-----------------------------------------------
|
25
|
+
|
26
|
+
def initialize(options = {})
|
27
|
+
@port = options[:port] || DEFAULT_PORT
|
28
|
+
@host = options[:host] || DEFAULT_HOST
|
29
|
+
@verbose = options[:verbose] || false
|
30
|
+
@cache_manager = CacheManager.new
|
31
|
+
@request_count = 0 # 请求计数,用于调试
|
32
|
+
|
33
|
+
# 初始化请求处理程序
|
34
|
+
@server_handler = ServerHandler.new(@cache_manager, verbose: @verbose)
|
35
|
+
@download_handler = DownloadHandler.new(@cache_manager, verbose: @verbose)
|
36
|
+
|
37
|
+
# 初始化包构建队列
|
38
|
+
@builder_queue = PackageBuilderQueue.instance
|
39
|
+
end
|
40
|
+
|
41
|
+
def start
|
42
|
+
begin
|
43
|
+
# 确保正在运行的服务器已停止
|
44
|
+
stop_running_server(@port)
|
45
|
+
|
46
|
+
# 打印启动信息 - 这个信息保留,因为它是服务器实际启动的指示
|
47
|
+
# UI.puts "启动UniPod服务器: #{@host}:#{@port}" - 不再需要,Command类已经显示
|
48
|
+
|
49
|
+
# 创建HTTP服务器
|
50
|
+
create_http_server
|
51
|
+
|
52
|
+
# 注册路由处理
|
53
|
+
setup_routes
|
54
|
+
|
55
|
+
# 注册退出处理
|
56
|
+
register_shutdown_hooks
|
57
|
+
|
58
|
+
# 启动包构建队列
|
59
|
+
@builder_queue.start(@cache_manager, verbose: @verbose)
|
60
|
+
|
61
|
+
# 预加载包信息
|
62
|
+
preload_packages
|
63
|
+
|
64
|
+
# 显示缓存目录信息 - Command类已经显示类似信息,不需要重复
|
65
|
+
# display_cache_directories
|
66
|
+
|
67
|
+
# 启动服务器确认
|
68
|
+
UI.success "UniPod服务器已启动,地址:http://#{@host}:#{@port}"
|
69
|
+
UI.puts "服务器正在运行,按Ctrl+C停止..."
|
70
|
+
|
71
|
+
# 开始服务
|
72
|
+
@server.start
|
73
|
+
rescue => e
|
74
|
+
UI.error "启动服务器时出错: #{e.message}"
|
75
|
+
UI.error e.backtrace.join("\n") if @verbose
|
76
|
+
raise
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def stop
|
81
|
+
begin
|
82
|
+
# 停止包构建队列
|
83
|
+
@builder_queue.stop if @builder_queue
|
84
|
+
|
85
|
+
# 停止HTTP服务器
|
86
|
+
@server.shutdown if @server
|
87
|
+
UI.puts "服务器已停止"
|
88
|
+
rescue => e
|
89
|
+
UI.error "停止服务器时出错: #{e.message}"
|
90
|
+
UI.error e.backtrace.join("\n") if @verbose
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
#-----------------------------------------------
|
95
|
+
# 服务器设置和配置
|
96
|
+
#-----------------------------------------------
|
97
|
+
|
98
|
+
private
|
99
|
+
|
100
|
+
def create_http_server
|
101
|
+
@server = WEBrick::HTTPServer.new(
|
102
|
+
Port: @port.to_i,
|
103
|
+
Host: @host,
|
104
|
+
Logger: create_logger,
|
105
|
+
AccessLog: create_access_log,
|
106
|
+
DoNotReverseLookup: true, # 提高性能
|
107
|
+
RequestTimeout: 120, # 增加超时时间
|
108
|
+
MaxClients: 10 # 减少线程数以降低内存使用
|
109
|
+
)
|
110
|
+
end
|
111
|
+
|
112
|
+
def register_shutdown_hooks
|
113
|
+
# 注册优雅退出的处理程序
|
114
|
+
[:INT, :TERM].each do |signal|
|
115
|
+
trap(signal) do
|
116
|
+
UI.puts "\n正在停止服务器..."
|
117
|
+
@server.shutdown
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def create_logger
|
123
|
+
log_level = @verbose ? WEBrick::Log::DEBUG : WEBrick::Log::WARN
|
124
|
+
WEBrick::Log.new($stderr, log_level)
|
125
|
+
end
|
126
|
+
|
127
|
+
def create_access_log
|
128
|
+
@verbose ? [[$stderr, WEBrick::AccessLog::COMBINED_LOG_FORMAT]] : []
|
129
|
+
end
|
130
|
+
|
131
|
+
def stop_running_server(port)
|
132
|
+
begin
|
133
|
+
# 尝试使用系统命令终止占用端口的进程
|
134
|
+
if RUBY_PLATFORM =~ /mswin|mingw|cygwin/
|
135
|
+
# Windows系统
|
136
|
+
system("FOR /F \"tokens=5\" %a in ('netstat -ano ^| findstr :#{port}') do taskkill /F /PID %a")
|
137
|
+
else
|
138
|
+
# Unix类系统
|
139
|
+
system("lsof -i:#{port} | grep -v PID | awk '{print $2}' | xargs kill -9 2>/dev/null || true")
|
140
|
+
end
|
141
|
+
rescue => e
|
142
|
+
# 忽略错误,继续尝试启动服务器
|
143
|
+
log_verbose "尝试停止现有服务器时出错: #{e.message}"
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
# 预加载包信息,避免首次请求延迟
|
148
|
+
def preload_packages
|
149
|
+
Thread.new do
|
150
|
+
log_verbose "开始预加载包信息..."
|
151
|
+
@cache_manager.get_all_packages
|
152
|
+
log_verbose "包信息预加载完成!"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def display_cache_directories
|
157
|
+
# 使用Config类获取缓存目录路径
|
158
|
+
require 'unipod/config'
|
159
|
+
cache_dir = UniPod::Config.cache_dir rescue "未设置"
|
160
|
+
scoped_dir = File.join(cache_dir, 'scoped') rescue "未设置"
|
161
|
+
packages_dir = File.join(cache_dir, 'packages') rescue "未设置"
|
162
|
+
tarballs_dir = File.join(cache_dir, 'tarballs') rescue "未设置"
|
163
|
+
|
164
|
+
UI.puts "============ 缓存目录结构 ============"
|
165
|
+
UI.puts "UniPod缓存目录: #{cache_dir}"
|
166
|
+
UI.puts "索引仓库目录: #{scoped_dir}"
|
167
|
+
UI.puts "包Git仓库目录: #{packages_dir}"
|
168
|
+
UI.puts "包压缩文件目录: #{tarballs_dir}"
|
169
|
+
end
|
170
|
+
|
171
|
+
#-----------------------------------------------
|
172
|
+
# 路由设置和请求处理 - 仅保留Unity Package Manager使用的API
|
173
|
+
#-----------------------------------------------
|
174
|
+
|
175
|
+
def setup_routes
|
176
|
+
# 主路由处理所有请求
|
177
|
+
@server.mount_proc('/') do |req, res|
|
178
|
+
handle_main_route(req, res)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
def handle_main_route(req, res)
|
183
|
+
# 添加请求ID
|
184
|
+
req_id = @request_count += 1
|
185
|
+
|
186
|
+
# 添加CORS头
|
187
|
+
@server_handler.add_cors_headers(res)
|
188
|
+
|
189
|
+
# 记录请求详情
|
190
|
+
log_request_details(req, req_id) if @verbose
|
191
|
+
|
192
|
+
# 处理预检请求
|
193
|
+
if req.request_method == 'OPTIONS'
|
194
|
+
handle_options_request(res, req_id)
|
195
|
+
return
|
196
|
+
end
|
197
|
+
|
198
|
+
# 处理请求
|
199
|
+
begin
|
200
|
+
handle_request(req, res, req_id)
|
201
|
+
rescue => e
|
202
|
+
@server_handler.handle_request_error(e, res, req_id)
|
203
|
+
end
|
204
|
+
|
205
|
+
# 记录响应
|
206
|
+
log_verbose "[#{req_id}] 响应状态码: #{res.status}"
|
207
|
+
log_verbose "[#{req_id}] ==========请求结束==========\n"
|
208
|
+
end
|
209
|
+
|
210
|
+
def handle_options_request(res, req_id)
|
211
|
+
res.status = 200
|
212
|
+
log_verbose "[#{req_id}] 处理OPTIONS请求"
|
213
|
+
end
|
214
|
+
|
215
|
+
def log_request_details(req, req_id)
|
216
|
+
puts "\n[#{req_id}] ==========请求详情==========\n"
|
217
|
+
puts "[#{req_id}] 请求方法: #{req.request_method}"
|
218
|
+
puts "[#{req_id}] 请求路径: #{req.path}"
|
219
|
+
puts "[#{req_id}] 查询字符串: #{req.query_string}"
|
220
|
+
|
221
|
+
# 打印请求头
|
222
|
+
puts "[#{req_id}] 请求头:"
|
223
|
+
req.header.each do |k, v|
|
224
|
+
puts "[#{req_id}] #{k}: #{v.join(', ')}"
|
225
|
+
end
|
226
|
+
|
227
|
+
# 打印查询参数
|
228
|
+
if req.query
|
229
|
+
puts "[#{req_id}] 解析后的查询参数:"
|
230
|
+
req.query.each do |k, v|
|
231
|
+
puts "[#{req_id}] #{k}: #{v}"
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
def handle_request(req, res, req_id = 0)
|
237
|
+
method = req.request_method
|
238
|
+
path = req.path
|
239
|
+
|
240
|
+
query_string = req.query_string.nil? ? '' : req.query_string
|
241
|
+
log_verbose "[#{req_id}] #{method} #{path}#{query_string.empty? ? '' : '?' + query_string}"
|
242
|
+
|
243
|
+
# 处理首页请求 - 提供简单的欢迎页面或API信息
|
244
|
+
if path == '/' && method == 'GET'
|
245
|
+
@server_handler.handle_root_request(res, req_id)
|
246
|
+
return
|
247
|
+
end
|
248
|
+
|
249
|
+
# 添加CORS头
|
250
|
+
@server_handler.add_cors_headers(res)
|
251
|
+
|
252
|
+
# 处理OPTIONS请求
|
253
|
+
if method == 'OPTIONS'
|
254
|
+
res.status = 200
|
255
|
+
return
|
256
|
+
end
|
257
|
+
|
258
|
+
# 只处理GET和HEAD请求,Unity Package Manager只使用这两种方法
|
259
|
+
if method == 'GET' || method == 'HEAD'
|
260
|
+
route_get_request(req, res, req_id)
|
261
|
+
else
|
262
|
+
handle_unsupported_method(method, res, req_id)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
def route_get_request(req, res, req_id)
|
267
|
+
path = req.path
|
268
|
+
|
269
|
+
log_verbose "[#{req_id}] 收到GET请求: #{path}"
|
270
|
+
puts "\n🔍 [#{req_id}] API请求: GET #{path}"
|
271
|
+
puts "📝 请求参数: #{req.query.inspect}" if req.query && !req.query.empty?
|
272
|
+
|
273
|
+
begin
|
274
|
+
# 健康检查
|
275
|
+
if path == '/health' || path == '/healthz'
|
276
|
+
res.status = 200
|
277
|
+
res['Content-Type'] = 'application/json'
|
278
|
+
health_data = {
|
279
|
+
status: 'ok',
|
280
|
+
uptime: (Time.now - @start_time).to_i,
|
281
|
+
timestamp: Time.now.to_i
|
282
|
+
}
|
283
|
+
res.body = JSON.generate(health_data)
|
284
|
+
puts "✅ [#{req_id}] 健康检查响应: #{health_data.inspect}"
|
285
|
+
return
|
286
|
+
end
|
287
|
+
|
288
|
+
# 根路径 - 显示主页
|
289
|
+
if path == '/' || path == '/index.html'
|
290
|
+
@server_handler.handle_root_request(res, req_id)
|
291
|
+
puts "✅ [#{req_id}] 根路径请求处理完成"
|
292
|
+
return
|
293
|
+
end
|
294
|
+
|
295
|
+
# 确保CORS头被添加到所有响应
|
296
|
+
@server_handler.add_cors_headers(res)
|
297
|
+
|
298
|
+
# 搜索API - Unity Package Manager使用
|
299
|
+
if path == '/-/v1/search'
|
300
|
+
@server_handler.handle_search(req, res, req_id)
|
301
|
+
begin
|
302
|
+
response_data = JSON.parse(res.body)
|
303
|
+
puts "✅ [#{req_id}] 搜索API响应:"
|
304
|
+
puts JSON.pretty_generate(response_data)
|
305
|
+
rescue => e
|
306
|
+
puts "❌ [#{req_id}] 无法解析搜索响应JSON: #{e.message}"
|
307
|
+
end
|
308
|
+
# 包下载 - Unity Package Manager使用
|
309
|
+
elsif path =~ %r{^/([^/]+)/-/(.+\.tgz)$}
|
310
|
+
package_name = $1
|
311
|
+
tarball_name = $2
|
312
|
+
puts "📦 [#{req_id}] 包下载请求: 包名=#{package_name}, 文件=#{tarball_name}"
|
313
|
+
@download_handler.handle_package_download(package_name, tarball_name, res, req_id, req)
|
314
|
+
if res.status == 200
|
315
|
+
puts "✅ [#{req_id}] 包下载成功: #{package_name}/#{tarball_name}, 大小: #{res.body.bytesize} 字节"
|
316
|
+
else
|
317
|
+
begin
|
318
|
+
response_data = JSON.parse(res.body)
|
319
|
+
puts "❌ [#{req_id}] 包下载失败: #{res.status}"
|
320
|
+
puts JSON.pretty_generate(response_data)
|
321
|
+
rescue => e
|
322
|
+
puts "❌ [#{req_id}] 包下载失败: #{res.status}, 响应: #{res.body}"
|
323
|
+
end
|
324
|
+
end
|
325
|
+
# 单个包信息 - Unity Package Manager使用
|
326
|
+
elsif path =~ %r{^/([^/]+)$} && !path.include?('/-/')
|
327
|
+
package_name = $1
|
328
|
+
puts "📋 [#{req_id}] 包信息请求: 包名=#{package_name}"
|
329
|
+
@server_handler.handle_package_info(package_name, res, req_id, req)
|
330
|
+
begin
|
331
|
+
response_data = JSON.parse(res.body)
|
332
|
+
puts "✅ [#{req_id}] 包信息响应:"
|
333
|
+
puts JSON.pretty_generate(response_data)
|
334
|
+
rescue => e
|
335
|
+
puts "❌ [#{req_id}] 无法解析包信息响应JSON: #{e.message}"
|
336
|
+
end
|
337
|
+
# 未知路径
|
338
|
+
else
|
339
|
+
log_verbose "[#{req_id}] 未知GET路径: #{path}"
|
340
|
+
res.status = 404
|
341
|
+
res['Content-Type'] = 'application/json'
|
342
|
+
error_message = {
|
343
|
+
error: "Not Found",
|
344
|
+
code: "UNKNOWN_ROUTE",
|
345
|
+
message: "路径不存在: #{path}"
|
346
|
+
}
|
347
|
+
res.body = JSON.generate(error_message)
|
348
|
+
puts "❌ [#{req_id}] 未知路径响应: #{error_message.inspect}"
|
349
|
+
end
|
350
|
+
rescue => e
|
351
|
+
@server_handler.handle_request_error(e, res, req_id)
|
352
|
+
end
|
353
|
+
end
|
354
|
+
|
355
|
+
def handle_unsupported_method(method, res, req_id)
|
356
|
+
# 不支持的HTTP方法
|
357
|
+
res.status = 405
|
358
|
+
res['Content-Type'] = 'application/json'
|
359
|
+
res.body = JSON.generate({
|
360
|
+
error: "不支持的请求方法",
|
361
|
+
message: "Unity Package Manager只使用GET和HEAD方法,服务器不支持#{method}方法"
|
362
|
+
})
|
363
|
+
log_verbose "[#{req_id}] 不支持的请求方法: #{method}"
|
364
|
+
end
|
365
|
+
|
366
|
+
# 处理未知路径,返回404错误
|
367
|
+
def handle_unknown_path(path, res, req_id = 0)
|
368
|
+
log_verbose "[#{req_id}] 处理未知路径: #{path}"
|
369
|
+
|
370
|
+
# 返回404错误
|
371
|
+
res.status = 404
|
372
|
+
res['Content-Type'] = 'application/json'
|
373
|
+
@server_handler.add_cors_headers(res)
|
374
|
+
|
375
|
+
json_response = JSON.generate({
|
376
|
+
'error' => 'Not Found',
|
377
|
+
'path' => path,
|
378
|
+
'message' => "请求的路径不存在。Unity Package Manager使用的API为: /-/v1/search, /[package-name], /[package-name]/-/[file.tgz]"
|
379
|
+
})
|
380
|
+
res.body = json_response
|
381
|
+
@server_handler.puts_formatted_json(json_response, req_id, "未知路径响应") if @verbose
|
382
|
+
end
|
383
|
+
|
384
|
+
#-----------------------------------------------
|
385
|
+
# 工具方法
|
386
|
+
#-----------------------------------------------
|
387
|
+
|
388
|
+
def log_verbose(message)
|
389
|
+
UI.puts message if @verbose
|
390
|
+
end
|
391
|
+
end
|
392
|
+
end
|
393
|
+
end
|