Skip to content

Commit ed985e2

Browse files
committed
Merge branch '3.5.x'
2 parents 768e74a + 076576d commit ed985e2

File tree

13 files changed

+565
-67
lines changed

13 files changed

+565
-67
lines changed

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/Cookie.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ public interface Cookie {
3535
*
3636
* @return 表示 Cookie 版本号的 {@code int}。
3737
*/
38+
@Deprecated
3839
int version();
3940

4041
/**
@@ -43,6 +44,7 @@ public interface Cookie {
4344
*
4445
* @return 表示 Cookie 注释的 {@link String}。
4546
*/
47+
@Deprecated
4648
String comment();
4749

4850
/**
@@ -79,12 +81,20 @@ public interface Cookie {
7981

8082
/**
8183
* 判断 Cookie 是否仅允许在服务端获取。
82-
* <p>该属性并不是 Cookie 的标准,但是被浏览器支持。</p>
84+
* <p>其 HttpOnly 属性的格式为 {@code ;HttpOnly ...},如果存在则表示仅服务端可访问。</p>
8385
*
84-
* @return 如果 Cookie 仅允许在服务端获取,返回 {@code true},否则,返回 {@code false}。
86+
* @return 如果 Cookie 仅允许在服务端访问,则返回 {@code true},否则返回 {@code false}。
8587
*/
8688
boolean httpOnly();
8789

90+
/**
91+
* 获取 Cookie 的 SameSite 属性。
92+
* <p>其 SameSite 属性的格式为 {@code ;SameSite=VALUE ...},表示跨站请求策略。</p>
93+
*
94+
* @return SameSite 值,如 {@code "Strict"}、{@code "Lax"}、{@code "None"}。
95+
*/
96+
String sameSite();
97+
8898
/**
8999
* {@link Cookie} 的构建器。
90100
*/
@@ -107,18 +117,30 @@ interface Builder {
107117

108118
/**
109119
* 向当前构建器中设置 Cookie 的版本。
120+
* <p>
121+
* 此属性源自 <a href="https://datatracker.ietf.org/doc/html/rfc2965">RFC 2965</a>,
122+
* 但已在 <a href="https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2">RFC 6265</a>
123+
* 中移出标准定义。现代浏览器会忽略该属性。
124+
* </p>
110125
*
111126
* @param version 表示待设置的 Cookie 版本的 {@code int}。
112127
* @return 表示当前构建器的 {@link Builder}。
113128
*/
129+
@Deprecated
114130
Builder version(int version);
115131

116132
/**
117133
* 向当前构建器中设置 Cookie 的注释。
134+
* <p>
135+
* 此属性源自 <a href="https://datatracker.ietf.org/doc/html/rfc2965">RFC 2965</a>,
136+
* 但已在 <a href="https://datatracker.ietf.org/doc/html/rfc6265#section-4.1.2">RFC 6265</a>
137+
* 中移出标准定义。现代浏览器会忽略该属性。
138+
* </p>
118139
*
119140
* @param comment 表示待设置的 Cookie 注释的 {@link String}。
120141
* @return 表示当前构建器的 {@link Builder}。
121142
*/
143+
@Deprecated
122144
Builder comment(String comment);
123145

124146
/**
@@ -161,6 +183,19 @@ interface Builder {
161183
*/
162184
Builder httpOnly(boolean httpOnly);
163185

186+
/**
187+
* 向当前构建器中设置 Cookie 限制跨站请求时发送行为安全级别。
188+
* <p>
189+
* 该属性定义于 <a href="https://datatracker.ietf.org/doc/html/draft-ietf-httpbis-rfc6265bis#section-4.1.2.7">
190+
* RFC 6265bis 草案第 4.1.2.7 节</a>,用于控制跨站请求时是否发送 Cookie。
191+
* 尽管该规范尚处于草案阶段,但已被主流浏览器(如 Chrome、Firefox、Safari、Edge)广泛支持。
192+
* </p>
193+
*
194+
* @param sameSite SameSite 值,如 "Strict", "Lax", "None"。
195+
* @return 表示当前构建器的 {@link Builder}。
196+
*/
197+
Builder sameSite(String sameSite);
198+
164199
/**
165200
* 构建对象。
166201
*

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/client/support/DefaultHttpClassicClientRequest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ protected void commit() {
155155
if (this.isCommitted()) {
156156
return;
157157
}
158-
this.headers().set(COOKIE, this.cookies().toString());
158+
this.headers().set(COOKIE, this.cookies().toRequestHeaderValue());
159159
super.commit();
160160
}
161161

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/header/ConfigurableCookieCollection.java

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -31,15 +31,4 @@ public interface ConfigurableCookieCollection extends CookieCollection {
3131
static ConfigurableCookieCollection create() {
3232
return new DefaultCookieCollection();
3333
}
34-
35-
/**
36-
* 根据指定的消息头创建一个可读可写的 Cookie 集合。
37-
*
38-
* @param headerValue 表示指定消息头的 {@link HeaderValue}。
39-
* @return 表示创建出来的可读可写的 Cookie 集合的 {@link ConfigurableCookieCollection}。
40-
* @throws IllegalArgumentException 当 {@code headerValue} 为 {@code null} 时。
41-
*/
42-
static ConfigurableCookieCollection create(HeaderValue headerValue) {
43-
return new DefaultCookieCollection(headerValue);
44-
}
4534
}

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/header/CookieCollection.java

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,26 @@
1717
* @author 季聿阶
1818
* @since 2022-07-06
1919
*/
20-
public interface CookieCollection extends HeaderValue {
20+
public interface CookieCollection {
2121
/**
2222
* 获取指定名字的 {@link Cookie}。
23+
* <p>如果存在多个同名 Cookie,返回第一个匹配的 Cookie。</p>
2324
*
2425
* @param name 表示 Cookie 名字的 {@link Optional}{@code <}{@link String}{@code >}。
2526
* @return 表示指定名字的 {@link Cookie}。
2627
*/
2728
Optional<Cookie> get(String name);
2829

2930
/**
30-
* 获取所有的 {@link Cookie}。
31+
* 根据名字查找所有匹配的 {@link Cookie}。
32+
*
33+
* @param name 表示 Cookie 名字的 {@link String}。
34+
* @return 返回所有匹配名字的 {@link Cookie} 列表。
35+
*/
36+
List<Cookie> all(String name);
37+
38+
/**
39+
* 获取集合中所有的 {@link Cookie}。
3140
*
3241
* @return 表示所有 {@link Cookie} 列表的 {@link List}{@code <}{@link Cookie}{@code >}。
3342
*/
@@ -39,4 +48,20 @@ public interface CookieCollection extends HeaderValue {
3948
* @return 表示所有 {@link Cookie} 的数量的 {@code int}。
4049
*/
4150
int size();
51+
52+
/**
53+
* 将集合转换为 HTTP 请求头中 Cookie 形式的字符串。
54+
* <p>格式为 {@code name1=value1; name2=value2; ...}。</p>
55+
*
56+
* @return 表示请求头的字符串。
57+
*/
58+
String toRequestHeaderValue();
59+
60+
/**
61+
* 将集合转换为 HTTP 响应头形式的字符串列表。
62+
* <p>每个 Cookie 对应一个 {@code Set-Cookie: ...} 头。</p>
63+
*
64+
* @return 表示响应头列表的 {@link List}{@code <}{@link String}{@code >}。
65+
*/
66+
List<String> toResponseHeadersValues();
4267
}

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/server/support/DefaultHttpClassicServerResponse.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
import static modelengine.fit.http.protocol.MessageHeaderNames.CONNECTION;
1111
import static modelengine.fit.http.protocol.MessageHeaderNames.CONTENT_DISPOSITION;
1212
import static modelengine.fit.http.protocol.MessageHeaderNames.CONTENT_LENGTH;
13-
import static modelengine.fit.http.protocol.MessageHeaderNames.COOKIE;
13+
import static modelengine.fit.http.protocol.MessageHeaderNames.SET_COOKIE;
1414
import static modelengine.fit.http.protocol.MessageHeaderNames.TRANSFER_ENCODING;
1515
import static modelengine.fit.http.protocol.MessageHeaderValues.CHUNKED;
1616
import static modelengine.fit.http.protocol.MessageHeaderValues.KEEP_ALIVE;
@@ -255,7 +255,7 @@ protected void commit() {
255255
if (this.isCommitted()) {
256256
return;
257257
}
258-
this.headers().set(COOKIE, this.cookies().toString());
258+
this.headers().set(SET_COOKIE, this.cookies().toResponseHeadersValues());
259259
if (this.entity != null) {
260260
this.setContentTypeByEntity(this.headers(), this.entity);
261261
if (this.entity instanceof FileEntity) {

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/support/AbstractHttpClassicRequest.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
package modelengine.fit.http.support;
88

9+
import static modelengine.fit.http.protocol.MessageHeaderNames.COOKIE;
910
import static modelengine.fit.http.protocol.MessageHeaderNames.HOST;
1011
import static modelengine.fitframework.inspection.Validation.notNull;
1112

@@ -16,6 +17,7 @@
1617
import modelengine.fit.http.protocol.MessageHeaders;
1718
import modelengine.fit.http.protocol.QueryCollection;
1819
import modelengine.fit.http.protocol.RequestLine;
20+
import modelengine.fit.http.util.HttpUtils;
1921

2022
/**
2123
* 表示 {@link HttpClassicRequest} 的抽象实现类。
@@ -24,6 +26,8 @@
2426
* @since 2022-11-23
2527
*/
2628
public abstract class AbstractHttpClassicRequest extends AbstractHttpMessage implements HttpClassicRequest {
29+
private static final String COOKIE_DELIMITER = ";";
30+
2731
private final RequestLine startLine;
2832
private final MessageHeaders headers;
2933

@@ -38,6 +42,8 @@ public AbstractHttpClassicRequest(HttpResource httpResource, RequestLine startLi
3842
super(httpResource, startLine, headers);
3943
this.startLine = notNull(startLine, "The request line cannot be null.");
4044
this.headers = notNull(headers, "The message headers cannot be null.");
45+
String actualCookie = String.join(COOKIE_DELIMITER, this.headers.all(COOKIE));
46+
HttpUtils.parseCookies(actualCookie).forEach(this.cookies()::add);
4147
}
4248

4349
@Override

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/support/AbstractHttpClassicResponse.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,17 @@
66

77
package modelengine.fit.http.support;
88

9+
import static modelengine.fit.http.protocol.MessageHeaderNames.SET_COOKIE;
910
import static modelengine.fitframework.inspection.Validation.notNull;
1011

1112
import modelengine.fit.http.HttpClassicResponse;
1213
import modelengine.fit.http.HttpResource;
1314
import modelengine.fit.http.protocol.MessageHeaders;
1415
import modelengine.fit.http.protocol.RequestLine;
1516
import modelengine.fit.http.protocol.StatusLine;
17+
import modelengine.fit.http.util.HttpUtils;
18+
19+
import java.util.List;
1620

1721
/**
1822
* {@link HttpClassicResponse} 的默认实现。
@@ -33,6 +37,9 @@ public abstract class AbstractHttpClassicResponse extends AbstractHttpMessage im
3337
public AbstractHttpClassicResponse(HttpResource httpResource, StatusLine startLine, MessageHeaders headers) {
3438
super(httpResource, startLine, headers);
3539
this.startLine = notNull(startLine, "The status line cannot be null.");
40+
notNull(headers, "The headers cannot be null.");
41+
List<String> actualCookies = headers.all(SET_COOKIE);
42+
actualCookies.stream().map(HttpUtils::parseSetCookie).forEach(this.cookies()::add);
3643
}
3744

3845
@Override

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/support/AbstractHttpMessage.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
import static modelengine.fit.http.protocol.MessageHeaderNames.CONTENT_LENGTH;
1010
import static modelengine.fit.http.protocol.MessageHeaderNames.CONTENT_TYPE;
11-
import static modelengine.fit.http.protocol.MessageHeaderNames.COOKIE;
1211
import static modelengine.fit.http.protocol.MessageHeaderNames.TRANSFER_ENCODING;
1312
import static modelengine.fit.http.protocol.MessageHeaderValues.CHUNKED;
1413
import static modelengine.fitframework.inspection.Validation.notNull;
@@ -47,8 +46,6 @@
4746
* @since 2022-08-03
4847
*/
4948
public abstract class AbstractHttpMessage implements HttpMessage {
50-
private static final String COOKIE_DELIMITER = ";";
51-
5249
private final ParameterCollection parameters =
5350
ParameterCollection.create().set(DefaultContentType.CHARSET, StandardCharsets.UTF_8.name());
5451
private final HttpResource httpResource;
@@ -70,8 +67,7 @@ protected AbstractHttpMessage(HttpResource httpResource, StartLine startLine, Me
7067
this.httpResource = notNull(httpResource, "The http resource cannot be null.");
7168
this.startLine = notNull(startLine, "The start line cannot be null.");
7269
this.headers = notNull(headers, "The message headers cannot be null.");
73-
String actualCookie = String.join(COOKIE_DELIMITER, this.headers.all(COOKIE));
74-
this.cookies = ConfigurableCookieCollection.create(HttpUtils.parseHeaderValue(actualCookie));
70+
this.cookies = ConfigurableCookieCollection.create();
7571
}
7672

7773
@Override

framework/fit/java/fit-builtin/services/fit-http-classic/definition/src/main/java/modelengine/fit/http/support/DefaultCookieCollection.java

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -6,18 +6,22 @@
66

77
package modelengine.fit.http.support;
88

9-
import static modelengine.fitframework.inspection.Validation.notNull;
9+
import static modelengine.fit.http.util.HttpUtils.COOKIES_FORMAT_SEPARATOR;
10+
import static modelengine.fit.http.util.HttpUtils.COOKIE_PAIR_SEPARATOR;
1011

1112
import modelengine.fit.http.Cookie;
1213
import modelengine.fit.http.header.ConfigurableCookieCollection;
1314
import modelengine.fit.http.header.CookieCollection;
14-
import modelengine.fit.http.header.HeaderValue;
15-
import modelengine.fit.http.header.support.DefaultHeaderValue;
16-
import modelengine.fit.http.header.support.DefaultParameterCollection;
15+
import modelengine.fit.http.util.HttpUtils;
16+
import modelengine.fitframework.util.CollectionUtils;
1717
import modelengine.fitframework.util.StringUtils;
1818

19+
import java.util.ArrayList;
1920
import java.util.Collections;
21+
import java.util.LinkedHashMap;
2022
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Objects;
2125
import java.util.Optional;
2226
import java.util.stream.Collectors;
2327

@@ -27,49 +31,56 @@
2731
* @author 季聿阶
2832
* @since 2022-07-06
2933
*/
30-
public class DefaultCookieCollection extends DefaultHeaderValue implements ConfigurableCookieCollection {
31-
/**
32-
* 初始化 {@link DefaultCookieCollection} 的新实例。
33-
*/
34-
public DefaultCookieCollection() {
35-
super(StringUtils.EMPTY, new DefaultParameterCollection());
36-
}
34+
public class DefaultCookieCollection implements ConfigurableCookieCollection {
35+
private final Map<String, List<Cookie>> store = new LinkedHashMap<>();
3736

38-
/**
39-
* 使用指定的消息头初始化 {@link DefaultCookieCollection} 的新实例。
40-
*
41-
* @param headerValue 表示消息头的 {@link HeaderValue}。
42-
* @throws IllegalArgumentException 当 {@code headerValue} 为 {@code null} 时。
43-
*/
44-
public DefaultCookieCollection(HeaderValue headerValue) {
45-
super(notNull(headerValue, "The header value cannot be null.").value(), headerValue.parameters());
37+
@Override
38+
public Optional<Cookie> get(String name) {
39+
List<Cookie> cookies = this.store.get(name);
40+
if (CollectionUtils.isEmpty(cookies)) {
41+
return Optional.empty();
42+
}
43+
return Optional.of(cookies.get(0));
4644
}
4745

4846
@Override
49-
public Optional<Cookie> get(String name) {
50-
return this.parameters().get(name).map(value -> Cookie.builder().name(name).value(value).build());
47+
public List<Cookie> all(String name) {
48+
return this.store.getOrDefault(name, Collections.emptyList());
5149
}
5250

5351
@Override
5452
public List<Cookie> all() {
55-
return Collections.unmodifiableList(this.parameters()
56-
.keys()
57-
.stream()
58-
.map(this::get)
59-
.filter(Optional::isPresent)
60-
.map(Optional::get)
61-
.collect(Collectors.toList()));
53+
return this.store.values().stream().flatMap(List::stream).collect(Collectors.toList());
6254
}
6355

6456
@Override
6557
public int size() {
66-
return this.parameters().size();
58+
return this.store.values().stream().mapToInt(List::size).sum();
6759
}
6860

6961
@Override
7062
public void add(Cookie cookie) {
71-
if (cookie != null) {
72-
this.parameters().set(cookie.name(), cookie.value());
63+
if (cookie == null || StringUtils.isBlank(cookie.name())) {
64+
return;
65+
}
66+
if (HttpUtils.isInvalidCookiePair(cookie.name(), cookie.value())) {
67+
throw new IllegalArgumentException("Invalid cookie: name or value is not allowed.");
7368
}
69+
this.store.computeIfAbsent(cookie.name(), k -> new ArrayList<>());
70+
List<Cookie> list = this.store.get(cookie.name());
71+
list.removeIf(c -> Objects.equals(c.path(), cookie.path()) && Objects.equals(c.domain(), cookie.domain()));
72+
list.add(cookie);
73+
}
74+
75+
@Override
76+
public String toRequestHeaderValue() {
77+
return all().stream()
78+
.map(c -> c.name() + COOKIE_PAIR_SEPARATOR + c.value())
79+
.collect(Collectors.joining(COOKIES_FORMAT_SEPARATOR));
80+
}
81+
82+
@Override
83+
public List<String> toResponseHeadersValues() {
84+
return all().stream().map(HttpUtils::formatSetCookie).collect(Collectors.toList());
7485
}
7586
}

0 commit comments

Comments
 (0)