[netty5: HttpHeaders & HttpHeadersFactory]-源码分析

发布于:2025-07-05 ⋅ 阅读:(17) ⋅ 点赞:(0)

HttpHeaders

HttpHeaders 是用于存储和操作HTTP请求或响应头部字段的接口。

// DefaultHttpHeaders, HttpHeadersFactory.TrailingHttpHeaders 
public interface HttpHeaders extends Iterable<Entry<CharSequence, CharSequence>> {

    static HttpHeaders emptyHeaders() {
        return newHeaders(2, true, true, true);
    }

    static HttpHeaders newHeaders() {
        return newHeaders(true);
    }

    static HttpHeaders newHeaders(boolean validate) {
        return newHeaders(16, validate, validate, validate);
    }

    static HttpHeaders newHeaders(int sizeHint, boolean checkNames, boolean checkCookies, boolean checkValues) {
        return new DefaultHttpHeaders(sizeHint, checkNames, checkCookies, checkValues);
    }

    HttpHeaders copy();

    @Nullable
    CharSequence get(CharSequence name);

    default CharSequence get(final CharSequence name, final CharSequence defaultValue) {
        final CharSequence value = get(name);
        return value != null ? value : defaultValue;
    }
    
    @Nullable
    CharSequence getAndRemove(CharSequence name);

    default CharSequence getAndRemove(final CharSequence name, final CharSequence defaultValue) {
        final CharSequence value = getAndRemove(name);
        return value == null ? defaultValue : value;
    }

    Iterator<CharSequence> valuesIterator(CharSequence name);

    default Iterable<CharSequence> values(CharSequence name) {
        return () -> (Iterator<CharSequence>) valuesIterator(name);
    }
    
    default boolean contains(final CharSequence name) {
        return get(name) != null;
    }
    
    default boolean contains(CharSequence name, CharSequence value) {
        return AsciiString.contentEquals(get(name), value);
    }
    
    default boolean containsIgnoreCase(CharSequence name, CharSequence value) {
        return AsciiString.contentEqualsIgnoreCase(get(name), value);
    }

    int size();

    default boolean isEmpty() {
        return size() == 0;
    }
    
    Set<CharSequence> names();
    
    HttpHeaders add(CharSequence name, CharSequence value);

    HttpHeaders add(CharSequence name, Iterable<? extends CharSequence> values);
    
    default HttpHeaders add(CharSequence name, Iterator<? extends CharSequence> valuesItr) {
        while (valuesItr.hasNext()) {
            add(name, valuesItr.next());
        }
        return this;
    }

    HttpHeaders add(CharSequence name, CharSequence... values);
    
    HttpHeaders add(HttpHeaders headers);
    
    HttpHeaders set(CharSequence name, CharSequence value);
    
    HttpHeaders set(CharSequence name, Iterable<? extends CharSequence> values);

    default HttpHeaders set(CharSequence name, Iterator<? extends CharSequence> valueItr) {
        remove(name);
        while (valueItr.hasNext()) {
            add(name, valueItr.next());
        }
        return this;
    }

    default HttpHeaders set(CharSequence name, CharSequence... values) {
        remove(name);
        for (CharSequence value : values) {
            add(name, value);
        }
        return this;
    }

    default HttpHeaders set(final HttpHeaders headers) {
        if (headers != this) {
            clear();
            add(headers);
        }
        return this;
    }


    default HttpHeaders replace(final HttpHeaders headers) {
        if (headers != this) {
            for (final CharSequence key : headers.names()) {
                remove(key);
            }
            add(headers);
        }
        return this;
    }

    boolean remove(CharSequence name);
    
    boolean remove(CharSequence name, CharSequence value);

    boolean removeIgnoreCase(CharSequence name, CharSequence value);

    HttpHeaders clear();

    @Override
    Iterator<Entry<CharSequence, CharSequence>> iterator();

    @Override
    default Spliterator<Entry<CharSequence, CharSequence>> spliterator() {
        return Spliterators.spliterator(iterator(), size(), Spliterator.SIZED);
    }

    @Override
    String toString();

    default String toString(BiFunction<? super CharSequence, ? super CharSequence, CharSequence> filter) {
        return HeaderUtils.toString(this, filter);
    }
    
    @Nullable
    HttpCookiePair getCookie(CharSequence name);
    
    @Nullable
    HttpSetCookie getSetCookie(CharSequence name);

    default Iterable<HttpCookiePair> getCookies() {
        return () -> (Iterator<HttpCookiePair>) getCookiesIterator();
    }

