Rubyのaws-sdk-s3を使っていてマルチパートアップロードをしようと試していたら
完了(Complete)リクエストで次のようなエラーに遭遇したのでメモを残しておく
Aws::S3::Errors::MalformedXML: The XML you provided was not well-formed or did not validate against our published schema
少し調べてみたところパターンは色々あるが
指定された XML の形式が正しくないか、発行されたスキーマに対して検証されませんでした
ということなので
そもそも、どのような通信しているのか確認する
下記の記事を参考に通信内容を見ることでリクエストの内容が間違っていないか確認する
AWS SDK for Rubyでhttp_wire_traceオプションを使ってAPIとの通信内容を出力する -- ぺけみさお
AWS SDKのtraceモードで通信の中身を見る
client生成時にhttp_wire_trace: true
を追加するだけ
client = Aws::S3::Client.new(region: 'ap-northeast-1', http_wire_trace: true)
- 出力結果
..... ..... ..... ..... <- "POST /100M_sample.out_d91654bcbd22f250d6be41e1a0fa6d02?uploadId=xxxxxxxxxx-- HTTP/1.1\r\nAccept-Encoding: \r\nUser-Agent: aws-sdk-ruby3/3.130.0 ruby/3.1.1 x86_64-linux aws-sdk-s3/1.113.0\r\nExpect: 100-continue\r\nHost: presigned-upload-post-sample.s3.ap-northeast-1.amazonaws.com\r\nX-Amz-Date: 20220318T104220Z\r\nX-Amz-Content-Sha256: xxxxxxxxx\r\nAuthorization: AWS4-HMAC-SHA256 Credential=xxxxxxxxxx/20220318/ap-northeast-1/s3/aws4_request, SignedHeaders=host;user-agent;x-amz-content-sha256;x-amz-date, Signature=xxxxxxxxxx\r\nContent-Length: 138\r\nAccept: */*\r\n\r\n" -> "HTTP/1.1 100 Continue\r\n" -> "\r\n" <- "<CompleteMultipartUpload xmlns=\"http://s3.amazonaws.com/doc/2006-03-01/\"><Part></Part><Part></Part><Part></Part></CompleteMultipartUpload>" -> "HTTP/1.1 400 Bad Request\r\n" -> "x-amz-request-id: 4HCRHHNNJ4FBTZ24\r\n" -> "x-amz-id-2: MlNa5AE+ikBQs/xxxxxxxxxx\r\n" -> "Content-Type: application/xml\r\n" -> "Transfer-Encoding: chunked\r\n" -> "Date: Fri, 18 Mar 2022 10:42:23 GMT\r\n" -> "Server: AmazonS3\r\n" -> "Connection: close\r\n" -> "\r\n" -> "118\r\n" reading 280 bytes... -> "<Error><Code>MalformedXML</Code><Message>The XML you provided was not well-formed or did not validate against our published schema</Message><RequestId>4HCRHHNNJ4FBTZ24</RequestId><HostId>MlNa5AE+ikBQs/xxxxxxxxxx</HostId></Error>" read 280 bytes reading 2 bytes... -> "\r\n" read 2 bytes -> "0\r\n" -> "\r\n" Conn close Aws::S3::Errors::MalformedXML: The XML you provided was not well-formed or did not validate against our published schema
Partに何も入っていないように見える…
これは怪しそうということでAPIのドキュメントを見に行く
CompleteMultipartUpload - Amazon Simple Storage Service
やはりPartの中にはETag
やPartNumber
などが必要そうだが、中身が入っていないのが問題のよう
sdkのcomplete_multipart_upload
を呼び出すときにはPartの中身も指定しているのにXMLになると抜け落ちている…
XMLの生成でパラメータが入っていないってのが問題のようなのでどこで値が入らなくなっているのか見つける必要がある
ソースコードを追ってみる
呼び出し側のコードは下記
params
にリクエストパラメータが入ってくる
client = Aws::S3::Client.new(region: 'ap-northeast-1') res = client.complete_multipart_upload( bucket: ENV['AWS_BUCKET'], key: params['object_key'], multipart_upload: { parts: params['parts'] }, upload_id: params['upload_id'], )
こんなかんじの
{"upload_id"=>"xxxxxxxxxx"parts"=>[{"etag"=>"6c89658d051ac5d1938ae1b749700753", "part_number"=>1}, {"etag"=>"6c89658d051ac5d1938ae1b749700753", "part_number"=>2}, {"etag"=>"cca90700d98a132d7a1f1a2740d7e067", "part_number"=>3}], "object_key"=>"multipart_upload/100M_sample.out"}
色々追ってみた結果下記
- gems/aws-sdk-core/lib/aws-sdk-core/xml/builder.rb
aws-sdk-ruby/builder.rb at version-3 · aws/aws-sdk-ruby
def structure(name, ref, values) if values.empty? node(name, ref) else node(name, ref, structure_attrs(ref, values)) do ref.shape.members.each do |member_name, member_ref| next if values[member_name].nil? next if xml_attribute?(member_ref) member(member_ref.location_name, member_ref, values[member_name]) end end end end
この関数が怪しかった
valuesにパラメータが渡ってくる
member_nameはsymbolが渡ってくる(:partとか)
next if values[member_name].nil?
ここで子要素がある場合はmember
を呼び出して子nodeを生成する流れになっている
渡すパラメータのキーがシンボルになっていないと判定に失敗(子要素が無いと判断される)する、というだけだった…
フロント側のリクエスト内容をsymbolizeせずそのままパラメータに流したのでこのような現象に遭遇した模様…
気を取り直してパラメータのキーをシンボルにして渡すようにしたらcompleteできたのと、S3のコンソールから確認もできた
まとめ
AWSのRuby SDKを使う場合はパラメータのハッシュキーにはシンボルを使う
ちなみに文字列のキーでもXMLでパラメータを生成できるようコードいじってみたがテストが結構こけて、さっと対応できなさそうだったため諦めた
時間結構溶かしたので気を付けたい