Some rest bugfix

This commit is contained in:
oxsean 2024-06-20 05:40:08 +00:00
parent d53f9b7c6e
commit ec327bc2ea
13 changed files with 204 additions and 30 deletions

View File

@ -50,12 +50,20 @@ public final class CodecUtils {
.createCodec(url, frameworkModel, mediaType);
}
public HttpMessageDecoder determineHttpMessageDecoder(String mediaType) {
return determineHttpMessageDecoder(null, null, mediaType);
}
public HttpMessageEncoder determineHttpMessageEncoder(URL url, FrameworkModel frameworkModel, String mediaType) {
return determineHttpMessageEncoderFactory(mediaType)
.orElseThrow(() -> new UnsupportedMediaTypeException(mediaType))
.createCodec(url, frameworkModel, mediaType);
}
public HttpMessageEncoder determineHttpMessageEncoder(String mediaType) {
return determineHttpMessageEncoder(null, null, mediaType);
}
public Optional<HttpMessageDecoderFactory> determineHttpMessageDecoderFactory(String mediaType) {
Assert.notNull(mediaType, "mediaType must not be null");
return decoderCache.computeIfAbsent(mediaType, k -> {

View File

@ -25,6 +25,9 @@ import org.apache.dubbo.common.utils.DateUtils;
import org.apache.dubbo.common.utils.JsonUtils;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.remoting.http12.HttpCookie;
import org.apache.dubbo.remoting.http12.HttpRequest.FileUpload;
import org.apache.dubbo.remoting.http12.message.MediaType;
import org.apache.dubbo.remoting.http12.message.codec.CodecUtils;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.apache.dubbo.rpc.protocol.tri.rest.RestException;
import org.apache.dubbo.rpc.protocol.tri.rest.RestParameterException;
@ -93,6 +96,7 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.apache.dubbo.common.utils.StringUtils.tokenizeToList;
import static org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils.getActualGenericType;
import static org.apache.dubbo.rpc.protocol.tri.rest.util.TypeUtils.getActualType;
@ -104,13 +108,16 @@ public class GeneralTypeConverter implements TypeConverter {
private static final Logger LOGGER = LoggerFactory.getLogger(GeneralTypeConverter.class);
private final ConverterUtil converterUtil;
private final CodecUtils codecUtils;
public GeneralTypeConverter() {
converterUtil = null;
codecUtils = null;
}
public GeneralTypeConverter(FrameworkModel frameworkModel) {
converterUtil = frameworkModel.getBeanFactory().getOrRegisterBean(ConverterUtil.class);
codecUtils = frameworkModel.getBeanFactory().getOrRegisterBean(CodecUtils.class);
}
@Override
@ -255,7 +262,7 @@ public class GeneralTypeConverter implements TypeConverter {
case "java.lang.Class":
return TypeUtils.loadClass(str);
case "[B":
return str.getBytes(StandardCharsets.UTF_8);
return str.getBytes(UTF_8);
case "[C":
return str.toCharArray();
case "java.util.OptionalInt":
@ -432,7 +439,7 @@ public class GeneralTypeConverter implements TypeConverter {
switch (targetClass.getName()) {
case "java.lang.String":
return new String(bytes, StandardCharsets.UTF_8);
return new String(bytes, UTF_8);
case "java.lang.Double":
case "double":
return ByteBuffer.wrap(bytes).getDouble();
@ -568,6 +575,11 @@ public class GeneralTypeConverter implements TypeConverter {
return StreamUtils.readBytes(is);
}
}
if (source instanceof FileUpload) {
try (InputStream is = ((FileUpload) source).inputStream()) {
return StreamUtils.readBytes(is);
}
}
if (source instanceof Character) {
char c = (Character) source;
return new byte[] {(byte) (c >> 8), (byte) c};
@ -1078,10 +1090,15 @@ public class GeneralTypeConverter implements TypeConverter {
return false;
}
private static Object jsonToObject(String value, Type targetType) {
private Object jsonToObject(String value, Type targetType) {
if (isMaybeJSON(value)) {
try {
return JsonUtils.toJavaObject(value, targetType);
if (codecUtils == null || !(targetType instanceof Class)) {
return JsonUtils.toJavaObject(value, targetType);
}
return codecUtils
.determineHttpMessageDecoder(MediaType.APPLICATION_JSON.getName())
.decode(new ByteArrayInputStream(value.getBytes(UTF_8)), (Class<?>) targetType);
} catch (Throwable t) {
LOGGER.debug("Failed to parse [{}] from json string [{}]", targetType, value, t);
}

View File

@ -100,16 +100,16 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
new MethodWalker().walk(service.getClass(), (classes, consumer) -> {
for (int i = 0, size = resolvers.size(); i < size; i++) {
RequestMappingResolver resolver = resolvers.get(i);
ServiceMeta serviceMeta = new ServiceMeta(classes, service, url, resolver.getRestToolKit());
if (!resolver.accept(serviceMeta, sd)) {
ServiceMeta serviceMeta = new ServiceMeta(classes, sd, service, url, resolver.getRestToolKit());
if (!resolver.accept(serviceMeta)) {
continue;
}
RequestMapping classMapping = resolver.resolve(serviceMeta);
consumer.accept((methods) -> {
Method method = methods.get(0);
MethodDescriptor md = sd.getMethod(method.getName(), method.getParameterTypes());
MethodMeta methodMeta = new MethodMeta(methods, serviceMeta);
if (!resolver.accept(methodMeta, md)) {
MethodMeta methodMeta = new MethodMeta(methods, md, serviceMeta);
if (!resolver.accept(methodMeta)) {
return;
}
RequestMapping methodMapping = resolver.resolve(methodMeta);
@ -122,6 +122,7 @@ public final class DefaultRequestMappingRegistry implements RequestMappingRegist
}
md = new ReflectionMethodDescriptor(method);
((ReflectionServiceDescriptor) sd).addMethod(md);
methodMeta.setMethodDescriptor(md);
}
if (classMapping != null) {
methodMapping = classMapping.combine(methodMapping);

View File

@ -18,8 +18,6 @@ package org.apache.dubbo.rpc.protocol.tri.rest.mapping;
import org.apache.dubbo.common.extension.ExtensionScope;
import org.apache.dubbo.common.extension.SPI;
import org.apache.dubbo.rpc.model.MethodDescriptor;
import org.apache.dubbo.rpc.model.ServiceDescriptor;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.MethodMeta;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.ServiceMeta;
import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
@ -29,13 +27,13 @@ public interface RequestMappingResolver {
RestToolKit getRestToolKit();
default boolean accept(ServiceMeta serviceMeta, ServiceDescriptor serviceDescriptor) {
default boolean accept(ServiceMeta serviceMeta) {
return true;
}
RequestMapping resolve(ServiceMeta serviceMeta);
default boolean accept(MethodMeta methodMeta, MethodDescriptor methodDescriptor) {
default boolean accept(MethodMeta methodMeta) {
return true;
}

View File

@ -16,6 +16,8 @@
*/
package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
import org.apache.dubbo.rpc.model.MethodDescriptor;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@ -27,13 +29,15 @@ public final class MethodMeta extends AnnotationSupport {
private final List<Method> hierarchy;
private final Method method;
private MethodDescriptor methodDescriptor;
private ParameterMeta[] parameters;
private final ServiceMeta serviceMeta;
public MethodMeta(List<Method> hierarchy, ServiceMeta serviceMeta) {
public MethodMeta(List<Method> hierarchy, MethodDescriptor methodDescriptor, ServiceMeta serviceMeta) {
super(serviceMeta.getToolKit());
this.hierarchy = hierarchy;
method = hierarchy.get(0);
method = methodDescriptor == null ? hierarchy.get(0) : methodDescriptor.getMethod();
this.methodDescriptor = methodDescriptor;
this.serviceMeta = serviceMeta;
}
@ -72,6 +76,14 @@ public final class MethodMeta extends AnnotationSupport {
return method;
}
public MethodDescriptor getMethodDescriptor() {
return methodDescriptor;
}
public void setMethodDescriptor(MethodDescriptor methodDescriptor) {
this.methodDescriptor = methodDescriptor;
}
public ParameterMeta[] getParameters() {
return parameters;
}

View File

@ -16,6 +16,9 @@
*/
package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
import org.apache.dubbo.rpc.model.MethodDescriptor;
import org.apache.dubbo.rpc.model.MethodDescriptor.RpcType;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
@ -47,7 +50,8 @@ public final class MethodParameterMeta extends ParameterMeta {
@Override
public boolean isSingle() {
return methodMeta.getMethod().getParameterCount() == 1;
MethodDescriptor methodDescriptor = methodMeta.getMethodDescriptor();
return methodDescriptor.getRpcType() != RpcType.UNARY || methodDescriptor.getParameterClasses().length == 1;
}
@Override

View File

@ -17,6 +17,7 @@
package org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.rpc.model.ServiceDescriptor;
import org.apache.dubbo.rpc.protocol.tri.rest.util.PathUtils;
import org.apache.dubbo.rpc.protocol.tri.rest.util.RestToolKit;
@ -30,14 +31,21 @@ public final class ServiceMeta extends AnnotationSupport {
private final List<Class<?>> hierarchy;
private final Class<?> type;
private final Object service;
private final ServiceDescriptor serviceDescriptor;
private final URL url;
private final String contextPath;
private List<MethodMeta> exceptionHandlers;
public ServiceMeta(Collection<Class<?>> hierarchy, Object service, URL url, RestToolKit toolKit) {
public ServiceMeta(
Collection<Class<?>> hierarchy,
ServiceDescriptor serviceDescriptor,
Object service,
URL url,
RestToolKit toolKit) {
super(toolKit);
this.hierarchy = new ArrayList<>(hierarchy);
this.serviceDescriptor = serviceDescriptor;
type = this.hierarchy.get(0);
this.service = service;
this.url = url;
@ -52,6 +60,10 @@ public final class ServiceMeta extends AnnotationSupport {
return type;
}
public ServiceDescriptor getServiceDescriptor() {
return serviceDescriptor;
}
public Object getService() {
return service;
}

View File

@ -19,7 +19,6 @@ package org.apache.dubbo.rpc.protocol.tri.rest.support.basic;
import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.rpc.model.FrameworkModel;
import org.apache.dubbo.rpc.model.MethodDescriptor;
import org.apache.dubbo.rpc.protocol.tri.rest.cors.CorsUtils;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping.Builder;
@ -53,8 +52,8 @@ public class BasicRequestMappingResolver implements RequestMappingResolver {
}
@Override
public boolean accept(MethodMeta methodMeta, MethodDescriptor methodDescriptor) {
return methodDescriptor != null || methodMeta.findAnnotation(Annotations.Mapping) != null;
public boolean accept(MethodMeta methodMeta) {
return methodMeta.getMethodDescriptor() != null || methodMeta.findAnnotation(Annotations.Mapping) != null;
}
@Override

View File

@ -17,7 +17,6 @@
package org.apache.dubbo.rpc.protocol.tri.rest
import org.apache.dubbo.remoting.http12.message.MediaType
import org.apache.dubbo.rpc.protocol.tri.rest.service.Book
import org.apache.dubbo.rpc.protocol.tri.rest.service.DemoServiceImpl
@ -42,15 +41,37 @@ class RestProtocolTest extends BaseServiceTest {
'/hello.yml?name=world' | 'hello world'
}
def "post test"() {
def "argument test"() {
expect:
runner.post(path, body) == output
where:
path | body | output
'/postTest' | '["Sam","8"]' | 'Sam is 8 years old'
'/postTest' | '{"name": "Sam", "age": 8}' | 'Sam is 8 years old'
'/postTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old'
'/postTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old'
path | body | output
'/argTest' | '["Sam","8"]' | 'Sam is 8 years old'
'/argTest' | '{"name": "Sam", "age": 8}' | 'Sam is 8 years old'
'/argTest?name=Sam' | '{"age": 8}' | 'Sam is 8 years old'
}
def "bean argument get test"() {
expect:
runner.get(path, Book.class).price == output
where:
path | output
'/beanArgTest' | 0
'/beanArgTest?quote=5' | 0
'/beanArgTest?book={"price": 6}' | 6
'/beanArgTest?book={"price": 6}&quote=5' | 5
}
def "bean argument post test"() {
expect:
runner.post(path, body, Book.class).price == output
where:
path | body | output
'/beanArgTest' | [] | 0
'/beanArgTest' | [quote: 5] | 0
'/beanArgTest' | [book: new Book(price: 6)] | 6
'/beanArgTest' | [book: new Book(price: 6), quote: 5] | 5
'/beanArgTest' | [price: 6, quote: 5] | 0
}
def "bean test"() {
@ -77,8 +98,8 @@ class RestProtocolTest extends BaseServiceTest {
expect:
runner.post(request) == output
where:
path | output
'/postTest' | 'Sam is 8 years old'
path | output
'/argTest' | 'Sam is 8 years old'
}
def "override mapping test"() {
@ -110,4 +131,29 @@ class RestProtocolTest extends BaseServiceTest {
'/noInterface' | 'ok'
'/noInterfaceAndMapping' | '404'
}
def "use interface argument name test"() {
expect:
runner.get(path) contains output
where:
path | output
'/argNameTest?name=Sam' | 'Sam'
}
def "pb server stream test"() {
expect:
runner.posts(path, body).size() == output
where:
path | body | output
'/pbServerStream' | '{"service": "3"}' | 3
'/pbServerStream' | '{}' | 0
}
def "pb server stream get test"() {
expect:
runner.gets(path).size() == output
where:
path | output
'/pbServerStream?request={"service": "3"}' | 3
}
}

View File

@ -16,14 +16,20 @@
*/
package org.apache.dubbo.rpc.protocol.tri.rest.service;
import org.apache.dubbo.common.stream.StreamObserver;
import org.apache.dubbo.remoting.http12.rest.Mapping;
import io.grpc.health.v1.HealthCheckRequest;
import io.grpc.health.v1.HealthCheckResponse;
@Mapping("/")
public interface DemoService {
String hello(String name);
String postTest(String name, int age);
String argTest(String name, int age);
Book beanArgTest(Book book, Integer quote);
Book buy(Book book);
@ -33,4 +39,8 @@ public interface DemoService {
String say(String name, Long count);
String say(String name);
String argNameTest(String name);
void pbServerStream(HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver);
}

View File

@ -16,8 +16,16 @@
*/
package org.apache.dubbo.rpc.protocol.tri.rest.service;
import org.apache.dubbo.common.stream.StreamObserver;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.remoting.http12.rest.Mapping;
import io.grpc.health.v1.HealthCheckRequest;
import io.grpc.health.v1.HealthCheckResponse;
import io.grpc.health.v1.HealthCheckResponse.ServingStatus;
import static io.grpc.health.v1.HealthCheckResponse.newBuilder;
public class DemoServiceImpl implements DemoService {
@Override
@ -26,10 +34,20 @@ public class DemoServiceImpl implements DemoService {
}
@Override
public String postTest(String name, int age) {
public String argTest(String name, int age) {
return name + " is " + age + " years old";
}
@Override
public Book beanArgTest(Book book, Integer quote) {
if (book == null) {
book = new Book();
} else if (quote != null) {
book.setPrice(quote);
}
return book;
}
@Override
public Book buy(Book book) {
return book;
@ -58,4 +76,22 @@ public class DemoServiceImpl implements DemoService {
public String noInterfaceAndMapping() {
return "ok";
}
@Override
public String argNameTest(String name1) {
return name1;
}
@Override
public void pbServerStream(HealthCheckRequest request, StreamObserver<HealthCheckResponse> responseObserver) {
String service = request.getService();
if (StringUtils.isNotEmpty(service)) {
int count = Integer.parseInt(service);
for (int i = 0; i < count; i++) {
responseObserver.onNext(
newBuilder().setStatus(ServingStatus.SERVING).build());
}
}
responseObserver.onCompleted();
}
}

View File

@ -16,6 +16,8 @@
*/
package org.apache.dubbo.rpc.protocol.tri.test;
import java.util.List;
public interface TestRunner {
TestResponse run(TestRequest request);
@ -28,16 +30,24 @@ public interface TestRunner {
<T> T get(String path, Class<T> type);
<T> List<T> gets(String path, Class<T> type);
String get(String path);
List<String> gets(String path);
<T> T post(TestRequest request, Class<T> type);
String post(TestRequest request);
<T> T post(String path, Object body, Class<T> type);
<T> List<T> posts(String path, Object body, Class<T> type);
String post(String path, Object body);
List<String> posts(String path, Object body);
<T> T put(TestRequest request, Class<T> type);
String put(TestRequest request);

View File

@ -217,11 +217,21 @@ final class TestRunnerImpl implements TestRunner {
return get(new TestRequest(path), type);
}
@Override
public <T> List<T> gets(String path, Class<T> type) {
return run(new TestRequest(path).setMethod(HttpMethods.GET.name())).getBodies(type);
}
@Override
public String get(String path) {
return get(new TestRequest(path));
}
@Override
public List<String> gets(String path) {
return gets(path, String.class);
}
@Override
public <T> T post(TestRequest request, Class<T> type) {
return run(request.setMethod(HttpMethods.POST.name()), type);
@ -237,11 +247,22 @@ final class TestRunnerImpl implements TestRunner {
return post(new TestRequest(path).setBody(body), type);
}
@Override
public <T> List<T> posts(String path, Object body, Class<T> type) {
return run(new TestRequest(path).setMethod(HttpMethods.POST.name()).setBody(body))
.getBodies(type);
}
@Override
public String post(String path, Object body) {
return post(new TestRequest(path).setBody(body));
}
@Override
public List<String> posts(String path, Object body) {
return posts(path, body, String.class);
}
@Override
public <T> T put(TestRequest request, Class<T> type) {
return run(request.setMethod(HttpMethods.PUT.name()), type);