Skip to content
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

Implementation of range header #1703

Merged
merged 8 commits into from
Jun 30, 2024
Merged

Implementation of range header #1703

merged 8 commits into from
Jun 30, 2024

Conversation

huitema
Copy link
Collaborator

@huitema huitema commented Jun 23, 2024

Adding an option to encode a range in GET and POST requests.

This is motivated by issue #1702, but the implementation differs somewhat: the new "range" and "range_length" arguments are only added to the "extended" methods h3zero_create_request_header_frame_ex and h3zero_create_post_header_frame_ex, so as to minimize the churn in existing projects. No support in the "connect" method, because range does not make sense there. Or does it?

This implementation is insufficient. At a minimum, we need unit tests for the frame creation APIs. But it would be better to have a way to exercise the option. Maybe allow "document" arguments in the "picoquicdemo" app to include a range, as in:

picoquicdemo test.privateoctopus.com 4433 /something#1000-50000

Then, split the argument into path and range components.

But really, for a complete implementation, we should make sure that the demo server supports the range request, and only returns the requested range. At list for requests, maybe for "post" too, but we need to understand what "range" means for a post.

Copy link
Collaborator

@fbussery fbussery left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering if picoquicdemo, instead of

picoquicdemo test.privateoctopus.com 4433 /something#1000-50000

could align with curl and use such command:

picoquicdemo -H "Range: bytes=0-499" -H "Accept-Encoding: gzip" ... test.privateoctopus.com 4433 /something

@huitema
Copy link
Collaborator Author

huitema commented Jun 25, 2024

The picoquic demo CLI allows for downloading multiple files. Options like "-H" would apply the same range to all files. Isn't that a problem?

@fbussery
Copy link
Collaborator

sorry, you're right

@huitema
Copy link
Collaborator Author

huitema commented Jun 27, 2024

The last two commit allow specifying ranges as part of the "scenario" syntax used by picoquicdemo. The h3zero tests verify that this is correctly parsed, the test value being:

"test.html:#bytes=80-800;/cgi-sink:10000:#bytes=100-1000;"

@huitema
Copy link
Collaborator Author

huitema commented Jun 27, 2024

@fbussery I have not been able to try the range requests in picoquicdemo -- I would need to implement the server side first, and that may take some time. Can you test this PR against a server that supports ranges?

@fbussery
Copy link
Collaborator

I did test it quicky. And It is not working. I will test your branch in our env today to check if I have the same status. I run the test in your env this way: picoquicdemo -l log.txt -G -v dash.akamaized.net 443 /akamai/bbb_30fps/bbb_30fps_3840x2160_12000k/bbb_30fps_3840x2160_12000k_12.m4v:#bytes=100-20000

@huitema
Copy link
Collaborator Author

huitema commented Jun 27, 2024

Yes, I tried this locally and got an error. This is visible in qlog:

image

Error code 268 (0x10c) is "H3_REQUEST_CANCELLED (0x010c): The request or its response (including pushed response) is cancelled".

@huitema
Copy link
Collaborator Author

huitema commented Jun 28, 2024

@fbussery: I added parsing code for the range header, and then used that in a unit test, which works. I tried repeating the test against the akamai server, and it still fails.

I think that we are missing a few steps. I read RFC 7233, and there are a few interesting points:

  1. There is a special response code: 206 Partial Content, if the response is a range. If the response is code 200, the whole doc is returned.
  2. There is multipart component (one per range) with an indication of which range is server.
  3. Servers may use the "accept range" header to indicate that they accept range requests. However, the RFC is clear that range requests can be sent even if that header has not been received yet.

Do we know who is developing the Akamai server? They may be able to help.

@fbussery
Copy link
Collaborator

Hello,
I'm very sorry of this bad commit. It is definitively not working and I don't understand how I managed to test it. Still a mistery.
I'm very busy on an other topic now, so I can't work on it these next days.
For your info, I just tested the server akamai with curl http3 and it is working. Here is the following headers:

