How should I configure my headers to make an HTTP/2 POST to APNs to avoid "Received duplicate pseudo-header field" error?
Asked Answered
D

1

7

I'm pretty new to HTTP stuff, primarily stick to iOS so please bear with me. I'm using the httpx python library to try and send a notification to an iPhone because I have to make an HTTP/2 POST to do so. Apple's Documentation says it requires ":method" and ":path" headers but I when I try to make the POST with these headers included,

headers = { 
    ':method' : 'POST', 
    ':path' : '/3/device/{}'.format(deviceToken),  
    ...
    }

I get the error

h2.exceptions.ProtocolError: Received duplicate pseudo-header field b':path

It's pretty apparent there's a problem with having the ":path" header included but I'm also required to send it so I'm not sure what I'm doing wrong. Apple's Documentation also says to

Encode the :path and authorization values as literal header fields without indexing.

Encode all other fields as literal header fields with incremental indexing.

I really don't know what that means or how to implement that or if it's related. I would think httpx would merge my ":path" header with the default one to eliminate the duplicate but I'm just spitballing here.

Full Traceback

File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpx/_client.py", line 992, in post
    return self.request(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpx/_client.py", line 733, in request
    return self.send(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpx/_client.py", line 767, in send
    response = self._send_handling_auth(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpx/_client.py", line 805, in _send_handling_auth
    response = self._send_handling_redirects(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpx/_client.py", line 837, in _send_handling_redirects
    response = self._send_single_request(request, timeout)
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpx/_client.py", line 861, in _send_single_request
    (status_code, headers, stream, ext) = transport.request(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpcore/_sync/connection_pool.py", line 218, in request
    response = connection.request(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpcore/_sync/connection.py", line 106, in request
    return self.connection.request(method, url, headers, stream, ext)
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpcore/_sync/http2.py", line 119, in request
    return h2_stream.request(method, url, headers, stream, ext)
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpcore/_sync/http2.py", line 292, in request
    self.send_headers(method, url, headers, has_body, timeout)
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpcore/_sync/http2.py", line 330, in send_headers
    self.connection.send_headers(self.stream_id, headers, end_stream, timeout)
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/httpcore/_sync/http2.py", line 227, in send_headers
    self.h2_state.send_headers(stream_id, headers, end_stream=end_stream)
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/h2/connection.py", line 770, in send_headers
    frames = stream.send_headers(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/h2/stream.py", line 865, in send_headers
    frames = self._build_headers_frames(
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/h2/stream.py", line 1252, in _build_headers_frames
    encoded_headers = encoder.encode(headers)
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/hpack/hpack.py", line 249, in encode
    for header in headers:
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/h2/utilities.py", line 496, in inner
    for header in headers:
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/h2/utilities.py", line 441, in _validate_host_authority_header
    for header in headers:
  File "/Users/User/.pyenv/versions/3.9.0/lib/python3.9/site-packages/h2/utilities.py", line 338, in _reject_pseudo_header_fields
    raise ProtocolError(
h2.exceptions.ProtocolError: Received duplicate pseudo-header field b':method'

Request:

devServer = "https://api.sandbox.push.apple.com:443"
title = "some title"
notification = { "aps": { "alert": title, "sound": "someSound.caf" } }
client = httpx.Client(http2=True)
try:
    r = client.post(devServer, json=notification, headers=headers)
finally:
    client.close()
Dandle answered 15/12, 2020 at 21:54 Comment(6)
What does your http request call look like?Petropavlovsk
@Petropavlovsk added it to my question.Dandle
Take ":path" & ":method" out of your headers - httpx, via http2, puts those in automatically. (I use more or less the same code, minus those headers!)Wiersma
@Wiersma So how do I pass in the device token without the ":path" header? I figured I could go without the ":method" since POST could be inferred but I'm just not sure how to pass the device token info otherwise...Dandle
@Dandle put the path into the URL you pass to the post function. Right now, you just put in devServer, instead make it something like this: '{}/3/device/{}'.format(devServer, deviceToken)Wiersma
@Wiersma that was it! I'm dumb, thank you!!Dandle
M
4

Just need to append '/3/device/{}'.format(deviceToken) to the devServer url as the path, and the ":path" pseudo-header will be automatically set to it.

that is,

devServer = 'https://api.sandbox.push.apple.com:443/3/device/{}'.format(deviceToken) 

Explanation:

The ":path", ":method" and ":scheme" pseudo-headers generally wouldn't need to be added manually in http2

Reference: Hypertext Transfer Protocol Version 2 (HTTP/2)

Menander answered 29/12, 2020 at 5:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.