-
Notifications
You must be signed in to change notification settings - Fork 922
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
[WIP] Introduce CompositeHttpHeadersBase
to replace HttpHeadersUtil.merge()
#5340
base: main
Are you sure you want to change the base?
Changes from all commits
259c16f
d7275e8
aaf426d
18f4920
a2c2728
e9fcd38
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 | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,212 @@ | ||||||||||||||
/* | ||||||||||||||
* Copyright 2023 LINE Corporation | ||||||||||||||
* | ||||||||||||||
* LINE Corporation licenses this file to you under the Apache License, | ||||||||||||||
* version 2.0 (the "License"); you may not use this file except in compliance | ||||||||||||||
* with the License. You may obtain a copy of the License at: | ||||||||||||||
* | ||||||||||||||
* https://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||
* | ||||||||||||||
* Unless required by applicable law or agreed to in writing, software | ||||||||||||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT | ||||||||||||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the | ||||||||||||||
* License for the specific language governing permissions and limitations | ||||||||||||||
* under the License. | ||||||||||||||
*/ | ||||||||||||||
|
||||||||||||||
package com.linecorp.armeria.common; | ||||||||||||||
|
||||||||||||||
import static com.google.common.base.Preconditions.checkArgument; | ||||||||||||||
import static com.linecorp.armeria.common.StringMultimap.DEFAULT_SIZE_HINT; | ||||||||||||||
import static java.util.Objects.requireNonNull; | ||||||||||||||
|
||||||||||||||
import java.util.List; | ||||||||||||||
import java.util.Map; | ||||||||||||||
import java.util.function.Supplier; | ||||||||||||||
|
||||||||||||||
import com.google.common.collect.ImmutableList; | ||||||||||||||
|
||||||||||||||
import com.linecorp.armeria.common.annotation.Nullable; | ||||||||||||||
|
||||||||||||||
import io.netty.util.AsciiString; | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* The composite container implementation which wraps {@link HttpHeadersBase}s | ||||||||||||||
* to avoid expensive copy operations. | ||||||||||||||
*/ | ||||||||||||||
class CompositeHttpHeadersBase | ||||||||||||||
extends CompositeStringMultimap</* IN_NAME */ CharSequence, /* NAME */ AsciiString> | ||||||||||||||
implements HttpHeaderGetters { | ||||||||||||||
|
||||||||||||||
private static final Supplier<StringMultimap<CharSequence, AsciiString>> DEFAULT_APPENDER_SUPPLIER = | ||||||||||||||
() -> new HttpHeadersBase(DEFAULT_SIZE_HINT); | ||||||||||||||
|
||||||||||||||
CompositeHttpHeadersBase(HttpHeaderGetters... parents) { | ||||||||||||||
super(from(parents), DEFAULT_APPENDER_SUPPLIER); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
CompositeHttpHeadersBase(@Nullable List<HttpHeaderGetters> additionals, | ||||||||||||||
List<HttpHeaderGetters> parents, | ||||||||||||||
@Nullable List<HttpHeaderGetters> defaults) { | ||||||||||||||
super(additionals != null ? from(additionals) : null, | ||||||||||||||
from(parents), | ||||||||||||||
defaults != null ? from(defaults) : null, | ||||||||||||||
DEFAULT_APPENDER_SUPPLIER); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private static List<StringMultimap<CharSequence, AsciiString>> from(HttpHeaderGetters... headers) { | ||||||||||||||
if (headers.length == 0) { | ||||||||||||||
return ImmutableList.of(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final ImmutableList.Builder<StringMultimap<CharSequence, AsciiString>> builder = | ||||||||||||||
ImmutableList.builder(); | ||||||||||||||
for (HttpHeaderGetters header : headers) { | ||||||||||||||
if (header instanceof HttpHeadersBase) { | ||||||||||||||
builder.add((HttpHeadersBase) header); | ||||||||||||||
} else { | ||||||||||||||
builder.add(new HttpHeadersBase(header)); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return builder.build(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
private static List<StringMultimap<CharSequence, AsciiString>> from(List<HttpHeaderGetters> headers) { | ||||||||||||||
if (headers.isEmpty()) { | ||||||||||||||
return ImmutableList.of(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final ImmutableList.Builder<StringMultimap<CharSequence, AsciiString>> builder = | ||||||||||||||
ImmutableList.builder(); | ||||||||||||||
for (HttpHeaderGetters header : headers) { | ||||||||||||||
if (header instanceof HttpHeadersBase) { | ||||||||||||||
builder.add((HttpHeadersBase) header); | ||||||||||||||
} else { | ||||||||||||||
builder.add(new HttpHeadersBase(header)); | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
return builder.build(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final void contentLength(long contentLength) { | ||||||||||||||
checkArgument(contentLength >= 0, "contentLength: %s (expected: >= 0)", contentLength); | ||||||||||||||
remove0(HttpHeaderNames.CONTENT_LENGTH); | ||||||||||||||
((HttpHeadersBase) appender()).contentLength(contentLength); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public long contentLength() { | ||||||||||||||
return getLong(HttpHeaderNames.CONTENT_LENGTH, -1); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final void contentLengthUnknown() { | ||||||||||||||
remove0(HttpHeaderNames.CONTENT_LENGTH); | ||||||||||||||
((HttpHeadersBase) appender()).contentLengthUnknown(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public boolean isContentLengthUnknown() { | ||||||||||||||
return ((HttpHeaderGetters) appender()).isContentLengthUnknown(); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final void contentType(MediaType contentType) { | ||||||||||||||
requireNonNull(contentType, "contentType"); | ||||||||||||||
remove0(HttpHeaderNames.CONTENT_TYPE); | ||||||||||||||
((HttpHeadersBase) appender()).contentType(contentType); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public @Nullable MediaType contentType() { | ||||||||||||||
final String contentTypeString = get(HttpHeaderNames.CONTENT_TYPE); | ||||||||||||||
if (contentTypeString == null) { | ||||||||||||||
return null; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
try { | ||||||||||||||
return MediaType.parse(contentTypeString); | ||||||||||||||
} catch (IllegalArgumentException ex) { | ||||||||||||||
return null; | ||||||||||||||
Check warning on line 130 in core/src/main/java/com/linecorp/armeria/common/CompositeHttpHeadersBase.java Codecov / codecov/patchcore/src/main/java/com/linecorp/armeria/common/CompositeHttpHeadersBase.java#L129-L130
|
||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final void contentDisposition(ContentDisposition contentDisposition) { | ||||||||||||||
requireNonNull(contentDisposition, "contentDisposition"); | ||||||||||||||
remove0(HttpHeaderNames.CONTENT_DISPOSITION); | ||||||||||||||
((HttpHeadersBase) appender()).contentDisposition(contentDisposition); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public @Nullable ContentDisposition contentDisposition() { | ||||||||||||||
final String contentDispositionString = get(HttpHeaderNames.CONTENT_DISPOSITION); | ||||||||||||||
if (contentDispositionString == null) { | ||||||||||||||
return null; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
try { | ||||||||||||||
return ContentDisposition.parse(contentDispositionString); | ||||||||||||||
} catch (IllegalArgumentException ex) { | ||||||||||||||
return null; | ||||||||||||||
Check warning on line 150 in core/src/main/java/com/linecorp/armeria/common/CompositeHttpHeadersBase.java Codecov / codecov/patchcore/src/main/java/com/linecorp/armeria/common/CompositeHttpHeadersBase.java#L149-L150
|
||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final void endOfStream(boolean endOfStream) { | ||||||||||||||
((HttpHeadersBase) appender()).endOfStream(endOfStream); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public boolean isEndOfStream() { | ||||||||||||||
for (StringMultimapGetters<CharSequence, AsciiString> delegate : delegates()) { | ||||||||||||||
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. Shouldn't we check 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.
As we discussed like above, I think it's not mandatory to always check Cause if one of headers is end-of-stream, we return composite headers' 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.
Nice point! I considered this case and on So we don't have to check appender is null or not when using 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 apologize for my previous comment. It doesn't contain all the information CompositeHttpHeadersBase headers =
new CompositeHttpHeadersBase(additionalHeaders, endOfStreamTrueHeaders);
headers.endOfStream(false);
What I meant was that we need to ensure that the appender is not created if we change the logic to check for the appender first. π |
||||||||||||||
if (((HttpHeaderGetters) delegate).isEndOfStream()) { | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
return false; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public final int hashCode() { | ||||||||||||||
final int hashCode = super.hashCode(); | ||||||||||||||
return isEndOfStream() ? ~hashCode : hashCode; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public boolean equals(@Nullable Object o) { | ||||||||||||||
if (this == o) { | ||||||||||||||
return true; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
if (!(o instanceof HttpHeaderGetters)) { | ||||||||||||||
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. Is this intentional not using 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. // CompositeStingMultimap
if (that instanceof CompositeStringMultimap ||
that instanceof StringMultimap) { // allow StringMultimap to compare too
for (NAME name : names()) {
if (!getAll((IN_NAME) name.toString())
.equals(that.getAll((IN_NAME) name.toString()))) {
return false;
}
} Aha right. on So i intended to just check Hmm should we allow only 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 think it's okay because they are equal if they have the same headers no matter what the type is. armeria/core/src/main/java/com/linecorp/armeria/common/ByteArrayHttpData.java Lines 78 to 83 in 2aa149b
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 got it! I added related test case :) (compare equality with CompositeHeader and just Header) |
||||||||||||||
return false; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
// `contentLengthUnknown` is excluded from the comparison since it is not a field expressing headers | ||||||||||||||
// data. | ||||||||||||||
return isEndOfStream() == ((HttpHeaderGetters) o).isEndOfStream() && super.equals(o); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
@Override | ||||||||||||||
public final String toString() { | ||||||||||||||
final int size = size(); | ||||||||||||||
final boolean isEndOfStream = isEndOfStream(); | ||||||||||||||
if (size == 0) { | ||||||||||||||
return isEndOfStream ? "[EOS]" : "[]"; | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final StringBuilder sb = new StringBuilder(7 + size * 20); | ||||||||||||||
if (isEndOfStream) { | ||||||||||||||
sb.append("[EOS, "); | ||||||||||||||
} else { | ||||||||||||||
sb.append('['); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
for (Map.Entry<AsciiString, String> e : this) { | ||||||||||||||
sb.append(e.getKey()).append('=').append(e.getValue()).append(", "); | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
final int length = sb.length(); | ||||||||||||||
sb.setCharAt(length - 2, ']'); | ||||||||||||||
return sb.substring(0, length - 1); | ||||||||||||||
} | ||||||||||||||
} |
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.
Considering the current usage, which involves three different headers in HttpHeadersUtil, it seems that this constructor isn't necessary. Do you have a specific reason for adding it?
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.
armeria/core/src/main/java/com/linecorp/armeria/internal/common/HttpHeadersUtil.java
Lines 57 to 62 in c6b8906
If we don't have this constructor, how can we pass
additioanls
headers toCompositeStringMultimap
? π€armeria/core/src/main/java/com/linecorp/armeria/common/AbstractHttpHeadersBuilder.java
Lines 32 to 40 in c6b8906
armeria/core/src/main/java/com/linecorp/armeria/common/DefaultHttpHeadersBuilder.java
Lines 24 to 30 in c6b8906
Also, this constructor is needed for next PR, to add
CompositeHttpHeaderBuilder
similar withHttpHeaderBuilder
!It uses
new HttpHeaderBase(..)
constuructor.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 constructor accepts multiple
HttpHeaders
. Can't users simply specify the additional headers that take precedence as the first argument?I might miss this. Could you share an example of how it will be used?