The 21 curl exercises by Julia Evans are a compact collection of assignments to practice the usage of cURL.
Here is my take at it:
- Request https://httpbin.org:
› curl -X GET 'https://httpbin.org'
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
[...]
- Request https://httpbin.org/anything. httpbin.org/anything will look at the request you made, parse it, and echo back to you what you requested. curl’s default is to make a GET request:
› curl -X GET 'https://httpbin.org/anything'
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "*/*",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- As curl’s default is GET the
-X GET
could be omitted.
- Make a POST request to https://httpbin.org/anything
› 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": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- Make a GET request to https://httpbin.org/anything, but this time add some query parameters (set value=panda):
› curl -X GET 'https://httpbin.org/anything' -d 'value=pada'
{
"args": {},
"data": "",
"files": {},
"form": {
"value": "pada"
},
"headers": {
"Accept": "_/_",
"Content-Length": "10",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
1. Request google's robots.txt file (www.google.com/robots.txt):
```zsh
› curl -X GET 'https://www.google.com/robots.txt'
User-agent: \*
Disallow: /search
Allow: /search/about
Allow: /search/static
[...]
- Make a GET request to https://httpbin.org/anything and set the header User-Agent: elephant.
› curl -X GET 'https://httpbin.org/anything' -H 'User-Agent: elephant'
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "_/_",
"Host": "httpbin.org",
"User-Agent": "elephant"
},
"json": null,
"method": "GET",
"origin": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- Make a DELETE request to https://httpbin.org/anything
› 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": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- Request https://httpbin.org/anything and also get the response headers
› curl -X GET 'https://httpbin.org/anything' -i
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: \*
Content-Type: application/json
[...]
{
"args": {},
[...]
}
- Make a POST request to https://httpbin.com/anything with the JSON body {“value”: “panda”}:
› curl -X POST 'https://httpbin.org/anything' -d '{"value": "panda"}'
{
"args": {},
"data": "",
"files": {},
"form": {
"{\"value\": \"panda\"}": ""
},
"headers": {
"Accept": "_/_",
"Content-Length": "18",
"Content-Type": "application/x-www-form-urlencoded",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "POST",
"origin": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- The transmitted data ends up in the
"form"
field.
- Make the same POST request as the previous exercise, but set the Content-Type header to application/json (because POST requests need to have a content type that matches their body). Look at the json field in the response to see the difference from the previous one:
› curl -X POST 'https://httpbin.org/anything' \
-H 'Content-Type: application/json' \
-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": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- Nice, the data got explicitly recognised as JSON data.
- Make a GET request to https://httpbin.org/anything and set the header Accept-Encoding: gzip (what happens? why?):
› curl -X GET 'https://httpbin.org/anything' -H 'Accept-Encoding: gzip'
M?A? E????4
??it?´h@?F?.Z??
??ߟ??s$??yI???X?$??lE?B?\*=?c?2??tF?4l?~A??m?ᦵl?{̿b<?]?u??v| ???{?ӊ3?&?I???.9:A??o???r???????????????`??k??Y??,?ڣ?ȿ?)1ja"????DK?K?
@%
- Looks like whatever got transferred, was ‘gzip’ compressed and got spat out into the shell.
- Put a bunch of a JSON in a file and then make a POST request to https://httpbin.org/anything with the JSON in that file as the body:
› curl -X POST 'https://httpbin.org/anything' \
-H 'Content-Type: application/json' \
-d @bunchof.json
{
"args": {},
"data": "{ \"firstName\": \"John\", \"lastName\": \"Smith\", \"isAlive\": true, \"age\": 27, \"address\": { \"streetAddress\": \"21 2nd Street\", \"city\": \"New York\", \"state\": \"NY\", \"postalCode\": \"10021-3100\" }, \"phoneNumbers\": [ { \"type\": \"home\", \"number\": \"212 555-1234\" }, { \"type\": \"office\", \"number\": \"646 555-4567\" }, { \"type\": \"mobile\", \"number\": \"123 456-7890\" } ], \"children\": [], \"spouse\": null}",
"files": {},
"form": {},
"headers": {
"Accept": "_/_",
"Content-Length": "447",
"Content-Type": "application/json",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": {
"address": {
"city": "New York",
"postalCode": "10021-3100",
"state": "NY",
"streetAddress": "21 2nd Street"
},
"age": 27
[...]
},
"method": "POST",
"origin": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- Make a request to https://httpbin.org/image and set the header ‘Accept: image/png’. Save the output to a PNG file and open the file in an image viewer. Try the same thing with with different Accept: headers:
› curl -X GET 'https://httpbin.org/image' -H 'Accept: image/png' -o someImage.png
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 8090 100 8090 0 0 18879 0 --:--:-- --:--:-- --:--:-- 18901
- Nice a
- Also other image formats are available. Try yourself with
curl -X GET 'https://httpbin.org/image'
- Make a PUT request to https://httpbin.org/anything :
› 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": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- Request https://httpbin.org/image/jpeg, save it to a file, and open that file in your image editor:
› curl -X GET 'https://httpbin.org/image/jpeg' -o someJpeg.jpeg
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 35588 100 35588 0 0 63869 0 --:--:-- --:--:-- --:--:-- 63777
- Is that a Black-backed jackal?
- Request https://www.twitter.com. You’ll get an empty response. Get curl to show you the response headers too, and try to figure out why the response was empty:
› curl -X GET 'https://www.twitter.com' -i
HTTP/2 301
content-length: 0
date: Fri, 30 Aug 2019 16:23:25 GMT
location: https://twitter.com/
server: tsa_o
set-cookie: personalization_id="v1_Q9zuTfFFOE9k7hSxDhPo8g=="; Max-Age=63072000; Expires=Sun, 29 Aug 2021 16:23:25 GMT; Path=/; Domain=.twitter.com
set-cookie: guest_id=v1%3A156718220573814829; Max-Age=63072000; Expires=Sun, 29 Aug 2021 16:23:25 GMT; Path=/; Domain=.twitter.com
strict-transport-security: max-age=631138519
x-connection-hash: d98cf4f8396275a8ba48bedc5451fbc6
x-response-time: 118
- With
content-length: 0
the response is indeed empty. The status messageHTTP/2 301
signals that the content was Moved Permanently tolocation: https://twitter.com/
.
- Make any request to https://httpbin.org/anything and just set some nonsense headers (like panda: elephant):
› curl -X GET 'https://httpbin.org/anything' -H 'panda: elephant'
{
"args": {},
"data": "",
"files": {},
"form": {},
"headers": {
"Accept": "_/_",
"Host": "httpbin.org",
"Panda": "elephant",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
- No mayor changes besides that the header gets transferred unaltered, which makes it a nice vector for control- and meta-data.
- Request https://httpbin.org/status/404 and https://httpbin.org/status/200. Request them again and get curl to show the response headers:
› curl -X GET 'https://httpbin.org/status/404' -i
HTTP/1.1 404 NOT FOUND
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: \*
[...]
› curl -X GET 'https://httpbin.org/status/200' -i
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: \*
[...]
- The responses are almost the same, exept that the
HTTP
status messages differ,HTTP/1.1 404 NOT FOUND
vs.HTTP/1.1 200 OK
.
- Request https://httpbin.org/anything and set a username and password (with -u username:password)
› curl -X GET 'https://httpbin.org/anything' -u 'username:password' -v
[...]
- Server auth using Basic with user 'username'
> GET /anything HTTP/1.1
> Host: httpbin.org
> Authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
> [...]
> {
> "args": {},
> "data": "",
> "files": {},
> "form": {},
> "headers": {
"Accept": "*/*",
"Authorization": "Basic dXNlcm5hbWU6cGFzc3dvcmQ=",
"Host": "httpbin.org",
"User-Agent": "curl/7.54.0"
},
"json": null,
"method": "GET",
"origin": "94.79.129.22, 94.79.129.22",
"url": "https://httpbin.org/anything"
}
[...]
- Most important thing here is see what did
cURL
do with the-u 'username:password'
option, therefor the-v
. It transformed it into aAuthorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ=
header field. Where the “username:password” got Base64 encoded. - And be warned this about as secret as the plaintext of it!
- Download the Twitter homepage (https://twitter.com) in Spanish by setting the Accept-Language: es-ES header.
› curl -X GET 'https://twitter.com' -H 'Accept-Language: es-Es'
[...]
- Could be Spanish…, to much content to parse with my eyes.
- Make a request to the Stripe API with curl. (see https://stripe.com/docs/development for how, they give you a test API key). Try making exactly the same request to https://httpbin.org/anything:
#!/bin/sh
curl https://api.stripe.com/v1/payment_intents \
-u sk_test_Oy**\*\*\***: \
-d amount=999 \
-d currency=eur \
-d payment_method_types[]=card \
-d receipt_email="jenny.rosen@example.com"
{
"id": "pi_1FD**\*\*\***",
"object": "payment_intent",
"amount": 999,
"amount_capturable": 0,
"amount_received": 0,
"application": null,
"application_fee_amount": null,
"canceled_at": null,
"cancellation_reason": null,
[...]
- To get the API key you need to do the little sign up dance, but with the examples on their site, this went smoothly.
- Not so sure about the second part of the exercise, as well as I don’t publish the test API key here, I would not send that API key to any foreign server. So I didn’t.
All in all a nice exercise. Julia Evans, thank you for the inspiration.
[Mehr]