package org.kisst.http4j.server;
import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.io.EofException;
import org.eclipse.jetty.server.*;
import org.eclipse.jetty.server.handler.AbstractHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.kisst.http4j.server.HttpCall.UnauthorizedException;
import org.kisst.props4j.Props;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
public class HttpServer extends AbstractHandler {
public static class PageRedirectedException extends RuntimeException {
private static final long serialVersionUID = 1L;
public final String url;
public PageRedirectedException(String url) { super("call redirected to "+url); this.url=url; }
}
private final static Logger logger=LoggerFactory.getLogger(HttpServer.class);
private Server server=null;
protected final HttpCallHandler handler;
public final HttpServerConfiguration config;
public HttpServer(Props props, HttpCallHandler handler) {
this.config=new HttpServerConfiguration(props);
this.handler=handler;
}
public void startListening() {
server=createServer();
try {
server.start();
} catch (Exception e) { throw new RuntimeException(e);}
}
private Server createServer() {
Server server = new Server();
// http
if (this.config.httpEnabled) {
ServerConnector httpServerConnector = new ServerConnector(server);
httpServerConnector.setHost(this.config.host);
httpServerConnector.setPort(config.httpPort);
httpServerConnector.setIdleTimeout(this.config.idleTimeout);
server.addConnector(httpServerConnector);
logger.info("HTTP enabled on port: " + config.httpPort);
} else
logger.info("HTTP disabled");
// https
if (this.config.httpsEnabled) {
HttpConfiguration httpConfiguration = new HttpConfiguration();
httpConfiguration.setSecurePort(this.config.httpsPort);
httpConfiguration.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfiguration);
SslContextFactory sslContextFactory = createSslContextFactory();
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, HttpVersion.HTTP_1_1.asString());
ServerConnector sslServerConnector = new ServerConnector(server, sslConnectionFactory, httpConnectionFactory);
sslServerConnector.setHost(this.config.host);
sslServerConnector.setPort(this.config.httpsPort);
sslServerConnector.setIdleTimeout(this.config.idleTimeout);
server.addConnector(sslServerConnector);
logger.info("HTTPS enabled on port: " + config.httpsPort);
} else
logger.info("HTTPS disabled");
NCSARequestLog requestLog = new NCSARequestLog("logs/jetty-yyyy_mm_dd.request.log");
requestLog.setAppend(true);
requestLog.setExtended(false);
//requestLog.setLogTimeZone("GMT");
requestLog.setLogTimeZone("CET");
server.setRequestLog(requestLog);
server.setHandler(this);
return server;
}
/**
* These settings are advised at
* <a href="http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html">
* http://www.eclipse.org/jetty/documentation/current/configuring-ssl.html</a>
*/
private SslContextFactory createSslContextFactory() {
SslContextFactory factory = new SslContextFactory();
factory.setKeyStorePath(config.httpsKeyStorePath);
factory.setKeyStorePassword(config.httpsKeyStorePassword);
factory.setKeyManagerPassword(config.httpsKeyManagerPassword);
//factory.setTrustStorePath(config.httpsTrustStorePath);
//factory.setTrustStorePassword(config.httpsTrustStorePassword);
factory.setIncludeCipherSuites("TLS_DHE_RSA.*", "TLS_ECDHE.*");
factory.setExcludeCipherSuites(".*NULL.*", ".*RC4.*", ".*MD5.*", ".*DES.*", ".*DSS.*");
factory.setRenegotiationAllowed(false);
return factory;
}
public void join() {
try {
server.join();
} catch (Exception e) { throw new RuntimeException(e);}
logger.info("web server stopped");
server=null;
}
public void stopListening() {
final Server server=this.server; // remember it, because it will set it self to null
logger.info("Stopping web server");
try {
//server.setGracefulShutdown(1000);
//Thread.sleep(1000);
server.stop();
//Thread.sleep(3000);
for (Connector conn : server.getConnectors())
conn.stop();
server.destroy();
} catch (Exception e) { throw new RuntimeException(e);}
}
public void handle(String path, Request baseRequest , HttpServletRequest request, HttpServletResponse response) {
try {
String host=request.getServerName();
if (config.httpRedirectToHttps && ! request.isSecure()) {
StringBuilder url = new StringBuilder("https://");
url.append(request.getServerName());
url.append(":"+config.httpsPort);
if (request.getRequestURI() != null)
url.append(request.getRequestURI());
if (request.getQueryString() != null)
url.append("?").append(request.getQueryString());
response.sendRedirect(url.toString());
return;
}
if (config.restrictedToHost!=null) {
boolean allowed = false;
for (String h: config.restrictedToHost.split(","))
allowed=allowed || h.equals(host);
if (! allowed) {
response.sendError(403, "Server "+host+" is closed");
return;
}
}
HttpCall call=new HttpCall(baseRequest, request,response);
handler.handle(call,request.getRequestURI());
}
catch (PageRedirectedException e) { logger.info("redirect to {} from {}",e.url, request.getRequestURI()); }
catch (UnauthorizedException e) {
try {
response.resetBuffer();
response.sendError(403, e.getMessage());
}
catch (IOException e1) { logger.error("IO exception when redirecting Unauthorized call",e); }
}
catch (Exception e) {
System.out.println("GENERAL");
try {
if (e instanceof HttpException) {
HttpException he=(HttpException) e;
response.sendError(he.code, e.getMessage());
e.printStackTrace();
}
else {
logger.error("Error when handling "+path,e);
StringWriter result = new StringWriter();
PrintWriter out = new PrintWriter(result);
out.println(e.getMessage());
out.println("<pre>");
e.printStackTrace(out);
out.println("</pre>");
response.sendError(500, result.toString());
}
} catch (IOException e1) {
// ignore the new error, and now write to the logfile anyway
logger.error("Error when handling "+path, e);
}
}
finally {
try {
if (!response.isCommitted())
response.flushBuffer();
}
catch (EofException e) { logger.error("EOF error during FlushBuffer "+e); }
catch (IOException e) { logger.error("IO error during FlushBuffer ",e); }
}
}
public static class HttpException extends RuntimeException {
private static final long serialVersionUID = 1L;
private final int code;
public HttpException(int code, String msg) {super(msg); this.code=code; }
public HttpException(int code, String msg, Throwable e) {super(msg, e); this.code=code; }
public HttpException(int code, Throwable e) {super(e); this.code=code; }
}
}