blob: 23c1a9cbf7d45470ae4e567f3ea2b2727dc7fae3 [file] [log] [blame]
/*
* 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.shardingsphere.elasticjob.restful.pipeline;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpMethod;
import io.netty.util.ReferenceCountUtil;
import org.apache.shardingsphere.elasticjob.restful.RestfulController;
import org.apache.shardingsphere.elasticjob.restful.annotation.ContextPath;
import org.apache.shardingsphere.elasticjob.restful.annotation.Mapping;
import org.apache.shardingsphere.elasticjob.restful.handler.HandleContext;
import org.apache.shardingsphere.elasticjob.restful.handler.Handler;
import org.apache.shardingsphere.elasticjob.restful.handler.HandlerMappingRegistry;
import org.apache.shardingsphere.elasticjob.restful.handler.HandlerNotFoundException;
import org.apache.shardingsphere.elasticjob.restful.mapping.MappingContext;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Optional;
/**
* If a HTTP request reached, HTTP request dispatcher would lookup a proper Handler for the request.
* Assemble a {@link HandleContext} with HTTP request and {@link MappingContext}, then pass it to the next in-bound handler.
*/
@Sharable
public final class HttpRequestDispatcher extends ChannelInboundHandlerAdapter {
private static final String TRAILING_SLASH = "/";
private final HandlerMappingRegistry mappingRegistry = new HandlerMappingRegistry();
private final boolean trailingSlashSensitive;
public HttpRequestDispatcher(final List<RestfulController> restfulControllers, final boolean trailingSlashSensitive) {
this.trailingSlashSensitive = trailingSlashSensitive;
initMappingRegistry(restfulControllers);
}
@SuppressWarnings({"unchecked", "NullableProblems"})
@Override
public void channelRead(final ChannelHandlerContext ctx, final Object msg) {
HandleContext<Handler> handleContext = (HandleContext<Handler>) msg;
FullHttpRequest request = handleContext.getHttpRequest();
if (!trailingSlashSensitive) {
request.setUri(appendTrailingSlashIfAbsent(request.uri()));
}
Optional<MappingContext<Handler>> mappingContext = mappingRegistry.getMappingContext(request);
if (mappingContext.isPresent()) {
handleContext.setMappingContext(mappingContext.get());
ctx.fireChannelRead(handleContext);
} else {
ReferenceCountUtil.release(request);
throw new HandlerNotFoundException(request.uri());
}
}
private void initMappingRegistry(final List<RestfulController> restfulControllers) {
for (RestfulController restfulController : restfulControllers) {
Class<? extends RestfulController> controllerClass = restfulController.getClass();
String contextPath = Optional.ofNullable(controllerClass.getAnnotation(ContextPath.class)).map(ContextPath::value).orElse("");
for (Method method : controllerClass.getMethods()) {
Mapping mapping = method.getAnnotation(Mapping.class);
if (null == mapping) {
continue;
}
HttpMethod httpMethod = HttpMethod.valueOf(mapping.method());
String path = mapping.path();
String fullPathPattern = resolveFullPath(contextPath, path);
if (!trailingSlashSensitive) {
fullPathPattern = appendTrailingSlashIfAbsent(fullPathPattern);
}
mappingRegistry.addMapping(httpMethod, fullPathPattern, new Handler(restfulController, method));
}
}
}
private String resolveFullPath(final String contextPath, final String pattern) {
return Optional.ofNullable(contextPath).orElse("") + pattern;
}
private String appendTrailingSlashIfAbsent(final String uri) {
String[] split = uri.split("\\?");
if (1 == split.length) {
return uri.endsWith(TRAILING_SLASH) ? uri : uri + TRAILING_SLASH;
}
String path = split[0];
return path.endsWith(TRAILING_SLASH) ? uri : path + TRAILING_SLASH + "?" + split[1];
}
}