Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
爱吃血肠
spring-framework
提交
14e2c680
S
spring-framework
项目概览
爱吃血肠
/
spring-framework
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
S
spring-framework
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
前往新版Gitcode,体验更适合开发者的 AI 搜索 >>
提交
14e2c680
编写于
6月 12, 2019
作者:
R
Rossen Stoyanchev
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Support for RSocket composite metadata
Closes gh-22798
上级
9fb973d4
变更
9
隐藏空白更改
内联
并排
Showing
9 changed file
with
352 addition
and
112 deletion
+352
-112
spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java
...gframework/messaging/rsocket/DefaultRSocketRequester.java
+126
-16
spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterBuilder.java
...ork/messaging/rsocket/DefaultRSocketRequesterBuilder.java
+15
-3
spring-messaging/src/main/java/org/springframework/messaging/rsocket/MessageHandlerAcceptor.java
...ngframework/messaging/rsocket/MessageHandlerAcceptor.java
+35
-11
spring-messaging/src/main/java/org/springframework/messaging/rsocket/MessagingRSocket.java
...g/springframework/messaging/rsocket/MessagingRSocket.java
+29
-14
spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
...g/springframework/messaging/rsocket/RSocketRequester.java
+64
-34
spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java
...ework/messaging/rsocket/DefaultRSocketRequesterTests.java
+69
-32
spring-messaging/src/test/java/org/springframework/messaging/rsocket/RSocketClientToServerIntegrationTests.java
...saging/rsocket/RSocketClientToServerIntegrationTests.java
+3
-1
spring-messaging/src/test/java/org/springframework/messaging/rsocket/RSocketServerToClientIntegrationTests.java
...saging/rsocket/RSocketServerToClientIntegrationTests.java
+2
-1
spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java
...ava/org/springframework/http/ContentDispositionTests.java
+9
-0
未找到文件。
spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequester.java
浏览文件 @
14e2c680
...
...
@@ -16,12 +16,19 @@
package
org.springframework.messaging.rsocket
;
import
java.
nio.charset.StandardCharset
s
;
import
java.
util.Array
s
;
import
java.util.Collections
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
import
io.netty.buffer.ByteBuf
;
import
io.netty.buffer.ByteBufAllocator
;
import
io.netty.buffer.CompositeByteBuf
;
import
io.netty.buffer.Unpooled
;
import
io.rsocket.Payload
;
import
io.rsocket.RSocket
;
import
io.rsocket.metadata.CompositeMetadataFlyweight
;
import
org.reactivestreams.Publisher
;
import
reactor.core.publisher.Flux
;
import
reactor.core.publisher.Mono
;
...
...
@@ -32,6 +39,10 @@ import org.springframework.core.ResolvableType;
import
org.springframework.core.codec.Decoder
;
import
org.springframework.core.codec.Encoder
;
import
org.springframework.core.io.buffer.DataBuffer
;
import
org.springframework.core.io.buffer.DataBufferFactory
;
import
org.springframework.core.io.buffer.DataBufferUtils
;
import
org.springframework.core.io.buffer.NettyDataBuffer
;
import
org.springframework.core.io.buffer.NettyDataBufferFactory
;
import
org.springframework.lang.Nullable
;
import
org.springframework.util.Assert
;
import
org.springframework.util.MimeType
;
...
...
@@ -44,24 +55,42 @@ import org.springframework.util.MimeType;
*/
final
class
DefaultRSocketRequester
implements
RSocketRequester
{
static
final
MimeType
COMPOSITE_METADATA
=
new
MimeType
(
"message"
,
"x.rsocket.composite-metadata.v0"
);
static
final
MimeType
ROUTING
=
new
MimeType
(
"message"
,
"x.rsocket.routing.v0"
);
static
final
List
<
MimeType
>
METADATA_MIME_TYPES
=
Arrays
.
asList
(
COMPOSITE_METADATA
,
ROUTING
);
private
static
final
Map
<
String
,
Object
>
EMPTY_HINTS
=
Collections
.
emptyMap
();
private
final
RSocket
rsocket
;
@Nullable
private
final
MimeType
dataMimeType
;
private
final
MimeType
metadataMimeType
;
private
final
RSocketStrategies
strategies
;
private
DataBuffer
emptyDataBuffer
;
private
final
DataBuffer
emptyDataBuffer
;
DefaultRSocketRequester
(
RSocket
rsocket
,
@Nullable
MimeType
dataMimeType
,
RSocketStrategies
strategies
)
{
DefaultRSocketRequester
(
RSocket
rsocket
,
MimeType
dataMimeType
,
MimeType
metadataMimeType
,
RSocketStrategies
strategies
)
{
Assert
.
notNull
(
rsocket
,
"RSocket is required"
);
Assert
.
notNull
(
dataMimeType
,
"'dataMimeType' is required"
);
Assert
.
notNull
(
metadataMimeType
,
"'metadataMimeType' is required"
);
Assert
.
notNull
(
strategies
,
"RSocketStrategies is required"
);
Assert
.
isTrue
(
METADATA_MIME_TYPES
.
contains
(
metadataMimeType
),
()
->
"Unexpected metadatata mime type: '"
+
metadataMimeType
+
"'"
);
this
.
rsocket
=
rsocket
;
this
.
dataMimeType
=
dataMimeType
;
this
.
metadataMimeType
=
metadataMimeType
;
this
.
strategies
=
strategies
;
this
.
emptyDataBuffer
=
this
.
strategies
.
dataBufferFactory
().
wrap
(
new
byte
[
0
]);
}
...
...
@@ -72,6 +101,16 @@ final class DefaultRSocketRequester implements RSocketRequester {
return
this
.
rsocket
;
}
@Override
public
MimeType
dataMimeType
()
{
return
this
.
dataMimeType
;
}
@Override
public
MimeType
metadataMimeType
()
{
return
this
.
metadataMimeType
;
}
@Override
public
RequestSpec
route
(
String
route
)
{
return
new
DefaultRequestSpec
(
route
);
...
...
@@ -82,13 +121,28 @@ final class DefaultRSocketRequester implements RSocketRequester {
return
(
Void
.
class
.
equals
(
elementType
.
resolve
())
||
void
.
class
.
equals
(
elementType
.
resolve
()));
}
private
DataBufferFactory
bufferFactory
()
{
return
this
.
strategies
.
dataBufferFactory
();
}
private
class
DefaultRequestSpec
implements
RequestSpec
{
private
final
String
route
;
private
final
Map
<
Object
,
MimeType
>
metadata
=
new
LinkedHashMap
<>(
4
);
public
DefaultRequestSpec
(
String
route
)
{
Assert
.
notNull
(
route
,
"'route' is required"
);
metadata
(
route
,
ROUTING
);
}
DefaultRequestSpec
(
String
route
)
{
this
.
route
=
route
;
@Override
public
RequestSpec
metadata
(
Object
metadata
,
MimeType
mimeType
)
{
Assert
.
isTrue
(
this
.
metadata
.
isEmpty
()
||
metadataMimeType
().
equals
(
COMPOSITE_METADATA
),
"Additional metadata entries supported only with composite metadata"
);
this
.
metadata
.
put
(
metadata
,
mimeType
);
return
this
;
}
@Override
...
...
@@ -122,7 +176,7 @@ final class DefaultRSocketRequester implements RSocketRequester {
}
else
{
Mono
<
Payload
>
payloadMono
=
Mono
.
fromCallable
(()
->
encode
Value
(
input
,
ResolvableType
.
forInstance
(
input
),
null
))
.
fromCallable
(()
->
encode
Data
(
input
,
ResolvableType
.
forInstance
(
input
),
null
))
.
map
(
this
::
firstPayload
)
.
doOnDiscard
(
Payload
.
class
,
Payload:
:
release
)
.
switchIfEmpty
(
emptyPayload
());
...
...
@@ -139,14 +193,14 @@ final class DefaultRSocketRequester implements RSocketRequester {
if
(
adapter
!=
null
&&
!
adapter
.
isMultiValue
())
{
Mono
<
Payload
>
payloadMono
=
Mono
.
from
(
publisher
)
.
map
(
value
->
encode
Value
(
value
,
dataType
,
encoder
))
.
map
(
value
->
encode
Data
(
value
,
dataType
,
encoder
))
.
map
(
this
::
firstPayload
)
.
switchIfEmpty
(
emptyPayload
());
return
new
DefaultResponseSpec
(
payloadMono
);
}
Flux
<
Payload
>
payloadFlux
=
Flux
.
from
(
publisher
)
.
map
(
value
->
encode
Value
(
value
,
dataType
,
encoder
))
.
map
(
value
->
encode
Data
(
value
,
dataType
,
encoder
))
.
switchOnFirst
((
signal
,
inner
)
->
{
DataBuffer
data
=
signal
.
get
();
if
(
data
!=
null
)
{
...
...
@@ -163,16 +217,28 @@ final class DefaultRSocketRequester implements RSocketRequester {
}
@SuppressWarnings
(
"unchecked"
)
private
<
T
>
DataBuffer
encodeValue
(
T
value
,
ResolvableType
valueType
,
@Nullable
Encoder
<?>
encoder
)
{
private
<
T
>
DataBuffer
encodeData
(
T
value
,
ResolvableType
valueType
,
@Nullable
Encoder
<?>
encoder
)
{
if
(
value
instanceof
DataBuffer
)
{
return
(
DataBuffer
)
value
;
}
if
(
encoder
==
null
)
{
encoder
=
strategies
.
encoder
(
ResolvableType
.
forInstance
(
value
),
dataMimeType
);
valueType
=
ResolvableType
.
forInstance
(
value
);
encoder
=
strategies
.
encoder
(
valueType
,
dataMimeType
);
}
return
((
Encoder
<
T
>)
encoder
).
encodeValue
(
value
,
strategies
.
dataB
ufferFactory
(),
valueType
,
dataMimeType
,
EMPTY_HINTS
);
value
,
b
ufferFactory
(),
valueType
,
dataMimeType
,
EMPTY_HINTS
);
}
private
Payload
firstPayload
(
DataBuffer
data
)
{
return
PayloadUtils
.
createPayload
(
getMetadata
(),
data
);
DataBuffer
metadata
;
try
{
metadata
=
getMetadata
();
return
PayloadUtils
.
createPayload
(
metadata
,
data
);
}
catch
(
Throwable
ex
)
{
DataBufferUtils
.
release
(
data
);
throw
ex
;
}
}
private
Mono
<
Payload
>
emptyPayload
()
{
...
...
@@ -180,7 +246,51 @@ final class DefaultRSocketRequester implements RSocketRequester {
}
private
DataBuffer
getMetadata
()
{
return
strategies
.
dataBufferFactory
().
wrap
(
this
.
route
.
getBytes
(
StandardCharsets
.
UTF_8
));
if
(
metadataMimeType
().
equals
(
COMPOSITE_METADATA
))
{
CompositeByteBuf
metadata
=
getAllocator
().
compositeBuffer
();
this
.
metadata
.
forEach
((
key
,
value
)
->
{
DataBuffer
dataBuffer
=
encodeMetadata
(
key
,
value
);
CompositeMetadataFlyweight
.
encodeAndAddMetadata
(
metadata
,
getAllocator
(),
value
.
toString
(),
dataBuffer
instanceof
NettyDataBuffer
?
((
NettyDataBuffer
)
dataBuffer
).
getNativeBuffer
()
:
Unpooled
.
wrappedBuffer
(
dataBuffer
.
asByteBuffer
()));
});
return
asDataBuffer
(
metadata
);
}
Assert
.
isTrue
(
this
.
metadata
.
size
()
<
2
,
"Composite metadata required for multiple entries"
);
Map
.
Entry
<
Object
,
MimeType
>
entry
=
this
.
metadata
.
entrySet
().
iterator
().
next
();
Assert
.
isTrue
(
metadataMimeType
().
equals
(
entry
.
getValue
()),
()
->
"Expected metadata MimeType '"
+
metadataMimeType
()
+
"', actual "
+
this
.
metadata
);
return
encodeMetadata
(
entry
.
getKey
(),
entry
.
getValue
());
}
@SuppressWarnings
(
"unchecked"
)
private
<
T
>
DataBuffer
encodeMetadata
(
Object
metadata
,
MimeType
mimeType
)
{
if
(
metadata
instanceof
DataBuffer
)
{
return
(
DataBuffer
)
metadata
;
}
ResolvableType
type
=
ResolvableType
.
forInstance
(
metadata
);
Encoder
<
T
>
encoder
=
strategies
.
encoder
(
type
,
mimeType
);
Assert
.
notNull
(
encoder
,
()
->
"No encoder for metadata "
+
metadata
+
", mimeType '"
+
mimeType
+
"'"
);
return
encoder
.
encodeValue
((
T
)
metadata
,
bufferFactory
(),
type
,
mimeType
,
EMPTY_HINTS
);
}
private
ByteBufAllocator
getAllocator
()
{
return
bufferFactory
()
instanceof
NettyDataBufferFactory
?
((
NettyDataBufferFactory
)
bufferFactory
()).
getByteBufAllocator
()
:
ByteBufAllocator
.
DEFAULT
;
}
private
DataBuffer
asDataBuffer
(
ByteBuf
byteBuf
)
{
if
(
bufferFactory
()
instanceof
NettyDataBufferFactory
)
{
return
((
NettyDataBufferFactory
)
bufferFactory
()).
wrap
(
byteBuf
);
}
else
{
DataBuffer
dataBuffer
=
bufferFactory
().
wrap
(
byteBuf
.
nioBuffer
());
byteBuf
.
release
();
return
dataBuffer
;
}
}
}
...
...
@@ -259,7 +369,7 @@ final class DefaultRSocketRequester implements RSocketRequester {
}
private
DataBuffer
retainDataAndReleasePayload
(
Payload
payload
)
{
return
PayloadUtils
.
retainDataAndReleasePayload
(
payload
,
strategies
.
dataB
ufferFactory
());
return
PayloadUtils
.
retainDataAndReleasePayload
(
payload
,
b
ufferFactory
());
}
}
...
...
spring-messaging/src/main/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterBuilder.java
浏览文件 @
14e2c680
...
...
@@ -44,6 +44,8 @@ final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder {
@Nullable
private
MimeType
dataMimeType
;
private
MimeType
metadataMimeType
=
DefaultRSocketRequester
.
COMPOSITE_METADATA
;
private
List
<
Consumer
<
RSocketFactory
.
ClientRSocketFactory
>>
factoryConfigurers
=
new
ArrayList
<>();
@Nullable
...
...
@@ -53,11 +55,18 @@ final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder {
@Override
public
RSocketRequester
.
Builder
dataMimeType
(
MimeType
mimeType
)
{
public
RSocketRequester
.
Builder
dataMimeType
(
@Nullable
MimeType
mimeType
)
{
this
.
dataMimeType
=
mimeType
;
return
this
;
}
@Override
public
RSocketRequester
.
Builder
metadataMimeType
(
MimeType
mimeType
)
{
Assert
.
notNull
(
mimeType
,
"`metadataMimeType` is required"
);
this
.
metadataMimeType
=
mimeType
;
return
this
;
}
@Override
public
RSocketRequester
.
Builder
rsocketFactory
(
Consumer
<
RSocketFactory
.
ClientRSocketFactory
>
configurer
)
{
this
.
factoryConfigurers
.
add
(
configurer
);
...
...
@@ -100,10 +109,13 @@ final class DefaultRSocketRequesterBuilder implements RSocketRequester.Builder {
RSocketFactory
.
ClientRSocketFactory
rsocketFactory
=
RSocketFactory
.
connect
();
MimeType
dataMimeType
=
getDataMimeType
(
rsocketStrategies
);
rsocketFactory
.
dataMimeType
(
dataMimeType
.
toString
());
rsocketFactory
.
metadataMimeType
(
this
.
metadataMimeType
.
toString
());
this
.
factoryConfigurers
.
forEach
(
consumer
->
consumer
.
accept
(
rsocketFactory
));
return
rsocketFactory
.
transport
(
transport
).
start
()
.
map
(
rsocket
->
new
DefaultRSocketRequester
(
rsocket
,
dataMimeType
,
rsocketStrategies
));
return
rsocketFactory
.
transport
(
transport
)
.
start
()
.
map
(
rsocket
->
new
DefaultRSocketRequester
(
rsocket
,
dataMimeType
,
this
.
metadataMimeType
,
rsocketStrategies
));
}
private
RSocketStrategies
getRSocketStrategies
()
{
...
...
spring-messaging/src/main/java/org/springframework/messaging/rsocket/MessageHandlerAcceptor.java
浏览文件 @
14e2c680
...
...
@@ -24,9 +24,9 @@ import io.rsocket.RSocket;
import
io.rsocket.SocketAcceptor
;
import
reactor.core.publisher.Mono
;
import
org.springframework.core.io.buffer.DataBufferFactory
;
import
org.springframework.lang.Nullable
;
import
org.springframework.messaging.Message
;
import
org.springframework.util.Assert
;
import
org.springframework.util.MimeType
;
import
org.springframework.util.MimeTypeUtils
;
import
org.springframework.util.StringUtils
;
...
...
@@ -47,16 +47,28 @@ public final class MessageHandlerAcceptor extends RSocketMessageHandler
@Nullable
private
MimeType
defaultDataMimeType
;
private
MimeType
defaultMetadataMimeType
=
DefaultRSocketRequester
.
COMPOSITE_METADATA
;
/**
* Configure the default content type to use for data payloads if the
* {@code SETUP} frame did not specify one.
* <p>By default this is not set.
* @param mimeType the MimeType to use
*/
public
void
setDefaultDataMimeType
(
@Nullable
MimeType
mimeType
)
{
this
.
defaultDataMimeType
=
mimeType
;
}
/**
* Configure the default content type to use for data payloads.
* <p>By default this is not set. However a server acceptor will use the
* content type from the {@link ConnectionSetupPayload}, so this is typically
* required for clients but can also be used on servers as a fallback.
* @param defaultDataMimeType the MimeType to use
* Configure the default {@code MimeType} for payload data if the
* {@code SETUP} frame did not specify one.
* <p>By default this is set to {@code "message/x.rsocket.composite-metadata.v0"}
* @param mimeType the MimeType to use
*/
public
void
setDefaultDataMimeType
(
@Nullable
MimeType
defaultDataMimeType
)
{
this
.
defaultDataMimeType
=
defaultDataMimeType
;
public
void
setDefaultMetadataMimeType
(
MimeType
mimeType
)
{
Assert
.
notNull
(
mimeType
,
"'metadataMimeType' is required"
);
this
.
defaultMetadataMimeType
=
mimeType
;
}
...
...
@@ -76,12 +88,24 @@ public final class MessageHandlerAcceptor extends RSocketMessageHandler
}
private
MessagingRSocket
createRSocket
(
ConnectionSetupPayload
setupPayload
,
RSocket
rsocket
)
{
MimeType
dataMimeType
=
StringUtils
.
hasText
(
setupPayload
.
dataMimeType
())
?
MimeTypeUtils
.
parseMimeType
(
setupPayload
.
dataMimeType
())
:
this
.
defaultDataMimeType
;
RSocketRequester
requester
=
RSocketRequester
.
wrap
(
rsocket
,
dataMimeType
,
getRSocketStrategies
());
DataBufferFactory
bufferFactory
=
getRSocketStrategies
().
dataBufferFactory
();
return
new
MessagingRSocket
(
this
,
getRouteMatcher
(),
requester
,
dataMimeType
,
bufferFactory
);
Assert
.
notNull
(
dataMimeType
,
"No `dataMimeType` in the ConnectionSetupPayload and no default value"
);
MimeType
metadataMimeType
=
StringUtils
.
hasText
(
setupPayload
.
dataMimeType
())
?
MimeTypeUtils
.
parseMimeType
(
setupPayload
.
metadataMimeType
())
:
this
.
defaultMetadataMimeType
;
Assert
.
notNull
(
dataMimeType
,
"No `metadataMimeType` in the ConnectionSetupPayload and no default value"
);
RSocketRequester
requester
=
RSocketRequester
.
wrap
(
rsocket
,
dataMimeType
,
metadataMimeType
,
getRSocketStrategies
());
return
new
MessagingRSocket
(
this
,
getRouteMatcher
(),
requester
,
dataMimeType
,
metadataMimeType
,
getRSocketStrategies
().
dataBufferFactory
());
}
}
spring-messaging/src/main/java/org/springframework/messaging/rsocket/MessagingRSocket.java
浏览文件 @
14e2c680
...
...
@@ -16,6 +16,7 @@
package
org.springframework.messaging.rsocket
;
import
java.nio.charset.StandardCharsets
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.function.Function
;
...
...
@@ -23,6 +24,7 @@ import io.rsocket.AbstractRSocket;
import
io.rsocket.ConnectionSetupPayload
;
import
io.rsocket.Payload
;
import
io.rsocket.RSocket
;
import
io.rsocket.metadata.CompositeMetadata
;
import
org.reactivestreams.Publisher
;
import
reactor.core.publisher.Flux
;
import
reactor.core.publisher.Mono
;
...
...
@@ -61,24 +63,31 @@ class MessagingRSocket extends AbstractRSocket {
private
final
RSocketRequester
requester
;
@Nullable
private
MimeType
dataMimeType
;
private
final
MimeType
dataMimeType
;
private
final
MimeType
metadataMimeType
;
private
final
DataBufferFactory
bufferFactory
;
MessagingRSocket
(
RSocketMessageHandler
messageHandler
,
RouteMatcher
routeMatcher
,
RSocketRequester
requester
,
@Nullable
MimeType
defaultD
ataMimeType
,
RSocketRequester
requester
,
MimeType
dataMimeType
,
MimeType
metad
ataMimeType
,
DataBufferFactory
bufferFactory
)
{
Assert
.
notNull
(
messageHandler
,
"'messageHandler' is required"
);
Assert
.
notNull
(
routeMatcher
,
"'routeMatcher' is required"
);
Assert
.
notNull
(
requester
,
"'requester' is required"
);
Assert
.
notNull
(
requester
,
"'dataMimeType' is required"
);
Assert
.
notNull
(
requester
,
"'metadataMimeType' is required"
);
Assert
.
isTrue
(
DefaultRSocketRequester
.
METADATA_MIME_TYPES
.
contains
(
metadataMimeType
),
()
->
"Unexpected metadatata mime type: '"
+
metadataMimeType
+
"'"
);
this
.
messageHandler
=
messageHandler
;
this
.
routeMatcher
=
routeMatcher
;
this
.
requester
=
requester
;
this
.
dataMimeType
=
defaultDataMimeType
;
this
.
dataMimeType
=
dataMimeType
;
this
.
metadataMimeType
=
metadataMimeType
;
this
.
bufferFactory
=
bufferFactory
;
}
...
...
@@ -169,13 +178,21 @@ class MessagingRSocket extends AbstractRSocket {
}
private
String
getDestination
(
Payload
payload
)
{
// TODO:
// For now treat the metadata as a simple string with routing information.
// We'll have to get more sophisticated once the routing extension is completed.
// https://github.com/rsocket/rsocket-java/issues/568
return
payload
.
getMetadataUtf8
();
if
(
this
.
metadataMimeType
.
equals
(
DefaultRSocketRequester
.
COMPOSITE_METADATA
))
{
CompositeMetadata
metadata
=
new
CompositeMetadata
(
payload
.
metadata
(),
false
);
for
(
CompositeMetadata
.
Entry
entry
:
metadata
)
{
String
mimeType
=
entry
.
getMimeType
();
if
(
DefaultRSocketRequester
.
ROUTING
.
toString
().
equals
(
mimeType
))
{
return
entry
.
getContent
().
toString
(
StandardCharsets
.
UTF_8
);
}
}
return
""
;
}
else
if
(
this
.
metadataMimeType
.
equals
(
DefaultRSocketRequester
.
ROUTING
))
{
return
payload
.
getMetadataUtf8
();
}
// Should not happen (given constructor assertions)
throw
new
IllegalArgumentException
(
"Unexpected metadata MimeType"
);
}
private
DataBuffer
retainDataAndReleasePayload
(
Payload
payload
)
{
...
...
@@ -187,9 +204,7 @@ class MessagingRSocket extends AbstractRSocket {
headers
.
setLeaveMutable
(
true
);
RouteMatcher
.
Route
route
=
this
.
routeMatcher
.
parseRoute
(
destination
);
headers
.
setHeader
(
DestinationPatternsMessageCondition
.
LOOKUP_DESTINATION_HEADER
,
route
);
if
(
this
.
dataMimeType
!=
null
)
{
headers
.
setContentType
(
this
.
dataMimeType
);
}
headers
.
setContentType
(
this
.
dataMimeType
);
headers
.
setHeader
(
RSocketRequesterMethodArgumentResolver
.
RSOCKET_REQUESTER_HEADER
,
this
.
requester
);
if
(
replyMono
!=
null
)
{
headers
.
setHeader
(
RSocketPayloadReturnValueHandler
.
RESPONSE_HEADER
,
replyMono
);
...
...
spring-messaging/src/main/java/org/springframework/messaging/rsocket/RSocketRequester.java
浏览文件 @
14e2c680
...
...
@@ -19,6 +19,7 @@ package org.springframework.messaging.rsocket;
import
java.net.URI
;
import
java.util.function.Consumer
;
import
io.rsocket.ConnectionSetupPayload
;
import
io.rsocket.RSocket
;
import
io.rsocket.RSocketFactory
;
import
io.rsocket.transport.ClientTransport
;
...
...
@@ -47,15 +48,28 @@ public interface RSocketRequester {
*/
RSocket
rsocket
();
// For now we treat metadata as a simple string that is the route.
// This will change after the resolution of:
// https://github.com/rsocket/rsocket-java/issues/568
/**
* Return the data {@code MimeType} selected for the underlying RSocket
* at connection time. On the client side this is configured via
* {@link RSocketRequester.Builder#dataMimeType(MimeType)} while on the
* server side it's obtained from the {@link ConnectionSetupPayload}.
*/
MimeType
dataMimeType
();
/**
* Entry point to prepare a new request to the given route.
* <p>For requestChannel interactions, i.e. Flux-to-Flux the metadata is
* attached to the first request payload.
* @param route the routing destination
* Return the metadata {@code MimeType} selected for the underlying RSocket
* at connection time. On the client side this is configured via
* {@link RSocketRequester.Builder#metadataMimeType(MimeType)} while on the
* server side it's obtained from the {@link ConnectionSetupPayload}.
*/
MimeType
metadataMimeType
();
/**
* Begin to specify a new request with the given route to a handler on the
* remote side. The route will be encoded in the metadata of the first
* payload.
* @param route the route to a handler
* @return a spec for further defining and executing the request
*/
RequestSpec
route
(
String
route
);
...
...
@@ -72,31 +86,19 @@ public interface RSocketRequester {
}
/**
* Wrap an existing {@link RSocket}. T
ypically used in a client or server
*
responder to wrap the remote
{@code RSocket}.
* Wrap an existing {@link RSocket}. T
his is typically used in a responder,
*
client or server, to wrap the remote/sending
{@code RSocket}.
* @param rsocket the RSocket to wrap
* @param dataMimeType the data MimeType, obtained from the
* {@link io.rsocket.ConnectionSetupPayload} (server) or the
* {@link io.rsocket.RSocketFactory.ClientRSocketFactory} (client)
* @param dataMimeType the data MimeType from the {@code ConnectionSetupPayload}
* @param metadataMimeType the metadata MimeType from the {@code ConnectionSetupPayload}
* @param strategies the strategies to use
* @return the created RSocketRequester
*/
static
RSocketRequester
wrap
(
RSocket
rsocket
,
@Nullable
MimeType
dataMimeType
,
RSocketStrategies
strategies
)
{
return
new
DefaultRSocketRequester
(
rsocket
,
dataMimeType
,
strategies
);
}
static
RSocketRequester
wrap
(
RSocket
rsocket
,
MimeType
dataMimeType
,
MimeType
metadataMimeType
,
RSocketStrategies
strategies
)
{
/**
* Create a new {@code RSocketRequester} from the given {@link RSocket} and
* strategies for encoding and decoding request and response payloads.
* @param rsocket the sending RSocket to use
* @param dataMimeType the MimeType for data (from the SETUP frame)
* @param strategies encoders, decoders, and others
* @return the created RSocketRequester wrapper
* @deprecated use {@link #wrap(RSocket, MimeType, RSocketStrategies)} instead
*/
@Deprecated
static
RSocketRequester
create
(
RSocket
rsocket
,
@Nullable
MimeType
dataMimeType
,
RSocketStrategies
strategies
)
{
return
new
DefaultRSocketRequester
(
rsocket
,
dataMimeType
,
strategies
);
return
new
DefaultRSocketRequester
(
rsocket
,
dataMimeType
,
metadataMimeType
,
strategies
);
}
...
...
@@ -107,20 +109,37 @@ public interface RSocketRequester {
interface
Builder
{
/**
* Configure the MimeType to use for payload data. This is
set on the
* {@code SETUP} frame for the whole connection.
* Configure the MimeType to use for payload data. This is
then
*
specified on the
{@code SETUP} frame for the whole connection.
* <p>By default this is set to the first concrete MimeType supported
* by the configured encoders and decoders.
* @param mimeType the data MimeType to use
*/
RSocketRequester
.
Builder
dataMimeType
(
MimeType
mimeType
);
RSocketRequester
.
Builder
dataMimeType
(
@Nullable
MimeType
mimeType
);
/**
* Configure the MimeType to use for payload metadata. This is then
* specified on the {@code SETUP} frame for the whole connection.
* <p>At present the metadata MimeType must be
* {@code "message/x.rsocket.routing.v0"} to allow the request
* {@link RSocketRequester#route(String) route} to be encoded, or it
* could also be {@code "message/x.rsocket.composite-metadata.v0"} in
* which case the route can be encoded along with other metadata entries.
* <p>By default this is set to
* {@code "message/x.rsocket.composite-metadata.v0"}.
* @param mimeType the data MimeType to use
*/
RSocketRequester
.
Builder
metadataMimeType
(
MimeType
mimeType
);
/**
* Configure the {@code ClientRSocketFactory}.
* <p><strong>Note:</strong> Please, do not set the {@code dataMimeType}
* directly on the underlying {@code RSocketFactory.ClientRSocketFactory},
* and use {@link #dataMimeType(MimeType)} instead.
* @param configurer the configurer to apply
* <p><strong>Note:</strong> This builder provides shortcuts for certain
* {@code ClientRSocketFactory} options it needs to know about such as
* {@link #dataMimeType(MimeType)} and {@link #metadataMimeType(MimeType)}.
* Please, use these shortcuts vs configuring them directly on the
* {@code ClientRSocketFactory} so that the resulting
* {@code RSocketRequester} is aware of those changes.
* @param configurer consumer to customize the factory
*/
RSocketRequester
.
Builder
rsocketFactory
(
Consumer
<
RSocketFactory
.
ClientRSocketFactory
>
configurer
);
...
...
@@ -169,6 +188,17 @@ public interface RSocketRequester {
*/
interface
RequestSpec
{
/**
* Use this to append additional metadata entries if the RSocket
* connection is configured to use composite metadata. If not, an
* {@link IllegalArgumentException} will be raised.
* @param metadata an Object, to be encoded with a suitable
* {@link org.springframework.core.codec.Encoder Encoder}, or a
* {@link org.springframework.core.io.buffer.DataBuffer DataBuffer}
* @param mimeType the mime type that describes the metadata
*/
RequestSpec
metadata
(
Object
metadata
,
MimeType
mimeType
);
/**
* Provide request payload data. The given Object may be a synchronous
* value, or a {@link Publisher} of values, or another async type that's
...
...
spring-messaging/src/test/java/org/springframework/messaging/rsocket/DefaultRSocketRequesterTests.java
浏览文件 @
14e2c680
...
...
@@ -19,6 +19,7 @@ package org.springframework.messaging.rsocket;
import
java.nio.charset.StandardCharsets
;
import
java.time.Duration
;
import
java.util.Arrays
;
import
java.util.Iterator
;
import
java.util.List
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.function.Function
;
...
...
@@ -28,6 +29,7 @@ import io.reactivex.Observable;
import
io.reactivex.Single
;
import
io.rsocket.AbstractRSocket
;
import
io.rsocket.Payload
;
import
io.rsocket.metadata.CompositeMetadata
;
import
org.junit.Before
;
import
org.junit.Test
;
import
org.reactivestreams.Publisher
;
...
...
@@ -61,37 +63,41 @@ public class DefaultRSocketRequesterTests {
private
RSocketRequester
requester
;
private
RSocketStrategies
strategies
;
private
final
DefaultDataBufferFactory
bufferFactory
=
new
DefaultDataBufferFactory
();
@Before
public
void
setUp
()
{
RSocketStrategies
strategies
=
RSocketStrategies
.
builder
()
this
.
strategies
=
RSocketStrategies
.
builder
()
.
decoder
(
StringDecoder
.
allMimeTypes
())
.
encoder
(
CharSequenceEncoder
.
allMimeTypes
())
.
build
();
this
.
rsocket
=
new
TestRSocket
();
this
.
requester
=
RSocketRequester
.
wrap
(
this
.
rsocket
,
MimeTypeUtils
.
TEXT_PLAIN
,
strategies
);
this
.
requester
=
RSocketRequester
.
wrap
(
this
.
rsocket
,
MimeTypeUtils
.
TEXT_PLAIN
,
DefaultRSocketRequester
.
ROUTING
,
this
.
strategies
);
}
@Test
public
void
s
inglePayload
()
{
public
void
s
endMono
()
{
// data(Object)
testS
inglePayload
(
spec
->
spec
.
data
(
"bodyA"
),
"bodyA"
);
testS
inglePayload
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
map
(
l
->
"bodyA"
)),
"bodyA"
);
testS
inglePayload
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
then
()),
""
);
testS
inglePayload
(
spec
->
spec
.
data
(
Single
.
timer
(
10
,
MILLISECONDS
).
map
(
l
->
"bodyA"
)),
"bodyA"
);
testS
inglePayload
(
spec
->
spec
.
data
(
Completable
.
complete
()),
""
);
testS
endMono
(
spec
->
spec
.
data
(
"bodyA"
),
"bodyA"
);
testS
endMono
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
map
(
l
->
"bodyA"
)),
"bodyA"
);
testS
endMono
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
then
()),
""
);
testS
endMono
(
spec
->
spec
.
data
(
Single
.
timer
(
10
,
MILLISECONDS
).
map
(
l
->
"bodyA"
)),
"bodyA"
);
testS
endMono
(
spec
->
spec
.
data
(
Completable
.
complete
()),
""
);
// data(Publisher<T>, Class<T>)
testS
inglePayload
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
map
(
l
->
"bodyA"
),
String
.
class
),
"bodyA"
);
testS
inglePayload
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
map
(
l
->
"bodyA"
),
Object
.
class
),
"bodyA"
);
testS
inglePayload
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
then
(),
Void
.
class
),
""
);
testS
endMono
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
map
(
l
->
"bodyA"
),
String
.
class
),
"bodyA"
);
testS
endMono
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
map
(
l
->
"bodyA"
),
Object
.
class
),
"bodyA"
);
testS
endMono
(
spec
->
spec
.
data
(
Mono
.
delay
(
MILLIS_10
).
then
(),
Void
.
class
),
""
);
}
private
void
testS
inglePayload
(
Function
<
RequestSpec
,
ResponseSpec
>
mapper
,
String
expectedValue
)
{
private
void
testS
endMono
(
Function
<
RequestSpec
,
ResponseSpec
>
mapper
,
String
expectedValue
)
{
mapper
.
apply
(
this
.
requester
.
route
(
"toA"
)).
send
().
block
(
Duration
.
ofSeconds
(
5
));
assertThat
(
this
.
rsocket
.
getSavedMethodName
()).
isEqualTo
(
"fireAndForget"
);
...
...
@@ -100,22 +106,22 @@ public class DefaultRSocketRequesterTests {
}
@Test
public
void
multiPayload
()
{
public
void
sendFlux
()
{
String
[]
values
=
new
String
[]
{
"bodyA"
,
"bodyB"
,
"bodyC"
};
Flux
<
String
>
stringFlux
=
Flux
.
fromArray
(
values
).
delayElements
(
MILLIS_10
);
// data(Object)
test
MultiPayload
(
spec
->
spec
.
data
(
stringFlux
),
values
);
test
MultiPayload
(
spec
->
spec
.
data
(
Flux
.
empty
()),
""
);
test
MultiPayload
(
spec
->
spec
.
data
(
Observable
.
fromArray
(
values
).
delay
(
10
,
MILLISECONDS
)),
values
);
test
MultiPayload
(
spec
->
spec
.
data
(
Observable
.
empty
()),
""
);
test
SendFlux
(
spec
->
spec
.
data
(
stringFlux
),
values
);
test
SendFlux
(
spec
->
spec
.
data
(
Flux
.
empty
()),
""
);
test
SendFlux
(
spec
->
spec
.
data
(
Observable
.
fromArray
(
values
).
delay
(
10
,
MILLISECONDS
)),
values
);
test
SendFlux
(
spec
->
spec
.
data
(
Observable
.
empty
()),
""
);
// data(Publisher<T>, Class<T>)
test
MultiPayload
(
spec
->
spec
.
data
(
stringFlux
,
String
.
class
),
values
);
test
MultiPayload
(
spec
->
spec
.
data
(
stringFlux
.
cast
(
Object
.
class
),
Object
.
class
),
values
);
test
SendFlux
(
spec
->
spec
.
data
(
stringFlux
,
String
.
class
),
values
);
test
SendFlux
(
spec
->
spec
.
data
(
stringFlux
.
cast
(
Object
.
class
),
Object
.
class
),
values
);
}
private
void
test
MultiPayload
(
Function
<
RequestSpec
,
ResponseSpec
>
mapper
,
String
...
expectedValues
)
{
private
void
test
SendFlux
(
Function
<
RequestSpec
,
ResponseSpec
>
mapper
,
String
...
expectedValues
)
{
this
.
rsocket
.
reset
();
mapper
.
apply
(
this
.
requester
.
route
(
"toA"
)).
retrieveFlux
(
String
.
class
).
blockLast
(
Duration
.
ofSeconds
(
5
));
...
...
@@ -129,19 +135,50 @@ public class DefaultRSocketRequesterTests {
assertThat
(
payloads
.
get
(
0
).
getDataUtf8
()).
isEqualTo
(
""
);
}
else
{
assertThat
(
payloads
.
stream
().
map
(
Payload:
:
getMetadataUtf8
).
toArray
(
String
[]::
new
)).
isEqualTo
(
new
String
[]
{
"toA"
,
""
,
""
});
assertThat
(
payloads
.
stream
().
map
(
Payload:
:
getDataUtf8
).
toArray
(
String
[]::
new
)).
isEqualTo
(
expectedValues
);
assertThat
(
payloads
.
stream
().
map
(
Payload:
:
getMetadataUtf8
).
toArray
(
String
[]::
new
))
.
isEqualTo
(
new
String
[]
{
"toA"
,
""
,
""
});
assertThat
(
payloads
.
stream
().
map
(
Payload:
:
getDataUtf8
).
toArray
(
String
[]::
new
))
.
isEqualTo
(
expectedValues
);
}
}
@Test
public
void
send
()
{
String
value
=
"bodyA"
;
this
.
requester
.
route
(
"toA"
).
data
(
value
).
send
().
block
(
Duration
.
ofSeconds
(
5
));
public
void
sendCompositeMetadata
()
{
RSocketRequester
requester
=
RSocketRequester
.
wrap
(
this
.
rsocket
,
MimeTypeUtils
.
TEXT_PLAIN
,
DefaultRSocketRequester
.
COMPOSITE_METADATA
,
this
.
strategies
);
requester
.
route
(
"toA"
)
.
metadata
(
"My metadata"
,
MimeTypeUtils
.
TEXT_PLAIN
).
data
(
"bodyA"
)
.
send
()
.
block
(
Duration
.
ofSeconds
(
5
));
CompositeMetadata
entries
=
new
CompositeMetadata
(
this
.
rsocket
.
getSavedPayload
().
metadata
(),
false
);
Iterator
<
CompositeMetadata
.
Entry
>
iterator
=
entries
.
iterator
();
assertThat
(
iterator
.
hasNext
()).
isTrue
();
CompositeMetadata
.
Entry
entry
=
iterator
.
next
();
assertThat
(
entry
.
getMimeType
()).
isEqualTo
(
DefaultRSocketRequester
.
ROUTING
.
toString
());
assertThat
(
entry
.
getContent
().
toString
(
StandardCharsets
.
UTF_8
)).
isEqualTo
(
"toA"
);
assertThat
(
iterator
.
hasNext
()).
isTrue
();
entry
=
iterator
.
next
();
assertThat
(
entry
.
getMimeType
()).
isEqualTo
(
MimeTypeUtils
.
TEXT_PLAIN
.
toString
());
assertThat
(
entry
.
getContent
().
toString
(
StandardCharsets
.
UTF_8
)).
isEqualTo
(
"My metadata"
);
assertThat
(
iterator
.
hasNext
()).
isFalse
();
}
assertThat
(
this
.
rsocket
.
getSavedMethodName
()).
isEqualTo
(
"fireAndForget"
);
assertThat
(
this
.
rsocket
.
getSavedPayload
().
getMetadataUtf8
()).
isEqualTo
(
"toA"
);
assertThat
(
this
.
rsocket
.
getSavedPayload
().
getDataUtf8
()).
isEqualTo
(
"bodyA"
);
@Test
public
void
supportedMetadataMimeTypes
()
{
RSocketRequester
.
wrap
(
this
.
rsocket
,
MimeTypeUtils
.
TEXT_PLAIN
,
DefaultRSocketRequester
.
COMPOSITE_METADATA
,
this
.
strategies
);
RSocketRequester
.
wrap
(
this
.
rsocket
,
MimeTypeUtils
.
TEXT_PLAIN
,
DefaultRSocketRequester
.
ROUTING
,
this
.
strategies
);
assertThatIllegalArgumentException
().
isThrownBy
(()
->
RSocketRequester
.
wrap
(
this
.
rsocket
,
MimeTypeUtils
.
TEXT_PLAIN
,
MimeTypeUtils
.
TEXT_PLAIN
,
this
.
strategies
));
}
@Test
...
...
@@ -188,10 +225,10 @@ public class DefaultRSocketRequesterTests {
}
@Test
public
void
rejectFluxToMono
()
{
assertThatIllegalArgumentException
()
.
isThrownBy
(()
->
this
.
requester
.
route
(
""
).
data
(
Flux
.
just
(
"a"
,
"b"
)).
retrieveMono
(
String
.
class
))
.
withMessage
(
"No RSocket interaction model for Flux request to Mono response."
);
public
void
fluxToMonoIsRejected
()
{
assertThatIllegalArgumentException
()
.
isThrownBy
(()
->
this
.
requester
.
route
(
""
).
data
(
Flux
.
just
(
"a"
,
"b"
)).
retrieveMono
(
String
.
class
))
.
withMessage
(
"No RSocket interaction model for Flux request to Mono response."
);
}
private
Payload
toPayload
(
String
value
)
{
...
...
spring-messaging/src/test/java/org/springframework/messaging/rsocket/RSocketClientToServerIntegrationTests.java
浏览文件 @
14e2c680
...
...
@@ -101,7 +101,9 @@ public class RSocketClientToServerIntegrationTests {
.
verify
(
Duration
.
ofSeconds
(
5
));
assertThat
(
interceptor
.
getRSocketCount
()).
isEqualTo
(
1
);
assertThat
(
interceptor
.
getFireAndForgetCount
(
0
)).
as
(
"Fire and forget requests did not actually complete handling on the server side"
).
isEqualTo
(
3
);
assertThat
(
interceptor
.
getFireAndForgetCount
(
0
))
.
as
(
"Fire and forget requests did not actually complete handling on the server side"
)
.
isEqualTo
(
3
);
}
@Test
...
...
spring-messaging/src/test/java/org/springframework/messaging/rsocket/RSocketServerToClientIntegrationTests.java
浏览文件 @
14e2c680
...
...
@@ -106,8 +106,9 @@ public class RSocketServerToClientIntegrationTests {
RSocket
rsocket
=
null
;
try
{
rsocket
=
RSocketFactory
.
connect
()
.
setupPayload
(
DefaultPayload
.
create
(
""
,
destination
)
)
.
metadataMimeType
(
"message/x.rsocket.routing.v0"
)
.
dataMimeType
(
"text/plain"
)
.
setupPayload
(
DefaultPayload
.
create
(
""
,
destination
))
.
frameDecoder
(
PayloadDecoder
.
ZERO_COPY
)
.
acceptor
(
context
.
getBean
(
"clientAcceptor"
,
MessageHandlerAcceptor
.
class
))
.
transport
(
TcpClientTransport
.
create
(
"localhost"
,
7000
))
...
...
spring-web/src/test/java/org/springframework/http/ContentDispositionTests.java
浏览文件 @
14e2c680
...
...
@@ -36,6 +36,15 @@ import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException
*/
public
class
ContentDispositionTests
{
@Test
public
void
parseTest
()
{
ContentDisposition
disposition
=
ContentDisposition
.
parse
(
"form-data; name=\"foo\"; filename=\"foo.txt\"; size=123"
);
assertThat
(
disposition
).
isEqualTo
(
ContentDisposition
.
builder
(
"form-data"
)
.
name
(
"foo"
).
filename
(
"foo.txt"
).
size
(
123L
).
build
());
}
@Test
public
void
parse
()
{
ContentDisposition
disposition
=
ContentDisposition
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录