Rawven commented on issue #13965:
URL: https://github.com/apache/dubbo/issues/13965#issuecomment-2049311237
# Triple Rest Cors Plan
### 1. **Cors related implementation**
Under the rest/cors package of dubbo-rpc-triple module
CorsMeta configuration class currently supports the following modules and
methods related to inspection and processing of some attributes.
```java
public class CorsMeta {
.........
private List<String> allowedOrigins;
private List<OriginPattern> allowedOriginPatterns;
private List<String> allowedMethods;
private List<HttpMethods> resolvedMethods = DEFAULT_METHODS;
private List<String> allowedHeaders;
private List<String> exposedHeaders;
private Boolean allowCredentials;
private Boolean allowPrivateNetwork;
private Long maxAge;
.........
```
CorsProcessor, used to process requests. The following are the main methods
of DefaultCorsProcesso
```java
public class DefaultCorsProcessor implements CorsProcessor {
public boolean process(CorsMeta config, HttpRequest request,
HttpResponse response) {
// set vary header
setVaryHeaders(response);
// skip if is not a cors request
if (!isCorsRequest(request)) {
return true;
}
// skip if origin already contains in Access-Control-Allow-Origin
header
if (response.header(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN) !=
null)...
if (config == null) {
// if no cors config and is a preflight request
if (isPreFlight(request)) {
reject(response);
return false;
}
return true;
}
// handle cors request
return handleInternal(request, response, config,
isPreFlight(request));
}
private boolean isPreFlight(HttpRequest request) {
// preflight request is a OPTIONS request with
// Access-Control-Request-Method header
return request.method().equals(HttpMethods.OPTIONS.name())
&&
request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD) != null;
}
private boolean isCorsRequest(HttpRequest request) {
// skip if request has no origin header
String origin = request.header(RestConstants.ORIGIN);
if (origin == null) {
return false;
}
try {
URI uri = new URI(origin);
// return true if origin is not the same as request's scheme,
host and port
return !(Objects.equals(uri.getScheme(), request.scheme())
&& uri.getHost().equals(request.serverName())
//getPortByScheme
&& getPort(uri.getScheme(), uri.getPort()) ==
getPort(request.scheme(), request.serverPort()));
} catch (URISyntaxException e) {
// skip if origin is not a valid URI
return false;
}
}
protected boolean handleInternal(HttpRequest request, HttpResponse
response, CorsMeta config, boolean isPreLight) {
//check origin,method,header are allowed
String allowOrigin =
config.checkOrigin(request.header(RestConstants.ORIGIN));
........
List<HttpMethods> allowHttpMethods =
config.checkHttpMethods(getHttpMethods(request, isPreLight));
.......
List<String> allowHeaders =
config.checkHeaders(getHttpHeaders(request, isPreLight));
......
response.setHeader(RestConstants.ACCESS_CONTROL_ALLOW_ORIGIN,
allowOrigin);
//set allow method if is prelight
if (isPreLight) {
response.setHeader(
RestConstants.ACCESS_CONTROL_ALLOW_METHODS,
......
if (isPreLight && !allowHeaders.isEmpty()) ......
if (isPreLight && config.getMaxAge() != null)....
// set related config
if (!CollectionUtils.isEmpty(config.getExposedHeaders()))...
if (Boolean.TRUE.equals(config.getAllowCredentials())) .....
if (Boolean.TRUE.equals(config.getAllowPrivateNetwork()) .....
return true;
}
}
```
### 2.**Initialize Cors configuration**
**Global configuration**
Add RestCorsConfig under org.apache.dubbo.config.RestConfig
Add the CorsMeta attribute under
org.apache.dubbo.rpc.protocol.tri.rest.mapping.RequestMapping
```java
public class RestConfig implements Serializable {
.......
/**
* The config is used to set the Global CORS configuration properties.
*/
private CorsConfig corsConfig;
.......
}
public final class RequestMapping implements Condition<RequestMapping,
HttpRequest> {
.........
private final CorsMeta corsMeta;
}
```
**Configuration parsing will combine global, class, and method
configurations in
org.apache.dubbo.rpc.protocol.tri.rest.mapping.DefaultRequestMappingRegistry**
```java
public final class DefaultRequestMappingRegistry implements
RequestMappingRegistry {
private CorsMeta globalCorsMeta;
.....
@Override
public void register(Invoker<?> invoker) {
new MethodWalker().walk(service.getClass(), (classes, consumer) -> {
for (RequestMappingResolver resolver : resolvers) {
.....
// combine gloabl config in class
classMapping.setCorsMeta(classMapping.getCorsMeta().combine(getGlobalCorsMeta()));
consumer.accept((methods) -> {
.....
//combine class in method
methodMapping = classMapping.combine(methodMapping);
....
});
}
});
}
private CorsMeta getGlobalCorsMeta() {
if (globalCorsMeta == null) {
Configuration globalConfiguration =
ConfigurationUtils.getGlobalConfiguration(ApplicationModel.defaultModel());
globalCorsMeta = CorsUtil.resolveGlobalMeta(globalConfiguration);
}
return globalCorsMeta;
}
```
**spring@CrossOrigin annotation configuration**
```java
@Activate(onClass = "org.springframework.web.bind.annotation.RequestMapping")
public class SpringMvcRequestMappingResolver implements
RequestMappingResolver {
public RequestMapping resolve(ServiceMeta serviceMeta) {
......
AnnotationMeta<?> crossOrigin =
serviceMeta.findMergedAnnotation(Annotations.CrossOrigin);
return builder(requestMapping, responseStatus)
....
.cors(createCorsMeta(crossOrigin))
.build();
}
public RequestMapping resolve(MethodMeta methodMeta)
AnnotationMeta<?> crossOrigin =
methodMeta.findMergedAnnotation(Annotations.CrossOrigin);
return builder(requestMapping, responseStatus)
......
.cors(createCorsMeta(crossOrigin))
.build();
}
private CorsMeta createCorsMeta(AnnotationMeta<?> crossOrigin) {
CorsMeta meta = new CorsMeta();
if (crossOrigin == null) {
return meta;
}
meta.setAllowCredentials(Boolean.valueOf(crossOrigin.getString("allowCredentials")));
meta.setAllowedHeaders(Arrays.asList(crossOrigin.getStringArray("allowedHeaders")));
.....
return meta;
}
}
```
### 3.**Perform request interception processing**
**Add the CorsProcessor member variable under
org.apache.dubbo.rpc.protocol.tri.rest.mapping.DefaultRequestMappingRegistry,
and add the following processing code in the lookup method**
```java
public final class DefaultRequestMappingRegistry implements
RequestMappingRegistry {
.......
private CorsMeta globalCorsMeta;
private final CorsProcessor corsProcessor;
public DefaultRequestMappingRegistry(FrameworkModel frameworkModel){
corsProcessor =
frameworkModel.getBeanFactory().getOrRegisterBean(CorsProcessor.class);
}
public HandlerMeta lookup(HttpRequest request, HttpResponse response) {
.......
// Handle preflight requests for matching
String method = preprocessingCors(request, response);
List<Candidate> candidates = new ArrayList<>(size);
for (int i = 0; i < size; i++) {
Match<Registration> match = matches.get(i);
RequestMapping mapping = match.getValue().mapping.match(request,
match.getExpression());
if (mapping != null) {
Candidate candidate = new Candidate();
candidate.mapping = mapping;
candidate.meta = match.getValue().meta;
candidate.expression = match.getExpression();
candidate.variableMap = match.getVariableMap();
candidates.add(candidate);
}
}
........
RequestMapping mapping = winner.mapping;
//Handle request
processCors(method, mapping, request, response);
....
}
private String preprocessingCors(HttpRequest request, HttpResponse
response) {
if (Objects.equals(request.method(), HttpMethods.OPTIONS.name())) {
if (CorsProcessor.isPreFlight(request)) {
// replace option by request-method
String realMethod =
request.header(RestConstants.ACCESS_CONTROL_REQUEST_METHOD);
request.setMethod(realMethod);
return realMethod;
} else {
throw new HttpResultPayloadException(HttpResult.builder()
....
}
return null;
}
private void processCors(String method, RequestMapping mapping,
HttpRequest request, HttpResponse response) {
if (method != null) {
request.setMethod(HttpMethods.OPTIONS.name());
}
if (!corsProcessor.process(mapping.getCorsMeta(), request,
response)) {
throw new HttpResultPayloadException(HttpResult.builder()
.......
}
}
............
```
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]