diff --git a/lib/cloudtasker/backend/google_cloud_task_v1.rb b/lib/cloudtasker/backend/google_cloud_task_v1.rb index e63613f..1157c2f 100644 --- a/lib/cloudtasker/backend/google_cloud_task_v1.rb +++ b/lib/cloudtasker/backend/google_cloud_task_v1.rb @@ -119,12 +119,16 @@ def self.format_task_payload(payload) # Format dispatch_deadline to Google::Protobuf::Duration payload[:dispatch_deadline] = format_protobuf_duration(payload[:dispatch_deadline]) - # Encode job content to support UTF-8. Google Cloud Task - # expect content to be ASCII-8BIT compatible (binary) + # Setup headers payload[:http_request][:headers] ||= {} payload[:http_request][:headers][Cloudtasker::Config::CONTENT_TYPE_HEADER] = 'text/json' - payload[:http_request][:headers][Cloudtasker::Config::ENCODING_HEADER] = 'Base64' - payload[:http_request][:body] = Base64.encode64(payload[:http_request][:body]) + + # Conditionally encode job content to support UTF-8. + # Google Cloud Task expect content to be ASCII-8BIT compatible (binary) + if config.base64_encode_body + payload[:http_request][:headers][Cloudtasker::Config::ENCODING_HEADER] = 'Base64' + payload[:http_request][:body] = Base64.encode64(payload[:http_request][:body]) + end payload.compact end diff --git a/lib/cloudtasker/backend/google_cloud_task_v2.rb b/lib/cloudtasker/backend/google_cloud_task_v2.rb index e806dc1..ff01442 100644 --- a/lib/cloudtasker/backend/google_cloud_task_v2.rb +++ b/lib/cloudtasker/backend/google_cloud_task_v2.rb @@ -121,12 +121,16 @@ def self.format_task_payload(payload) # Format dispatch_deadline to Google::Protobuf::Duration payload[:dispatch_deadline] = format_protobuf_duration(payload[:dispatch_deadline]) - # Encode job content to support UTF-8. - # Google Cloud Task expect content to be ASCII-8BIT compatible (binary) + # Setup headers payload[:http_request][:headers] ||= {} payload[:http_request][:headers][Cloudtasker::Config::CONTENT_TYPE_HEADER] = 'text/json' - payload[:http_request][:headers][Cloudtasker::Config::ENCODING_HEADER] = 'Base64' - payload[:http_request][:body] = Base64.encode64(payload[:http_request][:body]) + + # Conditionally encode job content to support UTF-8. + # Google Cloud Task expect content to be ASCII-8BIT compatible (binary) + if config.base64_encode_body + payload[:http_request][:headers][Cloudtasker::Config::ENCODING_HEADER] = 'Base64' + payload[:http_request][:body] = Base64.encode64(payload[:http_request][:body]) + end payload.compact end diff --git a/lib/cloudtasker/config.rb b/lib/cloudtasker/config.rb index a4e5298..0aabd11 100644 --- a/lib/cloudtasker/config.rb +++ b/lib/cloudtasker/config.rb @@ -8,7 +8,8 @@ class Config attr_accessor :redis, :store_payloads_in_redis, :gcp_queue_prefix attr_writer :secret, :gcp_location_id, :gcp_project_id, :processor_path, :logger, :mode, :max_retries, - :dispatch_deadline, :on_error, :on_dead, :oidc, :local_server_ssl_verify + :dispatch_deadline, :on_error, :on_dead, :oidc, :local_server_ssl_verify, + :base64_encode_body # Max Cloud Task size in bytes MAX_TASK_SIZE = 100 * 1024 # 100 KB @@ -56,6 +57,9 @@ class Config # Default on_error Proc DEFAULT_ON_ERROR = ->(error, worker) {} + # Default base64 encoding flag + DEFAULT_BASE64_ENCODE_BODY = true + # Cache key prefix used to store workers in cache and retrieve # them later. WORKER_STORE_PREFIX = 'worker_store' @@ -301,5 +305,15 @@ def server_middleware def local_server_ssl_verify @local_server_ssl_verify.nil? ? DEFAULT_LOCAL_SERVER_SSL_VERIFY_MODE : @local_server_ssl_verify end + + # + # Return whether to base64 encode the task body when sending to Cloud Tasks. + # Encoding is enabled by default to support UTF-8 content. + # + # @return [Boolean] Whether to base64 encode the body. + # + def base64_encode_body + @base64_encode_body.nil? ? DEFAULT_BASE64_ENCODE_BODY : @base64_encode_body + end end end diff --git a/spec/cloudtasker/backend/google_cloud_task_v1_spec.rb b/spec/cloudtasker/backend/google_cloud_task_v1_spec.rb index 17a336c..575cad0 100644 --- a/spec/cloudtasker/backend/google_cloud_task_v1_spec.rb +++ b/spec/cloudtasker/backend/google_cloud_task_v1_spec.rb @@ -192,19 +192,39 @@ payload[:schedule_time] = described_class.format_protobuf_time(arg_payload[:schedule_time]) payload[:dispatch_deadline] = described_class.format_protobuf_duration(arg_payload[:dispatch_deadline]) payload[:http_request][:headers]['Content-Type'] = 'text/json' - payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' - payload[:http_request][:body] = Base64.encode64(arg_payload[:http_request][:body]) + if config.base64_encode_body + payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' + payload[:http_request][:body] = Base64.encode64(arg_payload[:http_request][:body]) + end payload.compact end - context 'with defined keys' do - it { is_expected.to eq(expected_payload) } + context 'with base64 encoding enabled' do + before { config.base64_encode_body = true } + + context 'with defined keys' do + it { is_expected.to eq(expected_payload) } + end + + context 'with nil keys' do + let(:arg_payload) { job_payload.merge(some_nil_key: nil) } + + it { is_expected.to eq(expected_payload) } + end end - context 'with nil keys' do - let(:arg_payload) { job_payload.merge(some_nil_key: nil) } + context 'with base64 encoding disabled' do + before { config.base64_encode_body = false } - it { is_expected.to eq(expected_payload) } + context 'with defined keys' do + it { is_expected.to eq(expected_payload) } + end + + context 'with nil keys' do + let(:arg_payload) { job_payload.merge(some_nil_key: nil) } + + it { is_expected.to eq(expected_payload) } + end end end @@ -255,8 +275,10 @@ payload[:schedule_time] = described_class.format_protobuf_time(job_payload[:schedule_time]) payload[:dispatch_deadline] = described_class.format_protobuf_duration(job_payload[:dispatch_deadline]) payload[:http_request][:headers]['Content-Type'] = 'text/json' - payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' - payload[:http_request][:body] = Base64.encode64(job_payload[:http_request][:body]) + if config.base64_encode_body + payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' + payload[:http_request][:body] = Base64.encode64(job_payload[:http_request][:body]) + end payload end diff --git a/spec/cloudtasker/backend/google_cloud_task_v2_spec.rb b/spec/cloudtasker/backend/google_cloud_task_v2_spec.rb index 362fd0f..16e876b 100644 --- a/spec/cloudtasker/backend/google_cloud_task_v2_spec.rb +++ b/spec/cloudtasker/backend/google_cloud_task_v2_spec.rb @@ -204,19 +204,39 @@ payload[:schedule_time] = described_class.format_protobuf_time(arg_payload[:schedule_time]) payload[:dispatch_deadline] = described_class.format_protobuf_duration(arg_payload[:dispatch_deadline]) payload[:http_request][:headers]['Content-Type'] = 'text/json' - payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' - payload[:http_request][:body] = Base64.encode64(arg_payload[:http_request][:body]) + if config.base64_encode_body + payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' + payload[:http_request][:body] = Base64.encode64(arg_payload[:http_request][:body]) + end payload.compact end - context 'with defined keys' do - it { is_expected.to eq(expected_payload) } + context 'with base64 encoding enabled' do + before { config.base64_encode_body = true } + + context 'with defined keys' do + it { is_expected.to eq(expected_payload) } + end + + context 'with nil keys' do + let(:arg_payload) { job_payload.merge(some_nil_key: nil) } + + it { is_expected.to eq(expected_payload) } + end end - context 'with nil keys' do - let(:arg_payload) { job_payload.merge(some_nil_key: nil) } + context 'with base64 encoding disabled' do + before { config.base64_encode_body = false } - it { is_expected.to eq(expected_payload) } + context 'with defined keys' do + it { is_expected.to eq(expected_payload) } + end + + context 'with nil keys' do + let(:arg_payload) { job_payload.merge(some_nil_key: nil) } + + it { is_expected.to eq(expected_payload) } + end end end @@ -267,8 +287,10 @@ payload[:schedule_time] = described_class.format_protobuf_time(job_payload[:schedule_time]) payload[:dispatch_deadline] = described_class.format_protobuf_duration(job_payload[:dispatch_deadline]) payload[:http_request][:headers]['Content-Type'] = 'text/json' - payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' - payload[:http_request][:body] = Base64.encode64(job_payload[:http_request][:body]) + if config.base64_encode_body + payload[:http_request][:headers]['Content-Transfer-Encoding'] = 'Base64' + payload[:http_request][:body] = Base64.encode64(job_payload[:http_request][:body]) + end payload end diff --git a/spec/cloudtasker/config_spec.rb b/spec/cloudtasker/config_spec.rb index e05b5a4..d0fce8f 100644 --- a/spec/cloudtasker/config_spec.rb +++ b/spec/cloudtasker/config_spec.rb @@ -16,6 +16,7 @@ let(:on_dead) { ->(e, w) {} } let(:oidc) { nil } let(:local_server_ssl_verify) { false } + let(:base64_encode_body) { true } let(:rails_hosts) { [] } let(:rails_secret) { 'rails_secret' } @@ -48,6 +49,7 @@ c.on_dead = on_dead c.oidc = oidc c.local_server_ssl_verify = local_server_ssl_verify + c.base64_encode_body = base64_encode_body end Cloudtasker.config @@ -337,6 +339,20 @@ end end + describe '#base64_encode_body' do + subject { config.base64_encode_body } + + context 'with value specified via config' do + it { is_expected.to eq(base64_encode_body) } + end + + context 'with no value' do + let(:base64_encode_body) { nil } + + it { is_expected.to eq(described_class::DEFAULT_BASE64_ENCODE_BODY) } + end + end + describe '#client_middleware' do subject(:middlewares) { config.client_middleware }