Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/import_export/remote_stream_upload.rb')
-rw-r--r--lib/gitlab/import_export/remote_stream_upload.rb117
1 files changed, 117 insertions, 0 deletions
diff --git a/lib/gitlab/import_export/remote_stream_upload.rb b/lib/gitlab/import_export/remote_stream_upload.rb
new file mode 100644
index 00000000000..f3bd241c0bd
--- /dev/null
+++ b/lib/gitlab/import_export/remote_stream_upload.rb
@@ -0,0 +1,117 @@
+# frozen_string_literal: true
+
+# This class downloads a file from one URL and uploads it to another URL
+# without having to save the file on the disk and loading the whole file in
+# memory. The download and upload are performed in chunks size of
+# `buffer_size`. A chunk is downloaded, then uploaded, then a next chunk is
+# downloaded and uploaded. This repeats until all the file is processed.
+
+module Gitlab
+ module ImportExport
+ class RemoteStreamUpload
+ def initialize(download_url:, upload_url:, options: {})
+ @download_url = download_url
+ @upload_url = upload_url
+ @upload_method = options[:upload_method] || :post
+ @upload_content_type = options[:upload_content_type] || 'application/gzip'
+ end
+
+ def execute
+ receive_data(download_url) do |response, chunks|
+ send_data(upload_url, response.content_length, chunks) do |response|
+ if response.code != '200'
+ raise StreamError.new("Invalid response code while uploading file. Code: #{response.code}", response.body)
+ end
+ end
+ end
+ end
+ class StreamError < StandardError
+ attr_reader :response_body
+
+ def initialize(message, response_body = '')
+ super(message)
+ @response_body = response_body
+ end
+ end
+ class ChunkStream
+ DEFAULT_BUFFER_SIZE = 128.kilobytes
+
+ def initialize(chunks)
+ @chunks = chunks
+ @last_chunk = nil
+ @end_of_chunks = false
+ end
+
+ def read(n1 = nil, n2 = nil)
+ ensure_chunk&.read(n1, n2)
+ end
+
+ private
+
+ def ensure_chunk
+ return @last_chunk if @last_chunk && !@last_chunk.eof?
+ return if @end_of_chunks
+
+ @last_chunk = read_next_chunk
+ end
+
+ def read_next_chunk
+ next_chunk = StringIO.new
+
+ begin
+ next_chunk.write(@chunks.next) until next_chunk.size > DEFAULT_BUFFER_SIZE
+ rescue StopIteration
+ @end_of_chunks = true
+ end
+
+ next_chunk.rewind
+
+ next_chunk
+ end
+ end
+
+ private
+
+ attr_reader :download_url, :upload_url, :upload_method, :upload_content_type, :logger
+
+ def receive_data(uri)
+ http = Gitlab::HTTPConnectionAdapter.new(URI(uri), {}).connection
+
+ http.start do
+ request = Net::HTTP::Get.new(uri)
+ http.request(request) do |response|
+ if response.code == '200'
+ yield(response, response.enum_for(:read_body))
+ else
+ raise StreamError.new(
+ "Invalid response code while downloading file. Code: #{response.code}",
+ response.body
+ )
+ end
+ end
+ end
+ end
+
+ def send_data(uri, content_length, chunks)
+ http = Gitlab::HTTPConnectionAdapter.new(URI(uri), {}).connection
+
+ http.start do
+ request = upload_request_class(upload_method).new(uri)
+ request.body_stream = ChunkStream.new(chunks)
+ request.content_length = content_length
+ request.content_type = upload_content_type
+
+ http.request(request) do |response|
+ yield(response)
+ end
+ end
+ end
+
+ def upload_request_class(upload_method)
+ return Net::HTTP::Put if upload_method == :put
+
+ Net::HTTP::Post
+ end
+ end
+ end
+end