HTTPie vs cURL (Part 2)

capricorn, rock, animal

Over the last decades cURL was the king in CLI HTTP client area. It has many features which is enough to test APIs. However, panta rei and world is changing constantly. Even is such scope where one tool has such a great adoption ratio new challengers occur. One of them is HTTPie. This is quite new tool which getting popular and popular. At a glance it's quite interesting choice. Let look deeply and compare both. It's time to fight. HTTPie vs cURL.

capricorn, rock, animal

Show Response and Request Headers - HTTPie vs cURL

Now is time to dive deeper.

whales, ocean, diving

httpie (HTTPie vs cURL)

Such things are really important during the API testing or troubleshooting that's why when I've noticed it in HTTPie I was really surprised and happy. Ease and simplicity in this fields is remarkable.

Scheme

Output Options:
  --print WHAT, -p WHAT
      String specifying what the output should contain:
          'H' request headers
          'h' response headers

Example

$ http --print=Hh DELETE httpbin.org/delete
DELETE /delete HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 0
Host: httpbin.org
User-Agent: HTTPie/2.3.0

HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 389
Content-Type: application/json
Date: Thu, 28 Jan 2021 08:29:03 GMT
Server: gunicorn/19.9.0

Pretty cool, isn't it?

cURL

The good news is that it is feasible although requires some grepping skills 🙂 Firstly, we have to add 3 flags:

  • -v: to get headers
  • -s: to hide progres bar
  • -o: to write body to /dev/null
-v, --verbose

Makes curl verbose during the operation. Useful for debugging and seeing what's going on "under the hood". A line starting with '>' means "header data" sent by curl, '<' means "header data" received by curl that is hidden in normal cases, and a line starting with '*' means additional info provided by curl.

-s, --silent

Silent or quiet mode. Don't show progress meter or error messages. Makes Curl mute. It will still output the data you ask for, potentially even to the terminal/stdout unless you redirect it.

-o, --output <file>

Write output to <file> instead of stdout.

After that, rest is grepping and cutting the output. Example below

$ curl -vso /dev/null -XDELETE httpbin.org/delete 2>&1 | grep -E "(>|<)" | cut -c 3-
DELETE /delete HTTP/1.1
Host: httpbin.org
User-Agent: curl/7.58.0
Accept: */*

HTTP/1.1 200 OK
Date: Wed, 03 Mar 2021 21:58:09 GMT
Content-Type: application/json
Content-Length: 319
Connection: keep-alive
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

Here HTTPie is better.

Show body of request and response - HTTPie vs cURL

Yes, after headers the time has come for body, however it will be a little bit tricky. Let's show the body of the request! It's not common.

httpie

httpie is well prepared. There is a flag -print=Bb which does the job

$ http --print=Bb PUT httpbin.org/put name=John
{
    "name": "John"
}


{
    "args": {},
    "data": "{\"name\": \"John\"}",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json, */*;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Content-Length": "16",
        "Content-Type": "application/json",
        "Host": "httpbin.org",
        "User-Agent": "HTTPie/2.3.0",
        "X-Amzn-Trace-Id": "Root=1-6017bc01-1e2edc604e5b7217462be769"
    },
    "json": {
        "name": "John"
    },
    "origin": "1.2.3.4",
    "url": "http://httpbin.org/put"
}

cURL

On the other hand, in case of cURL this is much more difficult

