Integrating multiple file hosting services can become cumbersome as each platform has different APIs, authentication methods, and file-handling processes. Without a clear structure, this can lead to a messy codebase, slowing down development and making it harder to switch providers. That’s where the Client Pattern comes in. It offers a scalable, maintainable way to handle multiple service integrations without complicating your application’s core logic.
In this blog post, I’ll guide you through implementing the Client pattern for integrating multiple file hosting services, such as Gofile and DDownload. You’ll learn how to define a common base client and extend it for each provider, streamlining your integration process and ensuring consistent, maintainable code.
But before diving into that we must answer the following questions you might be wondering about: What is the Client pattern? Why use it?
What is the Client Pattern?
The Client Pattern focuses on creating a distinct module or class that acts as a bridge between your application and a particular service or library. It simplifies the process by centralizing key operations such as sending HTTP requests, managing authentication, and interpreting API responses, allowing the rest of the application to interact with the service through a clean, unified interface.
Think of the Client Pattern as a translator between your app and external services. It standardizes communication, so regardless of the file hosting provider you use, your app speaks the same language when it comes to uploads and downloads.
Why Use the Client Pattern?
The Client Pattern allows us to:
- Abstract common behavior into a base client.
- Easily switch between different file hosting providers.
- Handle the specific details of each provider without cluttering the main logic.
- As your application scales, integrating more file hosting providers becomes as simple as extending the base client, rather than reinventing the wheel for each new service.
- Keep the code clean and maintainable, allowing for easy integration of new services in the future.
Let’s start with the Base client.
At the heart of this design is a base client class that defines common behavior, such as HTTP client configuration and abstract methods for uploading and retrieving files. Each file hosting provider will extend this base class and implement its own version of these methods.
require 'net/http'
module FileHosting
module Base
class Client
def upload(file_path)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def retrieve(file_handle)
raise NotImplementedError, "#{self.class} has not implemented method '#{__method__}'"
end
def http_client(uri)
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = uri.scheme == 'https'
http.verify_mode = if @ssl_verify
OpenSSL::SSL::VERIFY_PEER
else
OpenSSL::SSL::VERIFY_NONE
end
http
end
end
end
end
Fundamental Aspects of the Base Client
- Abstract Methods: Both the upload and retrieve methods raise NotImplementedError. This ensures that subclasses (specific file hosting providers) must implement these methods.
- HTTP Client: The
http_client
method handles HTTP connections with SSL support. It allows optional SSL verification based on the @ssl_verify variable.
Gofile Client Implementation
Now that we have a solid foundation with the Base Client, let’s implement the Gofile client, a popular file hosting provider.
module FileHosting
module Providers
module Gofile
class Client < FileHosting::Base::Client
def initialize(api_key: ENV.fetch('GOFILE_API_KEY'))
@api_key = api_key
@base_url = 'https://api.gofile.io'
end
def upload(file_path)
uri = URI("#{select_server}/contents/uploadfile") # Select server and construct URI
request = Net::HTTP::Post.new(uri) # Create POST request
authorize(request) # Set authorization header with API key
request.set_form([
['file', File.open(file_path)] # Add file to request as multipart/form-data
], 'multipart/form-data')
response = http_client(uri).request(request) # Send request and get response
JSON.parse(response.body) # Parse and return JSON response
end
def retrieve(file_handle)
uri = URI("#{@base_url}/contents/#{file_handle}")
request = Net::HTTP::Get.new(uri)
authorize(request)
response = http_client(uri).request(request)
JSON.parse(response.body)
end
def select_server
uri = URI("#{@base_url}/servers?key=#{@api_key}")
request = Net::HTTP::Get.new(uri)
server_response = http_client(uri).request(request)
authorize(request)
server_data = JSON.parse(server_response.body)
raise 'Failed to get upload server' unless server_data['status'] == 'ok'
server_name = server_data['data']['servers'].sample['name']
@base_url.gsub('api', server_name)
end
def authorize(request)
request['Authorization'] = "Bearer #{@api_key}"
end
end
end
end
end
Fundamental Aspects of the Gofile Client
- API Key: The Gofile::Client is initialized with an API key for authentication.
- Upload File: The upload method selects the appropriate server and uploads the file using a multipart form.
- Retrieve File Info: The retrieve method retrieves information about a specific file from Gofile.
- Server Selection: The select_server method dynamically selects the best server for file uploads.
- Authorization: The authorize method sets the appropriate authorization headers for requests.
DDownload Client Implementation
Here’s how the DDownload client can be implemented by extending the base client:
module FileHosting
module Providers
module DDownload
class Client < FileHosting::Base::Client
def initialize(api_key: ENV.fetch('DDOWNLOAD_API_KEY'))
@api_key = api_key
@base_url = 'https://api-v2.ddownload.com/api'
end
def upload(file_path)
server_data = select_server
uri = URI(server_data['result'])
request = Net::HTTP::Post.new(uri)
request.set_form([
['key', @api_key],
['file', File.open(file_path)],
['sess_id', server_data['sess_id']]
], 'multipart/form-data')
response = http_client(uri).request(request)
JSON.parse(response.body)
end
def retrieve(file_handle)
uri = URI("#{@base_url}/file/info?key=#{@api_key}&file_code=#{file_handle}")
response = http_client(uri).get(uri)
JSON.parse(response.body)
end
def select_server
uri = URI("#{@base_url}/upload/server?key=#{@api_key}")
server_response = Net::HTTP.get_response(uri)
server_data = JSON.parse(server_response.body)
raise "Failed to get upload server: #{server_data['msg']}" unless server_data['status'] == 200
server_data
end
end
end
end
end
Fundamental Aspects of the DDownload Client:
- Server Selection: Just like Gofile, the DDownload client selects an upload server dynamically before uploading the file.
- Authorization: It uses an API key for authorization, passed as a form parameter in the upload requests.
- Upload File: Files are uploaded via multipart forms with an additional session ID (sess_id).
- Retrieve File Info: File details are retrieved using the retrieve method by providing the file handle.
Usage
file = Rails.root.join('app/assets/images/kitty.png')
gofile = FileHosting::Providers::Gofile::Client.new
gofile.upload(file) =>
{"data"=>
{"createTime"=>1727895101,
"downloadPage"=>"https://gofile.io/d/WqeeUZ",
"id"=>"508ab745-ac98-43e3-bf16-b0e316668ff7",
"md5"=>"e0e128f00d44667b4605cf0dbe8a475d",
"mimetype"=>"image/jpeg",
"modTime"=>1727895101,
"name"=>"kitty.jpg",
"parentFolder"=>"4d8d0a92-dc11-4311-8dc0-33f9bf9614ca",
"parentFolderCode"=>"WqeeUZ",
"servers"=>["store3"],
"size"=>243563,
"type"=>"file"},
"status"=>"ok"}
file = Rails.root.join('app/assets/images/kitty.png')
ddownload = FileHosting::Providers::DDownload::Client.new
ddownload.upload(file) =>
[{"file_code"=>"pt5h7u1h0tv9", "file_status"=>"OK"}]
Adding New Providers with Flexibility
Let’s consider a scenario where you want to give users the option to choose their preferred file hosting provider. The following service allows you to dynamically select the correct provider based on the user’s input or configuration.
module FileHosting
class Service
def self.create(provider)
case provider
when :gofile
FileHosting::Providers::Gofile::Client.new
when :ddownload
FileHosting::Providers::DDownload::Client.new
else
raise "Unsupported provider: #{provider}"
end
end
end
end
# usage
service = FileHosting::Service.create(:gofile)
service2 = FileHosting::Service.create(:ddownload)
Conclusion
By using the Client Pattern, we’ve built a flexible, maintainable system that allows us to integrate with multiple file hosting services easily. The Base Client provides common functionality, while each provider implements its own file upload and retrieval logic. This approach makes it easy to add new providers or update existing ones without disrupting the rest of the system.
This architecture is particularly useful when dealing with third-party APIs in various contexts.
As your application grows and requires integration with more file hosting providers,
this pattern will save time and reduce complexity.
Note: Another improvement you could add is the ability to return formatted responses for each provider. Instead of returning a hash object, you can normalize the response to a consistent format, providing a more user-friendly interface.
Hire an Expert
Integrating multiple file hosting services or third-party APIs can be challenging, but we’re here to make it simple! Our expert team specializes in creating scalable, maintainable solutions using design patterns like the Client Pattern to ensure clean, flexible, and efficient code. Whether you’re looking to refactor existing integrations or build a robust system from the ground up, our experienced developers are ready to help you deliver reliable, high-performance results.
Hire an expert from MagmaLabs today and experience the benefits of seamless, maintainable integrations!