    Iterator<HttpCookiePair> getCookiesIterator();
    
    default Iterable<HttpCookiePair> getCookies(CharSequence name) {
        return () -> (Iterator<HttpCookiePair>) getCookiesIterator(name);
    }
    
    Iterator<HttpCookiePair> getCookiesIterator(CharSequence name);

    default Iterable<HttpSetCookie> getSetCookies() {
        return () -> (Iterator<HttpSetCookie>) getSetCookiesIterator();
    }
    
    Iterator<HttpSetCookie> getSetCookiesIterator();

    default Iterable<HttpSetCookie> getSetCookies(CharSequence name) {
        return () -> (Iterator<HttpSetCookie>) getSetCookiesIterator(name);
    }

    Iterator<HttpSetCookie> getSetCookiesIterator(CharSequence name);
    
    default Iterable<HttpSetCookie> getSetCookies(CharSequence name, CharSequence domain, CharSequence path) {
        return () -> (Iterator<HttpSetCookie>) getSetCookiesIterator(name, domain, path);
    }

    Iterator<HttpSetCookie> getSetCookiesIterator(CharSequence name, CharSequence domain, CharSequence path);

    HttpHeaders addCookie(HttpCookiePair cookie);
    
    default HttpHeaders addCookie(final CharSequence name, final CharSequence value) {
        return addCookie(new DefaultHttpCookiePair(name, value));
    }

    HttpHeaders addSetCookie(HttpSetCookie cookie);
    
    default HttpHeaders addSetCookie(final CharSequence name, final CharSequence value) {
        return addSetCookie(new DefaultHttpSetCookie(name, value));
    }

    boolean removeCookies(CharSequence name);

    boolean removeSetCookies(CharSequence name);

    boolean removeSetCookies(CharSequence name, CharSequence domain, CharSequence path);
}

HttpCookiePair

HttpCookiePair 接口定义了 HTTP Cookie 键值对的结构,包括名称、值、是否被双引号包裹及其编码表示, 格式如下:

  1. <cookie-name>=<cookie-value>
  2. <cookie-name>=“<cookie-value>”
// DefaultHttpCookiePair
public interface HttpCookiePair {
    CharSequence name();

    CharSequence value();
    
    boolean isWrapped();

    CharSequence encodedCookie();
}

HttpSetCookie

HttpSetCookie 接口扩展了 HttpCookiePair,表示一个完整的 Set-Cookie,包含域、路径、生命周期、安全属性、SameSite 策略等信息,支持编码与过期计算。

Set-Cookie HTTP 头的格式大致如下:

Set-Cookie: <cookie-name>=<cookie-value>; Expires=<date>; Max-Age=<seconds>; Domain=<domain>; Path=<path>; Secure; HttpOnly; SameSite=<Lax|Strict|None>; Partitioned
字段名 是否必填 值类型 说明
name=value 字符串 Cookie 的名称和值,必须出现在首位。value 可包含特殊字符,建议进行 URL 编码。
Expires=<date> GMT 日期字符串 设定 Cookie 的过期时间,格式如 Wed, 09 Jun 2027 10:18:14 GMT。过期即被删除。
Max-Age=<秒数> 整数(秒) 设置 Cookie 的生存时间(从当前起多少秒),优先级高于 Expires
Domain=<domain> 字符串 指定 Cookie 可被哪些域访问,默认是当前域,不带子域;设置 .example.com 可包括子域。
Path=<path> 字符串 指定 Cookie 生效的路径,默认是当前路径及其子路径。常用 / 表示全站有效。
Secure 无值 表示仅在 HTTPS 连接中发送该 Cookie,保障传输安全。
HttpOnly 无值 禁止通过 JavaScript 访问该 Cookie,可防范 XSS 攻击。
SameSite Lax | Strict | None 控制是否允许跨站点请求携带该 Cookie。
Strict: 严格禁止跨站请求
Lax: 允许部分(如 GET 跳转)
None: 允许全部,需配合 Secure
Partitioned 无值 指示该 Cookie 为分区 Cookie(Partitioned Cookie)。必须与 SecureSameSite=None 一起使用,仅部分浏览器支持。
// DefaultHttpSetCookie
public interface HttpSetCookie extends HttpCookiePair {

    @Nullable
    CharSequence domain();
    
    @Nullable
    CharSequence path();
    
    @Nullable
    Long maxAge();

    @Nullable
    CharSequence expires();

