Julia Evans has a new blog post up, Curl Exercises, which landed on the front page of Hacker News today. In it, she brings up the idea of deliberate practice and outlines 21 exercises to better familiarize yourself with cURL.
I’m looking for a distraction from my current work with terraform, so let’s shave this yak!
The following are my solutions for the 21 exercise questions. I’ll try and go simply from memory; making note when I need to reference the man page.
⇒ curl https://httpbin.org
<!DOCTYPE html>
<html lang="en">
...
...
</html>
⇒ curl https://httpbin.org/anything
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re using the -X option for specifying a custom HTTP method.
⇒ curl -X POST https://httpbin.org/anything
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "POST",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
⇒ curl https://httpbin.org/anything\?value\=panda
{
"args": {
"value": "panda"
},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "<redacted>",
"url": "https://httpbin.org/anything?foo=bar"
}
⇒ curl https://www.google.com/robots.txt
User-agent: *
Disallow: /search
Allow: /search/about
Allow: /search/static
Allow: /search/howsearchworks
Disallow: /sdch
...
...
Sitemap: https://www.google.com/sitemap.xml
Here we’re using the -H option for specifying a custom HTTP header.
⇒ curl -H 'User-Agent: elephant' https://httpbin.org/anything
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "elephant"
},
"json": null,
"method": "GET",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re once again using the -X option for specifying a custom HTTP method.
⇒ curl -X DELETE https://httpbin.org/anything
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "DELETE",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re using the -d to send POST data to the server.
⇒ curl -X POST https://httpbin.org/anything -d '{"value": "panda"}'
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"value\": \"panda\"}": ""
},
"headers": {
"Accept": "*/*",
"Content-Length": "18",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": {
"value": "panda"
},
"method": "POST",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re using -i to print the response headers. I had to reference the man pages for this one as I normally just use -v whenever I’m debugging.
⇒ curl -i https://httpbin.org/anything
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Type: application/json
Date: Wed, 28 Aug 2019 00:46:22 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Length: 290
Connection: keep-alive
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re using the -H option for specifying a custom HTTP header.
⇒ curl -X POST -H 'Content-Type: application/json' https://httpbin.org/anything -d '{"value": "panda"}'
{
"args": {},
"data": "{\"value\": \"panda\"}",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "18",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": {
"value": "panda"
},
"method": "POST",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re using the -H option for specifying a custom HTTP header.
⇒ curl -H 'Accept-Encoding: gzip' https://httpbin.org/anything
U�A� E����4
��ݹ0��PE�Q0���x�Zc���?3&H�0�'kb�����=�F�� [Jj�����(���-���ݰ���`�v~�z�i��\ ������`T,�jTN�%A'h�V㥥�T�]�=5������o�H�w1�1�b%g���[؞��9Ƈb���i���}��}uB%
First off, we need JSON. Let’s grab some commit history of the repository for the codebase that generates this site using the GitHub API.
⇒ wget 'https://api.github.com/repos/ahawker/personal-site/commits?per_page=1' -O data.json
Here we’re once again using the -d option for sending data to the server. If the value given starts with @
, it should be preceded by a filename. cURL will then use the content of the file for POST data.
⇒ curl -X POST https://httpbin.org/anything -d @data.json
{
"args": {},
"data": "",
"files": {},
"form": {
"[ { \"sha\": \"b5f2e7d3d78d55b6046e554bf0329082d04e62ce\", \"node_id\": \"MDY6Q29tbWl0MjI1MzY1NjA6YjVmMmU3ZDNkNzhkNTViNjA0NmU1NTRiZjAzMjkwODJkMDRlNjJjZQ": "=\", \"commit\": { \"author\": { \"name\": \"Andrew Hawker\", \"email\": \"andrew.r.hawker@gmail.com\", \"date\": \"2019-08-27T20:33:36Z\" }, \"committer\": { \"name\": \"Andrew Hawker\", \"email\": \"andrew.r.hawker@gmail.com\", \"date\": \"2019-08-27T20:33:46Z\" }, \"message\": \"Exclude Makefile from jekyll build output\", \"tree\": { \"sha\": \"33a45d9727c740a205f677d4e50039c1d5d729f4\", \"url\": \"https://api.github.com/repos/ahawker/personal-site/git/trees/33a45d9727c740a205f677d4e50039c1d5d729f4\" }, \"url\": \"https://api.github.com/repos/ahawker/personal-site/git/commits/b5f2e7d3d78d55b6046e554bf0329082d04e62ce\", \"comment_count\": 0, \"verification\": { \"verified\": false, \"reason\": \"unsigned\", \"signature\": null, \"payload\": null } }, \"url\": \"https://api.github.com/repos/ahawker/personal-site/commits/b5f2e7d3d78d55b6046e554bf0329082d04e62ce\", \"html_url\": \"https://github.com/ahawker/personal-site/commit/b5f2e7d3d78d55b6046e554bf0329082d04e62ce\", \"comments_url\": \"https://api.github.com/repos/ahawker/personal-site/commits/b5f2e7d3d78d55b6046e554bf0329082d04e62ce/comments\", \"author\": { \"login\": \"ahawker\", \"id\": 178002, \"node_id\": \"MDQ6VXNlcjE3ODAwMg==\", \"avatar_url\": \"https://avatars1.githubusercontent.com/u/178002?v=4\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/ahawker\", \"html_url\": \"https://github.com/ahawker\", \"followers_url\": \"https://api.github.com/users/ahawker/followers\", \"following_url\": \"https://api.github.com/users/ahawker/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/ahawker/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/ahawker/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/ahawker/subscriptions\", \"organizations_url\": \"https://api.github.com/users/ahawker/orgs\", \"repos_url\": \"https://api.github.com/users/ahawker/repos\", \"events_url\": \"https://api.github.com/users/ahawker/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/ahawker/received_events\", \"type\": \"User\", \"site_admin\": false }, \"committer\": { \"login\": \"ahawker\", \"id\": 178002, \"node_id\": \"MDQ6VXNlcjE3ODAwMg==\", \"avatar_url\": \"https://avatars1.githubusercontent.com/u/178002?v=4\", \"gravatar_id\": \"\", \"url\": \"https://api.github.com/users/ahawker\", \"html_url\": \"https://github.com/ahawker\", \"followers_url\": \"https://api.github.com/users/ahawker/followers\", \"following_url\": \"https://api.github.com/users/ahawker/following{/other_user}\", \"gists_url\": \"https://api.github.com/users/ahawker/gists{/gist_id}\", \"starred_url\": \"https://api.github.com/users/ahawker/starred{/owner}{/repo}\", \"subscriptions_url\": \"https://api.github.com/users/ahawker/subscriptions\", \"organizations_url\": \"https://api.github.com/users/ahawker/orgs\", \"repos_url\": \"https://api.github.com/users/ahawker/repos\", \"events_url\": \"https://api.github.com/users/ahawker/events{/privacy}\", \"received_events_url\": \"https://api.github.com/users/ahawker/received_events\", \"type\": \"User\", \"site_admin\": false }, \"parents\": [ { \"sha\": \"87a8dbf738918ef206f263e409cdba2ea49d6a3d\", \"url\": \"https://api.github.com/repos/ahawker/personal-site/commits/87a8dbf738918ef206f263e409cdba2ea49d6a3d\", \"html_url\": \"https://github.com/ahawker/personal-site/commit/87a8dbf738918ef206f263e409cdba2ea49d6a3d\" } ] }]"
},
"headers": {
"Accept": "*/*",
"Content-Length": "3750",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "POST",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re using the -o option to specify the output of the command instead of stdout
.
⇒ curl -H 'Accept: image/png' https://httpbin.org/image -o image.png
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8090 100 8090 0 0 21910 0 --:--:-- --:--:-- --:--:-- 21924
Run open image.png
to view the content.
⇒ curl -X PUT https://httpbin.org/anything
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Content-Length": "0",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "PUT",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re once again using the -o option to specify the output of the command instead of stdout
.
⇒ curl https://httpbin.org/image/jpeg -o image.jpeg
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 35588 100 35588 0 0 74331 0 --:--:-- --:--:-- --:--:-- 74451
Run open image.jpeg
to view the content.
You’ll need to use the -L option if you want cURL to automatically follow a redirect.
⇒ curl -i https://google.com
HTTP/1.1 301 Moved Permanently
Location: https://www.google.com/
Content-Type: text/html; charset=UTF-8
Date: Wed, 28 Aug 2019 17:17:53 GMT
Expires: Fri, 27 Sep 2019 17:17:53 GMT
Cache-Control: public, max-age=2592000
Server: gws
Content-Length: 220
X-XSS-Protection: 0
X-Frame-Options: SAMEORIGIN
Alt-Svc: quic=":443"; ma=2592000; v="46,43,39"
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>301 Moved</TITLE></HEAD><BODY>
<H1>301 Moved</H1>
The document has moved
<A HREF="https://www.google.com/">here</A>.
</BODY></HTML>
⇒ curl -H 'Panda: Elephant' https://httpbin.org/anything
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"Panda": "Elephant",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
Here we’re once again using the -i option to output the response headers.
⇒ curl https://httpbin.org/status/404
⇒ curl -i https://httpbin.org/status/404
HTTP/1.1 404 NOT FOUND
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Date: Wed, 28 Aug 2019 17:21:25 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Length: 0
Connection: keep-alive
⇒ curl https://httpbin.org/status/200
⇒ curl -i https://httpbin.org/status/200
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Date: Wed, 28 Aug 2019 17:22:12 GMT
Referrer-Policy: no-referrer-when-downgrade
Server: nginx
X-Content-Type-Options: nosniff
X-Frame-Options: DENY
X-XSS-Protection: 1; mode=block
Content-Length: 0
Connection: keep-alive
You’ll need to use the -u option to send a username/password for HTTP Basic Authentication.
⇒ curl -u foo:bar https://httpbin.org/anything
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Authorization": "Basic Zm9vOmJhcg==",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
⇒ curl -H 'Accept-Language: es-ES' https://twitter.com
<!DOCTYPE html>
<html lang="es" data-scribe-reduced-action-queue="true">
<head>
...
...
</body>
</html>
The API Key used below is the fake one from the Stripe Documentation page. Create a Stripe account and login to get your actual Test API Keys.
⇒ curl https://httpbin.org/anything \
-u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
-d amount=999 \
-d currency=usd \
-d source=tok_visa \
-d receipt_email="jenny.rosen@example.com"
{
"args": {},
"data": "",
"files": {},
"form": {
"amount": "999",
"currency": "usd",
"receipt_email": "jenny.rosen@example.com",
"source": "tok_visa"
},
"headers": {
"Accept": "*/*",
"Authorization": "Basic c2tfdGVzdF80ZUMzOUhxTHlqV0Rhcmp0VDF6ZHA3ZGM6",
"Content-Length": "77",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "POST",
"origin": "<redacted>",
"url": "https://httpbin.org/anything"
}
It was nice to learn about the -i option, even though I do have quite a bit of experience with cURL. I normally just use -v which spits out quite a bit more debug output.
I’d be interested to know of tools/products that provide a set of Kōan style exercises instead of the traditional First Start tutorial and Feature pages/notices.