/usr/local/bin/curl -v --http3 -H "Range: bytes=0-103" https://akamaibroadcasteruseast.akamaized.net/cmaf/live/657078/akasource/1719536401/chunk-stream_1-11926.m4s -o outputfile
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0* Host akamaibroadcasteruseast.akamaized.net:443 was resolved.
* IPv6: 2a02:26f0:2b00:22::687d:3ce, 2a02:26f0:2b00:22::687d:3d3
* IPv4: 96.16.122.56, 96.16.122.83
* WARNING: no socket in pollset, transfer may stall!
*   Trying 96.16.122.56:443...
*  CAfile: /etc/ssl/certs/ca-certificates.crt
*  CApath: /etc/ssl/certs
* Server certificate:
*  subject: C=US; ST=Massachusetts; L=Cambridge; O=Akamai Technologies, Inc.; CN=a248.e.akamai.net
*  start date: Apr 18 00:00:00 2024 GMT
*  expire date: Apr 19 23:59:59 2025 GMT
*  subjectAltName: host "akamaibroadcasteruseast.akamaized.net" matched cert's "*.akamaized.net"
*  issuer: C=US; O=DigiCert Inc; CN=DigiCert TLS RSA SHA256 2020 CA1
*  SSL certificate verify ok.
*   Certificate level 0: Public key type EC/prime256v1 (256/128 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 1: Public key type RSA (2048/112 Bits/secBits), signed using sha256WithRSAEncryption
*   Certificate level 2: Public key type RSA (2048/112 Bits/secBits), signed using sha1WithRSAEncryption
* Connected to akamaibroadcasteruseast.akamaized.net (96.16.122.56) port 443
* using HTTP/3
* [HTTP/3] [0] OPENED stream for https://akamaibroadcasteruseast.akamaized.net/cmaf/live/657078/akasource/1719536401/chunk-stream_1-11926.m4s
* [HTTP/3] [0] [:method: GET]
* [HTTP/3] [0] [:scheme: https]
* [HTTP/3] [0] [:authority: akamaibroadcasteruseast.akamaized.net]
* [HTTP/3] [0] [:path: /cmaf/live/657078/akasource/1719536401/chunk-stream_1-11926.m4s]
* [HTTP/3] [0] [user-agent: curl/8.9.0-DEV]
* [HTTP/3] [0] [accept: */*]
* [HTTP/3] [0] [range: bytes=0-103]
> GET /cmaf/live/657078/akasource/1719536401/chunk-stream_1-11926.m4s HTTP/3
> Host: akamaibroadcasteruseast.akamaized.net
> User-Agent: curl/8.9.0-DEV
> Accept: */*
> Range: bytes=0-103
> 
* Request completely sent off
* old SSL session ID is stale, removing
< HTTP/3 206 
< akamai-path-timestamp: i=1719560276.072;xi=1719560276.084;xo=1719560276.981;s=1719560276.990;
< last-modified: Thu, 01 Jan 1970 00:00:00 GMT
< x-akamai-live-origin-qos: d=2000;t=0.000
< x-akamai-server: Akamai-SMT
< pragma: no-cache
< akamai-mon-iucid-ing: 657078
< cache-control: max-age=31532657
< expires: Sat, 28 Jun 2025 07:37:58 GMT
< date: Fri, 28 Jun 2024 08:33:41 GMT
< content-range: bytes 0-103/37838
< content-length: 104
< akamai-mon-iucid-del: 621137
< quic-version: 0x00000001
< alt-svc: h3-Q050=":443"; ma=93600,quic=":443"; ma=93600; v="46,43"
< content-type: video/mp4
< access-control-max-age: 86400
< access-control-allow-credentials: true
< access-control-expose-headers: Server,range,hdntl,hdnts
< access-control-allow-headers: origin,range,hdntl,hdnts
< access-control-allow-methods: GET,POST,OPTIONS
< access-control-allow-origin: *
< 
{ [104 bytes data]
100   104  100   104    0     0   1861      0 --:--:-- --:--:-- --:--:--  1890
* Connection #0 to host akamaibroadcasteruseast.akamaized.net left intact

I will investigate further next week I hope.

@huitema
Copy link
Collaborator Author

huitema commented Jun 28, 2024

I tried repeating exactly the command that you used, and it still fails. If I remove the range specification, I do get the content of the whole chunk. I suspect that we are missing some negotiation step. It would be good to compare PCAPs of the successful session with curl and the failing session with picoquic, and look for potential differences.

@fbussery
Copy link
Collaborator

In fact, I found out how to make it work.
Seems that the order of the qpack code matters.
range must be sent after the user agent. (found experimentally)

so, if we apply

index f9b28a94..5d6e055f 100644
--- a/picohttp/h3zero.c
+++ b/picohttp/h3zero.c
@@ -903,10 +903,6 @@ uint8_t * h3zero_create_request_header_frame_ex(uint8_t * bytes, uint8_t * bytes
     bytes = h3zero_qpack_code_encode(bytes, bytes_max, 0xC0, 0x3F, H3ZERO_QPACK_SCHEME_HTTPS);
     /* Path: doc_name. Use literal plus reference format */
     bytes = h3zero_qpack_literal_plus_ref_encode(bytes, bytes_max, H3ZERO_QPACK_CODE_PATH, path, path_length);
-    /*Optional: range. Use literal plus reference format */
-    if (range_length > 0) {
-        bytes = h3zero_qpack_literal_plus_ref_encode(bytes, bytes_max, H3ZERO_QPACK_RANGE, (uint8_t const *)range, range_length);
-    }
     /*Authority: host. Use literal plus reference format */
     if (host != NULL) {
         bytes = h3zero_qpack_literal_plus_ref_encode(bytes, bytes_max, H3ZERO_QPACK_AUTHORITY, (uint8_t const *)host, strlen(host));
@@ -915,6 +911,10 @@ uint8_t * h3zero_create_request_header_frame_ex(uint8_t * bytes, uint8_t * bytes
     if (ua_string != NULL) {
         bytes = h3zero_qpack_literal_plus_ref_encode(bytes, bytes_max, H3ZERO_QPACK_USER_AGENT, (uint8_t const*)ua_string, strlen(ua_string));
     }
+    /*Optional: range. Use literal plus reference format */
+    if (range_length > 0) {
+        bytes = h3zero_qpack_literal_plus_ref_encode(bytes, bytes_max, H3ZERO_QPACK_RANGE, (uint8_t const *)range, range_length);
+    }
     return bytes;
 }

This works

../picoquicdemo -l log.txt -G -v dash.akamaized.net 443 /akamai/bbb_30fps/bbb_30fps_3840x2160_12000k/bbb_30fps_3840x2160_12000k_12.m4v:#bytes=100-2000

...
-rw-rw-r-- 1 fbussery fbussery  1901 juin  28 21:44 _akamai_bbb_30fps_bbb_30fps_3840x2160_12000k_bbb_30fps_3840x2160_12000k_12.m4v
-rw-rw-r-- 1 fbussery fbussery   450 juin  28 21:44 demo_ticket_store.bin
-rw-rw-r-- 1 fbussery fbussery     0 juin  28 21:44 demo_token_store.bin
-rw-rw-r-- 1 fbussery fbussery 10787 juin  28 21:44 log.txt

@huitema
Copy link
Collaborator Author

huitema commented Jun 28, 2024

Good find, but wow! I can't find that any part of RFC 7233 or 7232...

@huitema
Copy link
Collaborator Author

huitema commented Jun 29, 2024

I moved the code around a bit. The "range" header does not have to come after the UA header, but it must be sent after the "host" header. That makes more sense: the document is specified by the combination of path and host, thus both may be needed before a subset of the document can be specified.

@huitema huitema merged commit 113d9bd into master Jun 30, 2024
11 checks passed
@huitema huitema deleted the httpr2 branch July 18, 2024 03:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants