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" }
-
Request google’s robots.txt file (www.google.com/robots.txt):
› 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.