Skip to content

Commit

Permalink
[Feature 3.3] Triple Rest Cors Support (#14073)
Browse files Browse the repository at this point in the history
* feat(): add cors key

* feat(): add base cors class

* feat(): add cors class in rpc-triple and rest-spring

* feat(): add cors class in rpc-triple and rest-spring

* test(): add rpc-triple cors test

* fix(): fix CorsUtil bug

* fix(): fix CorsUtil bug

* fix(): fix objectUtil bug

* fix(): fix corsmeta set bug

* fix(): fix config load fail bug

* fix(): option method can not be look fail

* fix(): CorsMeta method will null

* fix(): request-header not set will fail

* refactor(): improve CorsMeta CorsProcess some code

* fix(): coreMeta combine priority

* test(): remove rest-spring cors test to sample

* docs(): add docs

* revert(): test version

* fix(): getCorsMeta can be null

* fix(): combine can be null

* fix(): save option and vary bug

* fix(): pom version

* fix(): spring version will cause allowPrivateWork resolve error

* fix(): ci

* refactor(): delete useless code

* refactor(): accept some sonarcloud issue

* refactor(): add @nullable to point the CorsMeta Attributes

* refactor(): style

* fix(): fix prelight logic

* fix(): remove credential & privateNetWork report

* refactor(): Move globalMetaMerge in RequestMappingResolver

* refactor(): use array replace corsConfig string

* refactor(): move CorsProcessor to CorsHeaderFilterAdapter

* fix(): fix unit test

* fix(): fix test failure

* fix(): delete useless param

* fix(): fix sonarcloud

* fix(): fix wrong class place & naming

* fix(): fix wrong static global corsMeta

* fix(): refactor CorsUtil from sonar issue

* feat(rest): refine cors support

* feat(rest): refine cors support

* feat(rest): refine cors support bugfix

* fix(): getBoolean will throw exception when null

* fix(rest-spring): fix crossOrigin allowCredentials is string

* fix(): fix globalCorsMeta load null

* fix(): fix vary header bug

* fix(): fix unit test && Fix cors specification

* fix(): fix pom

* fix(): fix combine bug

* fix(): fix some sonar issue

* fix(): fix style

* feat(rest): refine cors support

* fix(): fix style

* fix(): fix needed sonar issue

* refactor(): refactor CorsMeta.combine() and add comment

* fix(): Replenish license

* fix(): update test

* test(): Refactor the test class and add credential test cases

* test(): Refactor the test class and add credential test cases

* fix(rest): revert api HeaderFilter

* fix(): accept sonar issue

---------

Co-authored-by: Sean Yang <[email protected]>
Co-authored-by: earthchen <[email protected]>
  • Loading branch information
3 people authored May 23, 2024
1 parent 8368262 commit 1f6d441
Show file tree
Hide file tree
Showing 19 changed files with 1,301 additions and 25 deletions.
1 change: 1 addition & 0 deletions .licenserc.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ header:
- 'dubbo-config/dubbo-config-spring/src/test/java/org/apache/dubbo/config/spring/EmbeddedZooKeeper.java'
- 'dubbo-test/dubbo-test-common/src/main/java/org/apache/dubbo/test/common/utils/TestSocketUtils.java'
- 'dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/TriHttp2RemoteFlowController.java'
- 'dubbo-rpc/dubbo-rpc-triple/src/main/java/org/apache/dubbo/rpc/protocol/tri/rest/cors/CorsHeaderFilter.java'
- 'dubbo-common/src/main/java/org/apache/dubbo/common/threadpool/serial/SerializingExecutor.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractAotMojo.java'
- 'dubbo-maven-plugin/src/main/java/org/apache/dubbo/maven/plugin/aot/AbstractDependencyFilterMojo.java'
Expand Down
117 changes: 117 additions & 0 deletions dubbo-common/src/main/java/org/apache/dubbo/config/CorsConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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.config;

import java.io.Serializable;

public class CorsConfig implements Serializable {
private static final long serialVersionUID = 1L;

/**
* A list of origins for which cross-origin requests are allowed. Values may be a specific domain, e.g.
* {@code "https://domain1.com"}, or the CORS defined special value {@code "*"} for all origins.
* <p>By default this is not set which means that no origins are allowed.
* However, an instance of this class is often initialized further, e.g. for {@code @CrossOrigin}, via
* {@code org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta.Builder#applyDefault()}.
*/
private String[] allowedOrigins;

/**
* Set the HTTP methods to allow, e.g. {@code "GET"}, {@code "POST"},
* {@code "PUT"}, etc. The special value {@code "*"} allows all methods.
* <p>If not set, only {@code "GET"} and {@code "HEAD"} are allowed.
* <p>By default this is not set.
*/
private String[] allowedMethods;

/**
* /**
* Set the list of headers that a pre-flight request can list as allowed
* for use during an actual request. The special value {@code "*"} allows
* actual requests to send any header.
* <p>By default this is not set.
*/
private String[] allowedHeaders;

/**
* Set the list of response headers that an actual response might have
* and can be exposed to the client. The special value {@code "*"}
* allows all headers to be exposed.
* <p>By default this is not set.
*/
private String[] exposedHeaders;

/**
* Whether user credentials are supported.
* <p>By default this is not set (i.e. user credentials are not supported).
*/
private Boolean allowCredentials;

/**
* Configure how long, as a duration, the response from a pre-flight request
* can be cached by clients.
*/
private Long maxAge;

public String[] getAllowedOrigins() {
return allowedOrigins;
}

public void setAllowedOrigins(String[] allowedOrigins) {
this.allowedOrigins = allowedOrigins;
}

public String[] getAllowedMethods() {
return allowedMethods;
}

public void setAllowedMethods(String[] allowedMethods) {
this.allowedMethods = allowedMethods;
}

public String[] getAllowedHeaders() {
return allowedHeaders;
}

public void setAllowedHeaders(String[] allowedHeaders) {
this.allowedHeaders = allowedHeaders;
}

public String[] getExposedHeaders() {
return exposedHeaders;
}

public void setExposedHeaders(String[] exposedHeaders) {
this.exposedHeaders = exposedHeaders;
}

public Boolean getAllowCredentials() {
return allowCredentials;
}

public void setAllowCredentials(Boolean allowCredentials) {
this.allowCredentials = allowCredentials;
}

public Long getMaxAge() {
return maxAge;
}

public void setMaxAge(Long maxAge) {
this.maxAge = maxAge;
}
}
16 changes: 16 additions & 0 deletions dubbo-common/src/main/java/org/apache/dubbo/config/RestConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.apache.dubbo.config;

import org.apache.dubbo.config.support.Nested;

import java.io.Serializable;

/**
Expand Down Expand Up @@ -68,6 +70,12 @@ public class RestConfig implements Serializable {
*/
private String formatParameterName;

/**
* The config is used to set the Global CORS configuration properties.
*/
@Nested
private CorsConfig cors;

public Integer getMaxBodySize() {
return maxBodySize;
}
Expand Down Expand Up @@ -115,4 +123,12 @@ public String getFormatParameterName() {
public void setFormatParameterName(String formatParameterName) {
this.formatParameterName = formatParameterName;
}

public CorsConfig getCors() {
return cors;
}

public void setCors(CorsConfig cors) {
this.cors = cors;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,22 +18,27 @@

import org.apache.dubbo.common.extension.Activate;
import org.apache.dubbo.rpc.model.FrameworkModel;
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;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationSupport;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta;
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;

@Activate(onClass = "javax.ws.rs.Path")
public class JaxrsRequestMappingResolver implements RequestMappingResolver {

private final FrameworkModel frameworkModel;
private final RestToolKit toolKit;
private CorsMeta globalCorsMeta;

public JaxrsRequestMappingResolver(FrameworkModel frameworkModel) {
this.frameworkModel = frameworkModel;
toolKit = new JaxrsRestToolKit(frameworkModel);
}

Expand Down Expand Up @@ -65,10 +70,14 @@ public RequestMapping resolve(MethodMeta methodMeta) {
return null;
}
ServiceMeta serviceMeta = methodMeta.getServiceMeta();
if (globalCorsMeta == null) {
globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel);
}
return builder(methodMeta, path, httpMethod)
.name(methodMeta.getMethod().getName())
.contextPath(methodMeta.getServiceMeta().getContextPath())
.custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()))
.cors(globalCorsMeta)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
*/
package org.apache.dubbo.rpc.protocol.tri.rest.support.spring;

import org.apache.dubbo.common.constants.CommonConstants;
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.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;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMappingResolver;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.condition.ServiceVersionCondition;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.AnnotationMeta;
import org.apache.dubbo.rpc.protocol.tri.rest.mapping.meta.CorsMeta;
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;
Expand All @@ -35,6 +38,7 @@ public class SpringMvcRequestMappingResolver implements RequestMappingResolver {

private final FrameworkModel frameworkModel;
private volatile RestToolKit toolKit;
private CorsMeta globalCorsMeta;

public SpringMvcRequestMappingResolver(FrameworkModel frameworkModel) {
this.frameworkModel = frameworkModel;
Expand Down Expand Up @@ -62,9 +66,13 @@ public RequestMapping resolve(ServiceMeta serviceMeta) {
return null;
}
AnnotationMeta<?> responseStatus = serviceMeta.findMergedAnnotation(Annotations.ResponseStatus);
AnnotationMeta<?> crossOrigin = serviceMeta.findMergedAnnotation(Annotations.CrossOrigin);
String[] methods = requestMapping.getStringArray("method");
return builder(requestMapping, responseStatus)
.method(methods)
.name(serviceMeta.getType().getSimpleName())
.contextPath(serviceMeta.getContextPath())
.cors(buildCorsMeta(crossOrigin, methods))
.build();
}

Expand All @@ -80,10 +88,14 @@ public RequestMapping resolve(MethodMeta methodMeta) {
}
ServiceMeta serviceMeta = methodMeta.getServiceMeta();
AnnotationMeta<?> responseStatus = methodMeta.findMergedAnnotation(Annotations.ResponseStatus);
AnnotationMeta<?> crossOrigin = methodMeta.findMergedAnnotation(Annotations.CrossOrigin);
String[] methods = requestMapping.getStringArray("method");
return builder(requestMapping, responseStatus)
.method(methods)
.name(methodMeta.getMethod().getName())
.contextPath(serviceMeta.getContextPath())
.custom(new ServiceVersionCondition(serviceMeta.getServiceGroup(), serviceMeta.getServiceVersion()))
.cors(buildCorsMeta(crossOrigin, methods))
.build();
}

Expand All @@ -98,10 +110,34 @@ private Builder builder(AnnotationMeta<?> requestMapping, AnnotationMeta<?> resp
}
}
return builder.path(requestMapping.getValueArray())
.method(requestMapping.getStringArray("method"))
.param(requestMapping.getStringArray("params"))
.header(requestMapping.getStringArray("headers"))
.consume(requestMapping.getStringArray("consumes"))
.produce(requestMapping.getStringArray("produces"));
}

