Skip to content

Commit deae9a2

Browse files
committed
implement uploads
1 parent 8c95721 commit deae9a2

File tree

9 files changed

+190
-2
lines changed

9 files changed

+190
-2
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ __pycache__
1212
*.rej
1313
Thumbs.db
1414
pkg/
15-
.gem
15+
*.gem

Gemfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ gem 'rack'
66
gem 'puma'
77
gem 'thor'
88
gem 'json'
9+
gem 'httpx', '~> 1.3'
910

1011
group :development, :test do
1112
gem 'rspec', '~> 3.13'

Gemfile.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ GEM
22
remote: https://rubygems.org/
33
specs:
44
diff-lcs (1.5.1)
5+
http-2 (1.0.1)
6+
httpx (1.3.0)
7+
http-2 (>= 1.0.0)
58
json (2.7.1)
69
nio4r (2.7.1)
710
puma (6.4.2)
@@ -29,6 +32,7 @@ PLATFORMS
2932
ruby
3033

3134
DEPENDENCIES
35+
httpx (~> 1.3)
3236
json
3337
puma
3438
rack

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,6 @@ Hawksi is released under the MIT license. See the [LICENSE](LICENSE) file for de
6767

6868
## TODO
6969

70-
- [ ] Add upload command
70+
- [x] Add upload command
7171
- [ ] Add standard for code formatting
7272
- [ ] Store logs in log/hawksi.log

hawksi.gemspec

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ Gem::Specification.new do |spec|
3232
spec.add_dependency 'puma', '~> 5.0'
3333
spec.add_dependency 'thor', '~> 1.1'
3434
spec.add_dependency 'json', '~> 2.5'
35+
spec.add_dependency 'httpx', '~> 1.3'
36+
3537

3638
spec.add_development_dependency 'bundler', '~> 2.2'
3739
spec.add_development_dependency 'rake', '~> 13.0'

lib/cli.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
require_relative 'request_interceptor'
55
require_relative 'file_storage'
66
require_relative 'captures_cli'
7+
require_relative 'uploads_cli'
78

89
class CLI < Thor
910
desc "start", "Starts the Hawksi Interceptor server"
@@ -23,6 +24,9 @@ def stop
2324
desc "captures", "Manage captures"
2425
subcommand "captures", CapturesCLI
2526

27+
desc "uploads", "Uploads captured requests and responses"
28+
subcommand "uploads", UploadsCLI
29+
2630
desc "clear", "Clears stored request/response data"
2731
def clear
2832
FileUtils.rm_rf('./intercepted_data/requests')

lib/file_handler.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
require 'fileutils'
2+
require 'securerandom'
3+
4+
class FileHandler
5+
def initialize(base_dir, logger)
6+
@base_dir = base_dir
7+
@logger = logger
8+
end
9+
10+
def find_files
11+
glob_pattern = File.join(@base_dir, '{requests,responses}', '*.json')
12+
Dir.glob(glob_pattern)
13+
end
14+
15+
def generate_client_uuid
16+
uuid_file = File.join(@base_dir, 'client_uuid.txt')
17+
if File.exist?(uuid_file)
18+
client_uuid = File.read(uuid_file).strip
19+
@logger.info "Using existing client UUID: #{client_uuid}"
20+
else
21+
client_uuid = SecureRandom.uuid
22+
File.write(uuid_file, client_uuid)
23+
@logger.info "Generated and stored new client UUID: #{client_uuid}"
24+
end
25+
client_uuid
26+
end
27+
28+
def create_tar_gz_files(files)
29+
tar_gz_files = []
30+
files.each do |file|
31+
tar_file = "#{file}.tar"
32+
tar_gz_file = "#{tar_file}.gz"
33+
34+
unless File.exist?(tar_gz_file)
35+
# Create tarball
36+
system("tar -cf #{tar_file} #{file}")
37+
38+
# Compress tarball
39+
system("gzip #{tar_file}")
40+
end
41+
42+
43+
tar_gz_files << tar_gz_file
44+
end
45+
tar_gz_files
46+
end
47+
end