$ curl -X PUT "http://httpbin.org/put" -s -H "accept: application/json" -d '{"name": "John"}' --trace-ascii /dev/stdout | grep -v ==
=> Send header, 160 bytes (0xa0)
0000: PUT /put HTTP/1.1
0013: Host: httpbin.org
0026: User-Agent: curl/7.58.0
003f: accept: application/json
0059: Content-Length: 16
006d: Content-Type: application/x-www-form-urlencoded
009e: 
=> Send data, 16 bytes (0x10)
0000: {"name": "John"}
<= Recv header, 17 bytes (0x11)
0000: HTTP/1.1 200 OK
<= Recv header, 37 bytes (0x25)
0000: Date: Thu, 04 Mar 2021 21:06:25 GMT
<= Recv header, 32 bytes (0x20)
0000: Content-Type: application/json
<= Recv header, 21 bytes (0x15)
0000: Content-Length: 450
<= Recv header, 24 bytes (0x18)
0000: Connection: keep-alive
<= Recv header, 25 bytes (0x19)
0000: Server: gunicorn/19.9.0
<= Recv header, 32 bytes (0x20)
0000: Access-Control-Allow-Origin: *
<= Recv header, 40 bytes (0x28)
0000: Access-Control-Allow-Credentials: true
<= Recv header, 2 bytes (0x2)
0000: 
<= Recv data, 450 bytes (0x1c2)
0000: {.  "args": {}, .  "data": "", .  "files": {}, .  "form": {.    
0040: "{\"name\": \"John\"}": "".  }, .  "headers": {.    "Accept": "a
0080: pplication/json", .    "Content-Length": "16", .    "Content-Typ
00c0: e": "application/x-www-form-urlencoded", .    "Host": "httpbin.o
0100: rg", .    "User-Agent": "curl/7.58.0", .    "X-Amzn-Trace-Id": "
0140: Root=1-60414bd1-3600535b32dec9125c6c2f62".  }, .  "json": null, 
0180: .  "origin": "89.64.65.223", .  "url": "http://httpbin.org/put".
01c0: }.
{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "{\"name\": \"John\"}": ""
  }, 
  "headers": {
    "Accept": "application/json", 
    "Content-Length": "16", 
    "Content-Type": "application/x-www-form-urlencoded", 
    "Host": "httpbin.org", 
    "User-Agent": "curl/7.58.0", 
    "X-Amzn-Trace-Id": "Root=1-60414bd1-3600535b32dec9125c6c2f62"
  }, 
  "json": null, 
  "origin": "1.2.3.4", 
  "url": "http://httpbin.org/put"
}

An output isn't perfect but for troubleshooting is enough.

Download a file - HTTPie vs cURL

httpie

The -d flag switches HTTPie to the download mode, behaviour reminds the wget - response headers goes to stderr and a progress bar is visible

$ http --download https://github.com/httpie/httpie/archive/master.tar.gz
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://render.githubusercontent.com
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
Content-Type: application/x-gzip
Date: Sun, 07 Mar 2021 19:41:34 GMT
ETag: "c5ba4b7d336d769c6235115ba5cf2106043faf8d1ce122ab21a57e92c43bd1eb"
Strict-Transport-Security: max-age=31536000
Transfer-Encoding: chunked
Vary: Authorization,Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-GitHub-Request-Id: 103E:90E6:1F4283:2B2F84:60452C6E
X-XSS-Protection: 1; mode=block
content-disposition: attachment; filename=httpie-master.tar.gz

Downloading to "httpie-master.tar.gz"
Done. 1.69 MB in 0.23530s (7.19 MB/s)

Apart from just downloading, the tool provides: setting filename, piping and measuring the download.

Piping

$ http -d https://github.com/httpie/httpie/archive/master.tar.gz |  tar zxf -
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://render.githubusercontent.com
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
Content-Type: application/x-gzip
Date: Sun, 07 Mar 2021 20:26:56 GMT
ETag: "c5ba4b7d336d769c6235115ba5cf2106043faf8d1ce122ab21a57e92c43bd1eb"
Strict-Transport-Security: max-age=31536000
Transfer-Encoding: chunked
Vary: Authorization,Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-GitHub-Request-Id: 0FD4:7761:860C1:F4E8C:6045370F
X-XSS-Protection: 1; mode=block
content-disposition: attachment; filename=httpie-master.tar.gz

Downloading to "<stdout>"
Done. 1.69 MB in 0.78814s (2.15 MB/s)
$ ls
httpie-master

and resuming ...

$ http -dco master.tar.gz https://github.com/httpie/httpie/archive/master.tar.gz 
HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://render.githubusercontent.com
Content-Length: 1774119
Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox
Content-Type: application/x-gzip
Date: Sun, 07 Mar 2021 20:54:04 GMT
ETag: "c5ba4b7d336d769c6235115ba5cf2106043faf8d1ce122ab21a57e92c43bd1eb"
Strict-Transport-Security: max-age=31536000
Vary: Authorization,Accept-Encoding
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-GitHub-Request-Id: 0FC4:E8B0:1BBB7:984A4:60453D6C
X-XSS-Protection: 1; mode=block
content-disposition: attachment; filename=httpie-master.tar.gz

Downloading 1.69 MB to "master.tar.gz"
Done. 1.69 MB in 0.93541s (1.81 MB/s)

cURL

Similarly, here the same story. Of course, we can download the files! Moreover you can specify the output file name and resume interrupted downloading 🙂 Here some examples

