-
Notifications
You must be signed in to change notification settings - Fork 1.9k
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
Issue #12104 - HTTP/1.0 should produce a valid Connection
header, never blank/null.
#12105
Changes from all commits
4dd04ec
ba27531
e755458
88b145d
d73e1a0
e2b9423
ec489b0
01f4e64
040c046
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -575,6 +575,7 @@ private void generateHeaders(ByteBuffer header, ByteBuffer content, boolean last | |
|
||
// default field values | ||
HttpField transferEncoding = null; | ||
boolean http10 = _info.getHttpVersion() == HttpVersion.HTTP_1_0; | ||
boolean http11 = _info.getHttpVersion() == HttpVersion.HTTP_1_1; | ||
boolean close = false; | ||
boolean chunkedHint = _info.getTrailersSupplier() != null; | ||
|
@@ -627,23 +628,36 @@ else if (contentLength != field.getLongValue()) | |
|
||
case CONNECTION: | ||
{ | ||
boolean keepAlive = field.contains(HttpHeaderValue.KEEP_ALIVE.asString()); | ||
if (keepAlive && _info.getHttpVersion() == HttpVersion.HTTP_1_0 && _persistent == null) | ||
boolean hasValue = StringUtil.isNotBlank(field.getValue()); | ||
boolean hasKeepAlive = field.contains(HttpHeaderValue.KEEP_ALIVE.asString()); | ||
boolean hasClose = field.contains(HttpHeaderValue.CLOSE.asString()); | ||
|
||
if (http10 && hasKeepAlive) | ||
{ | ||
_persistent = true; | ||
// set persistence if unset | ||
if (_persistent == null) | ||
_persistent = true; | ||
} | ||
if (field.contains(HttpHeaderValue.CLOSE.asString())) | ||
else if (hasClose) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we want the else here. If it came There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was reading the Hop-by-hop parts of the spec in light of other tests with have for this. Looks like if we receive a comma delimited list, then it's a 400 Bad Request in normal operations. BUT we can create a response with multi-value I'm at a loss on the exact meaning of "our hop" in these cases. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need to 400 for both a Keep-Alive and Close. The close is a must-not persist and the Keep-Alive is a may persist, so they combine easily to a must-not persist. |
||
{ | ||
close = true; | ||
_persistent = false; | ||
} | ||
if (keepAlive && _persistent == Boolean.FALSE) | ||
|
||
if (_persistent == Boolean.FALSE && hasKeepAlive) | ||
{ | ||
field = new HttpField(HttpHeader.CONNECTION, | ||
Stream.of(field.getValues()).filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s)) | ||
.collect(Collectors.joining(", "))); | ||
// we need to strip the keep-alive values | ||
String resultingValue = Stream.of(field.getValues()) | ||
.filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s)) | ||
.collect(Collectors.joining(", ")); | ||
Comment on lines
+650
to
+652
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if string operations here would be better than streams... There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, I'm not a fan of the stream operation here, but it was here before I started this, so I left it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, so that. I agree a removeValue method would allow us to hide the details. |
||
if (StringUtil.isBlank(resultingValue)) | ||
hasValue = false; // all values stripped, do not produce a Connection header with no values | ||
else | ||
field = new HttpField(HttpHeader.CONNECTION, resultingValue); | ||
} | ||
putTo(field, header); | ||
|
||
if (hasValue) | ||
putTo(field, header); | ||
break; | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ | |
import java.util.concurrent.atomic.AtomicLong; | ||
import java.util.concurrent.atomic.AtomicReference; | ||
import java.util.concurrent.atomic.LongAdder; | ||
import java.util.stream.Collectors; | ||
|
||
import org.eclipse.jetty.http.BadMessageException; | ||
import org.eclipse.jetty.http.ComplianceViolation; | ||
|
@@ -1382,10 +1383,58 @@ public void demand() | |
} | ||
|
||
@Override | ||
public void prepareResponse(HttpFields.Mutable headers) | ||
public void prepareResponse(HttpFields.Mutable responseHeaders) | ||
{ | ||
if (_connectionKeepAlive && _version == HttpVersion.HTTP_1_0 && !headers.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString())) | ||
headers.add(HttpFields.CONNECTION_KEEPALIVE); | ||
boolean hasConnectionClose = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE.asString()); | ||
boolean hasConnectionKeepAlive = responseHeaders.contains(HttpHeader.CONNECTION, HttpHeaderValue.KEEP_ALIVE.toString()); | ||
Comment on lines
+1388
to
+1389
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. again, don't lookup values if we don't need them, especially in this case as it involves a full iteration to do each lookup. So lookup only if needed and perhaps look for close first, so it can override keepalive. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is that each of these could then be looked up multiple times instead. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. A single iteration through is best, I'll work on that after you have had a look at the In particular the new tests ...
Are you good with the set of expectations for those?
Once I get that set of tests sane, then I can look closer at the other tests that are now failing due to this PR. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you take just the changes to the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not saying avoid local variables with those looked up values (so we don't need to lookup multiple times), but I am saying delay doing the lookup until it is known that it is needed. |
||
|
||
if (_version == HttpVersion.HTTP_1_0 && _generator.isPersistent() && _connectionKeepAlive && !hasConnectionClose) | ||
{ | ||
// attempt to cover HTTP/1.0 case where the Connection response header | ||
// doesn't have a close value (set by the application) and it needs to | ||
// be represented as a `Connection: keep-alive` response instead due | ||
// to the existence of the `Connection: keep-alive` request header | ||
responseHeaders.add(HttpFields.CONNECTION_KEEPALIVE); | ||
} | ||
|
||
if (!_generator.isPersistent()) | ||
{ | ||
if (!hasConnectionClose) | ||
{ | ||
// Add missing "Connection: close" if not persistent | ||
responseHeaders.add(HttpHeader.CONNECTION, HttpHeaderValue.CLOSE); | ||
} | ||
|
||
if (hasConnectionKeepAlive) | ||
{ | ||
// if generator is not persistent, strip any keep-alive | ||
// response header values set by the application regardless of | ||
// HTTP/1.0 and HTTP/1.1 differences | ||
String resultingValue = responseHeaders.getValuesList(HttpHeader.CONNECTION) | ||
.stream() | ||
.filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s)) | ||
.collect(Collectors.joining(", ")); | ||
if (StringUtil.isBlank(resultingValue)) | ||
responseHeaders.remove(HttpHeader.CONNECTION); | ||
else | ||
responseHeaders.put(HttpHeader.CONNECTION, resultingValue); | ||
} | ||
} | ||
else | ||
{ | ||
// Strip "keep-alive" when using HTTP/1.1 on persistent connections | ||
if (_version == HttpVersion.HTTP_1_1 && hasConnectionKeepAlive) | ||
{ | ||
String resultingValue = responseHeaders.getValuesList(HttpHeader.CONNECTION) | ||
.stream() | ||
.filter(s -> !HttpHeaderValue.KEEP_ALIVE.is(s)) | ||
.collect(Collectors.joining(", ")); | ||
if (StringUtil.isBlank(resultingValue)) | ||
responseHeaders.remove(HttpHeader.CONNECTION); | ||
else | ||
responseHeaders.put(HttpHeader.CONNECTION, resultingValue); | ||
} | ||
} | ||
} | ||
|
||
@Override | ||
|
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.
Whilst this style is easily readable, it contains a lot of redundant code/tests. If
hasValue==false
, then you can break straight away and avoid subsequent work.If it
hasKeepAlive
is true, then in your code there is no need to lookuphasClose
(but see below).