package javaforce.service;
import java.io.*;
import java.net.*;
import java.util.*;
import java.security.*;
import javax.net.ssl.*;
import javaforce.*;
import static javaforce.service.WebSocketHandler.*;
/**
* Web server.
*
* Created : Aug 23, 2013
*/
public class WebServer {
private WebHandler api;
private WebSocketHandler wsapi;
private ServerSocket ss;
private boolean secure;
private boolean active = true;
private ArrayList clients = new ArrayList<>();
private Object clientsLock = new Object();
public static boolean config_enable_gzip = true;
public static boolean debug = false;
private static String upload_folder;
/** Start web server on non-secure port. */
public boolean start(WebHandler api, int port) {
return start(api, port, null);
}
/** Start web server on secure port using provided keys. */
public boolean start(WebHandler api, int port, KeyMgmt keys) {
secure = true;
this.api = api;
try {
if (keys != null) {
ss = JF.createServerSocketSSL(port, keys);
} else {
ss = new ServerSocket(port);
}
if (ss == null) {
throw new Exception("Failed to start server on port:" + port);
}
new Server(this).start();
} catch (Exception e) {
JFLog.log(e);
return false;
}
return true;
}
public void setWebSocketHandler(WebSocketHandler wsapi) {
this.wsapi = wsapi;
}
public void setUploadFolder(String folder) {
upload_folder = folder;
}
public void stop() {
active = false;
if (ss != null) {
try { ss.close(); } catch (Exception e) {}
ss = null;
}
//NOTE:this could be Connection thread callback so stop threads in another thread
new Thread() {public void run() {
while (clients.size() > 0) {
synchronized (clientsLock) {
if (clients.size() == 0) break;
Connection conn = clients.get(0);
conn.cancel();
}
JF.sleep(100);
}
}}.start();
}
public void setClientVerify(boolean state) {
SSLServerSocket ssl = (SSLServerSocket)ss;
ssl.setNeedClientAuth(state);
}
private static class Server extends Thread {
private WebServer web;
public Server(WebServer web) {
this.web = web;
}
public void run() {
setName("WebServer.Server");
Socket s;
while (web.active) {
try {
s = web.ss.accept();
Connection conn = new Connection(web, s);
synchronized (web.clientsLock) {
web.clients.add(conn);
}
conn.start();
} catch (SocketException e) {
if (debug) JFLog.log("WebServer.Server:disconnected");
} catch (Exception e) {
JFLog.log(e);
}
}
}
}
private static class Connection extends Thread {
private Socket s;
private InputStream is;
private WebServer web;
public Connection(WebServer web, Socket s) {
this.web = web;
this.s = s;
}
public void run() {
setName("WebServer.Connection");
//read request and pass to WebHandler
try {
StringBuilder request = new StringBuilder();
is = s.getInputStream();
while (web.active && s.isConnected()) {
int ch = is.read();
if (ch == -1) break;
request.append((char)ch);
if (!request.toString().endsWith("\r\n\r\n")) continue;
if (debug) JFLog.log("WebServer:Request detected!");
WebRequest req = new WebRequest();
req.secure = web.secure;
req.request = request.toString();
req.fields = req.request.split("\r\n");
req.is = is;
req.serverIP = s.getLocalAddress().getHostAddress();
if (req.serverIP.equals("0:0:0:0:0:0:0:1")) req.serverIP = "127.0.0.1";
req.serverPort = s.getLocalPort();
req.remoteIP = s.getInetAddress().getHostAddress();
req.remotePort = s.getPort();
WebResponse res = new WebResponse();
res.os = s.getOutputStream();
req.fields0 = req.fields[0].split(" ");
req.init(res);
if (isWebSocketRequest(req)) {
String url = req.getURL();
if (debug) JFLog.log("WebServer:WEB_SOCKET:" + url);
WebSocket socket = new WebSocket(s.getLocalAddress().getHostAddress(), s.getInetAddress().getHostAddress(), is, res.os, req.getHost(), req.fields0[1], req.cookies);
int action = WebSocketHandler.REJECT;
if (web.wsapi != null) {
action = web.wsapi.doWebSocketConnect(socket);
}
switch (action) {
case ACCEPT:
sendWebSocketAccepted(req, res);
processWebSocket(web, socket);
break;
case REJECT:
sendWebSocketDenied(req, res);
break;
case DETACH:
sendWebSocketAccepted(req, res);
s = null; //do not close
break;
}
break;
}
else if (WebUpload.isMultipartContent(req)) {
if (debug) JFLog.log("WebServer:Upload detected!");
if (upload_folder == null) {
res.setStatus(501, "Error - Uploads disabled");
} else {
WebUpload upload = new WebUpload();
req.files = upload.processRequest(req, upload_folder);
}
}
else if (req.fields0[0].equals("GET")) {
if (debug) JFLog.log("WebServer:GET:" + req.getQueryString());
req.method = "GET";
web.api.doGet(req, res);
}
else if (req.fields0[0].equals("POST")) {
if (debug) JFLog.log("WebServer:POST:" + req.getQueryString());
req.method = "POST";
web.api.doPost(req, res);
}
else {
if (debug) JFLog.log("WebServer:Unknown request!");
res.setStatus(501, "Error - Unsupported Method");
}
res.writeAll(req);
if (req.fields0[2].equals("HTTP/1.0")) break;
request.setLength(0);
}
if (s != null) {
s.close();
s = null;
}
} catch (SSLHandshakeException ssl) {
if (debug) JFLog.log("WebServer.Connection:SSL error");
} catch (SocketException e) {
if (debug) JFLog.log("WebServer.Connection:disconnected");
} catch (Exception e) {
JFLog.log(e);
}
synchronized (web.clientsLock) {
web.clients.remove(this);
}
}
public void cancel() {
if (s != null) {
try {s.close();} catch (Exception e) {}
s = null;
}
}
private boolean isWebSocketRequest(WebRequest req) {
boolean upgrade = false;
boolean websocket = false;
boolean haveKey = false;
for(int a=0;a 16777216) {
throw new Exception("WebSocket message > 16MB");
}
//now read data
byte[] data = JF.readAll(socket.is, (int)length);
//unmask data
for(int a=0;a 0x8) continue; //other control message
web.wsapi.doWebSocketMessage(socket, data, opcode);
}
} catch (SocketException e) {
if (debug) JFLog.log("WebServer.Websocket:disconnected");
} catch (Exception e) {
JFLog.log(e);
}
web.wsapi.doWebSocketClosed(socket);
}
/** Returns a chunk header for a block of data for transmission in Transfer-Encoding: chunked
* Make sure to send \r\n after actual block of data.
*/
public static byte[] chunkHeader(byte[] in) {
return String.format("%x\r\n", in.length).getBytes();
}
/** Process WebSocket within WebUIServlet context.
*
* This method creates a new Thread that runs in the WebUIServlet ClassLoader.
*/
public void attachWebSocket(WebSocket socket) {
new Thread() {
public void run() {
processWebSocket(WebServer.this, socket);
}
}.start();
}
}