-
Notifications
You must be signed in to change notification settings - Fork 3.1k
libfetch, fetch: Improve handling of HTTP request body, method, fields, and status code #1859
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
| struct url_stat stat; | ||
| }; | ||
|
|
||
| #ifdef _SYS_QUEUE_H_ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The #ifdef _SYS_QUEUE_H_ is a compromise. Currently, fetch.h includes none of the headers it relies on. Rather than changing this, or changing every consumer to include the sys/queue.h header, I added checks for it to only expose the parts that rely on it when the consumer has included it.
I'm open to handling this differently.
| */ | ||
| FILE *http_request(struct url *, const char *, | ||
| struct url_stat *, struct url *, const char *); | ||
| FILE *http_request_body(struct url *, const char *, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is not used outside http.c so while changing the parameters I also made it static.
| @@ -956,8 +994,7 @@ main(int argc, char *argv[]) | |||
| f_filename = optarg; | |||
| break; | |||
| case 'H': | |||
| warnx("the -H option is now implicit, " | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Seems worth replacing this warning with useful functionality. -H matches curl.
d85e0ee to
085d11a
Compare
|
085d11a to
c1c9b3e
Compare
|
|
3cc6de0 to
d600ab8
Compare
|
|
It would be best to implement support for HTTP trailer fields in libfetch while I'm here. Taking both headers and trailers into consideration, we would prefer to use more general terms like I have started implementing this change and will update this pull request after I finish testing the idea. |
d600ab8 to
659bbc5
Compare
I have a Lua binding for libfetch and a test server scripted with socat I've been testing with. I'll share more of the test harness details when I have some time. |
|
|
Thank you for taking the time to contribute to FreeBSD! |
34b02f0 to
283c438
Compare
This comment was marked as outdated.
This comment was marked as outdated.
283c438 to
659bbc5
Compare
|
On closer inspection, https://www.rfc-editor.org/rfc/rfc9112#section-6.3-4.1 notes that request messages are never close-delimited. The previous commit has been removed. |
|
I've just noticed that the group of HTTP response codes to treat as success in libfetch is missing a few. I'll be back with another revision to treat e.g. |
|
a440413 to
bc6618e
Compare
|
Modern APIs often rely on HTTP fields as part of their interface, both in requests and responses. Add a function to libfetch allowing additional request fields to be specified for an HTTP request and response fields to be returned to the caller. This function accepts an optional stream for the body content, as a nul-terminated C string may be inadequate for some content. If the length of the stream can be determined, a Content-Length header is added. Otherwise, the body is sent chunk-encoded. Signed-off-by: Ryan Moeller <[email protected]>
Modern APIs often use HTTP method and headers as part of their interface. Add functionality to fetch allowing HTTP method and headers to be specified. The option names align with those used by curl. Ability to provide request body data remains unimplemented. Signed-off-by: Ryan Moeller <[email protected]>
Add a -D/--data option to specify a file from which to read HTTP request body contents, with the special case that "-" will read from stdin. This option deviates from curl, taking a much more barebones approach for simplicity. Add a -C/--chunked option to specify that the HTTP request body contents are to use chunked transfer-encoding. Otherwise, it is automatically used by libfetch when the size of the file cannot be predetermined. Signed-off-by: Ryan Moeller <[email protected]>
Some HTTP 2xx (Successful) class status codes were being treated as errors. Add a definition for the full range of successful status codes and use it to test for a success response. This allows libfetch and fetch to receive the response body from more practical requests, such as POST responding 201 Created with content. Signed-off-by: Ryan Moeller <[email protected]>
bc6618e to
747ae41
Compare
|
|
I have another small commit extending the existing handling of Range and Content-Range headers in libfetch to better support range requests and partial responses according to RFC 9110 Section 14. However, I'm reluctant to continue expanding the original scope of this pull request. I'll hold off on pushing that commit unless requested, otherwise I can save it for another pull request. |
Modern APIs often rely on HTTP fields as part of their interface, both
in requests and responses.
lib/libfetch: Add fetchXReqHTTP(3)
Add a function to libfetch allowing additional request fields to be
specified for an HTTP request and response fields to be returned to the
caller.
This function accepts an optional stream for the body content, as a
nul-terminated C string may be inadequate for some content. If the
length of the stream can be determined, a Content-Length header is
added. Otherwise, the body is sent chunk-encoded.
usr.bin/fetch: Handle custom headers and method for HTTP
Add functionality to fetch allowing HTTP method and headers to be
specified. The option names align with those used by curl.
usr.bin/fetch: Add options for HTTP upload data
Add a -D/--data option to specify a file from which to read HTTP request
body contents, with the special case that "-" will read from stdin.
This option deviates from curl, taking a much more barebones approach
for simplicity.
Add a -C/--chunked option to specify that the HTTP request body contents
are to use chunked transfer-encoding. Otherwise, it is automatically
used by libfetch when the size of the file cannot be predetermined.
lib/libfetch: Accept all HTTP 2xx (Successful) status codes
Some HTTP 2xx (Successful) class status codes were being treated as
errors. Add a definition for the full range of successful status codes
and use it to test for a success response.
This allows libfetch and fetch to receive the response body from more
practical requests, such as POST responding 201 Created with content.
Demonstration
Here is an example of the new functionality being used to access the GitHub REST API. The API requires a valid bearer token in the Authorization header, along with a couple other recommended headers described in the documentation.
$ fetch -q \ -X GET \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "X-GitHub-Api-Version: 2022-11-28" \ -o - \ https://api.github.com/repos/ryan-moeller/kernel-nbd-client/branches [{"name":"main","commit":{"sha":"d69b0a1c8d2b63ab48726027e6e756ccf9a0f2fb","url":"https://api.github.com/repos/ryan-moeller/kernel-nbd-client/commits/d69b0a1c8d2b63ab48726027e6e756ccf9a0f2fb"},"protected":false},{"name":"structured","commit":{"sha":"4831ee5ece393e596262d76609a0be69d3d408bf","url":"https://api.github.com/repos/ryan-moeller/kernel-nbd-client/commits/4831ee5ece393e596262d76609a0be69d3d408bf"},"protected":false},{"name":"unix/stream","commit":{"sha":"7f020b22ec0e9414a212fe34b01701b23443d825","url":"https://api.github.com/repos/ryan-moeller/kernel-nbd-client/commits/7f020b22ec0e9414a212fe34b01701b23443d825"},"protected":false}]The (redundant, as it is the default) GET method is specified along with a valid token in an Authorization header, and the server responds with the requested data.
Specifying a different HTTP method is allowed by fetch(1) and rejected by the server as expected:
$ fetch -q \ -X TEST \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "X-GitHub-Api-Version: 2022-11-28" \ -o - \ https://api.github.com/repos/ryan-moeller/kernel-nbd-client/branches fetch: https://api.github.com/repos/ryan-moeller/kernel-nbd-client/branches: ForbiddenThe server does not respond to requests with an invalid token:
$ fetch -q \ -H "Accept: application/vnd.github+json" \ -H "Authorization: Bearer not$GITHUB_TOKEN" \ -H "X-GitHub-Api-Version: 2022-11-28" \ -o - \ https://api.github.com/repos/ryan-moeller/kernel-nbd-client/branches fetch: https://api.github.com/repos/ryan-moeller/kernel-nbd-client/branches: $ echo $? 1This is not the most compelling example, as this is a read-only use of the GitHub API to access public data that would have been allowed with no token. There are of course other API endpoints with stricter access controls where the headers are actually necessary. And this functionality becomes even more useful with support for fetch(1) uploading a request body. This enables use of common HTTP methods like PUT, POST, and PATCH.
For example, uploading a gist to GitHub uses most of the new features:
$ fetch -q \ -X POST \ -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ -H "Content-Type: application/json" \ -D - \ -o - \ https://api.github.com/gists <<EOF { "description": "fetch(1) test gist", "public": false, "files": { "demo.txt": { "content": "This gist was created using fetch(1)" } } } EOF <verbose JSON response omitted>-X POSTtells fetch to use the POST method for the HTTP request-H ...adds custom headers to the request-D -sends a request body from a file (stdin, in this case)Testing
FreeBSD has no tests for libfetch or fetch. I didn't go so far as to implement a Kyua test suite for inclusion in FreeBSD, but I do have some ancillary testing apparatus used to exercise my changes.
My test harness for this uses a Lua library wrapping libfetch with a Lua script exercising all request and response field sections as well as different request and response body transfer encodings and different success status codes. This can all be found here.
CI runs: https://cirrus-ci.com/build/5568610090876928
15.0-ALPHA4 test output: https://api.cirrus-ci.com/v1/task/5673940506378240/logs/libfetch_test.log