JavaScript
AWS

s3.getSignedUrl が変な URL を返してきたら

AWS SDK for JavaScript の話です。1分デバッグ記事。

import { S3 } from 'aws-sdk'

const s3 = new S3()

const signedUrl = s3.getSignedUrl('getObject', {
Bucket: 'nyan',
Key: 'wan.csv',
Expires: 60,
})

console.log(signedUrl)
// => https://s3.ap-northeast-1.amazonaws.com/
// !??!?!??!?????!??!?!??!?!?!??!?!?!??!??!

↓ 解決法

import { S3 } from 'aws-sdk'

const s3 = new S3()

const signedUrl = s3.getSignedUrl('getObject', {
Bucket: 'nyan',
Key: 'wan.csv',
Expires: 60,
}, (err, signedUrl) => {
if (err) {
console.error(err)
else {
console.log(signedUrl) // OK!
}
})

これがローカルだと両方ちゃんと動いたりするから不思議です。死ぬほどハマった。

まず getSignedUrl には2パターンの呼び方があって、1つは同期で戻り値に URL が返ってくるもの、もう1つはコールバックで URL が返ってくるものです。

TS の型定義はこんな感じです。

/**

* Get a pre-signed URL for a given operation name.
*/

getSignedUrl(operation: string, params: any, callback: (err: Error, url: string) => void): void;
/**
* Get a pre-signed URL for a given operation name.
*/

getSignedUrl(operation: string, params: any): string;

まじでコールバックがあるかないかだけですね。どっちでもよさそう。なんとなく互換性維持とかだと思ってた。

ここで「なんで同期と非同期両方あんの?」という疑問をちゃんと追っていたらこんなにハマらなかったと思います。

リファレンスを見るとちゃんと書いてあります。


Note: You must ensure that you have static or previously resolved credentials if you call this method synchronously (with no callback), otherwise it may not properly sign the request. If you cannot guarantee this (you are using an asynchronous credential provider, i.e., EC2 IAM roles), you should always call this method with an asynchronous callback.

https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#getSignedUrl-property


AWS SDK for JavaScript では 最初に API 呼び出しを解決するとき、 認証情報を解決します。ここで同期的に読める部分というのは、引数・環境変数などに認証情報が渡されているときに限られます。そうでないパターン、つまり 実行環境に割り当てられた Role から AssumeRole を経て一時的な認証情報をゲットしないといけない場合 に関しては、もちろんネットワークアクセスが行われるわけですから、同期的な方の getSignedUrl はまともに動きません。そりゃそうだという感じはあるんだけど。

認証情報はキャッシュされるので、他の API 呼び出しの後だったら同期の方でもちゃんと動くはずです。未検証だけど多分そうでしょう。おわり。

P.S.

なんでエラーや警告を返さないのかは謎です。困ることあるか?