cURLコマンドで「クエリ文字列が在るURL」を扱うときはURLを囲もう、という話

cURLコマンドでAPIを叩くとき、URL部分はダブルクオートで囲みましょう、という話です。
2020.06.08

お試しでAPIをサクッと叩くとき、cURLコマンドをよく使います。とてもお手軽で便利なのですが、雰囲気で使っていたのでハマりました。

ハマったこと

とあるAPIに対して、クエリ文字列付きで下記のようにアクセスしましたが、「APIキーが無効だよ」のメッセージが返ってきました。

curl https://xxxxx.com/todo?id=aaa&apikey=bbb

そのときは「APIキーを作ったばかりなのでしばらく待ってみよう。ドキュメントにも数時間待ってねと書いてあるし。」と考えましたが、24時間以上が経過しても「APIキーが無効だよ」が返ってくるのです。 わけが分かりませんでした。

何が起きてたか

シェルの制御に使う文字(&)がコマンドの引数にある場合、文字列の解釈がそこで終わっていました。 たとえば、上記の例を分かりやすくすると次のようになります。

curl https://xxxxx.com/todo?id=aaa&echo bbb

&はバックグランド実行指定のため、「curl https://xxxxx.com/todo?id=aaaをバックグランドで実行して、次にecho bbbを実行する動作」になっています。

解決方法

curlコマンドのURLをダブルクオートで囲めばOKでした。

curl "https://xxxxx.com/todo?id=aaa&apikey=bbb"

または&をエスケープシーケンスしてあげるとOKでした。

curl https://xxxxx.com/todo?id=aaa\&apikey=bbb

APIを作って再現してみた

受け取ったクエリ文字列をそのまま返すAPIを作って試してみました。

AWS SAMテンプレート

template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description:  QueryStringApiSample

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.7
      Timeout: 10
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get

Outputs:
  HelloWorldApi:
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello"

Lambdaコード

app.py

import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': event['queryStringParameters'],
        }),
    }

ビルド&デプロイ

sam build

sam package \
    --output-template-file packaged.yaml \
    --s3-bucket cm-fujii.genki-sam-test-bucket

sam deploy \
    --template-file packaged.yaml \
    --stack-name Query-String-Sample-Stack \
    --capabilities CAPABILITY_NAMED_IAM \
    --no-fail-on-empty-changeset

APIエンドポイント取得

aws cloudformation describe-stacks \
    --stack-name Query-String-Sample-Stack \
    --query 'Stacks[].Outputs'

NGの場合

&以降のクエリ文字列が無視されています。

$ curl https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello?id=aaa&apikey=bbb
{"message": {"id": "aaa"}}

OKの場合

&以降のクエリ文字列もバッチリ扱えました。

$ curl "https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello?foo=aaa&bar=bbb"
{"message": {"apikey": "bbb", "id": "aaa"}}

エスケープシーケンスした場合も同様にOKでした。

curl  https://xxxxx.execute-api.ap-northeast-1.amazonaws.com/Prod/hello?id=aaa\&apikey=bbb
{"message": {"apikey": "bbb", "id": "aaa"}}

さいごに

「もしかして……?」と気づくのに時間が掛かってしまいました。どなたかの参考になれば幸いです。