private CorsMeta buildCorsMeta(AnnotationMeta<?> crossOrigin, String[] methods) {
if (globalCorsMeta == null) {
globalCorsMeta = CorsUtils.getGlobalCorsMeta(frameworkModel);
}
if (crossOrigin == null) {
return globalCorsMeta;
}
String[] allowedMethods = crossOrigin.getStringArray("methods");
if (allowedMethods.length == 0) {
allowedMethods = methods;
if (allowedMethods.length == 0) {
allowedMethods = new String[] {CommonConstants.ANY_VALUE};
}
}
CorsMeta corsMeta = CorsMeta.builder()
.allowedOrigins(crossOrigin.getStringArray("origins"))
.allowedMethods(allowedMethods)
.allowedHeaders(crossOrigin.getStringArray("allowedHeaders"))
.exposedHeaders(crossOrigin.getStringArray("exposedHeaders"))
.allowCredentials(crossOrigin.getString("allowCredentials"))
.maxAge(crossOrigin.getNumber("maxAge"))
.build();
return globalCorsMeta.combine(corsMeta);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import java.util.Map;

import org.springframework.util.LinkedMultiValueMap;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.ExceptionHandler;

@CrossOrigin
public class SpringDemoServiceImpl implements SpringRestDemoService {
private static Map<String, Object> context;
private boolean called;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ protected void doOnNext(Object data) throws Throwable {
public final void onError(Throwable throwable) {
if (throwable instanceof HttpResultPayloadException) {
onNext(((HttpResultPayloadException) throwable).getResult());
doOnCompleted(null);
return;
}
try {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public enum HttpStatus {
OK(200),
CREATED(201),
ACCEPTED(202),
NO_CONTENT(204),
FOUND(302),
BAD_REQUEST(400),
UNAUTHORIZED(401),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,10 +162,10 @@ protected void onDataCompletion(MESSAGE message) {
protected void logError(Throwable t) {
if (t instanceof HttpStatusException) {
HttpStatusException e = (HttpStatusException) t;
if (e.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.getCode()) {
if (e.getStatusCode() >= HttpStatus.BAD_REQUEST.getCode()) {
LOGGER.debug("http status exception", e);
return;
}
return;
}
LOGGER.error(INTERNAL_ERROR, "", "", "server internal error", t);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,5 +57,14 @@ public final class RestConstants {
public static final String CASE_SENSITIVE_MATCH_KEY = CONFIG_PREFIX + "case-sensitive-match";
public static final String FORMAT_PARAMETER_NAME_KEY = CONFIG_PREFIX + "format-parameter-name";

/* Cors Configuration Key */
public static final String CORS_CONFIG_PREFIX = CONFIG_PREFIX + "cors.";
public static final String ALLOWED_ORIGINS = CORS_CONFIG_PREFIX + "allowed-origins";
public static final String ALLOWED_METHODS = CORS_CONFIG_PREFIX + "allowed-methods";
public static final String ALLOWED_HEADERS = CORS_CONFIG_PREFIX + "allowed-headers";
public static final String ALLOW_CREDENTIALS = CORS_CONFIG_PREFIX + "allow-credentials";
public static final String EXPOSED_HEADERS = CORS_CONFIG_PREFIX + "exposed-headers";
public static final String MAX_AGE = CORS_CONFIG_PREFIX + "max-age";

private RestConstants() {}
}
Loading

0 comments on commit 1f6d441

Please sign in to comment.