mirror of
https://bitbucket.org/projectswg/cucore.git
synced 2026-01-15 22:04:21 -05:00
Basic HTTPS server
This commit is contained in:
BIN
res/webserver/favicon.ico
Normal file
BIN
res/webserver/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
81
res/webserver/index.html
Normal file
81
res/webserver/index.html
Normal file
@@ -0,0 +1,81 @@
|
||||
<html>
|
||||
<head>
|
||||
<title>Game Server Diagnostics</title>
|
||||
<meta http-equiv="refresh" content="-1" >
|
||||
<style type="text/css">
|
||||
body {
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
.main_container {
|
||||
width: 75%;
|
||||
color: #000000;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.memory_container {
|
||||
width: 50%;
|
||||
max-width: 50%;
|
||||
max-height: 500px;
|
||||
float: left;
|
||||
color: #000000;
|
||||
display: inline-block;
|
||||
}
|
||||
.log_container {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
max-height: 500px;
|
||||
color: #000000;
|
||||
font-family: 'Courier New', Courier, 'Lucida Sans Typewriter', 'Lucida Typewriter', monospace;
|
||||
font-size: 13px;
|
||||
overflow: visible;
|
||||
}
|
||||
.server_info {
|
||||
width: 50%;
|
||||
max-width: 50%;
|
||||
max-height: 500px;
|
||||
float: right;
|
||||
display: inline-block;
|
||||
overflow: scroll;
|
||||
}
|
||||
</style>
|
||||
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
|
||||
<script>
|
||||
getUrlParameter = function getUrlParameter(sParam) {
|
||||
var sPageURL = decodeURIComponent(window.location.search.substring(1)), sURLVariables = sPageURL.split('&'), sParameterName, i;
|
||||
for (i = 0; i < sURLVariables.length; i++) {
|
||||
sParameterName = sURLVariables[i].split('=');
|
||||
if (sParameterName[0] === sParam) {
|
||||
return sParameterName[1] === undefined ? true : sParameterName[1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
function updateServerInfo() {
|
||||
d = new Date();
|
||||
$("#memory_usage").attr("src", "memory_usage.png?"+d.getTime())
|
||||
$("#server_info").attr("src", "server_info.html");
|
||||
}
|
||||
$(document).ready(function() {
|
||||
var time = getUrlParameter("refresh");
|
||||
if (time === undefined)
|
||||
time = 3000;
|
||||
setInterval(updateServerInfo, time);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div class="main_container">
|
||||
<div width="100%">
|
||||
<div class="memory_container">
|
||||
Memory Usage:<br />
|
||||
<image id="memory_usage" src="memory_usage.png" width="100%"/>
|
||||
</div>
|
||||
<iframe id="server_info" class="server_info" src="server_info.html" frameBorder="0" seamless="yes">
|
||||
Browser does not support iframes!
|
||||
</iframe>
|
||||
</div>
|
||||
<iframe id="log" class="log_container" src="log.html" frameBorder="0" seamless="yes">
|
||||
Browser does not support iframes!
|
||||
</iframe>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
1
res/webserver/log.html
Normal file
1
res/webserver/log.html
Normal file
@@ -0,0 +1 @@
|
||||
${LOG}
|
||||
0
res/webserver/online_players.html
Normal file
0
res/webserver/online_players.html
Normal file
12
res/webserver/server_info.html
Normal file
12
res/webserver/server_info.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<style type="text/css">
|
||||
.online_players_table {
|
||||
width: 100%;
|
||||
}
|
||||
.online_player_cell {
|
||||
border: 1px solid black;
|
||||
padding: 3px;
|
||||
}
|
||||
</style>
|
||||
<div class="server_info">
|
||||
${ONLINE_PLAYERS}
|
||||
</div>
|
||||
BIN
server.keystore
Normal file
BIN
server.keystore
Normal file
Binary file not shown.
@@ -37,7 +37,6 @@ import java.util.List;
|
||||
*/
|
||||
public abstract class Manager extends Service {
|
||||
|
||||
private static final ServerManager serverManager = ServerManager.getInstance();
|
||||
private List <Service> children;
|
||||
|
||||
public Manager() {
|
||||
@@ -52,19 +51,13 @@ public abstract class Manager extends Service {
|
||||
*/
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
boolean success = super.initialize(), cSuccess = true;
|
||||
long start = 0, end = 0;
|
||||
boolean success = super.initialize();
|
||||
synchronized (children) {
|
||||
for (Service child : children) {
|
||||
if (!success)
|
||||
break;
|
||||
start = System.nanoTime();
|
||||
cSuccess = child.initialize();
|
||||
end = System.nanoTime();
|
||||
serverManager.setServiceInitTime(child, (end-start)/1E6, cSuccess);
|
||||
if (!cSuccess) {
|
||||
if (!child.initialize()) {
|
||||
System.err.println(child.getClass().getSimpleName() + " failed to initialize!");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,19 +72,13 @@ public abstract class Manager extends Service {
|
||||
*/
|
||||
@Override
|
||||
public boolean start() {
|
||||
boolean success = super.start(), cSuccess = true;
|
||||
long start = 0, end = 0;
|
||||
boolean success = super.start();
|
||||
synchronized (children) {
|
||||
for (Service child : children) {
|
||||
if (!success)
|
||||
break;
|
||||
start = System.nanoTime();
|
||||
cSuccess = child.start();
|
||||
end = System.nanoTime();
|
||||
serverManager.setServiceStartTime(child, (end-start)/1E6, cSuccess);
|
||||
if (!cSuccess) {
|
||||
if (!child.start()) {
|
||||
System.err.println(child.getClass().getSimpleName() + " failed to start!");
|
||||
success = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,15 +116,10 @@ public abstract class Manager extends Service {
|
||||
*/
|
||||
@Override
|
||||
public boolean terminate() {
|
||||
boolean success = super.terminate(), cSuccess = true;
|
||||
long start = 0, end = 0;
|
||||
boolean success = super.terminate();
|
||||
synchronized (children) {
|
||||
for (Service child : children) {
|
||||
start = System.nanoTime();
|
||||
cSuccess = child.terminate();
|
||||
end = System.nanoTime();
|
||||
serverManager.setServiceTerminateTime(child, (end-start)/1E6, cSuccess);
|
||||
if (!cSuccess)
|
||||
if (!child.terminate())
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
@@ -174,7 +156,6 @@ public abstract class Manager extends Service {
|
||||
if (s == child || s.equals(child))
|
||||
return;
|
||||
}
|
||||
serverManager.addChild(this, s);
|
||||
children.add(s);
|
||||
}
|
||||
}
|
||||
@@ -187,7 +168,6 @@ public abstract class Manager extends Service {
|
||||
if (s == null)
|
||||
return;
|
||||
synchronized (children) {
|
||||
serverManager.removeChild(this, s);
|
||||
children.remove(s);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,222 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* -------------------------------------------------------------------------------- *
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package resources.control;
|
||||
|
||||
import intents.server.ServerStatusIntent;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import resources.control.Service;
|
||||
|
||||
/**
|
||||
* Might be best to keep this protected, from a server security standpoint
|
||||
*/
|
||||
final class ServerManager implements IntentReceiver {
|
||||
|
||||
private static final ServerManager INSTANCE = new ServerManager();
|
||||
|
||||
private final Map <String, ServiceStats> serviceStats;
|
||||
private ServerStatus status;
|
||||
|
||||
private ServerManager() {
|
||||
serviceStats = new HashMap<String, ServiceStats>();
|
||||
status = ServerStatus.OFFLINE;
|
||||
ServerPublicInterface.initialize(this);
|
||||
IntentManager.getInstance().registerForIntent(ServerStatusIntent.TYPE, this);
|
||||
}
|
||||
|
||||
public static final ServerManager getInstance() {
|
||||
return INSTANCE;
|
||||
}
|
||||
|
||||
public void onIntentReceived(Intent i) {
|
||||
if (i instanceof ServerStatusIntent)
|
||||
processServerStatusIntent((ServerStatusIntent) i);
|
||||
}
|
||||
|
||||
public boolean initialize() {
|
||||
ServerPublicInterface.initialize(this);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean terminate() {
|
||||
ServerPublicInterface.terminate();
|
||||
return true;
|
||||
}
|
||||
|
||||
public void addChild(Service parent, Service child) {
|
||||
getServiceStats(parent).addChild(getServiceStats(child));
|
||||
}
|
||||
|
||||
public void removeChild(Service parent, Service child) {
|
||||
getServiceStats(parent).removeChild(getServiceStats(child));
|
||||
}
|
||||
|
||||
public void setServiceInitTime(Service service, double timeMilliseconds, boolean success) {
|
||||
getServiceStats(service).addInitTime(timeMilliseconds, success);
|
||||
}
|
||||
|
||||
public void setServiceStartTime(Service service, double timeMilliseconds, boolean success) {
|
||||
getServiceStats(service).addStartTime(timeMilliseconds, success);
|
||||
}
|
||||
|
||||
public void setServiceTerminateTime(Service service, double timeMilliseconds, boolean success) {
|
||||
getServiceStats(service).addTerminateTime(timeMilliseconds, success);
|
||||
}
|
||||
|
||||
private void processServerStatusIntent(ServerStatusIntent i) {
|
||||
status = i.getStatus();
|
||||
}
|
||||
|
||||
public byte [] serializeControlTimes() {
|
||||
synchronized (serviceStats) {
|
||||
int size = 0;
|
||||
for (ServiceStats stats : serviceStats.values())
|
||||
size += stats.getSerializeSize();
|
||||
ByteBuffer data = ByteBuffer.allocate(size);
|
||||
for (ServiceStats stats : serviceStats.values())
|
||||
data.put(stats.serialize());
|
||||
return data.array();
|
||||
}
|
||||
}
|
||||
|
||||
public ServerStatus getServerStatus() {
|
||||
return status;
|
||||
}
|
||||
|
||||
private ServiceStats getServiceStats(Service service) {
|
||||
synchronized (serviceStats) {
|
||||
ServiceStats stats = serviceStats.get(service.getClass().getName());
|
||||
if (stats == null) {
|
||||
stats = new ServiceStats(service);
|
||||
serviceStats.put(service.getClass().getName(), stats);
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ServiceStats {
|
||||
private final Service service;
|
||||
private final List <ServiceControlTime> initTimes;
|
||||
private final List <ServiceControlTime> startTimes;
|
||||
private final List <ServiceControlTime> terminateTimes;
|
||||
private final List <ServiceStats> children;
|
||||
|
||||
public ServiceStats(Service service) {
|
||||
this.service = service;
|
||||
initTimes = new ArrayList<ServiceControlTime>();
|
||||
startTimes = new ArrayList<ServiceControlTime>();
|
||||
terminateTimes = new ArrayList<ServiceControlTime>();
|
||||
children = new ArrayList<ServiceStats>();
|
||||
}
|
||||
|
||||
public void addChild(ServiceStats service) {
|
||||
children.add(service);
|
||||
}
|
||||
|
||||
public void removeChild(ServiceStats service) {
|
||||
children.remove(service);
|
||||
}
|
||||
|
||||
public void addInitTime(double time, boolean success) {
|
||||
initTimes.add(new ServiceControlTime(time, success));
|
||||
}
|
||||
|
||||
public void addStartTime(double time, boolean success) {
|
||||
startTimes.add(new ServiceControlTime(time, success));
|
||||
}
|
||||
|
||||
public void addTerminateTime(double time, boolean success) {
|
||||
terminateTimes.add(new ServiceControlTime(time, success));
|
||||
}
|
||||
|
||||
public int getSerializeSize() {
|
||||
return 2 + service.getClass().getName().length() + 9*3;
|
||||
}
|
||||
|
||||
public byte [] serialize() {
|
||||
String name = service.getClass().getName();
|
||||
ByteBuffer data = ByteBuffer.allocate(getSerializeSize());
|
||||
data.putShort((short) name.length());
|
||||
data.put(name.getBytes(Charset.forName("UTF-8")));
|
||||
serializeLastControlTime(data, initTimes);
|
||||
serializeLastControlTime(data, startTimes);
|
||||
serializeLastControlTime(data, terminateTimes);
|
||||
return data.array();
|
||||
}
|
||||
|
||||
private void serializeLastControlTime(ByteBuffer data, List <ServiceControlTime> times) {
|
||||
if (initTimes.size() > times.size() || times.size() == 0)
|
||||
serializeControlTime(data, null);
|
||||
else
|
||||
serializeControlTime(data, times.get(initTimes.size()-1));
|
||||
}
|
||||
|
||||
private void serializeControlTime(ByteBuffer data, ServiceControlTime time) {
|
||||
if (time == null) {
|
||||
data.put((byte) 0);
|
||||
data.putDouble(Double.NaN);
|
||||
} else {
|
||||
data.put((byte) (time.isSuccess() ? 1 : 0));
|
||||
data.putDouble(time.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "ServiceStats[" + service.getClass().getName() + "]";
|
||||
}
|
||||
}
|
||||
|
||||
private static class ServiceControlTime {
|
||||
private final double time;
|
||||
private final boolean success;
|
||||
|
||||
public ServiceControlTime(double time, boolean success) {
|
||||
this.time = time;
|
||||
this.success = success;
|
||||
}
|
||||
|
||||
public double getTime() {
|
||||
return time;
|
||||
}
|
||||
|
||||
public boolean isSuccess() {
|
||||
return success;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "[" + (isSuccess()?"Success":"Failure") + " in " + getTime() + "ms]";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,191 +0,0 @@
|
||||
/***********************************************************************************
|
||||
* Copyright (c) 2015 /// Project SWG /// www.projectswg.com *
|
||||
* *
|
||||
* ProjectSWG is the first NGE emulator for Star Wars Galaxies founded on *
|
||||
* July 7th, 2011 after SOE announced the official shutdown of Star Wars Galaxies. *
|
||||
* Our goal is to create an emulator which will provide a server for players to *
|
||||
* continue playing a game similar to the one they used to play. We are basing *
|
||||
* it on the final publish of the game prior to end-game events. *
|
||||
* *
|
||||
* This file is part of Holocore. *
|
||||
* *
|
||||
* -------------------------------------------------------------------------------- *
|
||||
* *
|
||||
* Holocore is free software: you can redistribute it and/or modify *
|
||||
* it under the terms of the GNU Affero General Public License as *
|
||||
* published by the Free Software Foundation, either version 3 of the *
|
||||
* License, or (at your option) any later version. *
|
||||
* *
|
||||
* Holocore is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU Affero General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU Affero General Public License *
|
||||
* along with Holocore. If not, see <http://www.gnu.org/licenses/>. *
|
||||
* *
|
||||
***********************************************************************************/
|
||||
package resources.control;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.SocketException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
import resources.config.ConfigFile;
|
||||
import resources.network.UDPServer;
|
||||
import resources.network.UDPServer.UDPCallback;
|
||||
import resources.network.UDPServer.UDPPacket;
|
||||
import resources.server_info.Config;
|
||||
import resources.server_info.DataManager;
|
||||
|
||||
final class ServerPublicInterface {
|
||||
|
||||
private static final int PORT = 44452;
|
||||
private static final int PACKET_SIZE = 1024;
|
||||
|
||||
private static ServerPublicInterface publicInterface = null;
|
||||
|
||||
private ServerManager serverManager;
|
||||
private UDPServer server;
|
||||
private boolean initialized = false;
|
||||
private boolean terminated = false;
|
||||
|
||||
private ServerPublicInterface() {
|
||||
serverManager = null;
|
||||
}
|
||||
|
||||
public static final void initialize(ServerManager instance) {
|
||||
if (publicInterface == null)
|
||||
publicInterface = new ServerPublicInterface();
|
||||
publicInterface.initializeInterface(instance);
|
||||
}
|
||||
|
||||
public static final void terminate() {
|
||||
publicInterface.terminateInterface();
|
||||
}
|
||||
|
||||
private void initializeInterface(ServerManager instance) {
|
||||
if (!initialized) {
|
||||
serverManager = instance;
|
||||
server = createUdpServer();
|
||||
initialized = true;
|
||||
terminated = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void terminateInterface() {
|
||||
if (!terminated) {
|
||||
serverManager = null;
|
||||
if (server != null)
|
||||
server.close();
|
||||
initialized = false;
|
||||
terminated = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void onReceivedPacket(UDPPacket packet) {
|
||||
if (packet.getData().length == 0)
|
||||
return;
|
||||
InetAddress sender = packet.getAddress();
|
||||
int port = packet.getPort();
|
||||
DataInputStream dis = new DataInputStream(new ByteArrayInputStream(packet.getData()));
|
||||
try {
|
||||
while (dis.available() > 0) {
|
||||
processPacket(sender, port, dis);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void processPacket(InetAddress sender, int port, DataInputStream is) throws IOException {
|
||||
RequestType type = getType(is.readByte());
|
||||
switch (type) {
|
||||
case PING_PONG: processPing(sender, port, is); break;
|
||||
case STATUS: processServerStatus(sender, port, is); break;
|
||||
case CONTROL_TIMES: processControlTimes(sender, port, is); break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void processPing(InetAddress sender, int port, DataInputStream is) {
|
||||
server.send(port, sender, new byte[]{RequestType.PING_PONG.getType()});
|
||||
}
|
||||
|
||||
private void processServerStatus(InetAddress sender, int port, DataInputStream is) {
|
||||
ServerStatus status = serverManager.getServerStatus();
|
||||
ByteBuffer data = ByteBuffer.allocate(3 + status.name().length());
|
||||
data.put(RequestType.STATUS.getType());
|
||||
data.putShort((short) status.name().length());
|
||||
data.put(status.name().getBytes());
|
||||
server.send(port, sender, data.array());
|
||||
}
|
||||
|
||||
private void processControlTimes(InetAddress sender, int port, DataInputStream is) {
|
||||
byte [] controlTimes = serverManager.serializeControlTimes();
|
||||
byte [] timePacket = new byte[controlTimes.length+1];
|
||||
timePacket[0] = RequestType.CONTROL_TIMES.getType();
|
||||
System.arraycopy(controlTimes, 0, timePacket, 1, controlTimes.length);
|
||||
server.send(port, sender, timePacket);
|
||||
}
|
||||
|
||||
private UDPServer createUdpServer() {
|
||||
UDPServer server = null;
|
||||
InetAddress bindAddr = getBindAddr();
|
||||
try {
|
||||
if (bindAddr == null)
|
||||
server = new UDPServer(PORT, PACKET_SIZE);
|
||||
else
|
||||
server = new UDPServer(bindAddr, PORT, PACKET_SIZE);
|
||||
server.setCallback(new UDPCallback() {
|
||||
public void onReceivedPacket(UDPPacket packet) {
|
||||
ServerPublicInterface.this.onReceivedPacket(packet);
|
||||
}
|
||||
});
|
||||
} catch (SocketException e) {
|
||||
// Keep it quiet - nobody needs to know this class exists
|
||||
}
|
||||
return server;
|
||||
}
|
||||
|
||||
private InetAddress getBindAddr() {
|
||||
Config c = DataManager.getInstance().getConfig(ConfigFile.NETWORK);
|
||||
try {
|
||||
if (c.containsKey("BIND-ADDR"))
|
||||
return InetAddress.getByName(c.getString("BIND-ADDR", "127.0.0.1"));
|
||||
if (c.containsKey("INTERFACE-BIND-ADDR"))
|
||||
return InetAddress.getByName(c.getString("INTERFACE-BIND-ADDR", "127.0.0.1"));
|
||||
} catch (UnknownHostException e) {
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RequestType getType(byte b) {
|
||||
for (RequestType type : RequestType.values())
|
||||
if (type.getType() == b)
|
||||
return type;
|
||||
return RequestType.UNKNOWN;
|
||||
}
|
||||
|
||||
public enum RequestType {
|
||||
PING_PONG (0x00),
|
||||
STATUS (0x01),
|
||||
CONTROL_TIMES (0x02),
|
||||
UNKNOWN (0xFF);
|
||||
|
||||
private byte type;
|
||||
|
||||
RequestType(int type) {
|
||||
this.type = (byte) type;
|
||||
}
|
||||
|
||||
public byte getType() { return type; }
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,7 +50,7 @@ public abstract class Service implements IntentReceiver {
|
||||
*/
|
||||
public boolean initialize() {
|
||||
IntentManager.getInstance().initialize();
|
||||
return DataManager.getInstance().isInitialized() && ServerManager.getInstance().initialize();
|
||||
return DataManager.getInstance().isInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -78,7 +78,7 @@ public abstract class Service implements IntentReceiver {
|
||||
* @return TRUE if termination was successful, FALSE otherwise
|
||||
*/
|
||||
public boolean terminate() {
|
||||
return ServerManager.getInstance().terminate();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -55,6 +55,7 @@ import resources.control.Intent;
|
||||
import resources.control.Manager;
|
||||
import resources.control.ServerStatus;
|
||||
import resources.server_info.Config;
|
||||
import services.admin.OnlineInterfaceService;
|
||||
import services.galaxy.GalacticManager;
|
||||
import utilities.ThreadUtilities;
|
||||
|
||||
@@ -63,6 +64,8 @@ public class CoreManager extends Manager {
|
||||
private static final int galaxyId = 1;
|
||||
|
||||
private final ScheduledExecutorService shutdownService;
|
||||
|
||||
private OnlineInterfaceService onlineInterfaceService;
|
||||
private EngineManager engineManager;
|
||||
private GalacticManager galacticManager;
|
||||
private PrintStream packetOutput;
|
||||
@@ -76,9 +79,11 @@ public class CoreManager extends Manager {
|
||||
shutdownRequested = false;
|
||||
galaxy = getGalaxy();
|
||||
if (galaxy != null) {
|
||||
onlineInterfaceService = new OnlineInterfaceService();
|
||||
engineManager = new EngineManager(galaxy);
|
||||
galacticManager = new GalacticManager(galaxy);
|
||||
|
||||
|
||||
addChildService(onlineInterfaceService);
|
||||
addChildService(engineManager);
|
||||
addChildService(galacticManager);
|
||||
}
|
||||
|
||||
153
src/services/admin/OnlineInterfaceService.java
Normal file
153
src/services/admin/OnlineInterfaceService.java
Normal file
@@ -0,0 +1,153 @@
|
||||
package services.admin;
|
||||
|
||||
import intents.PlayerEventIntent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import resources.config.ConfigFile;
|
||||
import resources.control.Intent;
|
||||
import resources.control.Service;
|
||||
import resources.server_info.Config;
|
||||
import resources.server_info.Log;
|
||||
import services.admin.http.HttpServer;
|
||||
import services.admin.http.HttpServer.HttpServerCallback;
|
||||
import services.admin.http.HttpSocket;
|
||||
import services.admin.http.HttpSocket.HttpRequest;
|
||||
import services.admin.http.HttpStatusCode;
|
||||
import services.admin.http.HttpsServer;
|
||||
import utilities.ThreadUtilities;
|
||||
|
||||
public class OnlineInterfaceService extends Service implements HttpServerCallback {
|
||||
|
||||
private static final String TAG = "OnlineInterfaceService";
|
||||
|
||||
private final WebserverData data;
|
||||
private final WebserverHandler handler;
|
||||
private final Runnable dataCollectionRunnable;
|
||||
private ScheduledExecutorService executor;
|
||||
private HttpsServer httpsServer;
|
||||
private HttpServer httpServer;
|
||||
private boolean authorized;
|
||||
|
||||
public OnlineInterfaceService() {
|
||||
data = new WebserverData();
|
||||
handler = new WebserverHandler(data);
|
||||
dataCollectionRunnable = () -> collectData();
|
||||
authorized = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean initialize() {
|
||||
Config network = getConfig(ConfigFile.NETWORK);
|
||||
authorized = !network.getString("HTTPS-KEYSTORE-PASSWORD", "").isEmpty();
|
||||
if (!authorized)
|
||||
return super.initialize();
|
||||
httpServer = new HttpServer(getBindAddr(network, "HTTP-BIND-ADDR", "BIND-ADDR"), network.getInt("HTTP-PORT", 8080));
|
||||
httpsServer = new HttpsServer(getBindAddr(network, "HTTPS-BIND-ADDR", "BIND-ADDR"), network.getInt("HTTPS-PORT", 8081));
|
||||
httpServer.setMaxConnections(network.getInt("HTTP-MAX-CONNECTIONS", 2));
|
||||
httpsServer.setMaxConnections(network.getInt("HTTPS-MAX-CONNECTIONS", 5));
|
||||
if (!httpsServer.initialize(network)) {
|
||||
System.err.println("Failed to initialize HTTPS server! Incorrect password?");
|
||||
httpServer.stop();
|
||||
httpsServer.stop();
|
||||
super.initialize();
|
||||
return false;
|
||||
}
|
||||
executor = Executors.newSingleThreadScheduledExecutor(ThreadUtilities.newThreadFactory("ServerInterface-DataCollection"));
|
||||
registerForIntent(PlayerEventIntent.TYPE);
|
||||
return super.initialize();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean start() {
|
||||
if (authorized) {
|
||||
httpServer.setServerCallback(this);
|
||||
httpsServer.setServerCallback(this);
|
||||
httpServer.start();
|
||||
httpsServer.start();
|
||||
executor.scheduleAtFixedRate(dataCollectionRunnable, 0, 1, TimeUnit.SECONDS);
|
||||
}
|
||||
return super.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean stop() {
|
||||
if (authorized) {
|
||||
httpServer.stop();
|
||||
httpsServer.stop();
|
||||
executor.shutdownNow();
|
||||
try {
|
||||
executor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
return super.stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSocketCreated(HttpSocket socket) {
|
||||
Log.i(TAG, "Received connection from: %s:%d [%s]", socket.getInetAddress(), socket.getPort(), socket.isSecure() ? "secure" : "insecure");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestReceived(HttpSocket socket, HttpRequest request) {
|
||||
try {
|
||||
if (!request.getType().equals("GET")) {
|
||||
socket.send(HttpStatusCode.METHOD_NOT_ALLOWED);
|
||||
return;
|
||||
}
|
||||
if (!socket.isSecure()) {
|
||||
socket.redirect(new URL("https", httpsServer.getBindAddress().getHostName(), httpsServer.getBindPort(), request.getURI().getPath()).toString());
|
||||
return;
|
||||
}
|
||||
handler.handleRequest(socket, request);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onIntentReceived(Intent i) {
|
||||
if (i instanceof PlayerEventIntent) {
|
||||
PlayerEventIntent pei = (PlayerEventIntent) i;
|
||||
switch (pei.getEvent()) {
|
||||
case PE_ZONE_IN:
|
||||
data.addOnlinePlayer(pei.getPlayer());
|
||||
break;
|
||||
case PE_LOGGED_OUT:
|
||||
data.removeOnlinePlayer(pei.getPlayer());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void collectData() {
|
||||
Runtime r = Runtime.getRuntime();
|
||||
double memUsage = 1 - ((double) r.totalMemory() / r.maxMemory());
|
||||
data.addMemoryUsageData(memUsage);
|
||||
}
|
||||
|
||||
private InetAddress getBindAddr(Config c, String firstTry, String secondTry) {
|
||||
String t = firstTry;
|
||||
try {
|
||||
if (c.containsKey(firstTry))
|
||||
return InetAddress.getByName(c.getString(firstTry, "127.0.0.1"));
|
||||
t = secondTry;
|
||||
if (c.containsKey(secondTry))
|
||||
return InetAddress.getByName(c.getString(secondTry, "127.0.0.1"));
|
||||
} catch (UnknownHostException e) {
|
||||
System.err.println("NetworkListenerService: Unknown host for IP: " + t);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
60
src/services/admin/WebserverData.java
Normal file
60
src/services/admin/WebserverData.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package services.admin;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import resources.player.Player;
|
||||
|
||||
class WebserverData {
|
||||
|
||||
private static final Charset ASCII = Charset.forName("ASCII");
|
||||
|
||||
private final double [] memoryUsage;
|
||||
private final Set<Player> onlinePlayers;
|
||||
|
||||
public WebserverData() {
|
||||
memoryUsage = new double[60 * 5];
|
||||
onlinePlayers = new HashSet<>();
|
||||
}
|
||||
|
||||
public void addMemoryUsageData(double percent) {
|
||||
for (int i = 1; i < memoryUsage.length; i++)
|
||||
memoryUsage[i-1] = memoryUsage[i];
|
||||
memoryUsage[memoryUsage.length-1] = percent;
|
||||
}
|
||||
|
||||
public void addOnlinePlayer(Player player) {
|
||||
onlinePlayers.add(player);
|
||||
}
|
||||
|
||||
public void removeOnlinePlayer(Player player) {
|
||||
onlinePlayers.remove(player);
|
||||
}
|
||||
|
||||
public double [] getMemoryUsage() {
|
||||
return Arrays.copyOf(memoryUsage, memoryUsage.length);
|
||||
}
|
||||
|
||||
public Set<Player> getOnlinePlayers() {
|
||||
return Collections.unmodifiableSet(onlinePlayers);
|
||||
}
|
||||
|
||||
public String getLog() throws IOException {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(new File("log.txt")), ASCII));
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (reader.ready()) {
|
||||
builder.append(reader.readLine() + System.lineSeparator());
|
||||
}
|
||||
reader.close();
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
}
|
||||
205
src/services/admin/WebserverHandler.java
Normal file
205
src/services/admin/WebserverHandler.java
Normal file
@@ -0,0 +1,205 @@
|
||||
package services.admin;
|
||||
|
||||
import java.awt.Color;
|
||||
import java.awt.Font;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.RenderingHints;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import resources.player.Player;
|
||||
import resources.server_info.Log;
|
||||
import services.admin.http.HttpImageType;
|
||||
import services.admin.http.HttpSocket;
|
||||
import services.admin.http.HttpSocket.HttpRequest;
|
||||
import services.admin.http.HttpStatusCode;
|
||||
|
||||
class WebserverHandler {
|
||||
|
||||
private static final String TAG = "WebserverHandler";
|
||||
private static final Charset ASCII = Charset.forName("ASCII");
|
||||
|
||||
private final WebserverData data;
|
||||
private final Pattern variablePattern;
|
||||
|
||||
public WebserverHandler(WebserverData data) {
|
||||
this.data = data;
|
||||
variablePattern = Pattern.compile("\\$\\{.+\\}"); // Looks for variables: ${VAR_NAME}
|
||||
}
|
||||
|
||||
public void handleRequest(HttpSocket socket, HttpRequest request) throws IOException {
|
||||
Log.i(TAG, "Requested: " + request.getURI());
|
||||
String file = request.getURI().toASCIIString();
|
||||
if (file.contains("?"))
|
||||
file = file.substring(0, file.indexOf('?'));
|
||||
switch (file) {
|
||||
case "/memory_usage.png":
|
||||
socket.send(createMemoryUsage(), HttpImageType.PNG);
|
||||
break;
|
||||
default: {
|
||||
byte [] response = parseFile(file);
|
||||
if (response == null)
|
||||
socket.send(HttpStatusCode.NOT_FOUND, request.getURI() + " is not found!");
|
||||
else
|
||||
socket.send(getFileType(file), response);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedImage createMemoryUsage() {
|
||||
double [] memoryUsage = data.getMemoryUsage();
|
||||
final int graphHeight = 300;
|
||||
BufferedImage image = new BufferedImage(900, graphHeight + 25, BufferedImage.TYPE_3BYTE_BGR);
|
||||
Graphics2D g = image.createGraphics();
|
||||
int prevX = -1;
|
||||
int prevY = -1;
|
||||
Font font = g.getFont();
|
||||
g.setFont(font.deriveFont(Math.min(image.getHeight()/10.0f, 25.0f)));
|
||||
RenderingHints rh = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
|
||||
g.setRenderingHints(rh);
|
||||
g.setColor(Color.WHITE);
|
||||
g.drawRect(0, 0, image.getWidth()-1, graphHeight-1);
|
||||
g.drawLine(0, image.getHeight()-1, image.getWidth(), image.getHeight()-1);
|
||||
for (int i = 1; i < 10; i++) {
|
||||
g.drawLine(0, (int) ((i/10.0)*graphHeight), 10, (int) ((i/10.0)*graphHeight));
|
||||
}
|
||||
String inUse = getMemoryInUse();
|
||||
int width = g.getFontMetrics().stringWidth(inUse);
|
||||
g.drawString("Current: " + (int) (memoryUsage[memoryUsage.length-1] * 100) + "%", 5, image.getHeight() - 5);
|
||||
g.drawString(inUse, image.getWidth()-width - 5, image.getHeight() - 5);
|
||||
g.setColor(Color.RED);
|
||||
int start = 0;
|
||||
for (double d : memoryUsage) {
|
||||
if (d == 0)
|
||||
start++;
|
||||
else
|
||||
break;
|
||||
}
|
||||
for (int i = start; i < memoryUsage.length; i++) {
|
||||
int x = (int) ((double) i / memoryUsage.length * image.getWidth());
|
||||
int y = (int) ((1-memoryUsage[i]) * graphHeight);
|
||||
if (prevX != -1 && prevY != -1)
|
||||
g.drawLine(prevX, prevY, x, y);
|
||||
prevX = x;
|
||||
prevY = y;
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private String getMemoryInUse() {
|
||||
double memory = Runtime.getRuntime().totalMemory();
|
||||
String [] types = new String[]{"B", "KB", "MB", "GB"};
|
||||
String type = types[0];
|
||||
for (int i = 0; i < types.length && memory >= 1024; i++) {
|
||||
memory /= 1024;
|
||||
type = types[i];
|
||||
}
|
||||
return String.format("In-Use: %.2f%s", memory, type);
|
||||
}
|
||||
|
||||
private String getFileType(String filepath) {
|
||||
if (!filepath.contains("."))
|
||||
return "text/html";
|
||||
String type = filepath.substring(filepath.lastIndexOf('.')+1).toLowerCase(Locale.US);
|
||||
switch (type) {
|
||||
case "png":
|
||||
case "gif":
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
case "ico":
|
||||
return "image/" + type;
|
||||
default:
|
||||
return "text/" + type;
|
||||
}
|
||||
}
|
||||
|
||||
private byte [] parseFile(String filepath) throws IOException {
|
||||
File file = new File("res/webserver" + filepath);
|
||||
if (file.isDirectory())
|
||||
file = new File(file, "index.html");
|
||||
String type = getFileType(filepath);
|
||||
if (!verifyPath(file))
|
||||
return null;
|
||||
if (type.equalsIgnoreCase("text/html"))
|
||||
return parseHtmlFile(file).getBytes(ASCII);
|
||||
try (InputStream is = new FileInputStream(file)) {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(is.available());
|
||||
byte [] buffer = new byte[Math.min(1024, is.available())];
|
||||
while (is.available() > 0) {
|
||||
if (is.available() > buffer.length && buffer.length < 1024)
|
||||
buffer = new byte[Math.min(1024, is.available())];
|
||||
int len = is.read(buffer);
|
||||
baos.write(buffer, 0, len);
|
||||
}
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verifyPath(File file) {
|
||||
File parent = new File("res/webserver");
|
||||
if (!file.getAbsolutePath().startsWith(parent.getAbsolutePath()))
|
||||
return false;
|
||||
if (!file.isFile())
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private String parseHtmlFile(File file) throws IOException {
|
||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), ASCII))) {
|
||||
String line = null;
|
||||
StringBuilder builder = new StringBuilder();
|
||||
while (reader.ready()) {
|
||||
line = reader.readLine();
|
||||
if (line == null)
|
||||
break;
|
||||
Matcher matcher = variablePattern.matcher(line);
|
||||
while (matcher.find()) {
|
||||
String var = matcher.group();
|
||||
var = var.substring(2, var.length()-1);
|
||||
line = line.replaceAll("\\$\\{"+var+"\\}", getVariable(var));
|
||||
matcher.reset(line);
|
||||
}
|
||||
builder.append(line + System.lineSeparator());
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
|
||||
private String getVariable(String var) throws IOException {
|
||||
var = var.toLowerCase(Locale.US);
|
||||
switch (var) {
|
||||
case "log":
|
||||
return data.getLog().replace("\n", "\n<br />");
|
||||
case "online_players": {
|
||||
Set<Player> players = data.getOnlinePlayers();
|
||||
StringBuilder ret = new StringBuilder("Online Players: ["+players.size()+"]<br />");
|
||||
ret.append("<table class=\"online_players_table\"><tr><th>Username</th><th>User ID</th><th>Character</th><th>Character ID</th></tr>");
|
||||
for (Player p : players) {
|
||||
ret.append("<tr>");
|
||||
ret.append(String.format("<td class=\"online_player_cell\">%s</td>", p.getUsername()));
|
||||
ret.append(String.format("<td class=\"online_player_cell\">%d</td>", p.getUserId()));
|
||||
ret.append(String.format("<td class=\"online_player_cell\">%s</td>", p.getCharacterName()));
|
||||
ret.append(String.format("<td class=\"online_player_cell\">%d</td>", p.getCreatureObject().getObjectId()));
|
||||
ret.append("</tr>");
|
||||
}
|
||||
ret.append("</table>");
|
||||
return ret.toString();
|
||||
}
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
7
src/services/admin/http/HttpImageType.java
Normal file
7
src/services/admin/http/HttpImageType.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package services.admin.http;
|
||||
|
||||
public enum HttpImageType {
|
||||
GIF,
|
||||
PNG,
|
||||
JPG
|
||||
}
|
||||
196
src/services/admin/http/HttpServer.java
Normal file
196
src/services/admin/http/HttpServer.java
Normal file
@@ -0,0 +1,196 @@
|
||||
package services.admin.http;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.net.SocketException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import resources.server_info.Log;
|
||||
import services.admin.http.HttpSocket.HttpRequest;
|
||||
import utilities.ThreadUtilities;
|
||||
|
||||
public class HttpServer {
|
||||
|
||||
private static final String TAG = "HttpServer";
|
||||
|
||||
private final InetAddress addr;
|
||||
private final int port;
|
||||
|
||||
private ExecutorService executor;
|
||||
private ServerSocket serverSocket;
|
||||
private AtomicInteger currentConnections;
|
||||
private HttpServerCallback callback;
|
||||
private int maxConnections;
|
||||
private boolean secure;
|
||||
|
||||
protected HttpServer(InetAddress addr, int port, boolean secure) {
|
||||
this.addr = addr;
|
||||
this.port = port;
|
||||
this.currentConnections = new AtomicInteger(0);
|
||||
this.callback = null;
|
||||
this.maxConnections = 2;
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
public HttpServer(InetAddress addr, int port) {
|
||||
this(addr, port, false);
|
||||
}
|
||||
|
||||
public void start() {
|
||||
executor = Executors.newCachedThreadPool(ThreadUtilities.newThreadFactory(getThreadFactoryName()));
|
||||
try {
|
||||
serverSocket = createSocket();
|
||||
startAcceptThread(serverSocket, secure);
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
if (serverSocket != null) {
|
||||
try {
|
||||
serverSocket.close();
|
||||
} catch (IOException e1) {
|
||||
e1.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (executor != null) {
|
||||
executor.shutdownNow();
|
||||
try {
|
||||
executor.awaitTermination(1, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setServerCallback(HttpServerCallback callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void setMaxConnections(int maxConnections) {
|
||||
this.maxConnections = maxConnections;
|
||||
}
|
||||
|
||||
public final InetAddress getBindAddress() {
|
||||
return addr;
|
||||
}
|
||||
|
||||
public final int getBindPort() {
|
||||
return port;
|
||||
}
|
||||
|
||||
protected String getThreadFactoryName() {
|
||||
return "HttpServer-%d";
|
||||
}
|
||||
|
||||
protected ServerSocket createSocket() throws IOException {
|
||||
return new ServerSocket(port, 0, addr);
|
||||
}
|
||||
|
||||
private void startAcceptThread(ServerSocket serverSocket, boolean secure) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
acceptThread(serverSocket, secure);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void startSocketThread(HttpSocket socket) {
|
||||
executor.submit(() -> {
|
||||
try {
|
||||
socketThread(socket);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
try {
|
||||
socket.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
synchronized (currentConnections) {
|
||||
currentConnections.decrementAndGet();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void acceptThread(ServerSocket serverSocket, boolean secure) {
|
||||
Log.i(TAG, "Now listening over HTTP%s. Address: %s:%d", secure?"S":"", serverSocket.getInetAddress(), serverSocket.getLocalPort());
|
||||
while (serverSocket.isBound() && !serverSocket.isClosed()) {
|
||||
try {
|
||||
Socket socket = serverSocket.accept();
|
||||
HttpSocket httpSocket = new HttpSocket(socket, secure);
|
||||
if (!httpSocket.isOpened()) {
|
||||
httpSocket.close();
|
||||
continue;
|
||||
}
|
||||
if (currentConnections.get() >= maxConnections) {
|
||||
httpSocket.send(HttpStatusCode.SERVICE_UNAVAILABLE, "The server has reached it's maximum connection limit: " + maxConnections);
|
||||
socket.close();
|
||||
} else {
|
||||
if (currentConnections.incrementAndGet() > maxConnections) {
|
||||
socket.close();
|
||||
currentConnections.decrementAndGet();
|
||||
} else {
|
||||
startSocketThread(httpSocket);
|
||||
}
|
||||
}
|
||||
} catch (SocketException e) {
|
||||
if (serverSocket.isClosed())
|
||||
break;
|
||||
e.printStackTrace();
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
Log.i(TAG, "No longer listening over HTTP%s!", secure?"S":"");
|
||||
}
|
||||
|
||||
private void socketThread(HttpSocket socket) throws IOException {
|
||||
onSocketCreated(socket);
|
||||
HttpRequest request;
|
||||
while (socket.isOpened() && !socket.isClosed()) {
|
||||
request = socket.waitForRequest();
|
||||
if (request == null)
|
||||
break;
|
||||
if (request.getType() == null || request.getURI() == null || request.getHttpVersion() == null) {
|
||||
socket.send(HttpStatusCode.BAD_REQUEST);
|
||||
continue;
|
||||
}
|
||||
onRequestReceived(socket, request);
|
||||
}
|
||||
}
|
||||
|
||||
private void onSocketCreated(HttpSocket socket) {
|
||||
if (callback == null)
|
||||
return;
|
||||
try {
|
||||
callback.onSocketCreated(socket);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void onRequestReceived(HttpSocket socket, HttpRequest request) {
|
||||
if (callback == null)
|
||||
return;
|
||||
try {
|
||||
callback.onRequestReceived(socket, request);
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public interface HttpServerCallback {
|
||||
void onSocketCreated(HttpSocket socket);
|
||||
void onRequestReceived(HttpSocket socket, HttpRequest request);
|
||||
}
|
||||
|
||||
}
|
||||
263
src/services/admin/http/HttpSocket.java
Normal file
263
src/services/admin/http/HttpSocket.java
Normal file
@@ -0,0 +1,263 @@
|
||||
package services.admin.http;
|
||||
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.URI;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
|
||||
import resources.server_info.Log;
|
||||
|
||||
public class HttpSocket implements Closeable {
|
||||
|
||||
private static final Charset ASCII = Charset.forName("ASCII");
|
||||
|
||||
private final Socket socket;
|
||||
private final BufferedReader reader;
|
||||
private final BufferedWriter writer;
|
||||
private final OutputStream rawOutputStream;
|
||||
private final boolean secure;
|
||||
|
||||
public HttpSocket(Socket socket, boolean secure) {
|
||||
this.socket = socket;
|
||||
this.reader = createBufferedReader();
|
||||
this.writer = createBufferedWriter();
|
||||
this.rawOutputStream = getOutputStream();
|
||||
this.secure = secure;
|
||||
}
|
||||
|
||||
/**
|
||||
* Waits for the next HTTP request to come in, then returns the request
|
||||
* sent by the client - or null if the client closes the connection.
|
||||
* @return the HttpRequest sent by the client, or null if the connection is
|
||||
* closed
|
||||
*/
|
||||
public HttpRequest waitForRequest() {
|
||||
while (!socket.isClosed()) {
|
||||
String line = readLine();
|
||||
boolean hasData = line != null && !line.isEmpty();
|
||||
String [] req = null;
|
||||
Map<String, String> params = new HashMap<>();
|
||||
while (line != null && !line.isEmpty()) {
|
||||
if (req == null)
|
||||
req = line.split(" ", 3);
|
||||
String [] parts = line.split(": ", 2);
|
||||
if (parts.length == 2)
|
||||
params.put(parts[0], parts[1]);
|
||||
hasData = !line.isEmpty();
|
||||
line = readLine();
|
||||
}
|
||||
if (hasData) {
|
||||
String type = null;
|
||||
URI uri = null;
|
||||
String version = null;
|
||||
if (req.length >= 1)
|
||||
type = req[0];
|
||||
if (req.length >= 2)
|
||||
uri = URI.create(req[1]);
|
||||
if (req.length >= 3)
|
||||
version = req[2];
|
||||
return new HttpRequest(type, uri, version, params);
|
||||
}
|
||||
if (line == null)
|
||||
break;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void redirect(String url) throws IOException {
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("Location", url);
|
||||
send(HttpStatusCode.MOVED_PERMANENTLY, params, "text/html", "");
|
||||
}
|
||||
|
||||
public void send(HttpStatusCode code) throws IOException {
|
||||
send(code, "");
|
||||
}
|
||||
|
||||
public void send(String response) throws IOException {
|
||||
send(HttpStatusCode.OK, response);
|
||||
}
|
||||
|
||||
public void send(String contentType, byte [] response) throws IOException {
|
||||
send(HttpStatusCode.OK, new HashMap<>(), contentType, response);
|
||||
}
|
||||
|
||||
public void send(BufferedImage image, HttpImageType type) throws IOException {
|
||||
String contentType = "image";
|
||||
switch (type) {
|
||||
case GIF:
|
||||
contentType = "image/gif";
|
||||
break;
|
||||
case JPG:
|
||||
contentType = "image/jpeg";
|
||||
break;
|
||||
case PNG:
|
||||
contentType = "image/png";
|
||||
break;
|
||||
}
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream(image.getWidth() * image.getHeight() * 3);
|
||||
if (!ImageIO.write(image, type.name().toLowerCase(Locale.US), baos))
|
||||
throw new IllegalArgumentException("Cannot write image with type: " + type);
|
||||
send(HttpStatusCode.OK, new HashMap<>(), contentType, baos.toByteArray());
|
||||
}
|
||||
|
||||
public void send(HttpStatusCode code, String response) throws IOException {
|
||||
if (code == HttpStatusCode.OK && response.isEmpty())
|
||||
code = HttpStatusCode.NO_CONTENT;
|
||||
send(code, new HashMap<>(), "text/html", response);
|
||||
}
|
||||
|
||||
private void send(HttpStatusCode code, Map<String, String> params, String contentType, String response) throws IOException {
|
||||
params.put("Content-Length", Integer.toString(response.length()));
|
||||
params.put("Content-Type", contentType);
|
||||
sendHeader(code, params);
|
||||
writer.write(response);
|
||||
writer.flush();
|
||||
}
|
||||
|
||||
private void send(HttpStatusCode code, Map<String, String> params, String contentType, byte [] data) throws IOException {
|
||||
params.put("Content-Length", Integer.toString(data.length));
|
||||
params.put("Content-Type", contentType);
|
||||
sendHeader(code, params);
|
||||
writer.flush();
|
||||
rawOutputStream.write(data);
|
||||
rawOutputStream.flush();
|
||||
}
|
||||
|
||||
private void sendHeader(HttpStatusCode code, Map<String, String> params) throws IOException {
|
||||
write("HTTP/1.1 %d %s", code.getCode(), code.getName());
|
||||
for (Entry<String, String> param : params.entrySet())
|
||||
write(param.getKey() + ": " + param.getValue());
|
||||
writer.newLine();
|
||||
}
|
||||
|
||||
private String readLine() {
|
||||
try {
|
||||
return reader.readLine();
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
socket.close();
|
||||
}
|
||||
|
||||
public boolean isOpened() {
|
||||
return reader != null && writer != null;
|
||||
}
|
||||
|
||||
public boolean isClosed() {
|
||||
return socket.isClosed();
|
||||
}
|
||||
|
||||
public boolean isSecure() {
|
||||
return secure;
|
||||
}
|
||||
|
||||
public InetAddress getInetAddress() {
|
||||
return socket.getInetAddress();
|
||||
}
|
||||
|
||||
public int getPort() {
|
||||
return socket.getPort();
|
||||
}
|
||||
|
||||
private void write(String str, Object ... args) throws IOException {
|
||||
writer.write(String.format(str, args) + System.lineSeparator());
|
||||
Log.d("HttpSocket", str, args);
|
||||
}
|
||||
|
||||
private BufferedReader createBufferedReader() {
|
||||
try {
|
||||
return new BufferedReader(new InputStreamReader(socket.getInputStream(), ASCII));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private BufferedWriter createBufferedWriter() {
|
||||
try {
|
||||
return new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), ASCII));
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private OutputStream getOutputStream() {
|
||||
try {
|
||||
return socket.getOutputStream();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static class HttpRequest {
|
||||
private final String type;
|
||||
private final URI uri;
|
||||
private final String httpVersion;
|
||||
private final Map<String, String> params;
|
||||
|
||||
private HttpRequest(String requestType, URI uri, String httpVersion, Map<String, String> params) {
|
||||
this.type = requestType;
|
||||
this.uri = uri;
|
||||
this.httpVersion = httpVersion;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of HTTP request sent. It could be one of: GET, POST,
|
||||
* HEAD.
|
||||
* @return the type of HTTP request
|
||||
*/
|
||||
public String getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the requested URI sent
|
||||
* @return the requested URI
|
||||
*/
|
||||
public URI getURI() {
|
||||
return uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the HTTP version of the client, typical format is: HTTP/1.1
|
||||
* @return the HTTP version of the client
|
||||
*/
|
||||
public String getHttpVersion() {
|
||||
return httpVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets all the paramters sent in the HTTP request, such as:
|
||||
* "User-Agent", "Accept", etc.
|
||||
* @return the HTTP request parameters
|
||||
*/
|
||||
public Map<String, String> getParams() {
|
||||
return Collections.unmodifiableMap(params);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
48
src/services/admin/http/HttpStatusCode.java
Normal file
48
src/services/admin/http/HttpStatusCode.java
Normal file
@@ -0,0 +1,48 @@
|
||||
package services.admin.http;
|
||||
|
||||
public enum HttpStatusCode {
|
||||
/** The request is OK (this is the standard response for successful HTTP requests) */
|
||||
OK (200, "OK"),
|
||||
/** The request has been successfully processed, but is not returning any content */
|
||||
NO_CONTENT (204, "No Content"),
|
||||
/** The requested page has moved to a new URL */
|
||||
MOVED_PERMANENTLY (301, "Moved Permanently"),
|
||||
/** The requested page has moved temporarily to a new URL */
|
||||
FOUND (302, "Found"),
|
||||
/** The requested page can be found under a different URL */
|
||||
SEE_OTHER (303, "See Other"),
|
||||
/** The requested page has moved temporarily to a new URL */
|
||||
TEMPORARY_REDIRECT (307, "Temporary Redirect"),
|
||||
/** The request cannot be fulfilled due to bad syntax */
|
||||
BAD_REQUEST (400, "Bad Request"),
|
||||
/** The request was a legal request, but the server is refusing to respond to it.
|
||||
For use when authentication is possible but has failed or not yet been provided */
|
||||
UNAUTHORIZED (401, "Unauthorized"),
|
||||
/** The request was a legal request, but the server is refusing to respond to it */
|
||||
FORBIDDEN (403, "Forbidden"),
|
||||
/** The requested page could not be found but may be available again in the future */
|
||||
NOT_FOUND (404, "Not Found"),
|
||||
/** A request was made of a page using a request method not supported by that page */
|
||||
METHOD_NOT_ALLOWED (405, "Method Not Allowed"),
|
||||
/** A generic error message, given when no more specific message is suitable */
|
||||
INTERNAL_ERROR (500, "Internal Error"),
|
||||
/** The server is currently unavailable (overloaded or down) */
|
||||
SERVICE_UNAVAILABLE (503, "");
|
||||
|
||||
private int code;
|
||||
private String name;
|
||||
|
||||
HttpStatusCode(int code, String name) {
|
||||
this.code = code;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public int getCode() {
|
||||
return code;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
}
|
||||
81
src/services/admin/http/HttpsServer.java
Normal file
81
src/services/admin/http/HttpsServer.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package services.admin.http;
|
||||
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
|
||||
import javax.net.ssl.KeyManager;
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
|
||||
import resources.server_info.Config;
|
||||
import resources.server_info.Log;
|
||||
|
||||
public class HttpsServer extends HttpServer {
|
||||
|
||||
private static final String SERVER_KEYSTORE = "server.keystore";
|
||||
|
||||
private SSLContext sslContext;
|
||||
private SSLServerSocketFactory sslServerSocketFactory;
|
||||
|
||||
public HttpsServer(InetAddress addr, int port) {
|
||||
super(addr, port, true);
|
||||
}
|
||||
|
||||
public boolean initialize(Config config) {
|
||||
String pass = config.getString("HTTPS-KEYSTORE-PASSWORD", "");
|
||||
System.setProperty("javax.net.ssl.keyStore", SERVER_KEYSTORE);
|
||||
System.setProperty("javax.net.ssl.keyStorePassword", pass);
|
||||
try {
|
||||
sslContext = SSLContext.getInstance("TLSv1.2");
|
||||
KeyManager [] managers = createKeyManagers(pass.toCharArray());
|
||||
if (managers == null)
|
||||
return false;
|
||||
sslContext.init(managers, null, null);
|
||||
sslServerSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
protected String getThreadFactoryName() {
|
||||
return "HttpsServer-%d";
|
||||
}
|
||||
|
||||
protected ServerSocket createSocket() throws IOException {
|
||||
try {
|
||||
return sslServerSocketFactory.createServerSocket(getBindPort(), 0, getBindAddress());
|
||||
} catch (IOException e) {
|
||||
System.err.println("Failed to start HTTPS server!");
|
||||
Log.e("HttpsServer", "Failed to start HTTPS server!");
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private KeyManager [] createKeyManagers(char [] password) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, UnrecoverableKeyException {
|
||||
KeyStore ks = KeyStore.getInstance("JKS");
|
||||
InputStream ksIs = new FileInputStream(SERVER_KEYSTORE);
|
||||
try {
|
||||
ks.load(ksIs, password);
|
||||
} catch (IOException e) {
|
||||
return null; // Password invalid
|
||||
} finally {
|
||||
ksIs.close();
|
||||
}
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(ks, password);
|
||||
return kmf.getKeyManagers();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -228,7 +228,7 @@ public class LoginService extends Service {
|
||||
String message = "Sorry, you're banned!";
|
||||
sendPacket(player.getNetworkId(), new ErrorMessage(type, message, false));
|
||||
System.err.println("[" + id.getUsername() + "] Can't login - Banned! IP: " + id.getAddress() + ":" + id.getPort());
|
||||
Log.i("LoginService", "%s cannot login due to a ban, from %s:%d", id.getAddress(), id.getPort());
|
||||
Log.i("LoginService", "%s cannot login due to a ban, from %s:%d", player.getUsername(), id.getAddress(), id.getPort());
|
||||
player.setPlayerState(PlayerState.DISCONNECTED);
|
||||
new LoginEventIntent(player.getNetworkId(), LoginEvent.LOGIN_FAIL_BANNED).broadcast();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user