-
Notifications
You must be signed in to change notification settings - Fork 9.1k
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
ProtocolException: Expected ':status' header not present #640
Comments
I think it's twitter's fault. |
This looks like a bug in our HPACK decoder. Or possibly a bug in Twitter's HPACK encoder. (FYI @jpinner). Fetching |
Looks like this is an HPACK problem. I'm still trying to isolate the cause. I've got a reproducible test case: https://gist.github.com/swankjesse/9567930 |
@swankjesse I'll try to find some time to debug this today. Are you all using Twitter's HPACK encoder/decoder? |
We are using twitter's huffman codec for our own hpack. |
Thanks @jpinner. I'm not yet sure whether it's a bug in our client or your server. |
So, I just ran Seems
|
And here's for spdy 3
|
in both cases, I'm running with the following JVM arg
|
now, I'll try running 10 times on the same connection.. |
I ran the test case through our HPACK decoder and in all 6 responses decoded the ":status" header. My initial guess is that your decoder is evicting it from the table early. |
I am reliably getting a missing public static void main(String[] args) throws Exception {
OkHttpClient client = new OkHttpClient().setProtocols(Protocol.HTTP2_AND_HTTP_11);
URL url = new URL("https://twitter.com");
for (int i = 0; i < 10; i++) {
HttpURLConnection connection = client.open(url);
connection.setRequestMethod("HEAD");
int responseCode = connection.getResponseCode();
System.out.println(responseCode);
}
} |
@jpinner I'll check into eviction.. good idea! |
For comparison, Twitter's HPACK decoder has the following table sizes and removals after processing each header set: Current Header Table Size: 1558 So while processing the 6th response, ":status" is evicted from the table. If there is a bug in which entries the OkHttp decoder is evicting, it will not index the ":status" header correctly to re-emit. |
@adriancole can you run similar debugging output where you print the header table size after each header set is processed and print out whenever an entry is evicted from the table |
OK here's the run
diff --git a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java
index f125711..a409720 100644
--- a/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java
+++ b/okhttp/src/main/java/com/squareup/okhttp/internal/spdy/HpackDraft05.java
@@ -162,6 +162,7 @@ final class HpackDraft05 {
if (bytesToRecover > 0) {
// determine how many headers need to be evicted.
for (int j = headerTable.length - 1; j >= nextHeaderIndex && bytesToRecover > 0; j--) {
+ System.err.printf("Evicting %s which recovers %sbytes%n", headerTable[j], headerTable[j].hpackSize);
bytesToRecover -= headerTable[j].hpackSize;
headerTableByteCount -= headerTable[j].hpackSize;
headerCount--;
@@ -204,6 +205,7 @@ final class HpackDraft05 {
}
}
}
+ System.err.printf("Header table is %sbytes%n", headerTableByteCount);
}
private void clearReferenceSet() {
@@ -306,6 +308,9 @@ final class HpackDraft05 {
// Evict headers to the required length.
int bytesToRecover = (headerTableByteCount + delta) - maxHeaderTableByteCount;
+ if (bytesToRecover > 0) {
+ System.err.printf("Need to recover: %sbyes to add %s%n", bytesToRecover, entry);
+ }
int entriesEvicted = evictToRecoverBytes(bytesToRecover);
if (index == -1) { |
@adriancole is that run against https://gist.github.com/swankjesse/9567930 ? |
I suspect one issue might be that we are not taking into account huffman when doing length calculations.. |
doing header table size calculations I mean |
no I was running against twitter directly.. lemme try against that data. |
Here's the output on that.
|
scratch my suspicion about huffman lengths. looks like we indeed should use the non-encoded lengths. That's not to say there's no problem around huffman.
|
I have a loop running with 1000 streams against https://http2.iijplus.jp/push/test1 It is evicting in a way that isn't dropping status or causing errors. That's again, not to say there's no bug here, just there's only a couple servers running http/2 :P |
I'll decode the opcodes for the last response to see what the encoder thinks is going on. |
The first opcode emits a new huffman-encoded Content-Length (10711) using incremental indexing (0x078321988b). |
@adriancole -- btw the above happens before inserting set-cookie so nothing should have been evicted from the table yet. |
@adriancole @swankjesse At that line you toggle headers into and out of the reference set, but I think you are missing a call to emittedHeaders.add() if the header was added to the reference set. From http://tools.ietf.org/html/draft-ietf-httpbis-header-compression-05#section-3.2.1 -- if the indexed entry is in the header table and not in the reference set, it is first emitted, then added to the reference set. |
actually we do emit the reference set already after toggling. Needs a deeper look. |
While true that emitting at the line where toggle occurs works, the looks like the central area to look at is emitReferenceSet() vs readIndexedHeader where toggle occurs. Gotta run, but hope this helps! |
I don't see where you emit after toggling -- line 189 calls read indexed header where the toggling occurs on line 246. The header isn't added twice, what happens is that the encoder sees that we are about to evict a header that was in the header set but not emitted because it was already in the reference set. In order to make sure it gets emitted before evicting from the table, it indexes the header twice (once to remove it from the reference set which does not emit it, and then a second time to re-add it to the reference set which emits it immediately). Thus once the header is evicted from the table, it has already been emitted so we no longer need to track it. |
@adriancole to be more specific, it is important that the emission of the header happens immediately in case the header is evicted. Also it looks like there is a second bug in emitRefernceSet() -- according to the spec, only headers in the reference set that have not already been emitted in this header block should emitted, not all headers in the reference set. |
@jpinner so I was talking about if I added emission where you mentioned, |
Happy to help. Shameless plug: you could always just use https://github.com/twitter/hpack (v0.5.2 for hpack-draft-05) ;) |
Thanks @adriancole and @jpinner! |
(cherry picked from commit ca0aff1)
How to solve this in a production app, I am using retrofit 1.9 |
I am getting the same issue with OkHttp3. Does anyone have a solution? |
I am experiencing the same issue with OkHTTP 3.10.0 and nginx 1.13.12. When we disable http/2, it works. Any solution? Can we help with more information? |
@andre-paraense @Ahmedfir have you got a test server or real url we can test against? |
I got this attempting to reach https://twitter.com.
The text was updated successfully, but these errors were encountered: