Praktisk guide för att hålla dig till cURL
Från Just use cURL
Q: How do I organize my requests?
A: Put your shell scripts into directories, genius.
Så låt oss göra detta med praktiska exempel! Jag gjorde detta nyligen, så jag kan gå igenom det bit för bit hur jag gjorde.
Information om API jag önskar testa:
- API har tre miljöer: test, pt och prod.
- Varje miljö har två API-endpoints: sandbox och production.
- Autentisering sker med OAuth2 mot en HTTP-endpoint som har client credentials grant.
Följande setup kommer skapas i detta inlägg:
- En katalog med versionerade POSIX-kompatibla script
- Script för att skapa kontexter, t ex
TEST,PTellerPROD - Script för att skapa flera endpoints per kontext, t ex
SANDBOXellerPRODUCTION - Script för att förnya API access-tokens
- Script för att byta och visa aktuell kontext
- Exempel på script med API-anrop (
GET,POST,PUT,DELETEochPATCH). - Exempel på script för att integrationstesta API
Så här ser en typisk session ut:
. .envs/prod/sandbox
./renew-token
./saker/GET/lista-alla-saker.curl
./saker/POST/skapa-sak.curl "saken"
./saker/DELETE/radera-sak.curl "saken"
./saker/POST/skapa-sak.curl "sak2"
./saker/PUT/uppdatera-sak.curl "sak2" "saken"
./saker/DELETE/radera-sak.curl "saken"
Skapa nytt git-repo
Det går såklart att göra detta med annan backup-lösning också, men git är vad jag föredrar.
git init just-use-curl
cd just-use-curl
Jag skapar direkt en ignore-lista.
cat <<END > .gitignore
.curl-*-headers-file
END
Grundläggande filstruktur
Skapa litet filer och mappar:
mkdir {.envs/{test,pt,prod},.tests}
touch .envs/{test,pt,prod}/{sandbox,production}
touch {renew-token,set-secrets,who-am-i}
chmod +x {renew-token,set-secrets,who-am-i}
cat <<END > .gitignore
Nedbrytning om vad filerna gör:
renew-tokengör ett HTTP-anrop mot en autentiserings-endpoint och sparar token i en fil som cURL sedan läser in som headers.who-am-ivisar vilken kontext som för närvarande används.set-secretsär en wizard som ber om klient-ID och klient-hemlighet för API. Den är högst valfri.
Nedbrytning av script
Det är primärt tre script, och ett valfritt script.
init-env
Scriptet sätter upp en miljö genom att fråga efter två saker:
- Webbadress för API (
API_ENDPOINT_URL) - Webbadress för att skapa och förnya access tokens med OAuth2 (
TOKEN_ENDPOINT_URL).
Detta är innehållet:
#!/bin/sh
step=1
uenv=$(echo "$1" | tr "[:lower:]" "[:upper:]")
printf "Skapar miljö - %s\n" "$uenv"
printf "[%s/2] Ange API_ENDPOINT_URL och tryck ENTER: " "$step"
read -r aeu
step=$((step + 1))
printf "[%s/2] Ange TOKEN_ENDPOINT_URL och tryck ENTER: " "$step"
read -r teu
step=$((step + 1))
mkdir -p .envs/"$1"
touch .envs/"$1"/."$1"
cat <<END > .envs/"$1"/."$1"
export TOKEN_ENDPOINT_URL=$teu
export API_ENDPOINT_URL=$aeu
END
who-am-i
Ett script som skriver ut den aktuella kontexten. Kontexten väljs genom att source:a en av env-filerna, t ex såhär:
. .envs/pt/sandbox; ./who-am-i
Detta är innehållet.
#!/bin/sh
echo "TOKEN_ENDPOINT_URL: $TOKEN_ENDPOINT_URL"
echo "API_ENDPOINT_URL: $API_ENDPOINT_URL"
echo "API_KEY_TYPE: $API_KEY_TYPE"
echo "CLIENT_KEY: $CLIENT_KEY"
renew-token
Ett POSIX-script som kollar så att vi valt en kontext, och om vi har det - så skapas och sparas en ny access token.
Detta är innehållet:
#!/bin/sh
: "${CLIENT_KEY?CLIENT_KEY saknas - aktivera en miljö?}"
: "${CLIENT_SECRET?CLIENT_SECRET saknas - aktivera en miljö?}"
: "${API_KEY_TYPE?API_KEY_TYPE saknas - aktivera en miljö?}"
: "${TOKEN_ENDPOINT_URL?TOKEN_ENDPOINT_URL saknas - aktivera en miljö?}"
: "${API_ENDPOINT_URL?API_ENDPOINT_URL saknas - aktivera en miljö?}"
CREDENTIALS=$(echo "$CLIENT_KEY:$CLIENT_SECRET" | base64)
TOKEN=$(curl -s -X POST "$TOKEN_ENDPOINT_URL" -d "grant_type=client_credentials" -H "Authorization: Basic $CREDENTIALS" | jq '.["access_token"]' | sed -e "s/\"//g")
cat <<END > .curl-"$CLIENT_KEY"-headers-file
User-Agent: $USER-$(curl -V | head -n1 | awk '{print $1$2;}')
Accept: application/json
Authorization: Bearer $TOKEN
END
Valfritt: set-secrets
Ett script som ger instruktioner för att klistra in consumer keys och consumer secrets per miljö.
BRa att ha, men kan göras manuellt också. Här är innehållet:
#!/bin/sh
API=$(basename "$PWD")
clear
client_credentials () {
step=1
echo "[$step/5] Gå till API i utvecklarportalen, och dess Subscriptions."
echo " Välj där korrekt Application, och använd kopieringsverktyget"
echo " på värdena."
echo " OBS! ingen inklistring av *secrets* kommer att skrivas på"
echo " skärmen av säkerhetsskäl."
for env in "sandbox" "production"; do
uenv=$(echo "$env" | tr "[:lower:]" "[:upper:]")
step=$((step + 1))
printf "[%s/5] Klistra in %s \"Consumer Key\" och tryck ENTER: " "$step" "$uenv"
read -r ck
step=$((step + 1))
printf "[%s/5] Klistra in \"Consumer Secret\" för %s och tryck ENTER: " "$step" "$uenv"
stty -echo
read -r cs
stty echo
echo ""
cat <<- END > ./.envs/"$1"/"$env"
. ./.envs/$1/.$1
export API_KEY_TYPE=$env
export CLIENT_KEY=$ck
export CLIENT_SECRET=$cs
END
done
printf "Consumer key+secret är sparad för %s!\n" "$1"
}
echo "Sätter consumer key+secret för: $API"
for env in "test" "pt" "prod"; do
yN=""
while true; do
printf "Sätt secrets för %s i %s? [y/N]: " "$API" "$env"
read -r input
case "$input" in
[yY])
yN="y"
break
;;
""|[nN])
yN="n"
break
;;
*)
printf "[!] Felaktigt svar! Ange \"y\" eller \"n\".\n"
;;
esac
done
if [ "$yN" = "y" ]; then
client_credentials "$env"
fi
done
Skapa miljöerna
Varje miljö (test, pt och prod) har två endpoints, sandbox och production.
Följande kommandon skapar miljöerna:
./init-env test
./init-env pt
./init-env prod
Jag vill sedan ange OAuth-keys och -secrets, det gör jag med:
./set-secrets
Verifiera så allt funkar:
. .envs/pt/sandbox
./who-am-i
./renew-token
Skapa några endpoints
För att skapa endpoints används följande filstruktur:
- I roten ligger alla entiteter i API:et som kataloger, med så nära spegling som möjligt.
- Som underkataloger till dessa ligger alla HTTP-verb: GET, POST, etc.
- I dessa ligger script med filändelsen
.curl. - Om man vill ha flera exempel, t ex med statuskoder, kan en extra filändelse användas.
Exempel på hur detta kan se ut:
thing/
├── GET
│ ├── list-all.curl
│ └── list-selection.curl
└── POST
├── append-new.error.curl
└── append-new.ok.curl
Endpoint som bara hämtar data (GET, OPTIONS, HEAD)
Behöver inga argument.
Exempel på hur det kan se ut:
#!/bin/sh
res="thing"
curl -s \
-H @.curl-"$CLIENT_KEY"-headers-file \
"$API_ENDPOINT_URL"/"$res" \
| python3 -m json.tool
Standardverbet i cURL är GET. Använd -X [OPTIONS, HEAD] för att ändra verbet.
(I exemplet här är det ett JSON-API, så Pythons inbyggda json.tool används för att snygga till utskriften)
Endpoints som behöver indata (t ex POST, PUT, PATCH)
Behöver argument, t ex via stdin eller som variabel i filen.
Exempel på hur det kan se ut:
#!/bin/sh
res="thing"
# definiera data i filen
# exempel: ./thing/POST/new.curl
data="code=success&payload=allgood"
resp=$(curl -s -w "%{http_code}\n" \
-H @.curl-"$CLIENT_KEY"-headers-file \
-d "$data" "$API_ENDPOINT_URL"/"$res")
# skicka in stdin som payload
# exempel: cat payload.txt | ./thing/POST/new.curl
resp=$(curl -s -w "%{http_code}\n" \
-H @.curl-"$CLIENT_KEY"-headers-file \
-d "$(cat -)" "$API_ENDPOINT_URL"/"$res")
# skicka in första argumentet som data
# exempel: ./thing/POST/new.curl "foo=1"
resp=$(curl -s -w "%{http_code}\n" \
-H @.curl-"$CLIENT_KEY"-headers-file \
-d "$1" "$API_ENDPOINT_URL"/"$res")
# skicka in en sträng som data och placera den i nyckeln "payload"
# exempel: ./thing/POST/new.curl hej
resp=$(curl -s -w "%{http_code}\n" \
-H @.curl-"$CLIENT_KEY"-headers-file \
-d "payload=$1" "$API_ENDPOINT_URL"/"$res")
# Skriv ut HTTP-statuskod från svar om ingen övrig data skickades med,
# och formattera JSON om sådan finns
if [ "$(echo "$resp" | wc -w)" -eq 1 ]
then
echo "HTTP $resp"
else
echo "$resp" | tr -d '\n' | sed -r 's/[0-9]+$//' | python3 -m json.tool
fi
Standardverbet när data skickas (curl -d) är POST. Använd -X [DELETE, PATCH, PUT] för att ändra verbet.
(I exemplet här är det ett JSON-API, så Pythons inbyggda json.tool används för att snygga till utskriften)
Endpoints som behöver ange ett ID eller referens (GET, DELETE, PUT, PATCH)
#!/bin/sh
res="thing"
# Ange id/referens med första argumentet,
# exempel: ./thing/DELETE/delete-single.curl 23
curl -X DELETE -s -w "HTTP %{http_code}\n" \
-H @.curl-"$CLIENT_KEY"-headers-file \
"$API_ENDPOINT_URL"/"$res"/"$1"
# Ange id/referens med stdin
# exempel: echo 23 | ./thing/DELETE/delete-single.curl
curl -X DELETE -s -w "HTTP %{http_code}\n" \
-H @.curl-"$CLIENT_KEY"-headers-file \
"$API_ENDPOINT_URL"/"$res"/"$(cat -)"
Använd -X [DELETE, PATCH, PUT] för att ändra verbet, eller skippa -X för att falla tillbaka på GET. Kika på tidigare exempel på hur koden kan utökas med en if-sats som skriver ut statuskod eller paylaod beroende på svar.
(I exemplet här är det ett JSON-API, så Pythons inbyggda json.tool används för att snygga till utskriften)
Skala över tid
Först av allt - versionshantering med git är en bra start, och kan nog räcka med en bra tillämpning av taggar och branches.
Jag rekommenderar dock att låta filstrukturen spegla alla versioner av API som ligger i produktion, då det inte ovanligt att flera gamla och nya versioner samexisterar, te x på grund av krav på bakåtkompabilitet eller migreringsperiod.
Jag hade därför gjort något liknande över tid:
apis
├── README
├── demo-api
│ └── v2
│ │ ├── ...
│ │ ├── init-env
│ │ ├── README
│ │ ├── renew-token
│ │ ├── set-secrets
│ │ └── who-am-i
│ └── v3
│ ├── ...
│ ├── init-env
│ ├── README
│ ├── renew-token
│ ├── set-secrets
│ └── who-am-i
└── another-api
└── v1.0.0
├── ...
├── init-env
├── README
├── renew-token
├── set-secrets
└── who-am-i