api functionality pt1

This commit is contained in:
darkrose 2015-04-17 02:08:09 +10:00
parent bac883efe8
commit 61531780a4
8 changed files with 509 additions and 285 deletions

View File

@ -217,8 +217,6 @@ Client::Client(
//m_env_mutex.Init();
//m_con_mutex.Init();
m_httpclient = new HTTPClient(this);
m_mesh_update_thread.Start();
/*
@ -244,8 +242,6 @@ Client::~Client()
//JMutexAutoLock conlock(m_con_mutex); //bulk comment-out
m_con.Disconnect();
}
if (g_settings->getBool("enable_http"))
m_httpclient->stop();
m_mesh_update_thread.setRun(false);
while(m_mesh_update_thread.IsRunning())
@ -258,8 +254,6 @@ void Client::connect(Address address)
//JMutexAutoLock lock(m_con_mutex); //bulk comment-out
m_con.SetTimeoutMs(0);
m_con.Connect(address);
if (g_settings->getBool("enable_http"))
m_httpclient->start(address);
}
bool Client::connectedAndInitialized()

View File

@ -153,8 +153,6 @@ struct ClientEvent
};
};
class HTTPClient;
class Client : public con::PeerHandler, public InventoryManager
{
public:
@ -374,7 +372,6 @@ private:
bool m_server_hunger;
con::Connection m_con;
HTTPClient *m_httpclient;
ISoundManager *m_sound;
IrrlichtDevice *m_device;

View File

@ -118,8 +118,6 @@ void set_default_settings(Settings *settings)
// Server stuff
// "map-dir" doesn't exist by default.
settings->setDefault("motd", "");
settings->setDefault("server_name", "");
settings->setDefault("server_address", "");
settings->setDefault("max_users", "20");
settings->setDefault("strict_protocol_version_checking", "false");
settings->setDefault("disallow_empty_passwords","false");
@ -131,8 +129,7 @@ void set_default_settings(Settings *settings)
settings->setDefault("game_mode","adventure");
set_adventure_defaults(settings);
// only enable http on the server for now
// adventurous players can enable it on the client
// only enable http on the server, singleplayer doesn't need it
#ifndef SERVER
settings->setDefault("enable_http","false");
#else
@ -158,6 +155,17 @@ void set_default_settings(Settings *settings)
settings->setDefault("enable_experimental", "false");
settings->setDefault("enable_lavabuckets", "true");
settings->setDefault("enable_tnt", "true");
settings->setDefault("api_server", "api.voxelands.com");
#ifdef SERVER
settings->setDefault("api_announce","true");
#else
settings->setDefault("api_announce","false");
#endif
settings->setDefault("api_auth","true");
settings->setDefault("server_name", "");
settings->setDefault("server_address", "");
}
void set_creative_defaults(Settings *settings)
@ -191,7 +199,7 @@ void set_survival_defaults(Settings *settings)
settings->setDefault("enable_damage", "true");
settings->setDefault("enable_suffocation", "true");
settings->setDefault("enable_hunger", "true");
settings->setDefault("max_mob_level", "aggressive");
settings->setDefault("max_mob_level", "destructive");
settings->setDefault("initial_inventory", "false");
settings->setDefault("tool_wear","true");
}

View File

@ -298,7 +298,7 @@ int HTTPRemoteClient::handleAPI()
}else{
txt += "private\n";
}
txt += "summary,motd,mode,name,players,public,version";
txt += "summary,motd,mode,name,players,public,version,features";
send((char*)txt.c_str());
return 1;
}else if (u1 == "motd") {
@ -315,6 +315,10 @@ int HTTPRemoteClient::handleAPI()
txt = g_settings->get("server_address");
send((char*)txt.c_str());
return 1;
}else if (u1 == "features") {
std::string txt = "summary,motd,mode,name,players,public,version,features";
send((char*)txt.c_str());
return 1;
}else if (u1 == "version") {
std::string txt = VERSION_STRING;
send((char*)txt.c_str());
@ -512,203 +516,102 @@ void HTTPRemoteClient::sendHeaders()
m_socket->Send("\r\n",2);
}
#ifndef SERVER
#include "client.h"
/* main loop for the client http fetcher */
void * HTTPClientThread::Thread()
{
ThreadStarted();
log_register_thread("HTTPClientThread");
DSTACK(__FUNCTION_NAME);
BEGIN_DEBUG_EXCEPTION_HANDLER
while (getRun()) {
try{
m_client->step();
}catch (con::NoIncomingDataException &e) {
}catch(con::PeerNotFoundException &e) {
}
}
END_DEBUG_EXCEPTION_HANDLER(errorstream)
return NULL;
}
/*
* HTTPClient
* HTTP request
*/
/* constructor */
HTTPClient::HTTPClient(Client *client):
m_thread(this)
std::string http_request(char* host, char* url, char* post, int port)
{
m_client = client;
m_req_mutex.Init();
}
/* destructor */
HTTPClient::~HTTPClient()
{
}
/* start the client http fetcher thread */
void HTTPClient::start(const Address &address)
{
DSTACK(__FUNCTION_NAME);
// Stop thread if already running
m_thread.stop();
m_address = address;
m_connection_failures = 0;
// Start thread
m_thread.setRun(true);
m_thread.Start();
infostream<<"HTTPClient: Started"<<std::endl;
}
/* stop the client http fetcher thread */
void HTTPClient::stop()
{
DSTACK(__FUNCTION_NAME);
m_thread.stop();
infostream<<"HTTPClient: Threads stopped"<<std::endl;
}
/* the main function for the client loop */
void HTTPClient::step()
{
if (m_requests.size() == 0) {
#ifdef _WIN32
Sleep(1000);
#else
sleep(1);
#endif
return;
}
std::vector<HTTPRequest> p;
m_req_mutex.Lock();
p.swap(m_requests);
m_req_mutex.Unlock();
for (std::vector<HTTPRequest>::iterator i = p.begin(); i != p.end(); ++i) {
HTTPRequest q = *i;
m_socket = new TCPSocket();
if (m_socket == NULL) {
m_req_mutex.Lock();
m_requests.push_back(q);
m_req_mutex.Unlock();
continue;
}
if (m_socket->Connect(m_address) == false) {
delete m_socket;
m_req_mutex.Lock();
m_requests.push_back(q);
m_req_mutex.Unlock();
m_connection_failures++;
/* assume the server has no http */
if (m_connection_failures > 4) {
stop();
p.clear();
return;
}
continue;
}
m_connection_failures = 0;
m_recv_headers.clear();
m_send_headers.clear();
get(q.url);
int r = 0;
int h = -1;
m_recv_headers.setResponse(0);
h = m_recv_headers.read(m_socket);
if (h == -1 || m_recv_headers.getResponse() == 500) {
delete m_socket;
m_req_mutex.Lock();
m_requests.push_back(q);
m_req_mutex.Unlock();
continue;
}
r = m_recv_headers.getResponse();
delete m_socket;
}
p.clear();
}
/* add a request to the http client queue */
void HTTPClient::pushRequest(HTTPRequestType type, std::string &data)
{
if (m_thread.getRun() == false)
return;
switch (type) {
case HTTPREQUEST_NULL:
break;
default:;
}
}
/* add a request to the http client queue */
void HTTPClient::pushRequest(std::string &url, std::string &data)
{
if (m_thread.getRun() == false)
return;
HTTPRequest r(url,data);
m_req_mutex.Lock();
m_requests.push_back(r);
m_req_mutex.Unlock();
}
/* send a http GET request to the server */
bool HTTPClient::get(std::string &url)
{
m_send_headers.setLength(0);
m_send_headers.setMethod("GET");
m_send_headers.setUrl(url);
sendHeaders();
return true;
}
/* send http headers to the server */
void HTTPClient::sendHeaders()
{
std::string v;
Address addr;
TCPSocket *sock;
HTTPResponseHeaders headers;
int s;
char buff[1024];
char buff[2048];
s = snprintf(buff,1024,"%s %s HTTP/1.0\r\n",m_send_headers.getMethod().c_str(),m_send_headers.getUrl().c_str());
m_socket->Send(buff,s);
v = m_send_headers.getHeader("Content-Type");
if (v != "") {
s = snprintf(buff,1024,"Content-Type: %s\r\n",v.c_str());
m_socket->Send(buff,s);
addr.setPort(port);
if (!host || !host[0]) {
std::string h = g_settings->get("api_server");
if (h == "")
return "";
host = (char*)h.c_str();
}
s = m_send_headers.length();
s = snprintf(buff,1024,"Content-Length: %d\r\n",s);
m_socket->Send(buff,s);
addr.Resolve(host);
s = snprintf(buff,1024,"User: %s\r\n",m_client->getLocalPlayer()->getName());
m_socket->Send(buff,s);
sock = new TCPSocket();
if (!sock)
return "";
m_socket->Send("\r\n",2);
if (!sock->Connect(addr)) {
delete sock;
return "";
}
if (post) {
int l = strlen(post);
s = snprintf(buff,2048,
"POST %s HTTP/1.1\r\n"
"Host: %s\r\n"
"From: Voxelands HTTP Fetcher\r\n"
"User-Agent: Voxelands/%s (Irrlicht; Voxelands) Voxelands/%s\r\n"
"Accept: text/html,application/xhtml+xml,text/plain\r\n"
"Accept-Language: en-us,en\r\n"
"Accept-Charset: ISO-8859-1,utf-8\r\n"
"Content-Type: application/x-www-form-urlencoded\r\n"
"Content-Length: %d\r\n"
"Connection: close\r\n\r\n",
url,
host,
VERSION_STRING,
VERSION_STRING,
l
);
sock->Send(buff,s);
sock->Send(post,l);
}else{
s = snprintf(buff,2048,
"GET %s HTTP/1.1\r\n"
"Host: %s\r\n"
"From: Voxelands HTTP Fetcher\r\n"
"User-Agent: Voxelands/%s (Irrlicht; Voxelands) Voxelands/%s\r\n"
"Accept: text/html,application/xhtml+xml,text/plain\r\n"
"Accept-Language: en-us,en\r\n"
"Accept-Charset: ISO-8859-1,utf-8\r\n"
"Connection: close\r\n\r\n",
url,
host,
VERSION_STRING,
VERSION_STRING
);
sock->Send(buff,s);
}
if (!sock->WaitData(5000)) {
delete sock;
return "";
}
if (headers.read(sock) < 0) {
delete sock;
return "";
}
if (headers.getResponse() != 200) {
delete sock;
return "";
}
std::string response("");
while (response.size() < headers.getLength() && (s = sock->Receive(buff,2047)) > 0) {
buff[s] = 0;
response += buff;
}
delete sock;
return response;
}
#endif
/*
* HTTPHeaders
@ -825,3 +728,100 @@ int HTTPResponseHeaders::read(TCPSocket *sock)
return 0;
}
static char url_reserved[33] = {
'!',
'*',
'\'',
'(',
')',
';',
':',
'@',
'&',
'=',
'+',
'$',
',',
'/',
'?',
'#',
'[',
']',
'"',
'%',
'-',
'.',
'<',
'>',
'\\',
'^',
'_',
'`',
'{',
'|',
'}',
'~',
0
};
static int32_t is_reserved(char c)
{
int32_t i;
if (c < 33)
return 1;
for (i=0; url_reserved[i]; i++) {
if (c == url_reserved[i])
return 1;
}
return 0;
}
/* % encodes a string for http urls */
std::string http_url_encode(std::string &str)
{
int32_t i;
char buff[10];
char* in = (char*)str.c_str();
std::string out("");
for (i=0; in[i] != 0; i++) {
if (is_reserved(in[i])) {
sprintf(buff,"%.2X",in[i]);
out += "%";
out += buff;
continue;
}
out += in[i];
}
return out;
}
/* decodes a % encoded string from http urls */
std::string http_url_decode(std::string &str)
{
int32_t i;
int32_t o;
int32_t k;
char buff[10];
char* in = (char*)str.c_str();
std::string out("");
for (i=0,o=0; in[i] != 0; i++) {
if (in[i] == '%') {
i++;
if (in[i] == 0 || in[i+1] == 0)
return 0;
buff[0] = in[i++];
buff[1] = in[i];
buff[2] = 0;
k = strtol(buff,NULL,16);
if (!k)
break;
buff[0] = k;
buff[1] = 0;
out += buff;
continue;
}
out += in[i];
}
return out;
}

View File

@ -156,86 +156,7 @@ private:
Server *m_server;
};
enum HTTPRequestType {
HTTPREQUEST_NULL
};
struct HTTPRequest
{
HTTPRequest():
url(""),
post(""),
data("")
{
}
HTTPRequest(std::string &u, std::string &p, std::string &d)
{
url = u;
post = p;
data = d;
}
HTTPRequest(std::string &u, std::string &p)
{
url = u;
post = p;
data = "";
}
HTTPRequest(std::string &u)
{
url = u;
post = "";
data = "";
}
std::string url;
std::string post;
std::string data;
};
#ifndef SERVER
#include "client.h"
class HTTPClient;
class HTTPClientThread : public SimpleThread
{
HTTPClient *m_client;
public:
HTTPClientThread(HTTPClient *client):
SimpleThread(),
m_client(client)
{
}
void * Thread();
};
class HTTPClient
{
public:
HTTPClient(Client *client);
~HTTPClient();
void start(const Address &address);
void stop();
void step();
void pushRequest(HTTPRequestType type, std::string &data);
void pushRequest(std::string &url, std::string &data);
private:
bool get(std::string &url);
int read(char* buff, int size) {return m_socket->Receive(buff,size);}
int readline(char* buff, int size) {return m_socket->ReceiveLine(buff,size);}
void sendHeaders();
Address m_address;
TCPSocket *m_socket;
HTTPResponseHeaders m_recv_headers;
HTTPRequestHeaders m_send_headers;
std::vector<HTTPRequest> m_requests;
JMutex m_req_mutex;
HTTPClientThread m_thread;
Client *m_client;
int m_connection_failures;
};
#endif
std::string http_request(char* host, char* url, char* post=NULL, int port=80);
std::string http_url_encode(std::string &str);
#endif

View File

@ -50,6 +50,7 @@
#include "log.h"
#include "base64.h"
#include "sound.h"
#include "http.h"
#define PP(x) "("<<(x).X<<","<<(x).Y<<","<<(x).Z<<")"
@ -988,8 +989,7 @@ Server::Server(
m_env.getMap().addEventReceiver(this);
// If file exists, load environment metadata
if(fs::PathExists(m_mapsavedir+DIR_DELIM+"env_meta.txt"))
{
if (fs::PathExists(m_mapsavedir+DIR_DELIM+"env_meta.txt")) {
infostream<<"Server: Loading environment metadata"<<std::endl;
m_env.loadMeta(m_mapsavedir);
}
@ -1093,6 +1093,45 @@ void Server::start(unsigned short port)
m_thread.setRun(true);
m_thread.Start();
// Announce game server to api server
if (g_settings->getBool("api_announce")) {
std::string url("/announce");
std::string post("");
std::string pre("");
std::string sn = g_settings->get("server_name");
if (sn != "") {
post += pre+"server_name="+http_url_encode(sn);
pre = "&";
}
std::string sa = g_settings->get("server_address");
if (sa != "") {
post += pre+"server_address="+http_url_encode(sa);
pre = "&";
}
std::string sp = g_settings->get("port");
if (sp != "") {
post += pre+"server_port="+http_url_encode(sp);
pre = "&";
}
std::string response("");
if (post == "") {
response = http_request(NULL,(char*)url.c_str());
}else{
response = http_request(NULL,(char*)url.c_str(),(char*)post.c_str());
}
if (response == "" || response == "Server Not Found") {
errorstream<<"Server: Failed to announce to api server"<<std::endl;
}else{
if (sn == "")
g_settings->set("server_name",response);
if (sa == "")
g_settings->set("server_address",response);
infostream<<"Server: Announced to api server"<<std::endl;
}
}
infostream<<"Server: Started on port "<<port<<std::endl;
}

View File

@ -500,8 +500,11 @@ int TCPSocket::Receive(void *data, int size)
return r;
}
if (FillBuffer() < size)
int s = FillBuffer();
if (s < 1)
return 0;
if (s < size)
size = s;
memcpy(data,m_buff+m_bstart,size);
m_bstart+=size;

262
util/api.php Normal file
View File

@ -0,0 +1,262 @@
<?php
/*
CREATE DATABASE voxelands ;
USE voxelands ;
CREATE TABLE `servers` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR (255) NOT NULL,
`addr` VARCHAR (255) NOT NULL,
`port` INT(11) UNSIGNED,
`mode` VARCHAR (20) NOT NULL,
`motd` VARCHAR (255) NOT NULL,
`players` INT(11) UNSIGNED NOT NULL DEFAULT '0',
`public` VARCHAR (10) NOT NULL,
`version` VARCHAR (50) NOT NULL,
`features` VARCHAR (255) NOT NULL,
`lastreply` INT(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin ;
CREATE TABLE `players` (
`id` INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(255) NOT NULL,
`hash` VARCHAR(255) NOT NULL,
`cookie` VARCHAR(255) NOT NULL,
`server` INT(11) UNSIGNED NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_bin ;
CREATE USER 'apiuser'@'localhost' IDENTIFIED BY 'apipass';
GRANT SELECT,INSERT,UPDATE ON `servers` TO 'apiuser'@'localhost';
GRANT SELECT,INSERT,UPDATE ON `players` TO 'apiuser'@'localhost';
*/
$db = false;
function db_connect()
{
global $db;
if ($db !== false)
return;
$db = mysqli_connect("localhost","apiuser","apipass","voxelands");
if (mysqli_connect_errno())
$db = false;
}
function db_close()
{
global $db;
if ($db === false)
return;
$db->close();
$db = false;
}
function db_insert($server_name, $server_addr, $server_port)
{
global $db;
db_connect();
$name = db_escape($server_name);
$addr = db_escape($server_addr);
$port = db_escape($server_port);
$time = time();
$data = $db->query("SELECT `id` FROM `servers` WHERE `addr` = '$addr' AND `port` = '$port'");
if ($data && $data->num_rows > 0) {
$id = $data->fetch_array();
$id = $id['id'];
$data->close();
$data = $db->query("UPDATE `servers` SET `lastreply` = '$time', `name` = '$name' WHERE `id` = '$id'");
}else{
if ($data)
$data->close();
$db->query("INSERT INTO `servers` (`name`,`addr`,`port`,`lastreply`) VALUES ('$name','$addr','$port','$time')");
}
}
function db_query_server($server_name=false,$server_mode=false,$version=false,$public=false)
{
global $db;
db_connect();
$time = time()-1200;
$q = "SELECT * FROM `servers` WHERE `lastreply` > $time";
if ($server_name !== false) {
$name = db_escape($server_name);
$q .= " AND `name` LIKE '%$name%'";
}
if ($server_mode !== false) {
$mode = db_escape($server_mode);
$q .= " AND `mode` = '$mode'";
}
if ($version !== false) {
$v = db_escape($version);
$q .= " AND `mode` LIKE '%$v%'";
}
if ($public == 'public' || $public == 'private') {
$q .= " AND `public` = '$public'";
}
$data = $db->query($q);
if (!$data) {
return array();
}elseif ($data->num_rows < 1) {
$data->close();
return array();
}
$result = array();
while ($row = $data->fetch_array(MYSQLI_ASSOC)) {
$result[] = $row;
}
$data->close();
return $result;
}
function db_query_player($player_name=false,$count=false,$start=false)
{
global $db;
db_connect();
$q = "SELECT * FROM `players`";
if ($player_name !== false) {
$p = db_escape($player_name);
$q .= " WHERE `name` LIKE '%$p%'";
}
if ($start !== false && $count !== false) {
$start = intval($start);
$count = intval($count);
$q .= " LIMIT $start , $count";
}elseif ($count !== false) {
$count = intval($count);
$q .= " LIMIT $count";
}
$data = $db->query($q);
if (!$data) {
return array();
}elseif ($data->num_rows < 1) {
$data->close();
return array();
}
$result = array();
while ($row = $data->fetch_array(MYSQLI_ASSOC)) {
$result[] = $row;
}
$data->close();
return $result;
}
function db_escape($str)
{
global $db;
db_connect();
return $db->real_escape_string($str);
}
function check_server($host, $port)
{
$socket = socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
$timeout = array("sec" => 1, "usec" => 0);
socket_set_option($socket, SOL_SOCKET, SO_RCVTIMEO, $timeout);
$buf = "\x4f\x45\x74\x03\x00\x00\x00\x03\xff\xdc\x01";
socket_sendto($socket, $buf, strlen($buf), 0, $host, $port);
$buf = socket_read($socket, 1000);
if ($buf == "")
return false;
/* we got a reply, read the peer id then send a disconnect */
$peer_id = substr($buf, 9, 2);
$buf = "\x4f\x45\x74\x03".$peer_id."\x00\x00\x03";
socket_sendto($socket, $buf, strlen($buf), 0, $host, $port);
socket_close($socket);
return true;
}
function error_send($msg)
{
header("Content-Type: text/plain\r\n");
echo $msg;
}
function txt_send($txt)
{
header("Content-Type: text/plain\r\n");
echo $txt;
}
function html_send($html)
{
readfile($_SERVER['DOCUMENT_ROOT']."/header.html");
echo $html;
readfile($_SERVER['DOCUMENT_ROOT']."/footer.html");
}
function server_announce()
{
$server_addr = isset($_POST['server_address']) ? urldecode($_POST['server_address']) : $_SERVER['REMOTE_ADDR'];
$server_name = isset($_POST['server_name']) ? urldecode($_POST['server_name']) : $server_addr;
$server_port = isset($_POST['server_port']) ? urldecode($_POST['server_port']) : '30000';
if (!check_server($server_addr,$server_port))
return error_send("Server Not Found");
db_connect();
db_insert($server_name,$server_addr,$server_port);
db_close();
txt_send($server_addr);
}
function server_list()
{
$a = db_query_server();
$txt = "servers: ".count($a)."\n\n";
foreach ($a as $server) {
if ($server['mode'] == '')
$server['mode'] = 'adventure';
$txt .= <<<EOT
$server[name]
$server[mode]
$server[addr]:$server[port]
EOT;
}
txt_send($txt);
}
function player_find()
{
txt_send("hello world");
}
function home()
{
html_send("hello world");
}
$u = explode("/",$_SERVER['REQUEST_URI']);
if (count($u) < 2)
$u = array("","home");
if ($u[1] == "announce") {
server_announce();
}elseif ($u[1] == "list") {
server_list();
}elseif ($u[1] == "player") {
player_find();
}else{
home();
}
db_close();
?>