$ curl -L --output master.tar.gz https://github.com/httpie/httpie/archive/master.tar.gz
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   121  100   121    0     0    806      0 --:--:-- --:--:-- --:--:--   806
100 1732k    0 1732k    0     0  2267k      0 --:--:-- --:--:-- --:--:-- 10.0M

Resume is more tricky as documenation describes

-C, --continue-at <offset>

Continue/Resume a previous file transfer at the given offset. The given offset is the exact number of bytes that will be skipped, counting from the beginning of the source file before it is transferred to the destination. If used with uploads, the FTP server command SIZE will not be used by curl.

Use "-C -" to tell curl to automatically find out where/how to resume the transfer. It then uses the given output/input files to figure that out.

So, we need run following command

$ curl -C - URL

User agent - HTTPie vs cURL

Commonly used setting of User Agent (name of browser) is quite simple in both cases

httpie

$ http --print=HB PUT httpbin.org/put User-Agent:NEW_USER_AGENT name=John
PUT /put HTTP/1.1
Accept: application/json, */*;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Length: 16
Content-Type: application/json
Host: httpbin.org
User-Agent: NEW_USER_AGENT

{
    "name": "John"
}

cURL

$ curl -A NEW_AGENT -X GET "http://httpbin.org/get"
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "User-Agent": "NEW_AGENT", 
    "X-Amzn-Trace-Id": "Root=1-6017c6da-66f491f95d5cd7e607139a18"
  }, 
  "origin": "1.2.3.4", 
  "url": "http://httpbin.org/get"
}

Here is a draw.

Referer

Setting of the referer you can set as simple as user agent

httpie

$ http PUT httpbin.org/put Referer:https://google.com name=John
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 529
Content-Type: application/json
Date: Mon, 01 Feb 2021 09:37:39 GMT
Server: gunicorn/19.9.0

{
    "args": {},
    "data": "{\"name\": \"John\"}",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json, */*;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Content-Length": "16",
        "Content-Type": "application/json",
        "Host": "httpbin.org",
        "Referer": "https://google.com",
        "User-Agent": "HTTPie/2.3.0",
        "X-Amzn-Trace-Id": "Root=1-6017cbe3-118c895827ab0d353ba1f877"
    },
    "json": {
        "name": "John"
    },
    "origin": "1.2.3.4",
    "url": "http://httpbin.org/put"
}

cURL

$ curl -e https://google.com -X GET "http://httpbin.org/get"
{
  "args": {}, 
  "headers": {
    "Accept": "*/*", 
    "Host": "httpbin.org", 
    "Referer": "https://google.com", 
    "User-Agent": "curl/7.58.0", 
    "X-Amzn-Trace-Id": "Root=1-6017cc4b-77b56d1570a2b86369021d5f"
  }, 
  "origin": "1.2.3.4", 
  "url": "http://httpbin.org/get"
}

Nothing to add.

Upload file - HTTPie vs cURL

Uploading file is not easy

httpie

For HTTPie this is just simple POST operation with parameters but we have to use @ to simulate uploading. And remember just @ cause =@ will upload content of the file as a text field.

$ http -f POST httpbin.org/post img@file.png
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 3568
Content-Type: application/json
Date: Tue, 11 May 2021 04:30:13 GMT
Server: gunicorn/19.9.0

{
    "args": {},
    "data": "",
    "files": {
        "img": "data:image/png;base64,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"
    },
    "form": {},
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Content-Length": "2457",
        "Content-Type": "multipart/form-data; boundary=3068a0c706d841a2b12475ff3abc0e7d",
        "Host": "httpbin.org",
        "User-Agent": "HTTPie/2.3.0",
        "X-Amzn-Trace-Id": "Root=1-609a0855-66a917cc726c48973c6ec823"
    },
    "json": null,
    "origin": "1.2.3.4",
    "url": "http://httpbin.org/post"
}

When you add =@ for binary data such error appears

