package rfx.server.http; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.QueryStringDecoder; import java.lang.annotation.Annotation; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import org.apache.commons.lang3.exception.ExceptionUtils; import org.reflections.Reflections; import rfx.server.configs.ContentTypePool; import rfx.server.http.HttpProcessor.RedirectService; import rfx.server.http.common.NettyHttpUtil; import rfx.server.http.data.HttpRequestEvent; import rfx.server.http.data.service.DataService; import rfx.server.http.data.service.StringDataService; import rfx.server.log.handlers.StaticFileHandler; import rfx.server.util.RoundRobin; import rfx.server.util.StringPool; import rfx.server.util.StringUtil; import rfx.server.util.template.DataServiceProcessingUtil; /** * @author Trieu.nguyen * * the manager, the factory for HTTP processor instances, input: HttpRequest output: FullHttpResponse * */ public class HttpProcessorManager { private String contentType; Class<?> httpProcessorClass; RoundRobin<HttpProcessor> roundRobinRounter; static Map<String,String> uriMappingText = new HashMap<>(); static boolean autoOptimizeOutput = false; public static void setUriMappingText(String uri, String description) { uriMappingText.put(uri, description); } public static Map<String, String> getUriMappingText() { return uriMappingText; } public HttpProcessorManager(String contentType, Class<?> httpProcessorClass, int maxPoolSize) throws Exception { super(); initPool(contentType,httpProcessorClass, maxPoolSize); } void initPool(String contentType, Class<?> httpProcessorClass, int maxPoolSize) throws InstantiationException, IllegalAccessException { this.contentType = contentType; this.httpProcessorClass = httpProcessorClass; if(maxPoolSize == 1){ roundRobinRounter = new RoundRobin<>((HttpProcessor) httpProcessorClass.newInstance()); } else { List<HttpProcessor> pool = new ArrayList<>(maxPoolSize); for (int i = 0; i < maxPoolSize; i++) { HttpProcessor httpProcessor = (HttpProcessor) httpProcessorClass.newInstance(); pool.add(httpProcessor); } roundRobinRounter = new RoundRobin<>(pool); } } /** * the main router for all processors * * @return FullHttpResponse */ public FullHttpResponse doProcessing(HttpRequestEvent event) { DataService dataServe = null; FullHttpResponse response = null; HttpProcessor processor; try { processor = roundRobinRounter.next(); dataServe = processor.doProcessing(event); if(dataServe instanceof RedirectService){ String url = ((RedirectService)dataServe).getRedirectedUrl(); response = NettyHttpUtil.redirect(url); } else if(dataServe instanceof StringDataService){ StringDataService stringDataService = (StringDataService) dataServe; response = NettyHttpUtil.theHttpContent(stringDataService.toString(), stringDataService.getContentType(contentType)); } else { switch (contentType) { case ContentTypePool.TRACKING_GIF: response = StaticFileHandler.theBase64Image1pxGif(); break; case ContentTypePool.JSON: String json = StringUtil.convertObjectToSafeJson(dataServe); response = NettyHttpUtil.theHttpContent(json, contentType); break; default: response = DataServiceProcessingUtil.processOutput(event, dataServe, contentType); break; } } } catch (Throwable e) { e.printStackTrace(); StringBuilder s = new StringBuilder("Error###"); s.append(e.getMessage()); s.append(" ### <br>\n StackTrace: ").append(ExceptionUtils.getStackTrace(e)); response = NettyHttpUtil.theHttpContent(s.toString()); } finally { if(dataServe != null){ dataServe.freeResource(); } } if(response != null){ return response; } return NettyHttpUtil.theHttpContent(StringPool.BLANK); } public String getContentType() { return contentType; } public void setContentType(String contentType) { this.contentType = contentType; } /** * run at server bootstrap to init the pool of Http Event Processor * @throws Exception */ public static Map<String, HttpProcessorManager> initProcessorPool(String classpath, int filteredAccessMode, int processorPoolSize) throws Exception { Reflections reflections = new Reflections(classpath); Set<Class<?>> clazzes = reflections.getTypesAnnotatedWith(HttpProcessorConfig.class); Map<String, HttpProcessorManager> tempMap = new HashMap<>(); System.out.println("----------------------------initProcessorPool-----------------------------------"); String accessMode = (filteredAccessMode == HttpProcessorConfig.PUBLIC_ACCESS ? "PUBLIC" : "PRIVATE"); System.out.println(" Access Mode "+ accessMode); System.out.println("...classpath \""+ classpath + "\" processorPoolSize = "+ processorPoolSize); System.out.println("--------------------------------------------------------------------------------------"); for (Class<?> clazz : clazzes) { if (clazz.isAnnotationPresent(HttpProcessorConfig.class)) { Annotation annotation = clazz.getAnnotation(HttpProcessorConfig.class); HttpProcessorConfig config = (HttpProcessorConfig) annotation; if( config.privateAccess() == filteredAccessMode){ HttpProcessorManager manager = tempMap.get(config.uriPath()); if( manager == null ){ manager = new HttpProcessorManager(config.contentType(), clazz, processorPoolSize); if( StringUtil.isNotEmpty(config.uriPath()) ){ tempMap.put(config.uriPath(), manager); String s = clazz.getName() + " ;uriPath:"+config.uriPath()+" ;content-type:"+config.contentType(); System.out.println("... registered "+accessMode+" controller "+ s); HttpProcessorManager.setUriMappingText(accessMode+" "+config.uriPath(), s); } else if( StringUtil.isNotEmpty(config.uriPattern()) ){ tempMap.put(config.uriPattern(), manager); String s = clazz.getName() + " ;uriPattern:"+config.uriPattern() + " ;content-type:"+config.contentType(); System.out.println("... registered "+accessMode+" controller "+ s); HttpProcessorManager.setUriMappingText(accessMode+" "+"*/"+config.uriPattern()+"/*", s); } else { throw new IllegalArgumentException("the class "+clazz.getName() + " is missing uriPath or uriPattern config"); } } else { throw new IllegalArgumentException("duplicated "+ config.uriPath() + " , existed class " + manager.getClass().getName()); } } } } return Collections.unmodifiableMap(tempMap); } /** * routing to processor, matched by exact URI. E.g: http://example.com/get-data?id=1 will match URI "/get-data" * * @param handlers * @param qDecoder * @return */ public static final HttpProcessorManager routingForUriPath(Map<String, HttpProcessorManager> handlers, QueryStringDecoder qDecoder){ return handlers.get(qDecoder.path()); } /** * routing to processor, matched by pattern. E.g: http://example.com/v1/get-data/id_1 will match pattern "get-data" * * @param handlers * @param qDecoder * @param index * @return */ public static final HttpProcessorManager routingForUriPattern(Map<String, HttpProcessorManager> handlers, QueryStringDecoder qDecoder, int index){ String[] toks = qDecoder.path().split("/"); if(toks.length > index){ String pathPattern = toks[index]; return handlers.get(pathPattern); } return null; } }