diff --git a/docs/README.md b/docs/README.md index 5d1a2c11..ccdf53d2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -518,6 +518,12 @@ $ http https://api.github.com/search/repositories q==httpie per_page==1 GET /search/repositories?q=httpie&per_page=1 HTTP/1.1 ``` +You can even retrieve the `value` from a file by using the `param==@file` syntax. This would also effectively strip the newlines from the end. See [#file-based-separators] for more examples. + +```bash +$ http pie.dev/get text==@files/text.txt +``` + ### URL shortcuts for `localhost` Additionally, curl-like shorthand for localhost is supported. @@ -596,21 +602,48 @@ GET /../../etc/password HTTP/1.1 ## Request items -There are a few different *request item* types that provide a convenient mechanism for specifying HTTP headers, simple JSON and form data, files, and URL parameters. +There are a few different *request item* types that provide a convenient +mechanism for specifying HTTP headers, JSON and form data, files, +and URL parameters. This is a very practical way of constructing +HTTP requests from scratch on the CLI. -They are key/value pairs specified after the URL. All have in common that they become part of the actual request that is sent and that their type is distinguished only by the separator used: `:`, `=`, `:=`, `==`, `@`, `=@`, `:=@` and `==@`. The ones with an `@` expect a file path as value. +Each *request item* is simply a key/value pair separated with the following +characters: `:` (headers), `=` (data field, e.g JSON, Form), `:=` (raw data field) +`==` (query parameters), `@` (file upload). + +```bash +$ http PUT pie.dev/put \ + X-Date:today \ # Header + token==secret \ # Query parameter + name=John \ # Data field + age:=29 # Raw JSON +``` | Item Type | Description | | -----------------------------------------------------------: | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | HTTP Headers `Name:Value` | Arbitrary HTTP header, e.g. `X-API-Token:123` | -| URL parameters `name==value` | Appends the given name/value pair as a querystring parameter to the URL. The `==` separator is used. For reading the value from a file, use `==@`. | -| Data Fields `field=value`, `field=@file.txt` | Request data fields to be serialized as a JSON object (default), to be form-encoded (with `--form, -f`), or to be serialized as `multipart/form-data` (with `--multipart`) | +| URL parameters `name==value` | Appends the given name/value pair as a querystring parameter to the URL. The `==` separator is used. | +| Data Fields `field=value` | Request data fields to be serialized as a JSON object (default), to be form-encoded (with `--form, -f`), or to be serialized as `multipart/form-data` (with `--multipart`) | | Raw JSON fields `field:=json` | Useful when sending JSON and one or more fields need to be a `Boolean`, `Number`, nested `Object`, or an `Array`, e.g., `meals:='["ham","spam"]'` or `pies:=[1,2,3]` (note the quotes) | | File upload fields `field@/dir/file`, `field@file;type=mime` | Only available with `--form`, `-f` and `--multipart`. For example `screenshot@~/Pictures/img.png`, or `'cv@cv.txt;type=text/markdown'`. With `--form`, the presence of a file field results in a `--multipart` request | Note that the structured data fields aren’t the only way to specify request data: [raw request body](#raw-request-body) is a mechanism for passing arbitrary request data. +### File based separators + +Using file contents as values for specific fields is a very common use case, which can be achieved through adding the `@` suffix to +the operators above. For example instead of using a static string as the value for some header, you can use `:@` operator +to pass the desired value from a file. + +```bash +$ http POST pie.dev/post \ + X-Data:@files/text.txt # Read a header from a file + token==@files/text.txt # Read a query parameter from a file + name=@files/text.txt # Read a data field's value from a file + bookmarks:=@files/data.json # Embed a JSON object from a file +``` + ### Escaping rules You can use `\` to escape characters that shouldn’t be used as separators (or parts thereof). For instance, `foo\==bar` will become a data key/value pair (`foo=` and `bar`) instead of a URL parameter. @@ -1108,6 +1141,14 @@ Host: Any of these can be overwritten and some of them unset (see below). +### Reading headers from a file + +You can read headers from a file by using the `:@` operator. This would also effectively strip the newlines from the end. See [#file-based-separators] for more examples. + +```bash +$ http pie.dev/headers X-Data:@files/text.txt +``` + ### Empty headers and header un-setting To unset a previously specified header (such a one of the default headers), use `Header:`: diff --git a/httpie/cli/constants.py b/httpie/cli/constants.py index 577b7ba3..ea50ce43 100644 --- a/httpie/cli/constants.py +++ b/httpie/cli/constants.py @@ -15,6 +15,7 @@ SEPARATOR_HEADER = ':' SEPARATOR_HEADER_EMPTY = ';' SEPARATOR_CREDENTIALS = ':' SEPARATOR_PROXY = ':' +SEPARATOR_HEADER_EMBED = ':@' SEPARATOR_DATA_STRING = '=' SEPARATOR_DATA_RAW_JSON = ':=' SEPARATOR_FILE_UPLOAD = '@' @@ -41,6 +42,7 @@ SEPARATORS_GROUP_MULTIPART = frozenset({ # Separators for items whose value is a filename to be embedded SEPARATOR_GROUP_DATA_EMBED_ITEMS = frozenset({ + SEPARATOR_HEADER_EMBED, SEPARATOR_QUERY_EMBED_FILE, SEPARATOR_DATA_EMBED_FILE_CONTENTS, SEPARATOR_DATA_EMBED_RAW_JSON_FILE, @@ -56,6 +58,7 @@ SEPARATOR_GROUP_NESTED_JSON_ITEMS = frozenset([ SEPARATOR_GROUP_ALL_ITEMS = frozenset({ SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY, + SEPARATOR_HEADER_EMBED, SEPARATOR_QUERY_PARAM, SEPARATOR_QUERY_EMBED_FILE, SEPARATOR_DATA_STRING, diff --git a/httpie/cli/requestitems.py b/httpie/cli/requestitems.py index a275e7c6..e804911e 100644 --- a/httpie/cli/requestitems.py +++ b/httpie/cli/requestitems.py @@ -8,7 +8,8 @@ from .constants import ( SEPARATOR_DATA_EMBED_RAW_JSON_FILE, SEPARATOR_GROUP_NESTED_JSON_ITEMS, SEPARATOR_DATA_RAW_JSON, SEPARATOR_DATA_STRING, SEPARATOR_FILE_UPLOAD, SEPARATOR_FILE_UPLOAD_TYPE, SEPARATOR_HEADER, SEPARATOR_HEADER_EMPTY, - SEPARATOR_QUERY_PARAM, SEPARATOR_QUERY_EMBED_FILE, RequestType + SEPARATOR_HEADER_EMBED, SEPARATOR_QUERY_PARAM, + SEPARATOR_QUERY_EMBED_FILE, RequestType ) from .dicts import ( BaseMultiDict, MultipartRequestDataDict, RequestDataDict, @@ -48,6 +49,10 @@ class RequestItems: process_empty_header_arg, instance.headers, ), + SEPARATOR_HEADER_EMBED: ( + process_embed_header_arg, + instance.headers, + ), SEPARATOR_QUERY_PARAM: ( process_query_param_arg, instance.params, @@ -119,6 +124,10 @@ def process_header_arg(arg: KeyValueArg) -> Optional[str]: return arg.value or None +def process_embed_header_arg(arg: KeyValueArg) -> str: + return load_text_file(arg).rstrip('\n') + + def process_empty_header_arg(arg: KeyValueArg) -> str: if not arg.value: return arg.value diff --git a/tests/test_cli.py b/tests/test_cli.py index 4bb627e9..6504c8a9 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -85,6 +85,7 @@ class TestItemParsing: self.key_value_arg('bool:=true'), self.key_value_arg('file@' + FILE_PATH_ARG), self.key_value_arg('query==value'), + self.key_value_arg('Embedded-Header:@' + FILE_PATH_ARG), self.key_value_arg('string-embed=@' + FILE_PATH_ARG), self.key_value_arg('param-embed==@' + FILE_PATH_ARG), self.key_value_arg('raw-json-embed:=@' + JSON_FILE_PATH_ARG), @@ -96,7 +97,8 @@ class TestItemParsing: assert headers == { 'Header': 'value', 'Unset-Header': None, - 'Empty-Header': '' + 'Empty-Header': '', + 'Embedded-Header': FILE_CONTENT.rstrip('\n') } # Parsed data