如何构建有效的 ActiveStorage 直接上传请求到 GCS?
How to build a valid ActiveStorage direct upload request to GCS?
这是关于将 ActiveStorage 与 GCS 一起用于 API 客户端用例。 (Rails 5.2.1, 5.2.2)
我正在编写一个测试来探索如何制作一个模拟直接上传到 GCS 的请求,该请求由通用 DirectUploadsController 准备。 This generic controller 是 ActiveStorage 的一部分。这个想法是稍后在与同一后端对话的移动应用程序中复制代码。
AS 配置在开发环境中运行良好,既可以使用通过控制器上传,也可以使用 AS 附带的 JS 集成直接上传。这就是为什么我认为配置一定没问题。 ('test' 和 'development' env 在这个阶段使用完全相同的设置。)
测试代码在下框内
它总是从 RestClient.put
调用中引发 403 Forbidden 响应。
响应消息抱怨签名不匹配,详情如下。先上测试代码:
require 'test_helper'
class UploadControllerTest < ActionDispatch::IntegrationTest
test "direct upload from controller prepared blob" do
pathname = file_fixture('cube.png')
data = pathname.binread
content_type = "image/png"
post rails_direct_uploads_path, params: {
blob: {
filename: pathname.basename,
byte_size: pathname.size,
checksum: Digest::MD5.base64digest(data),
content_type: content_type
}
}
assert_equal 27195, pathname.size
assert_response :success
json = response.parsed_body
direct_upload = json["direct_upload"]
signed_url = direct_upload["url"]
headers = direct_upload["headers"]
assert_equal({ "Content-MD5" => "tmBHZQCm+qBzGFEaDwmpnA==" }, headers)
assert_match /&Signature=/, signed_url
assert_match /&Expires=/, signed_url
assert_match %r{^https://storage.googleapis.com}, signed_url
response = RestClient.put(
signed_url,
data,
headers.merge("Content-Type" => content_type)
)
assert_response :success
rescue RestClient::Forbidden => e
pp e.response.body
fail "Failing with 403 Forbidden" # always ends up here
end
end
生成的响应正文是这样的 XML:
<?xml version='1.0' encoding='UTF-8'?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
<StringToSign>PUT\n" +
"tmBHZQCm+qBzGFEaDwmpnA==\n" +
"image/png\n" +
"1544517548\n" +
"/planprop-test-bucket/gVn9zVCumGJxiu2kU6mFWUVV</StringToSign>
</Error>
错误代码为:
SignatureDoesNotMatch
以及随附的消息:
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
签名字符串的列出部分是校验和(上面断言)、过期时间(URL的一部分)、内容类型(上面断言)和对象(桶名和密钥,部分URL)。所以我没有看到可能会出现不匹配的部分。
有什么问题吗?
上面代码的问题是发送了application/x-www-form-urlencoded
的Content-Type
header,而签名时使用了none
要使其正常工作,请将 PUT 请求代码更改为
response = RestClient.put(
signed_url,
data,
headers.merge(content_type: "")
)
使用 nil
仍会在请求中显示此默认值。
顺便说一下,这种行为并不是 RestClient 独有的。但在我测试的许多 Ruby 个 http 客户端(Faraday、net/http、httpclient)中也是如此。 Excon 在这里是个例外,它不会在没有被告知的情况下发送默认内容类型 header。
这是关于将 ActiveStorage 与 GCS 一起用于 API 客户端用例。 (Rails 5.2.1, 5.2.2)
我正在编写一个测试来探索如何制作一个模拟直接上传到 GCS 的请求,该请求由通用 DirectUploadsController 准备。 This generic controller 是 ActiveStorage 的一部分。这个想法是稍后在与同一后端对话的移动应用程序中复制代码。
AS 配置在开发环境中运行良好,既可以使用通过控制器上传,也可以使用 AS 附带的 JS 集成直接上传。这就是为什么我认为配置一定没问题。 ('test' 和 'development' env 在这个阶段使用完全相同的设置。)
测试代码在下框内
它总是从 RestClient.put
调用中引发 403 Forbidden 响应。
响应消息抱怨签名不匹配,详情如下。先上测试代码:
require 'test_helper'
class UploadControllerTest < ActionDispatch::IntegrationTest
test "direct upload from controller prepared blob" do
pathname = file_fixture('cube.png')
data = pathname.binread
content_type = "image/png"
post rails_direct_uploads_path, params: {
blob: {
filename: pathname.basename,
byte_size: pathname.size,
checksum: Digest::MD5.base64digest(data),
content_type: content_type
}
}
assert_equal 27195, pathname.size
assert_response :success
json = response.parsed_body
direct_upload = json["direct_upload"]
signed_url = direct_upload["url"]
headers = direct_upload["headers"]
assert_equal({ "Content-MD5" => "tmBHZQCm+qBzGFEaDwmpnA==" }, headers)
assert_match /&Signature=/, signed_url
assert_match /&Expires=/, signed_url
assert_match %r{^https://storage.googleapis.com}, signed_url
response = RestClient.put(
signed_url,
data,
headers.merge("Content-Type" => content_type)
)
assert_response :success
rescue RestClient::Forbidden => e
pp e.response.body
fail "Failing with 403 Forbidden" # always ends up here
end
end
生成的响应正文是这样的 XML:
<?xml version='1.0' encoding='UTF-8'?>
<Error>
<Code>SignatureDoesNotMatch</Code>
<Message>
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
</Message>
<StringToSign>PUT\n" +
"tmBHZQCm+qBzGFEaDwmpnA==\n" +
"image/png\n" +
"1544517548\n" +
"/planprop-test-bucket/gVn9zVCumGJxiu2kU6mFWUVV</StringToSign>
</Error>
错误代码为:
SignatureDoesNotMatch
以及随附的消息:
The request signature we calculated does not match the signature you provided. Check your Google secret key and signing method.
签名字符串的列出部分是校验和(上面断言)、过期时间(URL的一部分)、内容类型(上面断言)和对象(桶名和密钥,部分URL)。所以我没有看到可能会出现不匹配的部分。
有什么问题吗?
上面代码的问题是发送了application/x-www-form-urlencoded
的Content-Type
header,而签名时使用了none
要使其正常工作,请将 PUT 请求代码更改为
response = RestClient.put(
signed_url,
data,
headers.merge(content_type: "")
)
使用 nil
仍会在请求中显示此默认值。
顺便说一下,这种行为并不是 RestClient 独有的。但在我测试的许多 Ruby 个 http 客户端(Faraday、net/http、httpclient)中也是如此。 Excon 在这里是个例外,它不会在没有被告知的情况下发送默认内容类型 header。