lib/file_uploader.rb

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
require 'httpx'
2+
require 'logger'
3+
4+
class FileUploader
5+
UPLOAD_URL = "https://run.mocky.io/v3/86b7c197-1ac7-4d7b-a985-24255ae89722"
6+
7+
def initialize(logger, client_uuid)
8+
@logger = logger
9+
@client_uuid = client_uuid
10+
end
11+
12+
def upload_files(tar_gz_files)
13+
log_upload_start(tar_gz_files)
14+
threads = create_upload_threads(tar_gz_files)
15+
wait_for_threads(threads)
16+
end
17+
18+
private
19+
20+
def log_upload_start(tar_gz_files)
21+
@logger.info "Starting upload of #{tar_gz_files.size} files."
22+
end
23+
24+
def create_upload_threads(tar_gz_files)
25+
tar_gz_files.each_slice(10).map do |batch|
26+
Thread.start { upload_batch(batch) }
27+
end
28+
end
29+
30+
def wait_for_threads(threads)
31+
threads.each(&:join)
32+
end
33+
34+
def upload_batch(batch)
35+
batch.each { |tar_gz_file| upload_file(tar_gz_file) }
36+
end
37+
38+
def upload_file(tar_gz_file)
39+
HTTPX.wrap do |client|
40+
response = post_file(client, tar_gz_file)
41+
log_upload_result(tar_gz_file, response)
42+
response
43+
end
44+
end
45+
46+
def post_file(client, tar_gz_file)
47+
client.post(UPLOAD_URL,
48+
headers: { "x-client-id" => @client_uuid },
49+
body: File.read(tar_gz_file))
50+
rescue => e
51+
@logger.error "Failed to upload #{tar_gz_file}: #{e.message}"
52+
nil
53+
end
54+
55+
def log_upload_result(tar_gz_file, response)
56+
if response
57+
@logger.info "Uploaded #{tar_gz_file}: #{response.status}"
58+
else
59+
@logger.error "Failed to upload #{tar_gz_file}"
60+
end
61+
end
62+
end

lib/uploads_cli.rb

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
require 'thor'
2+
require 'securerandom'
3+
require 'httpx'
4+
require 'fileutils'
5+
require 'open3'
6+
require_relative 'file_storage'
7+
require_relative 'file_uploader'
8+
require_relative 'file_handler'
9+
require 'logger'
10+
11+
class UploadsCLI < Thor
12+
desc "update", "Update uploaded requests and responses"
13+
option :base_dir, type: :string, desc: 'Base directory for storing intercepted data. Defaults to ./tmp/intercepted_data'
14+
15+
def initialize(*args)
16+
super
17+
@logger = Logger.new(STDOUT)
18+
@logger.level = Logger::INFO
19+
@base_dir = options[:base_dir] || FileStorage.base_dir
20+
@file_handler = FileHandler.new(@base_dir, @logger)
21+
@client_uuid = get_client_uuid
22+
@file_uploader = FileUploader.new(@logger, @client_uuid)
23+
end
24+
25+
def update(*args)
26+
set_base_dir
27+
files = find_files
28+
29+
if files.empty?
30+
@logger.info "No captured requests or responses found."
31+
return
32+
end
33+
34+
tar_gz_files = create_tar_gz_files(files)
35+
return unless valid_tar_gz_files?(tar_gz_files)
36+
37+
upload_files(tar_gz_files)
38+
end
39+
40+
private
41+
42+
def set_base_dir
43+
FileStorage.base_dir = @base_dir
44+
end
45+
46+
def get_client_uuid
47+
@file_handler.generate_client_uuid
48+
end
49+
50+
def find_files
51+
@file_handler.find_files
52+
end
53+
54+
def create_tar_gz_files(files)
55+
@file_handler.create_tar_gz_files(files)
56+
end
57+
58+
def valid_tar_gz_files?(tar_gz_files)
59+
return true if tar_gz_files.is_a?(Array)
60+
61+
@logger.error "Expected tar_gz_files to be an array, but got #{tar_gz_files.class}"
62+
false
63+
end
64+
65+
def upload_files(tar_gz_files)
66+
@file_uploader.upload_files(tar_gz_files)
67+
end
68+
end

0 commit comments

Comments
 (0)