$ http -f POST httpbin.org/post img=@file.png
usage: http [--json] [--form] [--multipart] [--boundary BOUNDARY] [--compress]
            [--pretty {all,colors,format,none}] [--style STYLE] [--unsorted]
            [--sorted] [--format-options FORMAT_OPTIONS] [--print WHAT]
            [--headers] [--body] [--verbose] [--all] [--history-print WHAT]
            [--stream] [--output FILE] [--download] [--continue] [--quiet]
            [--session SESSION_NAME_OR_PATH | --session-read-only SESSION_NAME_OR_PATH]
            [--auth USER[:PASS]] [--auth-type {basic,digest}] [--ignore-netrc]
            [--offline] [--proxy PROTOCOL:PROXY_URL] [--follow]
            [--max-redirects MAX_REDIRECTS] [--max-headers MAX_HEADERS]
            [--timeout SECONDS] [--check-status] [--path-as-is] [--chunked]
            [--verify VERIFY] [--ssl {ssl2.3,tls1,tls1.1,tls1.2}]
            [--ciphers CIPHERS] [--cert CERT] [--cert-key CERT_KEY]
            [--ignore-stdin] [--help] [--version] [--traceback]
            [--default-scheme DEFAULT_SCHEME] [--debug]
            [METHOD] URL [REQUEST_ITEM [REQUEST_ITEM ...]]
http: error: "img=@file.png": cannot embed the content of "file.png", not a UTF8 or ASCII-encoded text file

cURL

cURL has dedicated flag for uploading -T

Schema:

curl -T file_to_upload URL

Follow redirects

Redirecting policy is similar in both tools. By default HTTPie redirects are not followed.

ad, announce, announcement

httpie

Default you get message (You should be redirected automatically to target)

$ http  http://localhost/redirect/3
HTTP/1.1 302 FOUND
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 247
Content-Type: text/html; charset=utf-8
Date: Mon, 01 Feb 2021 20:05:30 GMT
Location: /relative-redirect/2
Server: gunicorn/19.9.0

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/relative-redirect/2">/relative-redirect/2</a>.  If not click the link.

with redirection (flog --follow)

$ http --follow http://localhost/redirect/3
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 254
Content-Type: application/json
Date: Mon, 01 Feb 2021 20:06:42 GMT
Server: gunicorn/19.9.0

{
    "args": {},
    "headers": {
        "Accept": "*/*",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "keep-alive",
        "Host": "localhost",
        "User-Agent": "HTTPie/2.3.0"
    },
    "origin": "172.17.0.1",
    "url": "http://localhost/get"
}

cURL

Default behaviour is exactly the same

$ curl -X GET "http://localhost/redirect/3" -H "accept: text/html"
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/relative-redirect/2">/relative-redirect/2</a>.  If not click the link.

with redirection (flag -L)

$ curl -X GET -L "http://localhost/redirect/3" -H "accept: text/html"
{
  "args": {}, 
  "headers": {
    "Accept": "text/html", 
    "Host": "localhost", 
    "User-Agent": "curl/7.58.0"
  }, 
  "origin": "172.17.0.1", 
  "url": "http://localhost/get"
}

Set Custom Headers

How to set custom header? Both tools allow it.

httpie

Schema

Header:Value

And example:

$ http --print=H http://localhost/headers User-Agent:dolega.dev/1.0 'Cookie:var1=yes;var2=no'
GET /headers HTTP/1.1
Accept: */*
Accept-Encoding: gzip, deflate
Connection: keep-alive
Cookie: var1=yes;var2=no
Host: localhost
User-Agent: dolega.dev/1.0

cURL

Only example

$ curl -X GET "http://localhost/headers" -H "accept: application/json" -H "User-Agent: dolega.dev/1.0" -H "Cookie: var1=yes;var2=no"
{
  "headers": {
    "Accept": "application/json", 
    "Cookie": "var1=yes;var2=no", 
    "Host": "localhost", 
    "User-Agent": "dolega.dev/1.0"
  }
}

As you can see httpie syntax is a little bit better (more readable) but this is not big thing.

JSON data

Now is a time to look closely to JSON and how these tools deal with it.

httpie

HTTPie can create non-string fields which is pretty cool. Below example without this feature

$ http PUT httpbin.org/put name=Mark age=29 isOnline=false protocols='["http", "https"]'
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 671
Content-Type: application/json
Date: Mon, 10 May 2021 04:41:58 GMT
Server: gunicorn/19.9.0

{
    "args": {},
    "data": "{\"name\": \"Mark\", \"age\": \"29\", \"isOnline\": \"false\", \"protocols\": \"[\\\"http\\\", \\\"https\\\"]\"}",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json, */*;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Content-Length": "88",
        "Content-Type": "application/json",
        "Host": "httpbin.org",
        "User-Agent": "HTTPie/2.3.0",
        "X-Amzn-Trace-Id": "Root=1-6098b996-6e350bd27d924c73774f751a"
    },
    "json": {
        "age": "29",
        "isOnline": "false",
        "name": "Mark",
        "protocols": "[\"http\", \"https\"]"
    },
    "origin": "1.2.3.4",
    "url": "http://httpbin.org/put"
}

