notebook

都内でWEB系エンジニアやってます。

aws-sdkのErrors::MalformedXML対応例

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との通信内容を出力する -- ぺけみさお

www.xmisao.com

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

docs.aws.amazon.com

やはりPartの中にはETagPartNumberなどが必要そうだが、中身が入っていないのが問題のよう

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でパラメータを生成できるようコードいじってみたがテストが結構こけて、さっと対応できなさそうだったため諦めた

時間結構溶かしたので気を付けたい