How can I use HTTParty to interact with REST APIs that require specific content types?
When working with REST APIs, different endpoints often require specific content types in both request headers and response handling. HTTParty provides flexible options for configuring content types, making it an excellent choice for API integration. This guide covers how to properly set and handle various content types when using HTTParty for API interactions.
Understanding Content Types in REST APIs
Content types tell the server what format your data is in and what format you expect in return. The most common content types for REST APIs include:
application/json
- JSON dataapplication/xml
- XML dataapplication/x-www-form-urlencoded
- Form datamultipart/form-data
- File uploadstext/plain
- Plain text
Setting Request Content Types
JSON Content Type
For most modern REST APIs, JSON is the preferred format. Here's how to configure HTTParty for JSON requests:
require 'httparty'
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
headers 'Content-Type' => 'application/json'
def create_user(user_data)
options = {
body: user_data.to_json,
headers: { 'Content-Type' => 'application/json' }
}
self.class.post('/users', options)
end
end
# Usage
client = ApiClient.new
response = client.create_user({
name: 'John Doe',
email: 'john@example.com'
})
XML Content Type
For APIs that require XML format:
require 'httparty'
class XmlApiClient
include HTTParty
base_uri 'https://api.example.com'
def send_xml_data(xml_content)
options = {
body: xml_content,
headers: {
'Content-Type' => 'application/xml',
'Accept' => 'application/xml'
}
}
self.class.post('/data', options)
end
end
# Usage with XML string
xml_data = <<~XML
<?xml version="1.0" encoding="UTF-8"?>
<user>
<name>John Doe</name>
<email>john@example.com</email>
</user>
XML
client = XmlApiClient.new
response = client.send_xml_data(xml_data)
Form Data Content Type
For traditional form submissions:
require 'httparty'
class FormApiClient
include HTTParty
base_uri 'https://api.example.com'
def submit_form_data(form_params)
options = {
body: form_params,
headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
}
self.class.post('/form-submit', options)
end
end
# Usage
client = FormApiClient.new
response = client.submit_form_data({
username: 'johndoe',
password: 'secretpassword'
})
Handling Response Content Types
Automatic JSON Parsing
HTTParty automatically parses JSON responses when the server returns the appropriate content type:
class JsonApiClient
include HTTParty
base_uri 'https://jsonplaceholder.typicode.com'
format :json
def get_user(id)
response = self.class.get("/users/#{id}")
if response.success?
# HTTParty automatically parses JSON response
puts response['name']
puts response['email']
return response.parsed_response
else
puts "Error: #{response.code} - #{response.message}"
end
end
end
Custom Response Parsing
For APIs with custom content types or specific parsing needs:
class CustomApiClient
include HTTParty
base_uri 'https://api.example.com'
def get_custom_data
options = {
headers: {
'Accept' => 'application/vnd.api+json',
'Content-Type' => 'application/vnd.api+json'
}
}
response = self.class.get('/custom-endpoint', options)
# Handle different content types
case response.headers['content-type']
when /application\/json/
JSON.parse(response.body)
when /application\/xml/
# Use a gem like Nokogiri for XML parsing
require 'nokogiri'
Nokogiri::XML(response.body)
when /text\/csv/
require 'csv'
CSV.parse(response.body, headers: true)
else
response.body
end
end
end
Working with File Uploads
When uploading files, you'll typically use multipart/form-data
:
require 'httparty'
class FileUploadClient
include HTTParty
base_uri 'https://api.example.com'
def upload_file(file_path, additional_params = {})
options = {
body: {
file: File.new(file_path, 'rb'),
**additional_params
},
# HTTParty automatically sets multipart/form-data when files are present
}
self.class.post('/upload', options)
end
def upload_with_explicit_content_type(file_path)
file_content = File.read(file_path)
options = {
body: file_content,
headers: {
'Content-Type' => 'application/octet-stream',
'Content-Disposition' => 'attachment; filename="document.pdf"'
}
}
self.class.post('/binary-upload', options)
end
end
# Usage
client = FileUploadClient.new
response = client.upload_file('/path/to/document.pdf', { description: 'Important document' })
Advanced Content Type Configuration
Global Headers Configuration
Set default content types for all requests in a class:
class ApiClient
include HTTParty
base_uri 'https://api.example.com'
headers 'Content-Type' => 'application/json',
'Accept' => 'application/json',
'User-Agent' => 'MyApp/1.0'
format :json
def initialize(api_key)
self.class.headers 'Authorization' => "Bearer #{api_key}"
end
def make_request(endpoint, data = nil)
if data
self.class.post(endpoint, body: data.to_json)
else
self.class.get(endpoint)
end
end
end
Dynamic Content Type Selection
Choose content types based on the data being sent:
class FlexibleApiClient
include HTTParty
base_uri 'https://api.example.com'
def send_data(endpoint, data, format: :json)
options = case format
when :json
{
body: data.to_json,
headers: { 'Content-Type' => 'application/json' }
}
when :xml
{
body: data.to_xml,
headers: { 'Content-Type' => 'application/xml' }
}
when :form
{
body: data,
headers: { 'Content-Type' => 'application/x-www-form-urlencoded' }
}
else
raise ArgumentError, "Unsupported format: #{format}"
end
self.class.post(endpoint, options)
end
end
# Usage
client = FlexibleApiClient.new
client.send_data('/users', user_data, format: :json)
client.send_data('/legacy-endpoint', form_data, format: :form)
Error Handling and Content Type Validation
Always validate content types and handle errors appropriately:
class RobustApiClient
include HTTParty
base_uri 'https://api.example.com'
def safe_api_call(endpoint, data, expected_content_type = 'application/json')
options = {
body: data.to_json,
headers: {
'Content-Type' => 'application/json',
'Accept' => expected_content_type
},
timeout: 30
}
begin
response = self.class.post(endpoint, options)
# Validate response content type
response_content_type = response.headers['content-type']
unless response_content_type&.include?(expected_content_type)
puts "Warning: Expected #{expected_content_type}, got #{response_content_type}"
end
if response.success?
response.parsed_response
else
handle_error_response(response)
end
rescue HTTParty::Error, Net::TimeoutError => e
puts "Network error: #{e.message}"
nil
end
end
private
def handle_error_response(response)
error_message = case response.headers['content-type']
when /application\/json/
response.parsed_response['error'] || response.parsed_response['message']
when /text\/html/
"HTML error response (likely 404 or 500)"
else
response.body
end
puts "API Error #{response.code}: #{error_message}"
nil
end
end
Best Practices
- Always specify both Content-Type and Accept headers when working with APIs that support multiple formats
- Use the
format
class method to set default parsing behavior - Handle different response content types gracefully with proper error checking
- Set appropriate timeouts when making API requests
- Validate content types in responses to catch API changes early
Integration with Web Scraping
When building comprehensive data collection systems, you might need to combine HTTParty's API capabilities with web scraping tools. For instance, after collecting data through APIs, you might need to handle browser sessions in Puppeteer for additional data that's only available through web interfaces.
For complex workflows involving both API calls and browser automation, consider monitoring network requests in Puppeteer to understand how web applications communicate with their backends.
Conclusion
HTTParty provides excellent flexibility for working with REST APIs that require specific content types. By properly configuring request headers, handling response formats, and implementing robust error handling, you can build reliable API integrations. Remember to always validate content types, handle errors gracefully, and choose the appropriate format for your specific use case.
The key to successful API integration is understanding both the expected request format and how to properly parse the response, regardless of the content type returned by the server.