However, it's not rocket science to enable that just one character: :

$ http PUT httpbin.org/put name=Mark age:=29 isOnline:=false protocols:='["http", "https"]'
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 660
Content-Type: application/json
Date: Mon, 10 May 2021 04:46:41 GMT
Server: gunicorn/19.9.0

{
    "args": {},
    "data": "{\"name\": \"Mark\", \"age\": 29, \"isOnline\": false, \"protocols\": [\"http\", \"https\"]}",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json, */*;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Content-Length": "78",
        "Content-Type": "application/json",
        "Host": "httpbin.org",
        "User-Agent": "HTTPie/2.3.0",
        "X-Amzn-Trace-Id": "Root=1-6098bab1-4bccb0a5664a5e0931bf85ea"
    },
    "json": {
        "age": 29,
        "isOnline": false,
        "name": "Mark",
        "protocols": [
            "http",
            "https"
        ]
    },
    "origin": "1.2.3.4",
    "url": "http://httpbin.org/put"
}

You can send JSON via redirect output

$ echo '{"hello": "world"}' | http POST localhost/post
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 458
Content-Type: application/json
Date: Mon, 01 Feb 2021 20:24:51 GMT
Server: gunicorn/19.9.0

{
    "args": {},
    "data": "{\"hello\": \"world\"}\n",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json, */*;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "keep-alive",
        "Content-Length": "19",
        "Content-Type": "application/json",
        "Host": "localhost",
        "User-Agent": "HTTPie/2.3.0"
    },
    "json": {
        "hello": "world"
    },
    "origin": "172.17.0.1",
    "url": "http://localhost/post"
}

and from file

$ http POST localhost/post < /tmp/tmp.json 
HTTP/1.1 200 OK
Access-Control-Allow-Credentials: true
Access-Control-Allow-Origin: *
Connection: keep-alive
Content-Length: 464
Content-Type: application/json
Date: Mon, 01 Feb 2021 20:26:41 GMT
Server: gunicorn/19.9.0

{
    "args": {},
    "data": "{\n\t\"key1\": \"value1\"\n}\n",
    "files": {},
    "form": {},
    "headers": {
        "Accept": "application/json, */*;q=0.5",
        "Accept-Encoding": "gzip, deflate",
        "Connection": "keep-alive",
        "Content-Length": "22",
        "Content-Type": "application/json",
        "Host": "localhost",
        "User-Agent": "HTTPie/2.3.0"
    },
    "json": {
        "key1": "value1"
    },
    "origin": "172.17.0.1",
    "url": "http://localhost/post"
}

The ways you can send JSON to the HTTPie is really awsome.

cURL

On the other hand cURL force you to add -H "Content-Type: application/json" header flag any time you want to end JSON to the endpoint. I know this is not losing control over the communication with endpoints but still it's slightly annoying.

$ curl -H "Content-Type: application/json" -X POST -d '{"key1":"val1"}' http://localhost/post
{
  "args": {}, 
  "data": "{\"key1\":\"val1\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "15", 
    "Content-Type": "application/json", 
    "Host": "localhost", 
    "User-Agent": "curl/7.58.0"
  }, 
  "json": {
    "key1": "val1"
  }, 
  "origin": "172.17.0.1", 
  "url": "http://localhost/post"
}

from file

$ curl -H "Content-Type: application/json" -X POST -d @/tmp/tmp.json http://localhost/post
{
  "args": {}, 
  "data": "{\t\"key1\": \"value1\"}", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept": "*/*", 
    "Content-Length": "19", 
    "Content-Type": "application/json", 
    "Host": "localhost", 
    "User-Agent": "curl/7.58.0"
  }, 
  "json": {
    "key1": "value1"
  }, 
  "origin": "172.17.0.1", 
  "url": "http://localhost/post"
}

I haven't find the way how to sedn raw JSON to the server. Maybe I omit something but here wins HTTPie.

Parts

Please continue reading

Part 1

Part 3

Leave a Reply

Your email address will not be published. Required fields are marked *