From 6118aa55f25978791dff55d3f1c30456b12a7335 Mon Sep 17 00:00:00 2001 From: namelessssssssssss <100946116+namelessssssssssss@users.noreply.github.com> Date: Sat, 16 Dec 2023 11:36:22 +0800 Subject: [PATCH] Support more content types for Triple protocol (#13387) * Add more content type support for triple * Add more content type support for triple * Add tests & Bug fix * Code style fix * Code style fix * Code style fix * Code style fix * Code style fix * Set codec related dependencies to provided * Change CodecUtil to bean * Support Triple response encode by Accept header * Support Triple response encode by Accept header * Fix npe * Refactor response encode * Bug fix * Style fix * Bug fix & Add log * Style fix * Bug fix * Update ExceptionUtilsTest.java * Refactor * Fix code style * Refactor * Refactor * Code style fix * Refactor * Refactor * Code style optimize * Code style optimize * Refactor MultipartCodec * Add tests & Remove some dep * Remove commons_fileupload from bom * Simplify depedencies & clean up * Fix jaxb version * Enhance reliability for MultipartCodec & Add tests * Enhance reliability for MultipartCodec * Add test cases * Add test cases * Add test cases * Add test cases * Add test for xml safety * Add test for xml safety * Refactor CodecFactory * Refactor CodecFactory * Refactor CodecFactory * Add codec cache * Fix npe * Fix npe --------- Co-authored-by: nameless --- dubbo-dependencies-bom/pom.xml | 12 + dubbo-distribution/dubbo-all/pom.xml | 4 +- dubbo-remoting/dubbo-remoting-http12/pom.xml | 9 + .../AbstractServerHttpChannelObserver.java | 18 +- .../remoting/http12/HttpHeaderNames.java | 4 +- .../dubbo/remoting/http12/HttpHeaders.java | 8 + .../http12/message/CodecMediaType.java | 26 ++ .../message/DefaultListeningDecoder.java | 8 +- .../http12/message/HttpMessageCodec.java | 35 +- .../http12/message/HttpMessageDecoder.java | 35 ++ ...ry.java => HttpMessageDecoderFactory.java} | 14 +- .../http12/message/HttpMessageEncoder.java | 31 ++ ...ry.java => HttpMessageEncoderFactory.java} | 19 +- .../remoting/http12/message/MediaType.java | 9 + .../http12/message/codec/CodecUtils.java | 148 +++++++ .../http12/message/{ => codec}/JsonCodec.java | 15 +- .../message/codec/JsonCodecFactory.java | 39 ++ .../message/{ => codec}/JsonPbCodec.java | 33 +- .../{ => codec}/JsonPbCodecFactory.java | 32 +- .../message/codec/MultipartDecoder.java | 269 ++++++++++++ .../codec/MultipartDecoderFactory.java | 44 ++ .../http12/message/codec/PlainTextCodec.java | 78 ++++ .../message/codec/PlainTextCodecFactory.java | 39 ++ .../message/codec/UrlEncodeFormCodec.java | 128 ++++++ .../codec/UrlEncodeFormCodecFactory.java | 47 +++ .../http12/message/codec/XmlCodec.java | 74 ++++ .../http12/message/codec/XmlCodecFactory.java | 39 ++ .../h1/NettyHttp1ConnectionHandler.java | 30 +- ...o.remoting.http12.message.HttpMessageCodec | 1 - ...ing.http12.message.HttpMessageCodecFactory | 2 - ...g.http12.message.HttpMessageDecoderFactory | 5 + ...g.http12.message.HttpMessageEncoderFactory | 4 + .../http12/message/codec/CodeUtilsTest.java | 71 ++++ .../http12/message/codec/CodecTest.java | 399 ++++++++++++++++++ .../remoting/http12/message/codec/User.java | 57 +++ .../http12/message/codec/XmlSafetyTest.java | 115 +++++ .../src/test/resources/log4j2-test.xml | 29 ++ .../h12/AbstractServerTransportListener.java | 40 +- ...bleCodec.java => CompressibleEncoder.java} | 31 +- .../tri/h12/grpc/GrpcCompositeCodec.java | 15 +- .../h12/grpc/GrpcCompositeCodecFactory.java | 14 +- .../tri/h12/grpc/GrpcHeaderNames.java | 3 +- .../GrpcHttp2ServerTransportListener.java | 7 +- .../h12/grpc/ProtobufHttpMessageCodec.java | 4 +- .../tri/h12/grpc/WrapperHttpMessageCodec.java | 2 +- .../DefaultHttp11ServerTransportListener.java | 17 +- .../GenericHttp2ServerTransportListener.java | 7 +- .../h12/http2/Http2ServerStreamObserver.java | 17 +- ...o.remoting.http12.message.HttpMessageCodec | 2 - ...ing.http12.message.HttpMessageCodecFactory | 1 - ...g.http12.message.HttpMessageDecoderFactory | 1 + ...g.http12.message.HttpMessageEncoderFactory | 1 + 52 files changed, 1838 insertions(+), 254 deletions(-) create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/CodecMediaType.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java rename dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/{HttpMessageCodecFactory.java => HttpMessageDecoderFactory.java} (75%) create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java rename dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/{JsonCodecFactory.java => HttpMessageEncoderFactory.java} (69%) create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java rename dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/{ => codec}/JsonCodec.java (93%) create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java rename dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/{ => codec}/JsonPbCodec.java (79%) rename dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/{ => codec}/JsonPbCodecFactory.java (56%) create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java delete mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec delete mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/User.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/XmlSafetyTest.java create mode 100644 dubbo-remoting/dubbo-remoting-http12/src/test/resources/log4j2-test.xml rename dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/{CompressibleCodec.java => CompressibleEncoder.java} (63%) delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec delete mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory create mode 100644 dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory diff --git a/dubbo-dependencies-bom/pom.xml b/dubbo-dependencies-bom/pom.xml index 4121138d8e0..384a14d0f3f 100644 --- a/dubbo-dependencies-bom/pom.xml +++ b/dubbo-dependencies-bom/pom.xml @@ -207,6 +207,8 @@ 2.38.0 3.3.0-beta.2-SNAPSHOT 3.1.5 + 4.0.1 + 2.3.3-b02 @@ -1073,6 +1075,16 @@ spring-cloud-openfeign-core ${open_feign_version} + + jakarta.xml.bind + jakarta.xml.bind-api + ${jakarta.xml.bind-api.version} + + + org.glassfish.jaxb + jaxb-runtime + ${jaxb-runtime.version} + diff --git a/dubbo-distribution/dubbo-all/pom.xml b/dubbo-distribution/dubbo-all/pom.xml index 1faf5861547..c92611d230c 100644 --- a/dubbo-distribution/dubbo-all/pom.xml +++ b/dubbo-distribution/dubbo-all/pom.xml @@ -949,10 +949,10 @@ META-INF/dubbo/internal/org.apache.dubbo.remoting.http.HttpBinder - META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec + META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory - META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory + META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.h2.Http2ServerTransportListenerFactory diff --git a/dubbo-remoting/dubbo-remoting-http12/pom.xml b/dubbo-remoting/dubbo-remoting-http12/pom.xml index a59990aaae6..5549f939937 100644 --- a/dubbo-remoting/dubbo-remoting-http12/pom.xml +++ b/dubbo-remoting/dubbo-remoting-http12/pom.xml @@ -60,5 +60,14 @@ netty-codec-http2 + + javax.xml.bind + jaxb-api + + + + org.glassfish.jaxb + jaxb-runtime + diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java index 65aaa6cd387..d13c4feeec0 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/AbstractServerHttpChannelObserver.java @@ -18,7 +18,7 @@ import org.apache.dubbo.remoting.http12.exception.EncodeException; import org.apache.dubbo.remoting.http12.exception.HttpStatusException; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; public abstract class AbstractServerHttpChannelObserver implements CustomizableHttpChannelObserver { @@ -32,18 +32,18 @@ public abstract class AbstractServerHttpChannelObserver implements CustomizableH private boolean headerSent; - private HttpMessageCodec httpMessageCodec; + private HttpMessageEncoder responseEncoder; public AbstractServerHttpChannelObserver(HttpChannel httpChannel) { this.httpChannel = httpChannel; } - public void setHttpMessageCodec(HttpMessageCodec httpMessageCodec) { - this.httpMessageCodec = httpMessageCodec; + public void setResponseEncoder(HttpMessageEncoder responseEncoder) { + this.responseEncoder = responseEncoder; } - protected HttpMessageCodec getHttpMessageCodec() { - return httpMessageCodec; + public HttpMessageEncoder getResponseEncoder() { + return responseEncoder; } @Override @@ -77,7 +77,7 @@ public void onNext(Object data) { } HttpOutputMessage outputMessage = encodeHttpOutputMessage(data); preOutputMessage(outputMessage); - this.httpMessageCodec.encode(outputMessage.getBody(), data); + this.responseEncoder.encode(outputMessage.getBody(), data); getHttpChannel().writeMessage(outputMessage); postOutputMessage(outputMessage); } catch (Throwable e) { @@ -114,7 +114,7 @@ public void onError(Throwable throwable) { errorResponse.setMessage(throwable.getMessage()); this.errorResponseCustomizer.accept(errorResponse, throwable); HttpOutputMessage httpOutputMessage = encodeHttpOutputMessage(errorResponse); - this.httpMessageCodec.encode(httpOutputMessage.getBody(), errorResponse); + this.responseEncoder.encode(httpOutputMessage.getBody(), errorResponse); getHttpChannel().writeMessage(httpOutputMessage); } catch (Throwable ex) { throwable = new EncodeException(ex); @@ -140,7 +140,7 @@ private void doSendHeaders(String statusCode) { .headers() .set( HttpHeaderNames.CONTENT_TYPE.getName(), - httpMessageCodec.contentType().getName()); + responseEncoder.mediaType().getName()); this.headersCustomizer.accept(httpMetadata.headers()); getHttpChannel().writeHeader(httpMetadata); this.headerSent = true; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java index 632dc0cdd06..65623ccacb7 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaderNames.java @@ -25,7 +25,9 @@ public enum HttpHeaderNames { TRANSFER_ENCODING("transfer-encoding"), - TE("te"); + TE("te"), + + ACCEPT("accept"); private final String name; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaders.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaders.java index 24407ad9f20..6c42ab2b275 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaders.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/HttpHeaders.java @@ -212,6 +212,14 @@ public Map toSingleValueMap() { return result; } + public String getContentType() { + return getFirst(HttpHeaderNames.CONTENT_TYPE.getName()); + } + + public String getAccept() { + return getFirst(HttpHeaderNames.ACCEPT.getName()); + } + @Override public boolean equals(Object other) { return (this == other || this.targetMap.equals(other)); diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/CodecMediaType.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/CodecMediaType.java new file mode 100644 index 00000000000..20bca3dc144 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/CodecMediaType.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message; + +public interface CodecMediaType { + + MediaType mediaType(); + + default boolean supports(String mediaType) { + return mediaType.startsWith(mediaType().getName()); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultListeningDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultListeningDecoder.java index dc57c7bf84a..dfb02c63de2 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultListeningDecoder.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/DefaultListeningDecoder.java @@ -20,14 +20,14 @@ public class DefaultListeningDecoder implements ListeningDecoder { - private final HttpMessageCodec httpMessageCodec; + private final HttpMessageDecoder httpMessageDecoder; private final Class[] targetTypes; private Listener listener; - public DefaultListeningDecoder(HttpMessageCodec httpMessageCodec, Class[] targetTypes) { - this.httpMessageCodec = httpMessageCodec; + public DefaultListeningDecoder(HttpMessageDecoder httpMessageDecoder, Class[] targetTypes) { + this.httpMessageDecoder = httpMessageDecoder; this.targetTypes = targetTypes; } @@ -38,7 +38,7 @@ public void setListener(Listener listener) { @Override public void decode(InputStream inputStream) { - Object[] decode = this.httpMessageCodec.decode(inputStream, targetTypes); + Object[] decode = this.httpMessageDecoder.decode(inputStream, targetTypes); this.listener.onMessage(decode); } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageCodec.java index 35a23532c38..86b8ae00a87 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageCodec.java @@ -16,40 +16,7 @@ */ package org.apache.dubbo.remoting.http12.message; -import org.apache.dubbo.common.extension.ExtensionScope; -import org.apache.dubbo.common.extension.SPI; -import org.apache.dubbo.remoting.http12.exception.DecodeException; -import org.apache.dubbo.remoting.http12.exception.EncodeException; - -import java.io.InputStream; -import java.io.OutputStream; - /** * for http body codec */ -@SPI(scope = ExtensionScope.FRAMEWORK) -public interface HttpMessageCodec { - - void encode(OutputStream outputStream, Object data) throws EncodeException; - - default void encode(OutputStream outputStream, Object[] data) throws EncodeException { - // default encode first data - this.encode(outputStream, data == null || data.length == 0 ? null : data[0]); - } - - Object decode(InputStream inputStream, Class targetType) throws DecodeException; - - default Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { - // default decode first target type - return new Object[] { - this.decode(inputStream, targetTypes == null || targetTypes.length == 0 ? null : targetTypes[0]) - }; - } - - MediaType contentType(); - - default boolean support(String contentType) { - MediaType mediaType = this.contentType(); - return mediaType.getName().startsWith(contentType); - } -} +public interface HttpMessageCodec extends HttpMessageEncoder, HttpMessageDecoder {} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java new file mode 100644 index 00000000000..94e247a57df --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoder.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message; + +import org.apache.dubbo.remoting.http12.exception.DecodeException; + +import java.io.InputStream; + +public interface HttpMessageDecoder extends CodecMediaType { + + Object decode(InputStream inputStream, Class targetType) throws DecodeException; + + default Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + // default decode first target type + return new Object[] { + this.decode(inputStream, targetTypes == null || targetTypes.length == 0 ? null : targetTypes[0]) + }; + } + + MediaType mediaType(); +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoderFactory.java similarity index 75% rename from dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageCodecFactory.java rename to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoderFactory.java index 9f8d3ac4979..43ae1c632d4 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageDecoderFactory.java @@ -21,18 +21,8 @@ import org.apache.dubbo.common.extension.SPI; import org.apache.dubbo.rpc.model.FrameworkModel; -/** - * for http body codec - */ @SPI(scope = ExtensionScope.FRAMEWORK) -public interface HttpMessageCodecFactory { - - HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel); - - MediaType contentType(); +public interface HttpMessageDecoderFactory extends CodecMediaType { - default boolean support(String contentType) { - MediaType mediaType = this.contentType(); - return mediaType.getName().startsWith(contentType); - } + HttpMessageDecoder createCodec(URL url, FrameworkModel frameworkModel, String mediaType); } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java new file mode 100644 index 00000000000..d4701ce5012 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoder.java @@ -0,0 +1,31 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message; + +import org.apache.dubbo.remoting.http12.exception.EncodeException; + +import java.io.OutputStream; + +public interface HttpMessageEncoder extends CodecMediaType { + + void encode(OutputStream outputStream, Object data) throws EncodeException; + + default void encode(OutputStream outputStream, Object[] data) throws EncodeException { + // default encode first data + this.encode(outputStream, data == null || data.length == 0 ? null : data[0]); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoderFactory.java similarity index 69% rename from dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonCodecFactory.java rename to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoderFactory.java index 64a7cd2f553..4793eecf41d 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/HttpMessageEncoderFactory.java @@ -17,21 +17,12 @@ package org.apache.dubbo.remoting.http12.message; import org.apache.dubbo.common.URL; -import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.common.extension.ExtensionScope; +import org.apache.dubbo.common.extension.SPI; import org.apache.dubbo.rpc.model.FrameworkModel; -@Activate -public class JsonCodecFactory implements HttpMessageCodecFactory { +@SPI(scope = ExtensionScope.FRAMEWORK) +public interface HttpMessageEncoderFactory extends CodecMediaType { - public static final String NAME = "json"; - - @Override - public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel) { - return new JsonCodec(); - } - - @Override - public MediaType contentType() { - return MediaType.APPLICATION_JSON_VALUE; - } + HttpMessageEncoder createCodec(URL url, FrameworkModel frameworkModel, String mediaType); } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java index 7703eeb50c3..1f35951b875 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/MediaType.java @@ -28,6 +28,15 @@ public class MediaType { public static final MediaType TEXT_EVENT_STREAM_VALUE = new MediaType("text", "event-stream"); + public static final MediaType MULTIPART_FORM_DATA = new MediaType("multipart", "form-data"); + + public static final MediaType APPLICATION_X_WWW_FROM_URLENCODED = + new MediaType("application", "x-www-form-urlencoded"); + + public static final MediaType APPLICATION_XML = new MediaType("application", "xml"); + + public static final MediaType TEXT_PLAIN = new MediaType("text", "plain"); + private final String name; private final String type; diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java new file mode 100644 index 00000000000..601d82c5797 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/CodecUtils.java @@ -0,0 +1,148 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.exception.UnsupportedMediaTypeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.netty.handler.codec.CodecException; + +public class CodecUtils { + + private final FrameworkModel frameworkModel; + + private final List decoders; + + private final List encoders; + + private final Map encoderCache; + + private final Map decoderCache; + + public CodecUtils(FrameworkModel frameworkModel) { + this.encoderCache = new ConcurrentHashMap<>(1); + this.decoderCache = new ConcurrentHashMap<>(1); + this.frameworkModel = frameworkModel; + this.decoders = frameworkModel + .getExtensionLoader(HttpMessageDecoderFactory.class) + .getActivateExtensions(); + this.encoders = frameworkModel + .getExtensionLoader(HttpMessageEncoderFactory.class) + .getActivateExtensions(); + decoders.forEach( + decoderFactory -> decoderCache.put(decoderFactory.mediaType().getName(), decoderFactory)); + encoders.forEach( + encoderFactory -> encoderCache.put(encoderFactory.mediaType().getName(), encoderFactory)); + } + + public HttpMessageDecoder determineHttpMessageDecoder(FrameworkModel frameworkModel, String contentType, URL url) { + return determineHttpMessageDecoderFactory(contentType).createCodec(url, frameworkModel, contentType); + } + + public HttpMessageEncoder determineHttpMessageEncoder(FrameworkModel frameworkModel, HttpHeaders headers, URL url) { + String mediaType = getEncodeMediaType(headers); + return determineHttpMessageEncoderFactory(mediaType).createCodec(url, frameworkModel, mediaType); + } + + public HttpMessageDecoderFactory determineHttpMessageDecoderFactory(String mediaType) { + HttpMessageDecoderFactory factory = decoderCache.computeIfAbsent(mediaType, k -> { + for (HttpMessageDecoderFactory decoderFactory : decoders) { + if (decoderFactory.supports(mediaType)) { + return decoderFactory; + } + } + return new UnsupportedCodecFactory(); + }); + if (factory instanceof UnsupportedCodecFactory) { + throw new UnsupportedMediaTypeException(mediaType); + } + return factory; + } + + public HttpMessageEncoderFactory determineHttpMessageEncoderFactory(String mediaType) { + HttpMessageEncoderFactory factory = encoderCache.computeIfAbsent(mediaType, k -> { + for (HttpMessageEncoderFactory encoderFactory : encoders) { + if (encoderFactory.supports(mediaType)) { + return encoderFactory; + } + } + return new UnsupportedCodecFactory(); + }); + if (factory instanceof UnsupportedCodecFactory) { + throw new UnsupportedMediaTypeException(mediaType); + } + return factory; + } + + public List getDecoders() { + return decoders; + } + + public List getEncoders() { + return encoders; + } + + static class UnsupportedCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + @Override + public MediaType mediaType() { + throw new CodecException(); + } + + @Override + public boolean supports(String mediaType) { + throw new CodecException(); + } + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + throw new CodecException(); + } + } + + public static String getEncodeMediaType(HttpHeaders headers) { + String mediaType = headers.getAccept(); + if (mediaType == null) { + mediaType = headers.getContentType(); + } + return mediaType; + } + + public static ByteArrayOutputStream toByteArrayStream(InputStream in) throws IOException { + ByteArrayOutputStream result = new ByteArrayOutputStream(); + byte[] buffer = new byte[1024]; + int length; + while ((length = in.read(buffer)) != -1) { + result.write(buffer, 0, length); + } + return result; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java similarity index 93% rename from dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonCodec.java rename to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java index 329707a5d55..da38387df0d 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodec.java @@ -14,12 +14,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.remoting.http12.message; +package org.apache.dubbo.remoting.http12.message.codec; -import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.common.utils.JsonUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; import java.io.InputStream; import java.io.OutputStream; @@ -29,20 +30,13 @@ import com.alibaba.fastjson2.JSONObject; -/** - * body is json - */ -@Activate public class JsonCodec implements HttpMessageCodec { - public static final HttpMessageCodec INSTANCE = new JsonCodec(); - @Override - public MediaType contentType() { + public MediaType mediaType() { return MediaType.APPLICATION_JSON_VALUE; } - @Override public void encode(OutputStream outputStream, Object unSerializedBody) throws EncodeException { try { try { @@ -56,7 +50,6 @@ public void encode(OutputStream outputStream, Object unSerializedBody) throws En } } - @Override public void encode(OutputStream outputStream, Object[] data) throws EncodeException { try { try { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java new file mode 100644 index 00000000000..25fdcdb3e65 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonCodecFactory.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +public class JsonCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + private final JsonCodec instance = new JsonCodec(); + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return instance; + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_JSON_VALUE; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonPbCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java similarity index 79% rename from dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonPbCodec.java rename to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java index 6b4a6ff5b84..caac2c3fb46 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonPbCodec.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodec.java @@ -14,13 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.remoting.http12.message; +package org.apache.dubbo.remoting.http12.message.codec; -import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.common.utils.ClassUtils; import org.apache.dubbo.common.utils.MethodUtils; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.MediaType; import java.io.IOException; import java.io.InputStream; @@ -30,25 +29,11 @@ import com.google.protobuf.Message; import com.google.protobuf.util.JsonFormat; -@Activate -public class JsonPbCodec implements HttpMessageCodec { - - private HttpMessageCodec jsonCodec; - - public void setJsonCodec(HttpMessageCodec jsonCodec) { - this.jsonCodec = jsonCodec; - } - - @Override - public MediaType contentType() { - return jsonCodec.contentType(); - } +public class JsonPbCodec extends JsonCodec { @Override - public boolean support(String contentType) { - return HttpMessageCodec.super.support(contentType) - && ClassUtils.isPresent( - "com.google.protobuf.Message", getClass().getClassLoader()); + public MediaType mediaType() { + return MediaType.APPLICATION_JSON_VALUE; } @Override @@ -62,12 +47,12 @@ public void encode(OutputStream outputStream, Object unSerializedBody) throws En } catch (IOException e) { throw new EncodeException(e); } - jsonCodec.encode(outputStream, unSerializedBody); + super.encode(outputStream, unSerializedBody); } @Override public void encode(OutputStream outputStream, Object[] data) throws EncodeException { - jsonCodec.encode(outputStream, data); + super.encode(outputStream, data); } @Override @@ -88,7 +73,7 @@ public Object decode(InputStream body, Class targetType) throws DecodeExcepti } catch (Throwable e) { throw new DecodeException(e); } - return jsonCodec.decode(body, targetType); + return super.decode(body, targetType); } @Override @@ -101,7 +86,7 @@ public Object[] decode(InputStream dataInputStream, Class[] targetTypes) thro } catch (Throwable e) { throw new DecodeException(e); } - return jsonCodec.decode(dataInputStream, targetTypes); + return super.decode(dataInputStream, targetTypes); } private static boolean isProtobuf(Class targetType) { diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonPbCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodecFactory.java similarity index 56% rename from dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonPbCodecFactory.java rename to dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodecFactory.java index bc82ce9ca07..e4b01f06dc8 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/JsonPbCodecFactory.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/JsonPbCodecFactory.java @@ -14,36 +14,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.apache.dubbo.remoting.http12.message; +package org.apache.dubbo.remoting.http12.message.codec; import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Activate; -import org.apache.dubbo.common.utils.ClassUtils; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.model.FrameworkModel; -@Activate(order = -100) -public class JsonPbCodecFactory implements HttpMessageCodecFactory { +@Activate +public class JsonPbCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { - @Override - public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel) { - HttpMessageCodec codec = frameworkModel - .getExtensionLoader(HttpMessageCodecFactory.class) - .getExtension(JsonCodecFactory.NAME) - .createCodec(url, frameworkModel); - JsonPbCodec jsonPbCodec = new JsonPbCodec(); - jsonPbCodec.setJsonCodec(codec); - return jsonPbCodec; - } + private final JsonPbCodec instance = new JsonPbCodec(); @Override - public MediaType contentType() { - return MediaType.APPLICATION_JSON_VALUE; + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return instance; } @Override - public boolean support(String contentType) { - return HttpMessageCodecFactory.super.support(contentType) - && ClassUtils.isPresent( - "com.google.protobuf.Message", getClass().getClassLoader()); + public MediaType mediaType() { + return MediaType.APPLICATION_JSON_VALUE; } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java new file mode 100644 index 00000000000..560e5db1a78 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoder.java @@ -0,0 +1,269 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.utils.StringUtils; +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class MultipartDecoder implements HttpMessageDecoder { + + private final URL url; + + private final FrameworkModel frameworkModel; + + private final String headerContentType; + + private final CodecUtils codecUtils; + + private static final String CRLF = "\r\n"; + + public MultipartDecoder(URL url, FrameworkModel frameworkModel, String contentType, CodecUtils codecUtils) { + this.url = url; + this.frameworkModel = frameworkModel; + this.headerContentType = contentType; + this.codecUtils = codecUtils; + } + + @Override + public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + Object[] res = decode(inputStream, new Class[] {targetType}); + return res.length > 1 ? res : res[0]; + } + + @Override + public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + try { + List parts = transferToParts(inputStream, headerContentType); + if (parts.size() != targetTypes.length) { + throw new DecodeException("The number of method parameters and multipart request bodies are different"); + } + Object[] res = new Object[parts.size()]; + + for (int i = 0; i < parts.size(); i++) { + Part part = parts.get(i); + + if (Byte[].class.equals(targetTypes[i]) || byte[].class.equals(targetTypes[i])) { + res[i] = part.content; + continue; + } + res[i] = codecUtils + .determineHttpMessageDecoder(frameworkModel, part.headers.getContentType(), url) + .decode(new ByteArrayInputStream(part.content), targetTypes[i]); + } + return res; + } catch (IOException ioException) { + throw new DecodeException("Decode multipart body failed:" + ioException.getMessage()); + } + } + + private List transferToParts(InputStream inputStream, String contentType) throws IOException { + String boundary = getBoundaryFromContentType(contentType); + if (StringUtils.isEmpty(boundary)) { + throw new DecodeException("Invalid boundary in Content-Type: " + contentType); + } + + final String delimiter = "--" + boundary; + + List parts = new ArrayList<>(); + boolean endOfStream = false; + + while (!endOfStream) { + ByteArrayOutputStream partData = new ByteArrayOutputStream(); + HttpHeaders headers = new HttpHeaders(); + + endOfStream = readPart(inputStream, delimiter, headers, partData); + + if (partData.size() > 0) { + parts.add(new Part(partData.toByteArray(), headers)); + } + } + + return parts; + } + + private String getBoundaryFromContentType(String contentType) { + String[] parts = contentType.split(";"); + for (String part : parts) { + part = part.trim(); + if (part.startsWith("boundary=")) { + return part.substring("boundary=".length()).trim(); + } + } + return null; + } + + private boolean readPart( + InputStream inputStream, String delimiter, HttpHeaders headers, ByteArrayOutputStream partData) + throws IOException { + // read and parse headers + if (readHeaders(inputStream, headers, delimiter)) { + // end of stream + return true; + } + return readBody(inputStream, delimiter, partData); + } + + private boolean readHeaders(InputStream inputStream, HttpHeaders httpHeaders, String delimiter) throws IOException { + + StringBuilder fullHeaderBuilder = new StringBuilder(); + String fullHeader = null; + byte[] buffer = new byte[128]; + int len; + boolean headerEnd = false; + boolean streamEnd = true; + final String endOfHeaderSign = CRLF + CRLF; + + byte[] delimiterBuffer = new byte[delimiter.length()]; + if (inputStream.read(delimiterBuffer) == -1) { + return true; + } + String readDelimiter = new String(delimiterBuffer, StandardCharsets.US_ASCII); + if (!Objects.equals(readDelimiter, delimiter)) { + throw new DecodeException("Multipart body boundary are different from header"); + } + while (!headerEnd) { + + inputStream.mark(Integer.MAX_VALUE); + + len = inputStream.read(buffer); + if (len == -1) { + break; + } + + // read to 2*CRLF (end of header) + String currentString = new String(buffer, 0, len, StandardCharsets.UTF_8); + fullHeaderBuilder.append(currentString); + + // check if currentString contains CRLF + int endIndex; + if ((endIndex = fullHeaderBuilder.indexOf(endOfHeaderSign)) != -1) { + // make stream reset to body start of current part + inputStream.reset(); + if (inputStream.skip(endIndex + endOfHeaderSign.length()) == endIndex + endOfHeaderSign.length()) { + streamEnd = false; + } + headerEnd = true; + fullHeader = fullHeaderBuilder.substring(delimiter.length(), endIndex); + } + } + if (streamEnd && !headerEnd) { + throw new DecodeException("Broken request: cannot found multipart body header end"); + } + + parseHeaderLine(httpHeaders, fullHeader.split(CRLF)); + + if (httpHeaders.getContentType() == null) { + httpHeaders.put(HttpHeaderNames.CONTENT_TYPE.getName(), Collections.singletonList("text/plain")); + } + + return streamEnd; + } + + private void parseHeaderLine(HttpHeaders headers, String[] headerLines) { + for (String headerLine : headerLines) { + int colonIndex = headerLine.indexOf(':'); + if (colonIndex != -1) { + String name = headerLine.substring(0, colonIndex).trim(); + String value = headerLine.substring(colonIndex + 1).trim(); + headers.put(name, Collections.singletonList(value)); + } + } + } + + private boolean readBody(InputStream inputStream, String delimiter, ByteArrayOutputStream partData) + throws IOException { + byte[] buffer = new byte[256]; + int len; + + while (true) { + inputStream.mark(Integer.MAX_VALUE); + len = inputStream.read(buffer); + if (len == -1) { + return true; + } + String currentString = new String(buffer, 0, len, StandardCharsets.US_ASCII); + if (currentString.contains(delimiter)) { + int indexOfDelimiter = currentString.indexOf(delimiter); + + // skip the CRLF of data tail + byte[] toWrite = new byte[indexOfDelimiter - 2]; + System.arraycopy(buffer, 0, toWrite, 0, indexOfDelimiter - 2); + partData.write(toWrite); + + // check end delimiter (--\r\n) to determine if this part is the last body part + // for compatibility with non-standard clients, we won't check the last CRLF + if (currentString.length() > indexOfDelimiter + delimiter.length() + 1 + && currentString.charAt(indexOfDelimiter + delimiter.length()) == '-' + && currentString.charAt(indexOfDelimiter + delimiter.length() + 1) == '-') { + return true; + } + + // read from stream to check end delimiter + else if (currentString.length() <= indexOfDelimiter + delimiter.length() + 1) { + byte[] endDelimiter = new byte[2]; + if (inputStream.read(endDelimiter) != 2) { + throw new DecodeException("Boundary end is incomplete"); + } + if (endDelimiter[0] == '-' && endDelimiter[1] == '-') { + return true; + } else { + inputStream.reset(); + inputStream.skip(toWrite.length + 2); + } + } else { + inputStream.reset(); + inputStream.skip(toWrite.length + 2); + } + return false; + } + partData.write(buffer, 0, len); + } + } + + @Override + public MediaType mediaType() { + return MediaType.MULTIPART_FORM_DATA; + } + + private static class Part { + + private final byte[] content; + + private final HttpHeaders headers; + + public Part(byte[] content, HttpHeaders headers) { + this.content = content; + this.headers = headers; + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java new file mode 100644 index 00000000000..2e55e0129fa --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/MultipartDecoderFactory.java @@ -0,0 +1,44 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +@Activate +public class MultipartDecoderFactory implements HttpMessageDecoderFactory { + + private CodecUtils codecUtils; + + public void setCodecUtils(CodecUtils codecUtils) { + this.codecUtils = codecUtils; + } + + @Override + public HttpMessageDecoder createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return new MultipartDecoder(url, frameworkModel, mediaType, codecUtils); + } + + @Override + public MediaType mediaType() { + return MediaType.MULTIPART_FORM_DATA; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java new file mode 100644 index 00000000000..3516c749dc1 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodec.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; + +public class PlainTextCodec implements HttpMessageCodec { + + private final String contentType; + + public PlainTextCodec(String contentType) { + this.contentType = contentType; + } + + @Override + public void encode(OutputStream outputStream, Object data) throws EncodeException { + if (!(data instanceof String)) { + throw new EncodeException("PlainText media-type only supports String as return type."); + } + try { + outputStream.write(((String) data).getBytes()); + } catch (IOException e) { + throw new EncodeException(e); + } + } + + @Override + public MediaType mediaType() { + return MediaType.TEXT_PLAIN; + } + + @Override + public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + try { + if (!String.class.equals(targetType)) { + throw new DecodeException("Plain text content only supports String as method param."); + } + Charset charset; + if (contentType.contains("charset=")) { + try { + charset = Charset.forName(contentType.substring(contentType.indexOf("charset=") + 8)); + } catch (Exception e) { + throw new DecodeException("Unsupported charset:" + e.getMessage()); + } + if (!charset.equals(StandardCharsets.UTF_8) && !charset.equals(StandardCharsets.US_ASCII)) { + String origin = CodecUtils.toByteArrayStream(inputStream).toString(charset.name()); + return new String(origin.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8); + } + } + return CodecUtils.toByteArrayStream(inputStream).toString(StandardCharsets.UTF_8.name()); + } catch (Exception e) { + throw new DecodeException(e); + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java new file mode 100644 index 00000000000..b8c400a7a75 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/PlainTextCodecFactory.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.extension.Activate; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +@Activate +public class PlainTextCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return new PlainTextCodec(mediaType); + } + + @Override + public MediaType mediaType() { + return MediaType.TEXT_PLAIN; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java new file mode 100644 index 00000000000..2e16b6d3828 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodec.java @@ -0,0 +1,128 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.convert.ConverterUtil; +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URLDecoder; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class UrlEncodeFormCodec implements HttpMessageCodec { + + private final ConverterUtil converterUtil; + + public UrlEncodeFormCodec(ConverterUtil converterUtil) { + this.converterUtil = converterUtil; + } + + @Override + public void encode(OutputStream outputStream, Object data) throws EncodeException { + try { + if (data instanceof String) { + outputStream.write(((String) data).getBytes()); + } else if (data instanceof Map) { + StringBuilder toWrite = new StringBuilder(); + for (Map.Entry e : ((Map) data).entrySet()) { + String k = e.getKey().toString(); + String v = e.getValue().toString(); + toWrite.append(k).append("=").append(v).append("&"); + } + if (toWrite.length() > 1) { + outputStream.write( + toWrite.substring(0, toWrite.length() - 1).getBytes()); + } + } else { + throw new EncodeException("UrlEncodeFrom media-type only supports String or Map as return type."); + } + } catch (Exception e) { + throw new EncodeException(e); + } + } + + @Override + public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + Object[] res = decode(inputStream, new Class[] {targetType}); + return res.length > 1 ? res : res[0]; + } + + @Override + public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { + try { + boolean toMap; + // key=value&key2=value2 -> method(map) + if (targetTypes.length == 1 && targetTypes[0].isAssignableFrom(HashMap.class)) { + toMap = true; + } + // key=value&key2=value2 -> method(value,value2) + else if (Arrays.stream(targetTypes) + .allMatch(clz -> String.class.isAssignableFrom(clz) || Number.class.isAssignableFrom(clz))) { + toMap = false; + } else { + throw new DecodeException( + "For x-www-form-urlencoded MIME type, please use Map/String/base-types as method param."); + } + String decoded = URLDecoder.decode( + CodecUtils.toByteArrayStream(inputStream).toString(), StandardCharsets.UTF_8.name()) + .trim(); + Map res = toMap(decoded, targetTypes, toMap); + if (toMap) { + return new Object[] {res}; + } else { + return res.values().toArray(); + } + } catch (Exception e) { + throw new DecodeException(e); + } + } + + private Map toMap(String formString, Class[] targetTypes, boolean toMap) { + Map res = new HashMap<>(1); + // key1=val1&key2=&key3=&key4=val4 + String[] parts = formString.split("&"); + for (int i = 0; i < parts.length; i++) { + String pair = parts[i]; + int index = pair.indexOf("="); + if (index < 1) { + throw new DecodeException("Broken request:" + formString); + } + String key = pair.substring(0, index); + String val = (index == pair.length() - 1) ? "" : pair.substring(index + 1); + res.put( + key, + toMap || targetTypes[i].equals(String.class) + // method params are Map or String, use plain text as value + ? val + // try convert to target types + : converterUtil.convertIfPossible(val, targetTypes[i])); + } + return res; + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_X_WWW_FROM_URLENCODED; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java new file mode 100644 index 00000000000..0b657ffd09b --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/UrlEncodeFormCodecFactory.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.common.convert.ConverterUtil; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +public class UrlEncodeFormCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + private final ConverterUtil converterUtil; + + private final UrlEncodeFormCodec instance; + + public UrlEncodeFormCodecFactory(FrameworkModel frameworkModel) { + this.converterUtil = frameworkModel.getBeanFactory().getBean(ConverterUtil.class); + this.instance = new UrlEncodeFormCodec(this.converterUtil); + } + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return instance; + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_X_WWW_FROM_URLENCODED; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java new file mode 100644 index 00000000000..9d2bd8f0204 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodec.java @@ -0,0 +1,74 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.exception.EncodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.MediaType; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.parsers.SAXParserFactory; +import javax.xml.transform.Source; +import javax.xml.transform.sax.SAXSource; + +import java.io.InputStream; +import java.io.OutputStream; +import java.io.StringReader; + +import org.xml.sax.InputSource; + +public class XmlCodec implements HttpMessageCodec { + + @Override + public void encode(OutputStream outputStream, Object data) throws EncodeException { + try { + Marshaller marshaller = JAXBContext.newInstance(data.getClass()).createMarshaller(); + marshaller.marshal(data, outputStream); + } catch (Exception e) { + throw new EncodeException(e); + } + } + + @Override + public Object decode(InputStream inputStream, Class targetType) throws DecodeException { + try { + SAXParserFactory spf = SAXParserFactory.newInstance(); + spf.setFeature("http://xml.org/sax/features/external-general-entities", false); + spf.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + spf.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + + // Do unmarshall operation + Source xmlSource = new SAXSource( + spf.newSAXParser().getXMLReader(), + new InputSource(new StringReader( + CodecUtils.toByteArrayStream(inputStream).toString()))); + JAXBContext context = JAXBContext.newInstance(targetType); + Unmarshaller unmarshaller = context.createUnmarshaller(); + return unmarshaller.unmarshal(xmlSource); + } catch (Exception e) { + throw new DecodeException(e); + } + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_XML; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java new file mode 100644 index 00000000000..3738c45cfb8 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/message/codec/XmlCodecFactory.java @@ -0,0 +1,39 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.common.URL; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +public class XmlCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { + + private final HttpMessageCodec instance = new XmlCodec(); + + @Override + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { + return instance; + } + + @Override + public MediaType mediaType() { + return MediaType.APPLICATION_XML; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java index 77d77248538..c43fa7cab60 100644 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/java/org/apache/dubbo/remoting/http12/netty4/h1/NettyHttp1ConnectionHandler.java @@ -27,10 +27,9 @@ import org.apache.dubbo.remoting.http12.h1.Http1ServerChannelObserver; import org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListener; import org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListenerFactory; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory; +import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.model.FrameworkModel; -import java.util.List; import java.util.concurrent.Executor; import io.netty.channel.ChannelHandlerContext; @@ -46,6 +45,8 @@ public class NettyHttp1ConnectionHandler extends SimpleChannelInboundHandler { try { http1TransportListener.onMetadata(http1Request); @@ -102,24 +106,14 @@ private Http1ServerTransportListener initTransportListenerIfNecessary( if (!StringUtils.hasText(contentType)) { throw new UnsupportedMediaTypeException(contentType); } - HttpMessageCodecFactory codecFactory = findSuitableCodec( - contentType, - frameworkModel.getExtensionLoader(HttpMessageCodecFactory.class).getActivateExtensions()); - if (codecFactory == null) { - throw new UnsupportedMediaTypeException(contentType); - } - this.errorResponseObserver = new Http1ServerChannelObserver(new NettyHttp1Channel(ctx.channel())); - this.errorResponseObserver.setHttpMessageCodec(codecFactory.createCodec(url, frameworkModel)); + // check ContentType + codecUtils.determineHttpMessageDecoder(frameworkModel, headers.getContentType(), url); return http1TransportListener; } - private static HttpMessageCodecFactory findSuitableCodec( - String contentType, List candidates) { - for (HttpMessageCodecFactory factory : candidates) { - if (factory.support(contentType)) { - return factory; - } - } - return null; + private void initErrorResponseObserver(ChannelHandlerContext ctx, Http1Request request) { + this.errorResponseObserver = new Http1ServerChannelObserver(new NettyHttp1Channel(ctx.channel())); + this.errorResponseObserver.setResponseEncoder( + codecUtils.determineHttpMessageEncoder(frameworkModel, request.headers(), url)); } } diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec deleted file mode 100644 index 10f9ef3dd98..00000000000 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec +++ /dev/null @@ -1 +0,0 @@ -json=org.apache.dubbo.remoting.http12.message.JsonCodec diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory deleted file mode 100644 index 0fcf5fb19ac..00000000000 --- a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory +++ /dev/null @@ -1,2 +0,0 @@ -json=org.apache.dubbo.remoting.http12.message.JsonCodecFactory -jsonpb=org.apache.dubbo.remoting.http12.message.JsonPbCodecFactory diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory new file mode 100644 index 00000000000..5637aa59753 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory @@ -0,0 +1,5 @@ +jsonpb=org.apache.dubbo.remoting.http12.message.codec.JsonPbCodecFactory +multipart=org.apache.dubbo.remoting.http12.message.codec.MultipartDecoderFactory +plaintext=org.apache.dubbo.remoting.http12.message.codec.PlainTextCodecFactory +xml=org.apache.dubbo.remoting.http12.message.codec.XmlCodecFactory +urlencoded=org.apache.dubbo.remoting.http12.message.codec.UrlEncodeFormCodecFactory \ No newline at end of file diff --git a/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory new file mode 100644 index 00000000000..2243b9e0322 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory @@ -0,0 +1,4 @@ +jsonpb=org.apache.dubbo.remoting.http12.message.codec.JsonPbCodecFactory +plaintext=org.apache.dubbo.remoting.http12.message.codec.PlainTextCodecFactory +xml=org.apache.dubbo.remoting.http12.message.codec.XmlCodecFactory +urlencoded=org.apache.dubbo.remoting.http12.message.codec.UrlEncodeFormCodecFactory \ No newline at end of file diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java new file mode 100644 index 00000000000..b8436c1c171 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodeUtilsTest.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.remoting.http12.HttpHeaderNames; +import org.apache.dubbo.remoting.http12.HttpHeaders; +import org.apache.dubbo.remoting.http12.exception.UnsupportedMediaTypeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; +import org.apache.dubbo.remoting.http12.message.MediaType; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import java.util.Collections; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +public class CodeUtilsTest { + + @Test + void testDetermineHttpCodec() { + CodecUtils codecUtils = new CodecUtils(FrameworkModel.defaultModel()); + HttpHeaders headers = new HttpHeaders(); + headers.put( + HttpHeaderNames.CONTENT_TYPE.getName(), + Collections.singletonList(MediaType.APPLICATION_JSON_VALUE.getName())); + HttpMessageDecoder decoder = + codecUtils.determineHttpMessageDecoder(FrameworkModel.defaultModel(), headers.getContentType(), null); + Assertions.assertNotNull(decoder); + Assertions.assertEquals(JsonPbCodec.class, decoder.getClass()); + + HttpMessageEncoder encoder; + // If no Accept header provided, use Content-Type to find encoder + encoder = codecUtils.determineHttpMessageEncoder(FrameworkModel.defaultModel(), headers, null); + Assertions.assertNotNull(encoder); + Assertions.assertEquals(JsonPbCodec.class, encoder.getClass()); + + HttpHeaders headers1 = new HttpHeaders(); + headers1.put( + HttpHeaderNames.CONTENT_TYPE.getName(), + Collections.singletonList(MediaType.MULTIPART_FORM_DATA.getName())); + decoder = + codecUtils.determineHttpMessageDecoder(FrameworkModel.defaultModel(), headers1.getContentType(), null); + Assertions.assertNotNull(decoder); + Assertions.assertEquals(MultipartDecoder.class, decoder.getClass()); + Assertions.assertThrows( + UnsupportedMediaTypeException.class, + () -> codecUtils.determineHttpMessageEncoder(FrameworkModel.defaultModel(), headers1, null)); + + headers1.put( + HttpHeaderNames.ACCEPT.getName(), + Collections.singletonList(MediaType.APPLICATION_JSON_VALUE.getName())); + encoder = codecUtils.determineHttpMessageEncoder(FrameworkModel.defaultModel(), headers1, null); + Assertions.assertNotNull(encoder); + Assertions.assertEquals(JsonPbCodec.class, encoder.getClass()); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java new file mode 100644 index 00000000000..f36f6cc76fe --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/CodecTest.java @@ -0,0 +1,399 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import org.apache.dubbo.remoting.http12.exception.DecodeException; +import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; +import org.apache.dubbo.rpc.model.FrameworkModel; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.Map; +import java.util.Random; + +import com.google.common.base.Charsets; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +public class CodecTest { + + final String MULTIPART_SAMPLE_1 = "--example-part-boundary\r\n" + + "Content-Disposition: form-data; name=\"username\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "LuYue\r\n" + + "--example-part-boundary\r\n" + + "Content-Disposition: form-data; name=\"userdetail\"\r\n" + + "Content-Type: application/json\r\n" + + "\r\n" + + "{\"location\":\"beijing\",\"username\":\"LuYue\"}\r\n" + + "--example-part-boundary\r\n" + + "Content-Disposition: form-data; name=\"userimg\"; filename=\"user.jpeg\"\r\n" + + "Content-Type: image/jpeg\r\n" + + "\r\n" + + "\r\n" + + "--example-part-boundary--\r\n"; + + final String MULTIPART_SAMPLE_2 = "--boundary123\r\n" + "Content-Disposition: form-data; name=\"text\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "simple text\r\n" + + "--boundary123\r\n" + + "Content-Disposition: form-data; name=\"file\"; filename=\"example.txt\"\r\n" + + "Content-Type: text/plain\r\n" + + "\r\n" + + "This is the content of the file.\r\n" + + "--boundary123--\r\n"; + + final String MULTIPART_SAMPLE_3 = "--boundaryABC\r\n" + "Content-Disposition: form-data; name=\"someContent\"\r\n" + + "\r\n" + + "这是一些中文内容\r\n" + + "--boundaryABC\r\n" + + "Content-Disposition: form-data; name=\"emoji\"\r\n" + + "\r\n" + + "\uD83D\uDE0A\r\n" + + "--boundaryABC--"; + + final String MULTIPART_SAMPLE_4 = "--longValue\r\n" + "Content-Disposition: form-data; name=\"long\"\r\n" + + "\r\n" + + "This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.\r\n" + + "--longValue--\r\n"; + + final String MULTIPART_SAMPLE_5 = "--specialChar\r\n" + "Content-Disposition: form-data; name=\"special\"\r\n" + + "\r\n" + + "Line 1\n" + + "Line 2\r\n" + + "--Line 3--\n" + + "Line 4\n\r\n" + + "--specialChar--"; + + CodecUtils codecUtils; + + @BeforeEach + void beforeAll() { + codecUtils = FrameworkModel.defaultModel().getBeanFactory().getOrRegisterBean(CodecUtils.class); + } + + @Test + void testMultipartForm1() { + InputStream in = new ByteArrayInputStream(MULTIPART_SAMPLE_1.getBytes()); + HttpMessageDecoder decoder = new MultipartDecoder( + null, FrameworkModel.defaultModel(), "multipart/form-data; boundary=example-part-boundary", codecUtils); + Object[] result = decoder.decode(in, new Class[] {String.class, User.class, byte[].class}); + Assertions.assertEquals("LuYue", result[0]); + Assertions.assertTrue(result[1] instanceof User); + Assertions.assertEquals("LuYue", ((User) result[1]).getUsername()); + Assertions.assertEquals("beijing", ((User) result[1]).getLocation()); + Assertions.assertEquals("", new String((byte[]) result[2], Charsets.UTF_8)); + } + + @Test + void testMultipartForm2() { + InputStream in = new ByteArrayInputStream(MULTIPART_SAMPLE_2.getBytes()); + HttpMessageDecoder decoder = new MultipartDecoder( + null, FrameworkModel.defaultModel(), "multipart/form-data; boundary=boundary123", codecUtils); + Object[] result = decoder.decode(in, new Class[] {String.class, byte[].class}); + Assertions.assertEquals("simple text", result[0]); + Assertions.assertEquals( + "This is the content of the file.", new String((byte[]) result[1], StandardCharsets.US_ASCII)); + } + + @Test + void testMultipartForm3() { + InputStream in = new ByteArrayInputStream(MULTIPART_SAMPLE_3.getBytes()); + HttpMessageDecoder decoder = new MultipartDecoder( + null, FrameworkModel.defaultModel(), "multipart/form-data; boundary=boundaryABC", codecUtils); + Object[] result = decoder.decode(in, new Class[] {String.class, String.class}); + Assertions.assertEquals("这是一些中文内容", result[0]); + Assertions.assertEquals("😊", result[1]); + } + + @Test + void testMultipartForm4() { + InputStream in = new ByteArrayInputStream(MULTIPART_SAMPLE_4.getBytes()); + HttpMessageDecoder decoder = new MultipartDecoder( + null, FrameworkModel.defaultModel(), "multipart/form-data; boundary=longValue", codecUtils); + Object[] result = decoder.decode(in, new Class[] {String.class}); + Assertions.assertEquals( + "This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.This is a really long value that continues for many lines.", + result[0]); + } + + @Test + void testMultipartForm5() { + InputStream in = new ByteArrayInputStream(MULTIPART_SAMPLE_5.getBytes()); + HttpMessageDecoder decoder = new MultipartDecoder( + null, FrameworkModel.defaultModel(), "multipart/form-data; boundary=specialChar", codecUtils); + Object[] result = decoder.decode(in, new Class[] {String.class}); + Assertions.assertEquals("Line 1\n" + "Line 2\r\n" + "--Line 3--\n" + "Line 4\n", result[0]); + } + + @Test + void testMultipartFormBodyBoundary() { + // check if codec can handle boundary correctly when the end delimiter just beyond the buffer. + // header buffer size: 128; body buffer size: 256 + // --example-boundary\r\n [20bytes] + // Content-Type: plain/text [paddings]\r\n\r\n [108bytes] + // body1r\n [238bytes, binary data] + // --example-boundary--\r\n [22bytes] , last --\r\n [4bytes] beyond buffer + byte[] boundary = "--example-boundary\r\n".getBytes(); + byte[] header = "Content-Type: plain/text".getBytes(); + byte[] headerPadding = new byte[128 - header.length - boundary.length - "\r\n\r\n".length()]; + byte[] headerBytes = new byte[128]; + byte[] body1 = new byte[238]; + byte[] end = "--example-boundary--\r\n".getBytes(); + byte[] bodyWithEnd = new byte[260]; + + Random random = new Random(); + random.nextBytes(body1); + body1[236] = '\r'; + body1[237] = '\n'; + Arrays.fill(headerPadding, (byte) 0); + + System.arraycopy(boundary, 0, headerBytes, 0, boundary.length); + System.arraycopy(header, 0, headerBytes, boundary.length, header.length); + + System.arraycopy(headerPadding, 0, headerBytes, boundary.length + header.length, headerPadding.length); + System.arraycopy( + "\r\n\r\n".getBytes(), + 0, + headerBytes, + boundary.length + header.length + headerPadding.length, + "\r\n\r\n".length()); + System.arraycopy(body1, 0, bodyWithEnd, 0, body1.length); + System.arraycopy(end, 0, bodyWithEnd, body1.length, end.length); + + byte[] fullRequestBody = new byte[256 + 128 + 4]; + System.arraycopy(headerBytes, 0, fullRequestBody, 0, headerBytes.length); + System.arraycopy(bodyWithEnd, 0, fullRequestBody, headerBytes.length, bodyWithEnd.length); + + HttpMessageDecoder decoder = new MultipartDecoderFactory() + .createCodec(null, FrameworkModel.defaultModel(), "multipart/form-data; boundary=example-boundary"); + byte[] res = (byte[]) decoder.decode(new ByteArrayInputStream(fullRequestBody), byte[].class); + + for (int k = 0; k < body1.length - 2; k++) { + Assertions.assertEquals(body1[k], res[k]); + } + } + + @Test + void testMultipartFormBodyBoundary2() { + // check if codec can handle boundary correctly when the end delimiter just beyond the buffer. + // header buffer size: 128; body buffer size: 256 + // --example-boundary-\r\n [21bytes] + // Content-Type: plain/text [paddings]\r\n\r\n [107bytes] + // body1r\n [237bytes, binary data] + // --example-boundary-\r\n [21bytes] , \r\n [2bytes] beyond buffer + // body2\r\n [7bytes, text data] + // --example-boundary--- [23bytes] + byte[] boundary = "--example-boundary-\r\n".getBytes(); + byte[] subHeader = "Content-Type: plain/text".getBytes(); + byte[] subHeaderWithCRLF = "Content-Type: plain/text\r\n\r\n".getBytes(); + byte[] headerPadding = new byte[128 - subHeader.length - boundary.length - "\r\n\r\n".length()]; + byte[] headerBytes = new byte[128]; + byte[] body1 = new byte[237]; + byte[] body1WithEnd = new byte[body1.length + boundary.length]; + byte[] body2 = "body2\r\n".getBytes(); + byte[] end = "--example-boundary---\r\n".getBytes(); + + Random random = new Random(); + random.nextBytes(body1); + body1[body1.length - 1] = '\n'; + body1[body1.length - 2] = '\r'; + Arrays.fill(headerPadding, (byte) 0); + + System.arraycopy(boundary, 0, headerBytes, 0, boundary.length); + System.arraycopy(subHeader, 0, headerBytes, boundary.length, subHeader.length); + System.arraycopy(headerPadding, 0, headerBytes, boundary.length + subHeader.length, headerPadding.length); + System.arraycopy( + "\r\n\r\n".getBytes(), + 0, + headerBytes, + boundary.length + subHeader.length + headerPadding.length, + "\r\n\r\n".length()); + System.arraycopy(body1, 0, body1WithEnd, 0, body1.length); + System.arraycopy(boundary, 0, body1WithEnd, body1.length, boundary.length); + + byte[] fullRequestBody = new byte + [headerBytes.length + body1WithEnd.length + subHeaderWithCRLF.length + body2.length + end.length]; + System.arraycopy(headerBytes, 0, fullRequestBody, 0, headerBytes.length); + System.arraycopy(body1WithEnd, 0, fullRequestBody, headerBytes.length, body1WithEnd.length); + System.arraycopy( + subHeaderWithCRLF, + 0, + fullRequestBody, + headerBytes.length + body1WithEnd.length, + subHeaderWithCRLF.length); + System.arraycopy( + body2, + 0, + fullRequestBody, + headerBytes.length + body1WithEnd.length + subHeaderWithCRLF.length, + body2.length); + System.arraycopy( + end, + 0, + fullRequestBody, + headerBytes.length + body1WithEnd.length + subHeaderWithCRLF.length + body2.length, + end.length); + + HttpMessageDecoder decoder = new MultipartDecoder( + null, FrameworkModel.defaultModel(), "multipart/form-data; boundary=example-boundary-", codecUtils); + Object[] r = + decoder.decode(new ByteArrayInputStream(fullRequestBody), new Class[] {byte[].class, String.class}); + byte[] res = (byte[]) r[0]; + for (int k = 0; k < body1.length - 2; k++) { + Assertions.assertEquals(body1[k], res[k]); + } + String res2 = (String) r[1]; + Assertions.assertEquals("body2", res2); + } + + @Test + void testUrlForm() { + String content = "Hello=World&Apache=Dubbo&id=10086"; + InputStream in = new ByteArrayInputStream(content.getBytes()); + UrlEncodeFormCodecFactory factory = new UrlEncodeFormCodecFactory(FrameworkModel.defaultModel()); + HttpMessageCodec codec = factory.createCodec(null, FrameworkModel.defaultModel(), null); + Object res = codec.decode(in, Map.class); + Assertions.assertTrue(res instanceof Map); + Map r = (Map) res; + Assertions.assertEquals("World", r.get("Hello")); + Assertions.assertEquals("Dubbo", r.get("Apache")); + Assertions.assertEquals("10086", r.get("id")); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + codec.encode(outputStream, r); + Assertions.assertEquals(content, outputStream.toString()); + try { + in.reset(); + } catch (IOException e) { + } + Object[] res2 = codec.decode(in, new Class[] {String.class, String.class, Long.class}); + Assertions.assertEquals("World", res2[0]); + Assertions.assertEquals("Dubbo", res2[1]); + Assertions.assertEquals(10086L, res2[2]); + outputStream = new ByteArrayOutputStream(); + codec.encode(outputStream, content); + Assertions.assertEquals(content, outputStream.toString()); + } + + @Test + void testUrlForm2() { + InputStream in = new ByteArrayInputStream("Hello=World&Apache=Dubbo&empty1=&empty2=".getBytes()); + HttpMessageCodec codec = new UrlEncodeFormCodecFactory(FrameworkModel.defaultModel()) + .createCodec(null, FrameworkModel.defaultModel(), null); + Object res = codec.decode(in, Map.class); + Assertions.assertTrue(res instanceof Map); + Map r = (Map) res; + Assertions.assertEquals("World", r.get("Hello")); + Assertions.assertEquals("Dubbo", r.get("Apache")); + Assertions.assertEquals("", r.get("empty1")); + Assertions.assertEquals("", r.get("empty2")); + } + + @Test + void testUrlForm3() { + InputStream in = new ByteArrayInputStream("empty1=&empty2=&Hello=world&empty3=&Apache=dubbo&".getBytes()); + HttpMessageCodec codec = new UrlEncodeFormCodecFactory(FrameworkModel.defaultModel()) + .createCodec(null, FrameworkModel.defaultModel(), null); + Object res = codec.decode(in, Map.class); + Assertions.assertTrue(res instanceof Map); + Map r = (Map) res; + Assertions.assertEquals("world", r.get("Hello")); + Assertions.assertEquals("dubbo", r.get("Apache")); + Assertions.assertEquals("", r.get("empty1")); + Assertions.assertEquals("", r.get("empty2")); + Assertions.assertEquals("", r.get("empty3")); + } + + @Test + void testUrlForm4() { + InputStream in = new ByteArrayInputStream("empty1=&empty2=&Hello=world&你好=世界&empty3=&Apache=dubbo&".getBytes()); + HttpMessageCodec codec = new UrlEncodeFormCodecFactory(FrameworkModel.defaultModel()) + .createCodec(null, FrameworkModel.defaultModel(), null); + Object res = codec.decode(in, Map.class); + Assertions.assertTrue(res instanceof Map); + Map r = (Map) res; + Assertions.assertEquals("world", r.get("Hello")); + Assertions.assertEquals("dubbo", r.get("Apache")); + Assertions.assertEquals("", r.get("empty1")); + Assertions.assertEquals("", r.get("empty2")); + Assertions.assertEquals("", r.get("empty3")); + Assertions.assertEquals("世界", r.get("你好")); + } + + @Test + void testXml() { + String content = "" + + "New YorkJohnDoe"; + InputStream in = new ByteArrayInputStream(content.getBytes()); + HttpMessageCodec codec = new XmlCodecFactory().createCodec(null, FrameworkModel.defaultModel(), null); + User user = (User) codec.decode(in, User.class); + Assertions.assertEquals("JohnDoe", user.getUsername()); + Assertions.assertEquals("New York", user.getLocation()); + + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + codec.encode(outputStream, user); + String res = outputStream.toString(); + Assertions.assertEquals(content, res); + } + + @Test + void testPlainText() { + byte[] asciiBytes = new byte[] { + 0x48, 0x65, 0x6C, 0x6C, + 0x6F, 0x2C, 0x20, 0x77, + 0x6F, 0x72, 0x6C, 0x64 + }; + byte[] utf8Bytes = new byte[] { + (byte) 0xE4, (byte) 0xBD, (byte) 0xA0, + (byte) 0xE5, (byte) 0xA5, (byte) 0xBD, + (byte) 0xEF, (byte) 0xBC, (byte) 0x8C, + (byte) 0xE4, (byte) 0xB8, (byte) 0x96, + (byte) 0xE7, (byte) 0x95, (byte) 0x8C + }; + byte[] utf16Bytes = new byte[] {0x4F, 0x60, 0x59, 0x7D, (byte) 0xFF, 0x0C, 0x4E, 0x16, 0x75, 0x4C}; + InputStream in = new ByteArrayInputStream(asciiBytes); + HttpMessageCodec codec = new PlainTextCodecFactory() + .createCodec(null, FrameworkModel.defaultModel(), "text/plain; charset=ASCII"); + String res = (String) codec.decode(in, String.class); + Assertions.assertEquals("Hello, world", res); + + in = new ByteArrayInputStream(utf8Bytes); + codec = new PlainTextCodec("text/plain; charset=UTF-8"); + res = (String) codec.decode(in, String.class); + Assertions.assertEquals("你好,世界", res); + + in = new ByteArrayInputStream(utf16Bytes); + codec = new PlainTextCodec("text/plain; charset=UTF-16"); + res = (String) codec.decode(in, String.class); + Assertions.assertEquals("你好,世界", res); + } + + @Test + void testUnsupportedCharset() { + HttpMessageCodec codec = new PlainTextCodec("text/plain; charset=unsupported"); + Assertions.assertThrows( + DecodeException.class, () -> codec.decode(new ByteArrayInputStream(new byte[] {}), String.class)); + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/User.java b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/User.java new file mode 100644 index 00000000000..314c4a1b61f --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/User.java @@ -0,0 +1,57 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import javax.xml.bind.annotation.XmlRootElement; + +import java.io.Serializable; + +@XmlRootElement +public class User implements Serializable { + + private String username; + + private String location; + + public User() {} + + public User(String username, String location) { + this.username = username; + this.location = location; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getLocation() { + return location; + } + + public void setLocation(String location) { + this.location = location; + } + + @Override + public String toString() { + return "User{" + "username='" + username + '\'' + ", location='" + location + '\'' + '}'; + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/XmlSafetyTest.java b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/XmlSafetyTest.java new file mode 100644 index 00000000000..bc5188ab5f9 --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/java/org/apache/dubbo/remoting/http12/message/codec/XmlSafetyTest.java @@ -0,0 +1,115 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF 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 + * + * http://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 org.apache.dubbo.remoting.http12.message.codec; + +import java.io.*; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.condition.OS; + +@EnabledOnOs(OS.LINUX) +public class XmlSafetyTest { + + ProcessChecker checker = new ProcessChecker(); + + @BeforeEach + void setUp() throws Exception { + checker.prepare(); + } + + @AfterEach + void check() throws Exception { + checker.check(); + } + + @Test + void testSafe1() { + try { + InputStream in = new ByteArrayInputStream(("\n" + " \n" + + " java.util.List\n" + + " \n" + + " \n" + + " \n" + + " " + "sleep" + "\n" + + " " + "60" + "\n" + + " \n" + + " \n" + + " start\n" + + " \n" + + " \n" + + "") + .getBytes()); + new XmlCodec().decode(in, Object.class); + } catch (Exception e) { + } + } + + @Test + void testSafe2() { + try { + InputStream in = new ByteArrayInputStream(("\n" + " \n" + + " \n" + + " " + "sleep" + "\n" + + " " + "60" + "\n" + + " \n" + + " \n" + + "") + .getBytes()); + new XmlCodec().decode(in, Object.class); + } catch (Exception e) { + } + } + + static class ProcessChecker { + Set processesBefore; + + public Set getProcesses() { + try { + Set processes = new HashSet<>(); + Process process = Runtime.getRuntime().exec("ps -e"); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { + String line; + while ((line = reader.readLine()) != null) { + processes.add(line); + } + } + return processes; + } catch (Exception e) { + } + return Collections.emptySet(); + } + + public void prepare() { + processesBefore = getProcesses(); + } + + public void check() throws Exception { + Set processesAfter = getProcesses(); + for (String msg : processesAfter) { + if (msg.contains("sleep")) { + throw new Exception("Command executed when XML deserialization."); + } + } + } + } +} diff --git a/dubbo-remoting/dubbo-remoting-http12/src/test/resources/log4j2-test.xml b/dubbo-remoting/dubbo-remoting-http12/src/test/resources/log4j2-test.xml new file mode 100644 index 00000000000..ba99f52cc2d --- /dev/null +++ b/dubbo-remoting/dubbo-remoting-http12/src/test/resources/log4j2-test.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java index 89f09e93a58..1b8d5090888 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/AbstractServerTransportListener.java @@ -26,6 +26,7 @@ import org.apache.dubbo.remoting.http12.HttpHeaderNames; import org.apache.dubbo.remoting.http12.HttpHeaders; import org.apache.dubbo.remoting.http12.HttpInputMessage; +import org.apache.dubbo.remoting.http12.HttpMetadata; import org.apache.dubbo.remoting.http12.HttpStatus; import org.apache.dubbo.remoting.http12.HttpTransportListener; import org.apache.dubbo.remoting.http12.RequestMetadata; @@ -33,9 +34,9 @@ import org.apache.dubbo.remoting.http12.exception.IllegalPathException; import org.apache.dubbo.remoting.http12.exception.UnimplementedException; import org.apache.dubbo.remoting.http12.exception.UnsupportedMediaTypeException; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; import org.apache.dubbo.remoting.http12.message.MethodMetadata; +import org.apache.dubbo.remoting.http12.message.codec.CodecUtils; import org.apache.dubbo.rpc.HeaderFilter; import org.apache.dubbo.rpc.Invoker; import org.apache.dubbo.rpc.PathResolver; @@ -83,7 +84,7 @@ public abstract class AbstractServerTransportListener
headerFilters; - private HttpMessageCodec httpMessageCodec; + private HttpMessageDecoder httpMessageDecoder; private Invoker invoker; @@ -103,6 +104,8 @@ public abstract class AbstractServerTransportListener
getInvoker(HEADER metadata, String serviceName) { return invoker; } - protected HttpMessageCodec determineHttpMessageCodec(String contentType) { - for (HttpMessageCodecFactory httpMessageCodecFactory : - frameworkModel.getExtensionLoader(HttpMessageCodecFactory.class).getActivateExtensions()) { - if (httpMessageCodecFactory.support(contentType)) { - return httpMessageCodecFactory.createCodec(invoker.getUrl(), frameworkModel); - } - } - return null; - } - private static ServiceDescriptor findServiceDescriptor(Invoker invoker, String serviceName, boolean hasStub) throws UnimplementedException { ServiceDescriptor result; @@ -421,8 +413,12 @@ protected MethodMetadata getMethodMetadata() { return methodMetadata; } - protected HttpMessageCodec getHttpMessageCodec() { - return httpMessageCodec; + protected HttpMessageDecoder getHttpMessageDecoder() { + return this.httpMessageDecoder; + } + + protected CodecUtils getCodecUtils() { + return this.codecUtils; } protected void setHttpMessageListener(HttpMessageListener httpMessageListener) { @@ -441,6 +437,10 @@ protected final URL getUrl() { return url; } + protected HttpMetadata getMetadata() { + return httpMetadata; + } + public boolean isHasStub() { return hasStub; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleEncoder.java similarity index 63% rename from dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleCodec.java rename to dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleEncoder.java index 7da92bd17f0..60c9caf5291 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/CompressibleEncoder.java @@ -16,22 +16,20 @@ */ package org.apache.dubbo.rpc.protocol.tri.h12; -import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoder; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.rpc.protocol.tri.compressor.Compressor; -import java.io.InputStream; import java.io.OutputStream; -public class CompressibleCodec implements HttpMessageCodec { +public class CompressibleEncoder implements HttpMessageEncoder { - private final HttpMessageCodec delegate; + private final HttpMessageEncoder delegate; private Compressor compressor = Compressor.NONE; - public CompressibleCodec(HttpMessageCodec delegate) { + public CompressibleEncoder(HttpMessageEncoder delegate) { this.delegate = delegate; } @@ -39,33 +37,16 @@ public void setCompressor(Compressor compressor) { this.compressor = compressor; } - @Override public void encode(OutputStream outputStream, Object data) throws EncodeException { delegate.encode(compressor.decorate(outputStream), data); } - @Override - public Object decode(InputStream inputStream, Class targetType) throws DecodeException { - return delegate.decode(inputStream, targetType); - } - - @Override public void encode(OutputStream outputStream, Object[] data) throws EncodeException { delegate.encode(outputStream, data); } @Override - public Object[] decode(InputStream inputStream, Class[] targetTypes) throws DecodeException { - return delegate.decode(inputStream, targetTypes); - } - - @Override - public boolean support(String contentType) { - return delegate.support(contentType); - } - - @Override - public MediaType contentType() { - return delegate.contentType(); + public MediaType mediaType() { + return delegate.mediaType(); } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java index 2c5041d6c82..96b18cf2ec3 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodec.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri.h12.grpc; -import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; @@ -30,13 +29,6 @@ import static org.apache.dubbo.common.constants.CommonConstants.PROTOBUF_MESSAGE_CLASS_NAME; -/** - * compatible low version. - * version < 3.3 - * - * @since 3.3 - */ -@Activate public class GrpcCompositeCodec implements HttpMessageCodec { private static final MediaType MEDIA_TYPE = new MediaType("application", "grpc"); @@ -106,15 +98,10 @@ private static void writeLength(OutputStream outputStream, int length) { } @Override - public MediaType contentType() { + public MediaType mediaType() { return MEDIA_TYPE; } - @Override - public boolean support(String contentType) { - return contentType.startsWith(MEDIA_TYPE.getName()); - } - private static boolean isProtobuf(Object data) { if (data == null) { return false; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java index 792fde774d0..29a7de9dfd5 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcCompositeCodecFactory.java @@ -19,18 +19,19 @@ import org.apache.dubbo.common.URL; import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory; +import org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.remoting.utils.UrlUtils; import org.apache.dubbo.rpc.model.FrameworkModel; @Activate -public class GrpcCompositeCodecFactory implements HttpMessageCodecFactory { +public class GrpcCompositeCodecFactory implements HttpMessageEncoderFactory, HttpMessageDecoderFactory { private static final MediaType MEDIA_TYPE = new MediaType("application", "grpc"); @Override - public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel) { + public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel, String mediaType) { final String serializeName = UrlUtils.serializationOrDefault(url); WrapperHttpMessageCodec wrapperHttpMessageCodec = new WrapperHttpMessageCodec(url, frameworkModel); wrapperHttpMessageCodec.setSerializeType(serializeName); @@ -39,12 +40,7 @@ public HttpMessageCodec createCodec(URL url, FrameworkModel frameworkModel) { } @Override - public MediaType contentType() { + public MediaType mediaType() { return MEDIA_TYPE; } - - @Override - public boolean support(String contentType) { - return contentType.startsWith(MEDIA_TYPE.getName()); - } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHeaderNames.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHeaderNames.java index 174a1756f41..e781bc0c71d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHeaderNames.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHeaderNames.java @@ -19,7 +19,8 @@ public enum GrpcHeaderNames { GRPC_STATUS("grpc-status"), GRPC_MESSAGE("grpc-message"), - GRPC_ENCODING("grpc-encoding"), + GRPC_ENCODING("grpc-encoding"), // client request compress type + GRPC_ACCEPT_ENCODING("grpc-accept-encoding"), // client required response compress type GRPC_TIMEOUT("grpc-timeout"), ; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java index d8f7655e546..de216cee562 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/GrpcHttp2ServerTransportListener.java @@ -210,10 +210,13 @@ public void onFragmentMessage(InputStream dataHeader, InputStream rawMessage) { // replace decoder HttpMessageListener httpMessageListener = GrpcHttp2ServerTransportListener.super.newHttpMessageListener(); - GrpcCompositeCodec grpcCompositeCodec = (GrpcCompositeCodec) getHttpMessageCodec(); + GrpcCompositeCodec grpcCompositeCodec = (GrpcCompositeCodec) getHttpMessageDecoder(); + grpcCompositeCodec.setDecodeTypes(getMethodMetadata().getActualRequestTypes()); grpcCompositeCodec.setEncodeTypes( new Class[] {getMethodMetadata().getActualResponseType()}); - grpcCompositeCodec.setDecodeTypes(getMethodMetadata().getActualRequestTypes()); + GrpcHttp2ServerTransportListener.super + .getServerChannelObserver() + .setResponseEncoder(grpcCompositeCodec); setHttpMessageListener(httpMessageListener); } transferToOutputStream(merge, new ByteArrayInputStream(data)); diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java index 1d6fce91df1..011fd6b8a93 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/ProtobufHttpMessageCodec.java @@ -16,7 +16,6 @@ */ package org.apache.dubbo.rpc.protocol.tri.h12.grpc; -import org.apache.dubbo.common.extension.Activate; import org.apache.dubbo.remoting.http12.exception.DecodeException; import org.apache.dubbo.remoting.http12.exception.EncodeException; import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; @@ -27,7 +26,6 @@ import java.io.InputStream; import java.io.OutputStream; -@Activate(onClass = "com.google.protobuf.Message") public class ProtobufHttpMessageCodec implements HttpMessageCodec { private static final MediaType MEDIA_TYPE = new MediaType("application", "x-protobuf"); @@ -51,7 +49,7 @@ public Object decode(InputStream inputStream, Class targetType) throws Decode } @Override - public MediaType contentType() { + public MediaType mediaType() { return MEDIA_TYPE; } } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java index ea68580736c..907f01cd30f 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/grpc/WrapperHttpMessageCodec.java @@ -129,7 +129,7 @@ public Object[] decode(InputStream inputStream, Class[] targetTypes) throws D } @Override - public MediaType contentType() { + public MediaType mediaType() { return MEDIA_TYPE; } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java index 2253fb6e225..389e48bac3e 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http1/DefaultHttp11ServerTransportListener.java @@ -26,7 +26,7 @@ import org.apache.dubbo.remoting.http12.h1.Http1ServerStreamChannelObserver; import org.apache.dubbo.remoting.http12.h1.Http1ServerTransportListener; import org.apache.dubbo.remoting.http12.message.DefaultListeningDecoder; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; +import org.apache.dubbo.remoting.http12.message.HttpMessageDecoder; import org.apache.dubbo.remoting.http12.message.ListeningDecoder; import org.apache.dubbo.remoting.http12.message.MediaType; import org.apache.dubbo.remoting.http12.message.MethodMetadata; @@ -60,12 +60,16 @@ private ServerCallListener startListener( switch (methodDescriptor.getRpcType()) { case UNARY: Http1ServerChannelObserver http1ChannelObserver = new Http1ServerChannelObserver(httpChannel); - http1ChannelObserver.setHttpMessageCodec(getHttpMessageCodec()); + http1ChannelObserver.setResponseEncoder(getCodecUtils() + .determineHttpMessageEncoder( + getFrameworkModel(), getHttpMetadata().headers(), getUrl())); return new AutoCompleteUnaryServerCallListener(invocation, invoker, http1ChannelObserver); case SERVER_STREAM: Http1ServerChannelObserver serverStreamChannelObserver = new Http1ServerStreamChannelObserver(httpChannel); - serverStreamChannelObserver.setHttpMessageCodec(getHttpMessageCodec()); + serverStreamChannelObserver.setResponseEncoder(getCodecUtils() + .determineHttpMessageEncoder( + getFrameworkModel(), getHttpMetadata().headers(), getUrl())); serverStreamChannelObserver.setHeadersCustomizer((headers) -> headers.set( HttpHeaderNames.CONTENT_TYPE.getName(), MediaType.TEXT_EVENT_STREAM_VALUE.getName())); return new AutoCompleteServerStreamServerCallListener(invocation, invoker, serverStreamChannelObserver); @@ -87,14 +91,13 @@ protected HttpMessageListener newHttpMessageListener() { setMethodDescriptor(methodDescriptor); setMethodMetadata(methodMetadata); setRpcInvocation(rpcInvocation); - HttpMessageCodec httpMessageCodec = getHttpMessageCodec(); ListeningDecoder listeningDecoder = - newListeningDecoder(httpMessageCodec, methodMetadata.getActualRequestTypes()); + newListeningDecoder(getHttpMessageDecoder(), methodMetadata.getActualRequestTypes()); return new DefaultHttpMessageListener(listeningDecoder); } - private ListeningDecoder newListeningDecoder(HttpMessageCodec codec, Class[] actualRequestTypes) { - DefaultListeningDecoder defaultListeningDecoder = new DefaultListeningDecoder(codec, actualRequestTypes); + private ListeningDecoder newListeningDecoder(HttpMessageDecoder decoder, Class[] actualRequestTypes) { + DefaultListeningDecoder defaultListeningDecoder = new DefaultListeningDecoder(decoder, actualRequestTypes); ServerCallListener serverCallListener = startListener(getRpcInvocation(), getMethodDescriptor(), getInvoker()); defaultListeningDecoder.setListener(serverCallListener::onMessage); return defaultListeningDecoder; diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java index c4b4f46007d..a859ae48ae2 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/GenericHttp2ServerTransportListener.java @@ -27,7 +27,6 @@ import org.apache.dubbo.remoting.http12.h2.Http2ServerChannelObserver; import org.apache.dubbo.remoting.http12.h2.Http2TransportListener; import org.apache.dubbo.remoting.http12.message.DefaultListeningDecoder; -import org.apache.dubbo.remoting.http12.message.JsonCodec; import org.apache.dubbo.remoting.http12.message.ListeningDecoder; import org.apache.dubbo.remoting.http12.message.MethodMetadata; import org.apache.dubbo.remoting.http12.message.NoOpStreamingDecoder; @@ -71,7 +70,6 @@ public GenericHttp2ServerTransportListener( .getExecutorSupport(url); this.streamingDecoder = newStreamingDecoder(); this.serverChannelObserver = new Http2ServerCallToObserverAdapter(frameworkModel, h2StreamChannel); - this.serverChannelObserver.setHttpMessageCodec(JsonCodec.INSTANCE); this.serverChannelObserver.setStreamingDecoder(streamingDecoder); } @@ -154,7 +152,7 @@ protected HttpMessageListener newHttpMessageListener() { } initializeServerCallListener(); DefaultListeningDecoder defaultListeningDecoder = new DefaultListeningDecoder( - getHttpMessageCodec(), getMethodMetadata().getActualRequestTypes()); + getHttpMessageDecoder(), getMethodMetadata().getActualRequestTypes()); defaultListeningDecoder.setListener(new Http2StreamingDecodeListener(serverCallListener)); streamingDecoder.setFragmentListener(new StreamingDecoder.DefaultFragmentListener(defaultListeningDecoder)); getServerChannelObserver().setStreamingDecoder(streamingDecoder); @@ -164,7 +162,8 @@ protected HttpMessageListener newHttpMessageListener() { @Override protected void onMetadataCompletion(Http2Header metadata) { super.onMetadataCompletion(metadata); - this.serverChannelObserver.setHttpMessageCodec(getHttpMessageCodec()); + this.serverChannelObserver.setResponseEncoder( + getCodecUtils().determineHttpMessageEncoder(getFrameworkModel(), metadata.headers(), getUrl())); this.serverChannelObserver.request(1); } diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java index 8460ee3df12..7a0f96c282d 100644 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/h12/http2/Http2ServerStreamObserver.java @@ -20,13 +20,12 @@ import org.apache.dubbo.remoting.http12.HttpMetadata; import org.apache.dubbo.remoting.http12.h2.H2StreamChannel; import org.apache.dubbo.remoting.http12.h2.Http2ServerChannelObserver; -import org.apache.dubbo.remoting.http12.message.HttpMessageCodec; import org.apache.dubbo.rpc.model.FrameworkModel; import org.apache.dubbo.rpc.protocol.tri.ServerStreamObserver; import org.apache.dubbo.rpc.protocol.tri.TripleProtocol; import org.apache.dubbo.rpc.protocol.tri.compressor.Compressor; import org.apache.dubbo.rpc.protocol.tri.h12.AttachmentHolder; -import org.apache.dubbo.rpc.protocol.tri.h12.CompressibleCodec; +import org.apache.dubbo.rpc.protocol.tri.h12.CompressibleEncoder; import org.apache.dubbo.rpc.protocol.tri.stream.StreamUtils; import java.util.Map; @@ -36,8 +35,6 @@ public class Http2ServerStreamObserver extends Http2ServerChannelObserver private final FrameworkModel frameworkModel; - private HttpMessageCodec httpMessageCodec; - private Map attachments; public Http2ServerStreamObserver(FrameworkModel frameworkModel, H2StreamChannel h2StreamChannel) { @@ -47,15 +44,9 @@ public Http2ServerStreamObserver(FrameworkModel frameworkModel, H2StreamChannel @Override public void setCompression(String compression) { - CompressibleCodec compressibleCodec = new CompressibleCodec(httpMessageCodec); - compressibleCodec.setCompressor(Compressor.getCompressor(frameworkModel, compression)); - super.setHttpMessageCodec(compressibleCodec); - } - - @Override - public void setHttpMessageCodec(HttpMessageCodec httpMessageCodec) { - super.setHttpMessageCodec(httpMessageCodec); - this.httpMessageCodec = httpMessageCodec; + CompressibleEncoder compressibleEncoder = new CompressibleEncoder(getResponseEncoder()); + compressibleEncoder.setCompressor(Compressor.getCompressor(frameworkModel, compression)); + super.setResponseEncoder(compressibleEncoder); } @Override diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec deleted file mode 100644 index 388cbd7e3e3..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodec +++ /dev/null @@ -1,2 +0,0 @@ -grpc=org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcCompositeCodec -protobuf=org.apache.dubbo.rpc.protocol.tri.h12.grpc.ProtobufHttpMessageCodec diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory deleted file mode 100644 index 39350a2c211..00000000000 --- a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageCodecFactory +++ /dev/null @@ -1 +0,0 @@ -grpc=org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcCompositeCodecFactory diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory new file mode 100644 index 00000000000..571f1464473 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageDecoderFactory @@ -0,0 +1 @@ +composite=org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcCompositeCodecFactory \ No newline at end of file diff --git a/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory new file mode 100644 index 00000000000..571f1464473 --- /dev/null +++ b/dubbo-rpc/dubbo-rpc-triple/src/main/resources/META-INF/dubbo/internal/org.apache.dubbo.remoting.http12.message.HttpMessageEncoderFactory @@ -0,0 +1 @@ +composite=org.apache.dubbo.rpc.protocol.tri.h12.grpc.GrpcCompositeCodecFactory \ No newline at end of file