    @Nullable
    default Long expiresAsMaxAge() {
        CharSequence expires = expires();
        if (expires != null) {
            Date expiresDate = DateFormatter.parseHttpDate(expires);
            if (expiresDate != null) {
                long maxAgeMillis = expiresDate.getTime() - System.currentTimeMillis();
                return maxAgeMillis / 1000 + (maxAgeMillis % 1000 != 0 ? 1 : 0);
            }
        }
        return null;
    }

    @Nullable
    SameSite sameSite();

    boolean isSecure();

    boolean isHttpOnly();

    CharSequence encodedSetCookie();

    enum SameSite {
        Lax, Strict, None
    }

    boolean isPartitioned();
}

HttpHeadersFactory

HttpHeadersFactory 是用于创建和配置 HTTP 头部对象及其校验行为的工厂接口。

// DefaultHttpHeadersFactory
public interface HttpHeadersFactory {

    HttpHeaders newHeaders();
    
    HttpHeaders newEmptyHeaders();

    int getSizeHint();

    boolean isValidatingNames();

    boolean isValidatingValues();

    boolean isValidatingCookies();
}

public final class DefaultHttpHeadersFactory implements HttpHeadersFactory {
    private static final int SIZE_HINT = 16;
    
    private static final DefaultHttpHeadersFactory FOR_HEADER =
            new DefaultHttpHeadersFactory(SIZE_HINT, true, true, true, false);
            
    private static final DefaultHttpHeadersFactory FOR_TRAILER =
            new DefaultHttpHeadersFactory(SIZE_HINT, true, true, true, true);
    private static final int MIN_SIZE_HINT = 2;

	// 用于提示内部数据结构(通常是哈希表)应该多大。
    private final int sizeHint;
    // 是否开启 HTTP 头名称的合法性校验(比如是否符合 RFC 规定的字符集等)。
    private final boolean validateNames;
    // 是否开启 HTTP 头的值的合法性校验。
    private final boolean validateValues;
    // 是否在解析 Cookie 时对 Cookie 内容做合法性校验(如格式、字符等)。
    private final boolean validateCookies;
    // 是否以“尾部头”(trailer header)专用的规则进行名称校验。
    private final boolean validateAsTrailer;

    private DefaultHttpHeadersFactory(int sizeHint, boolean validateNames, boolean validateValues, boolean validateCookies, boolean validateAsTrailer) {
        this.sizeHint = Math.max(MIN_SIZE_HINT, sizeHint); 
        this.validateNames = validateNames;
        this.validateValues = validateValues;
        this.validateCookies = validateCookies;
        this.validateAsTrailer = validateAsTrailer;
    }

    public static DefaultHttpHeadersFactory headersFactory() {
        return FOR_HEADER;
    }

    public static DefaultHttpHeadersFactory trailersFactory() {
        return FOR_TRAILER;
    }

    @Override
    public HttpHeaders newHeaders() {
        if (validateAsTrailer) {
            return new TrailingHttpHeaders(sizeHint, validateNames, validateCookies, validateValues);
        }
        return HttpHeaders.newHeaders(sizeHint, validateNames, validateCookies, validateValues);
    }

    @Override
    public HttpHeaders newEmptyHeaders() {
        if (validateAsTrailer) {
            return new TrailingHttpHeaders(MIN_SIZE_HINT, validateNames, validateCookies, validateValues);
        }
        return HttpHeaders.newHeaders(MIN_SIZE_HINT, validateNames, validateCookies, validateValues);
    }
	
	// ...

    private static final class TrailingHttpHeaders extends DefaultHttpHeaders {
        TrailingHttpHeaders(int arraySizeHint, boolean validateNames, boolean validateCookies, boolean validateValues) {
            super(arraySizeHint, validateNames, validateCookies, validateValues);
        }

		// 该类用于处理 HTTP 的 trailing headers —— 即 chunked 编码中的最后一块 trailer 部分。
		// RFC 7230 明确禁止 trailing headers 不能包含某些首部字段,如:Content-Length, Transfer-Encoding, Trailer
        @Override
        protected CharSequence validateKey(@Nullable CharSequence name, boolean forAdd) {
            if (HttpHeaderNames.CONTENT_LENGTH.contentEqualsIgnoreCase(name)
                    || HttpHeaderNames.TRANSFER_ENCODING.contentEqualsIgnoreCase(name)
                    || HttpHeaderNames.TRAILER.contentEqualsIgnoreCase(name)) {
                throw new IllegalArgumentException("Prohibited trailing header: " + name);
            }
            return super.validateKey(name, forAdd);
        }
    }
}