
io.rxmicro.rest.server.detail.component.CrossOriginResourceSharingPreflightRestController Maven / Gradle / Ivy
/*
* Copyright (c) 2020. https://rxmicro.io
*
* Licensed 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 io.rxmicro.rest.server.detail.component;
import io.rxmicro.common.ImpossibleException;
import io.rxmicro.http.error.ValidationException;
import io.rxmicro.rest.model.PathVariableMapping;
import io.rxmicro.rest.server.detail.model.CrossOriginResourceSharingResource;
import io.rxmicro.rest.server.detail.model.HttpRequest;
import io.rxmicro.rest.server.detail.model.HttpResponse;
import io.rxmicro.rest.server.detail.model.Registration;
import io.rxmicro.rest.server.detail.model.mapping.ExactUrlRequestMappingRule;
import io.rxmicro.rest.server.detail.model.mapping.RequestMappingRule;
import io.rxmicro.rest.server.detail.model.mapping.UrlTemplateRequestMappingRule;
import io.rxmicro.rest.server.local.component.PathMatcher;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
import static io.rxmicro.http.HttpStandardHeaderNames.ACCESS_CONTROL_ALLOW_CREDENTIALS;
import static io.rxmicro.http.HttpStandardHeaderNames.ACCESS_CONTROL_ALLOW_HEADERS;
import static io.rxmicro.http.HttpStandardHeaderNames.ACCESS_CONTROL_ALLOW_METHODS;
import static io.rxmicro.http.HttpStandardHeaderNames.ACCESS_CONTROL_ALLOW_ORIGIN;
import static io.rxmicro.http.HttpStandardHeaderNames.ACCESS_CONTROL_MAX_AGE;
import static io.rxmicro.http.HttpStandardHeaderNames.ACCESS_CONTROL_REQUEST_HEADERS;
import static io.rxmicro.http.HttpStandardHeaderNames.ACCESS_CONTROL_REQUEST_METHOD;
import static io.rxmicro.http.HttpStandardHeaderNames.CONTENT_LENGTH;
import static io.rxmicro.http.HttpStandardHeaderNames.ORIGIN;
import static io.rxmicro.http.HttpStandardHeaderNames.VARY;
import static io.rxmicro.rest.model.HttpMethod.OPTIONS;
import static java.util.concurrent.CompletableFuture.completedStage;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toUnmodifiableMap;
/**
* Used by generated code that created by the {@code RxMicro Annotation Processor}.
*
* @author nedis
* @hidden
* @since 0.1
*/
public final class CrossOriginResourceSharingPreflightRestController extends AbstractRestController {
private final Map exactUrlMapping;
private final List urlTemplateMapping;
public CrossOriginResourceSharingPreflightRestController(
final Set crossOriginResourceSharingResources) {
this.exactUrlMapping = crossOriginResourceSharingResources.stream()
.filter(r -> !r.isUrlSegmentsPresent())
.collect(toUnmodifiableMap(CrossOriginResourceSharingResource::getUri, identity()));
this.urlTemplateMapping = crossOriginResourceSharingResources.stream()
.filter(CrossOriginResourceSharingResource::isUrlSegmentsPresent)
.collect(Collectors.toUnmodifiableList());
}
@Override
public void register(final RestControllerRegistrar registrar) {
registrar.register(
this,
new Registration(
"",
"handle",
List.of(PathVariableMapping.class, HttpRequest.class),
this::handle,
false,
exactUrlMapping.keySet().stream()
.map(u -> new ExactUrlRequestMappingRule(OPTIONS.name(), u, false))
.toArray(RequestMappingRule[]::new)
),
new Registration(
"",
"handle",
List.of(PathVariableMapping.class, HttpRequest.class),
this::handle,
false,
urlTemplateMapping.stream()
.map(r -> new UrlTemplateRequestMappingRule(OPTIONS.name(), r.getUrlSegments(), false))
.toArray(RequestMappingRule[]::new)
)
);
}
@Override
public Class> getRestControllerClass() {
return CrossOriginResourceSharingPreflightRestController.class;
}
// https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
// https://www.w3.org/TR/cors/#preflight-request
// https://www.html5rocks.com/static/images/cors_server_flowchart.png
private CompletionStage handle(final PathVariableMapping pathVariableMapping,
final HttpRequest request) {
final String origin = request.getHeaders().getValue(ORIGIN);
validateOrigin(origin);
final CrossOriginResourceSharingResource resource = getResource(request);
validateAccessControlRequestMethod(request, resource);
validateAccessControlRequestHeaders(request, resource);
final HttpResponse httpResponse = httpResponseBuilder.build();
setCORSHeaders(origin, resource, httpResponse);
return completedStage(httpResponse);
}
private CrossOriginResourceSharingResource getResource(final HttpRequest request) {
final String uri = request.getUri();
final CrossOriginResourceSharingResource resource = exactUrlMapping.get(uri);
if (resource != null) {
return resource;
}
for (final CrossOriginResourceSharingResource sharingResource : urlTemplateMapping) {
final PathMatcher pathMatcher = new PathMatcher(sharingResource.getUrlSegments());
if (pathMatcher.matches(uri).matches()) {
return sharingResource;
}
}
throw new ImpossibleException("CrossOriginResourceSharingResource must be found");
}
private void validateOrigin(final String origin) {
if (origin == null) {
throw new ValidationException(
"Not a valid preflight request: Missing '?' header", ORIGIN);
}
}
private void validateAccessControlRequestMethod(final HttpRequest request,
final CrossOriginResourceSharingResource resource) {
final String accessControlRequestMethod = request.getHeaders().getValue(ACCESS_CONTROL_REQUEST_METHOD);
if (accessControlRequestMethod == null) {
throw new ValidationException(
"Not a valid preflight request: Missing '?' header", ACCESS_CONTROL_REQUEST_METHOD);
}
if (!resource.getAllowMethods().contains(accessControlRequestMethod)) {
throw new ValidationException(
"Not a valid preflight request: Method '?' not supported. Allowed methods are: {?}",
accessControlRequestMethod, resource.getAllowMethodsCommaSeparated());
}
}
private void validateAccessControlRequestHeaders(final HttpRequest request,
final CrossOriginResourceSharingResource resource) {
final String accessControlRequestHeaders = request.getHeaders().getValue(ACCESS_CONTROL_REQUEST_HEADERS);
if (accessControlRequestHeaders != null &&
Arrays.stream(accessControlRequestHeaders.split(","))
.map(s -> s.trim().toLowerCase(Locale.ENGLISH))
.noneMatch(h -> resource.getAllowHeaders().contains(h) || resource.getExposedHeaders().contains(h))) {
throw new ValidationException(
"Not a valid preflight request: Header(s) {?} not supported. Allowed header(s): {?}",
accessControlRequestHeaders, resource.getAllHeadersCommaSeparated().orElse(""));
}
}
private void setCORSHeaders(final String origin,
final CrossOriginResourceSharingResource resource,
final HttpResponse httpResponse) {
if (resource.getAllowOrigins().contains(origin)) {
httpResponse.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, origin);
} else {
httpResponse.setHeader(ACCESS_CONTROL_ALLOW_ORIGIN, resource.getFirstOrigin());
}
httpResponse.setHeader(VARY, ORIGIN);
httpResponse.setHeader(ACCESS_CONTROL_ALLOW_METHODS, resource.getAllowMethodsCommaSeparated());
resource.getAllHeadersCommaSeparated().ifPresent(headers ->
httpResponse.setHeader(ACCESS_CONTROL_ALLOW_HEADERS, headers));
if (resource.isAccessControlAllowCredentials()) {
httpResponse.setHeader(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
}
httpResponse.setHeader(ACCESS_CONTROL_MAX_AGE, resource.getAccessControlMaxAge());
httpResponse.setHeader(CONTENT_LENGTH, 0);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy