Webflux Spring 2.1.2自定义内容类型
我正试图通过WebClient发布以获取microsoft令牌:Webflux Spring 2.1.2自定义内容类型,spring,spring-webflux,Spring,Spring Webflux,我正试图通过WebClient发布以获取microsoft令牌: public WebClient getWebclient() { TcpClient client = TcpClient.create() .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) .doOnConnected(connection -> connection.addHandlerLast(new Re
public WebClient getWebclient() {
TcpClient client = TcpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000)
.doOnConnected(connection -> connection.addHandlerLast(new ReadTimeoutHandler(15)).addHandlerLast(new WriteTimeoutHandler(15)));
ExchangeStrategies strategies = ExchangeStrategies.builder()
.codecs(configurer -> {
configurer.registerDefaults(true);
FormHttpMessageReader formHttpMessageReader = new FormHttpMessageReader();
formHttpMessageReader.setEnableLoggingRequestDetails(true);
configurer.customCodecs().reader(formHttpMessageReader);
})
.build();
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(HttpClient.from(client).followRedirect(true)))
.exchangeStrategies(strategies)
.filter(logRequest())
.filter(logResponse())
.build();
}
MultiValueMap<String, String> credentials = new LinkedMultiValueMap<>();
credentials.add("grant_type", "password");
credentials.add("client_id", oauthClientId);
credentials.add("resource", oauthResource);
credentials.add("scope", oauthScope);
credentials.add("username", oauthUsername);
credentials.add("password", oauthPassword);
Mono<MicrosoftToken> response = webClientService.getWebclient().post()
.uri(oauthUrl)
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(BodyInserters.fromFormData(credentials))
.retrieve()
.onStatus(HttpStatus::is4xxClientError, clientResponse ->
Mono.error(new WebClientException(clientResponse.bodyToMono(String.class), clientResponse.statusCode())))
.bodyToMono(MicrosoftToken.class);
this.cachedToken = response.block();
我用spring版本2.0.0对此进行了测试,在新版本中没有添加字符集:
POST /common/oauth2/token HTTP/1.1
user-agent: ReactorNetty/0.7.5.RELEASE
host: login.microsoftonline.de
accept-encoding: gzip
Content-Type: application/x-www-form-urlencoded
Content-Length: 205
这里有两种方法:
webClient
.mutate()
.defaultHeaders(headers -> {
headers.add("Content-Type", ContentType.APPLICATION_FORM_URLENCODED.getMimeType()
}).build()
. uri(uri)
...
OR
webClient
.post()
.uri(uri)
.body(body)
.headers(headers -> getHttpHeaders())
...
private HttpHeaders getHttpHeaders(){
HttpHeaders headers = new HttpHeaders();
headers.add("Content-Type", "application/x-www-form-urlencoded")
return headers;
}
只有几种方法可以利用.headers或.defaultHeaders中的headers消费者
但老实说,我不认为字符集是个问题。如果您在响应中获得application/json,可能是因为Microsoft正在通过您在应用程序注册中指定的重定向url转发带有该标题的请求
好消息是这可能是可取的,因为Microsoft将令牌字段返回为json,这允许您调用.bodyTomino(MicrosoftToken)。我记得BodyInserters.fromFormData存在问题,因为它实际上没有对多值映射中的值进行编码
这就是我正在使用的:
private BodyInserter<String, ReactiveHttpOutputMessage> getBodyInserter(Map<String,String> parameters) {
credentials.add("grant_type", encode("password"));
credentials.add("client_id", encode(oauthClientId));
credentials.add("resource", encode(oauthResource));
// and so on..
// note that parameters is a regular Map - not a MultiValueMap
BodyInserter<String, ReactiveHttpOutputMessage> bodyInserter = BodyInserters.fromObject(
parameters.entrySet().stream()
.map(entry -> entry.getKey().concat("=").concat(entry.getValue()))
.collect(Collectors.joining("&", "", "")));
return bodyInserter;
}
private String encode(String str) {
try {
return URLEncoder.encode(str, StandardCharsets.UTF_8.name());
} catch (UnsupportedEncodingException e) {
log.error("Error encoding req body", e);
}
}
private BodyInserter getBodyInserter(映射参数){
添加(“授权类型”,编码(“密码”);
添加(“客户端id”,编码(oauthClientId));
添加(“资源”,编码(oauthResource));
//等等。。
//请注意,参数是常规映射,而不是多值映射
BodyInserter BodyInserter=BodyInserters.fromObject(
parameters.entrySet().stream()
.map(entry->entry.getKey().concat(“=”).concat(entry.getValue())
.collect(收集器.加入(&),“”,“”);
回体插入器;
}
私有字符串编码(字符串str){
试一试{
返回URLEncoder.encode(str,StandardCharsets.UTF_8.name());
}捕获(不支持的编码异常e){
log.error(“错误编码请求体”,e);
}
}
我花了一上午最长的时间才找到答案,但我最终还是成功了。问题是WebfluxBodyInserters.fromFormData
总是将内容类型设置为application/x-www-form-urlencoded;charset=…
无论您在标题中设置了什么
要解决此问题,请首先定义此方法:
/**
* This method is unfortunately necessary because of Spring Webflux's propensity to add {@code ";charset=..."}
* to the {@code Content-Type} header, which the Generic Chinese Device doesn't handle properly.
*
* @return a {@link FormInserter} that doesn't add the character set to the content type header
*/
private FormInserter<String> formInserter() {
return new FormInserter<String>() {
private final MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
@Override public FormInserter<String> with(final String key, final String value) {
data.add(key, value);
return this;
}
@Override public FormInserter<String> with(final MultiValueMap<String, String> values) {
data.addAll(values);
return this;
}
@Override public Mono<Void> insert(final ClientHttpRequest outputMessage, final Context context) {
final ResolvableType formDataType =
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
return new FormHttpMessageWriter() {
@Override protected MediaType getMediaType(final MediaType mediaType) {
if (MediaType.APPLICATION_FORM_URLENCODED.equals(mediaType)) {
return mediaType;
} else {
return super.getMediaType(mediaType);
}
}
}.write(Mono.just(this.data), formDataType,
MediaType.APPLICATION_FORM_URLENCODED,
outputMessage,
context.hints());
}
};
}
请注意,上面的方框主要用于演示目的。您可能希望也可能不希望阻止并等待响应。在使用WebClient发送请求时,您是否尝试过设置内容类型?你能显示一个实际发送请求的代码段吗?是的,我试过了,正在设置内容类型。但是Spring稍后将覆盖内容类型,或者更确切地说是将字符集添加到已设置的内容类型。请检查我的答案以获得正确的解决方案。谢谢分享!也解决了这个问题。
/**
* This method is unfortunately necessary because of Spring Webflux's propensity to add {@code ";charset=..."}
* to the {@code Content-Type} header, which the Generic Chinese Device doesn't handle properly.
*
* @return a {@link FormInserter} that doesn't add the character set to the content type header
*/
private FormInserter<String> formInserter() {
return new FormInserter<String>() {
private final MultiValueMap<String, String> data = new LinkedMultiValueMap<>();
@Override public FormInserter<String> with(final String key, final String value) {
data.add(key, value);
return this;
}
@Override public FormInserter<String> with(final MultiValueMap<String, String> values) {
data.addAll(values);
return this;
}
@Override public Mono<Void> insert(final ClientHttpRequest outputMessage, final Context context) {
final ResolvableType formDataType =
ResolvableType.forClassWithGenerics(MultiValueMap.class, String.class, String.class);
return new FormHttpMessageWriter() {
@Override protected MediaType getMediaType(final MediaType mediaType) {
if (MediaType.APPLICATION_FORM_URLENCODED.equals(mediaType)) {
return mediaType;
} else {
return super.getMediaType(mediaType);
}
}
}.write(Mono.just(this.data), formDataType,
MediaType.APPLICATION_FORM_URLENCODED,
outputMessage,
context.hints());
}
};
}
final SomeResponseObject response = WebClient
.builder()
.build()
.post()
.uri(someOrOtherUri)
.body(formInserter().with("param1", "value1")
.with("param2", "value2")
)
.retrieve()
.bodyToFlux(SomeReponseObject.class)
.blockLast();