package org.webpieces.webserver.impl;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import javax.inject.Inject;
import org.webpieces.frontend.api.FrontendConfig;
import org.webpieces.frontend.api.HttpFrontendManager;
import org.webpieces.frontend.api.HttpServer;
import org.webpieces.httpcommon.api.RequestListener;
import org.webpieces.nio.api.SSLEngineFactory;
import org.webpieces.nio.api.channels.TCPServerChannel;
import org.webpieces.router.api.RouterService;
import org.webpieces.router.api.exceptions.RouteNotFoundException;
import org.webpieces.router.api.routing.Nullable;
import org.webpieces.router.impl.compression.FileMeta;
import org.webpieces.templating.api.ProdTemplateModule;
import org.webpieces.util.logging.Logger;
import org.webpieces.util.logging.LoggerFactory;
import org.webpieces.util.net.URLEncoder;
import org.webpieces.webserver.api.WebServer;
import org.webpieces.webserver.api.WebServerConfig;
public class WebServerImpl implements WebServer {
private static final Logger log = LoggerFactory.getLogger(WebServerImpl.class);
@Inject
private WebServerConfig config;
@Inject @Nullable
private SSLEngineFactory factory;
@Inject
private HttpFrontendManager serverMgr;
@Inject
private RequestReceiver serverListener;
@Inject
private RouterService routingService;
private HttpServer httpServer;
private HttpServer httpsServer;
@Override
public RequestListener start() {
log.info("starting server");
routingService.start();
//validate html route id's and params on startup if 'org.webpieces.routeId.txt' exists
validateRouteIdsFromHtmlFiles();
FrontendConfig svrChanConfig = new FrontendConfig("http", config.getHttpListenAddress());
svrChanConfig.asyncServerConfig.functionToConfigureBeforeBind = config.getFunctionToConfigureServerSocket();
httpServer = serverMgr.createHttpServer(svrChanConfig, serverListener);
httpServer.start();
if(factory != null) {
FrontendConfig secureChanConfig = new FrontendConfig("https", config.getHttpsListenAddress(), 10000);
secureChanConfig.asyncServerConfig.functionToConfigureBeforeBind = config.getFunctionToConfigureServerSocket();
httpsServer = serverMgr.createHttpsServer(secureChanConfig, serverListener, factory);
httpsServer.start();
} else {
log.info("https port is disabled since configuration had no sslEngineFactory");
}
log.info("server started");
return serverListener;
}
private void validateRouteIdsFromHtmlFiles() {
try {
validateRouteIdsFromHtmlFilesImpl();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private void validateRouteIdsFromHtmlFilesImpl() throws IOException {
if(!config.isValidateRouteIdsOnStartup()) {
//This should be done in one unit test that boots the server to verify
//all routes in all pages exist and have no typos
log.info("Not validating routeIds due to configuration");
return;
}
String file = "/"+ProdTemplateModule.ROUTE_META_FILE;
log.info("Valiating routeIds from the recorded file="+file.substring(1));
URL url = getClass().getResource(file);
log.info("file found at location="+url);
if(url == null) {
throw new IllegalArgumentException("File not found on classpath="+file);
}
try (InputStream in = url.openStream();
InputStreamReader reader = new InputStreamReader(in);
BufferedReader bufReader = new BufferedReader(reader)) {
loopThroughFile(url, bufReader);
}
log.info("Validation of routeIds complete");
}
private void loopThroughFile(URL url, BufferedReader bufReader) throws IOException {
RouteNotFoundException firstException = null;
int count = 1;
String errorMsg = "";
String line;
while((line=bufReader.readLine())!=null) {
if("".equals(line.trim()))
continue;
String[] split = line.split("/");
if(split.length != 3)
throw new IllegalStateException("size="+split.length+" corrupt line="+line);
String type = split[0];
String location = URLEncoder.decode(split[1], StandardCharsets.UTF_8);
String meta = split[2];
try {
if(ProdTemplateModule.ROUTE_TYPE.equals(type)) {
processRoute(line, location, meta);
} else if(ProdTemplateModule.PATH_TYPE.equals(type)) {
processPath(url, line, location, meta);
} else
throw new IllegalStateException("wrong type. corrupt line="+line);
} catch(RouteNotFoundException e) {
if(firstException == null)
firstException = e;
errorMsg += "\n\nError "+(count++) + ": "+e.getMessage() +" location="+location+"\n entire line="+line;
}
}
if(firstException != null)
throw new RuntimeException("There were one or more invalid routeIds in html files="+errorMsg, firstException);
}
private void processPath(URL url, String line, String location, String urlPath) throws UnsupportedEncodingException {
String path = URLEncoder.decode(urlPath, StandardCharsets.UTF_8);
FileMeta meta = routingService.relativeUrlToHash(path);
if(meta == null)
throw new RouteNotFoundException("backing file for urlPath="+path+" was not found or route is missing to connect url to path. url="+url);
}
private void processRoute(String line, String location, String meta) throws UnsupportedEncodingException {
String[] split2 = meta.split(":");
if(split2.length != 3)
throw new IllegalStateException("size="+split2.length+" Corrupt line, wrong size="+line);
String routeId = URLEncoder.decode(split2[0], StandardCharsets.UTF_8);
String args = URLEncoder.decode(split2[1], StandardCharsets.UTF_8);
Map<String, String> argsWithFakeValues = new HashMap<>();
if(!"".equals(args.trim())) {
String[] argArray = args.split(",");
for(String arg : argArray) {
argsWithFakeValues.put(arg.trim(), "fakeValue");
}
}
log.info("validating recorded line="+line);
routingService.convertToUrl(routeId, argsWithFakeValues, true);
}
@Override
public void stop() {
httpServer.close();
if(httpsServer != null)
httpsServer.close();
}
@Override
public TCPServerChannel getUnderlyingHttpChannel() {
return httpServer.getUnderlyingChannel();
}
@Override
public TCPServerChannel getUnderlyingHttpsChannel() {
return httpsServer.getUnderlyingChannel();
}
}