#include "rebound.h"
#ifdef SERVER
#include <stdio.h>
#ifdef _MSC_VER
#define strncasecmp _strnicmp
#define strcasecmp _stricmp
#endif
#ifdef _WIN32
#include <WS2tcpip.h>
#include <tchar.h>
#include <io.h>
#define F_OK 0
#define access _access
#pragma comment(lib, "ws2_32.lib")
#else
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <netinet/in.h>
#endif #include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#define BUFSIZE 1024
const char* reb_server_header =
"HTTP/1.1 200 OK\n"
"Server: REBOUND Webserver\n"
"Cache-Control: no-cache, no-store, must-revalidate\n"
"Pragma: no-cache\n"
"Expires: 0\n"
"Content-type: text/html\n"
"\r\n";
const char* reb_server_header_png =
"HTTP/1.1 200 OK\n"
"Server: REBOUND Webserver\n"
"Content-type: image/png\n"
"\r\n";
#ifdef _WIN32
int sendBytes(SOCKET s, const void * buffer, int buflen){
int total = 0;
char *pbuf = (char*) buffer;
while (buflen > 0) {
int iResult = send(s, pbuf, buflen, 0);
if (iResult < 0) {
if (WSAGetLastError() == WSAEWOULDBLOCK) {
continue;
}
printf("send error: %d\n", WSAGetLastError());
return SOCKET_ERROR;
} else if (iResult == 0) {
printf("disconnected\n");
return 0;
} else {
pbuf += iResult;
buflen -= iResult;
total += iResult;
}
}
return total;
}
#endif
#ifdef _WIN32
static void reb_server_cerror(SOCKET clientS, char cause[]){
#else
static void reb_server_cerror(FILE *stream, char *cause){
#endif char* buf = NULL;
asprintf(&buf, "HTTP/1.1 501 Not Implemented\n"
"Content-type: text/html\n"
"\n"
"<html><title>REBOUND Webserver Error</title>"
"<body>\n"
"<h1>Error</h1>\n"
"<p>%s</p>\n"
"<hr><em>REBOUND Webserver</em>\n"
"</body></html>\n"
, cause);
printf("\nREBOUND Webserver error: %s\n", cause);
#ifdef _WIN32
sendBytes(clientS, buf, strlen(buf));
closesocket(clientS); #else
fwrite(buf, 1, strlen(buf), stream);
#endif free(buf);
}
static const unsigned char base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static unsigned char * base64_decode(const unsigned char *src, size_t len, size_t *out_len) {
unsigned char dtable[256], *out, *pos, block[4], tmp;
size_t i, count, olen;
int pad = 0;
memset(dtable, 0x80, 256);
for (i = 0; i < sizeof(base64_table) - 1; i++)
dtable[base64_table[i]] = (unsigned char) i;
dtable['='] = 0;
count = 0;
for (i = 0; i < len; i++) {
if (dtable[src[i]] != 0x80)
count++;
}
if (count == 0 || count % 4)
return NULL;
olen = count / 4 * 3;
pos = out = malloc(olen);
if (out == NULL)
return NULL;
count = 0;
for (i = 0; i < len; i++) {
tmp = dtable[src[i]];
if (tmp == 0x80)
continue;
if (src[i] == '=')
pad++;
block[count] = tmp;
count++;
if (count == 4) {
*pos++ = (block[0] << 2) | (block[1] >> 4);
*pos++ = (block[1] << 4) | (block[2] >> 2);
*pos++ = (block[2] << 6) | block[3];
count = 0;
if (pad) {
if (pad == 1)
pos--;
else if (pad == 2)
pos -= 2;
else {
free(out);
return NULL;
}
break;
}
}
}
*out_len = pos - out;
return out;
}
#ifndef _WIN32
void* reb_server_start(void* args){
#else
DWORD WINAPI reb_server_start(void* args){
#endif struct reb_server_data* data = (struct reb_server_data*)args;
struct reb_simulation* r = data->r;
if (access("rebound.html", F_OK)) {
reb_simulation_warning(r, "File rebound.html not found in current directory. Attempting to download it from github.");
char curl_cmd[] = "curl -L -s --output rebound.html https://github.com/hannorein/rebound/releases/latest/download/rebound.html";
system(curl_cmd);
if (access("rebound.html", F_OK)) {
reb_simulation_warning(r, "Automatic download failed. Manually download the file from github and place it in the current directory to enable browser based visualization.");
}else{
printf("Success: rebound.html downloaded.\n");
}
}
#ifndef _WIN32
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
int childfd;
unsigned int clientlen;
int optval;
struct sockaddr_in serveraddr;
struct sockaddr_in clientaddr;
FILE *stream;
char buf[BUFSIZE];
char method[BUFSIZE];
char uri[BUFSIZE];
char version[BUFSIZE];
data->socket = socket(AF_INET, SOCK_STREAM, 0);
if (data->socket < 0)
reb_exit("ERROR opening socket");
optval = 1;
setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int));
memset((char *) &serveraddr, 0, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
serveraddr.sin_port = htons(data->port);
if (bind(data->socket, (struct sockaddr *) &serveraddr, sizeof(serveraddr)) < 0){
char error_msg[BUFSIZE];
snprintf(error_msg, BUFSIZE, "Error binding to port %d. Port might be in use.\n", data->port);
reb_simulation_error(r, error_msg);
data->ready = -1;
return PTHREAD_CANCELED;
}
if (listen(data->socket, 5) < 0)
reb_exit("ERROR on listen");
printf("REBOUND Webserver listening on http://localhost:%d (not secure) ...\n",data->port);
clientlen = sizeof(clientaddr);
while (1) {
data->ready = 1;
childfd = accept(data->socket, (struct sockaddr *) &clientaddr, &clientlen);
if (childfd < 0) { return PTHREAD_CANCELED;
}
if ((stream = fdopen(childfd, "r+")) == NULL)
reb_exit("ERROR on fdopen");
char* request = fgets(buf, BUFSIZE, stream);
if (!request){
reb_server_cerror(stream, "Did not get request.");
fclose(stream);
close(childfd);
continue;
}
sscanf(buf, "%s %s %s\n", method, uri, version);
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
reb_server_cerror(stream, "Only GET+POST are implemented.");
fclose(stream);
close(childfd);
continue;
}
fgets(buf, BUFSIZE, stream);
unsigned long content_length = 0;
while(strcmp(buf, "\r\n")) {
char cl[BUFSIZE];
int ni = sscanf(buf, "Content-Length: %s\n", cl);
if (ni){
content_length = strtol(cl,NULL,10);
}
fgets(buf, BUFSIZE, stream);
}
if (!strcasecmp(uri, "/simulation")) {
char* bufp = NULL;
size_t sizep;
data->need_copy = 1;
pthread_mutex_lock(&(data->mutex));
reb_simulation_save_to_stream(r, &bufp,&sizep);
data->need_copy = 0;
pthread_mutex_unlock(&(data->mutex));
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
fwrite(bufp, 1, sizep, stream);
free(bufp);
}else if (!strncasecmp(uri, "/keyboard/",10)) {
int key = 0;
sscanf(uri, "/keyboard/%d", &key);
data->need_copy = 1;
pthread_mutex_lock(&(data->mutex));
int skip_default_keys = 0;
if (r->key_callback){
skip_default_keys = r->key_callback(r, key);
}
data->need_copy = 0;
pthread_mutex_unlock(&(data->mutex));
if (!skip_default_keys){
switch (key){
case 'Q':
data->r->status = REB_STATUS_USER;
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
fprintf(stream, "ok.\n");
break;
case ' ':
if (data->r->status == REB_STATUS_PAUSED){
printf("Resume.\n");
data->r->status = REB_STATUS_RUNNING;
}else if (data->r->status == REB_STATUS_RUNNING){
printf("Pause.\n");
data->r->status = REB_STATUS_PAUSED;
}
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
fprintf(stream, "ok.\n");
break;
case 264: if (data->r->status == REB_STATUS_PAUSED){
data->r->status = REB_STATUS_SINGLE_STEP;
printf("Step.\n");
}
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
fprintf(stream, "ok.\n");
break;
case 267: if (data->r->status == REB_STATUS_PAUSED){
data->r->status = REB_STATUS_SINGLE_STEP - 50;
printf("50 steps.\n");
}
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
fprintf(stream, "ok.\n");
break;
default:
break;
}
}else{
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
fprintf(stream, "ok.\n");
}
}else if (!strcasecmp(uri, "/") || !strcasecmp(uri, "/index.html") || !strcasecmp(uri, "/rebound.html")) {
struct stat sbuf;
if (stat("rebound.html", &sbuf) < 0) {
reb_server_cerror(stream, "rebound.html not found in current directory. Try `make rebound.html`.");
}else{
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
int fd = open("rebound.html", O_RDONLY);
void* p = mmap(0, sbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
fwrite(p, 1, sbuf.st_size, stream);
munmap(p, sbuf.st_size);
}
}else if (!strcasecmp(uri, "/favicon.ico")) {
fwrite(reb_server_header_png, 1, strlen(reb_server_header_png), stream);
fwrite(reb_favicon_png,1, reb_favicon_len, stream);
}else if (!strcasecmp(uri, "/screenshot")) {
data->need_copy = 1;
pthread_mutex_lock(&(data->mutex));
if (content_length==0){
printf("Received screenshot with size zero.");
goto screenshot_finish;
}
if (r->status != REB_STATUS_SCREENSHOT){
printf("Received screenshot but did not expect one.\n");
goto screenshot_finish;
}
if (data->screenshot) {
printf("Unable to receive screenshot as previous screenshot not freed.\n");
goto screenshot_finish;
}
char* dataURL = malloc(content_length);
int rc = fread(dataURL, content_length, 1, stream);
if (rc!=1){
printf("Error while reading screenshot data.\n");
free(dataURL);
goto screenshot_finish;
}
int rc_len = strlen(dataURL)+1;
char* base64 = strchr(dataURL, ',');
if (content_length != rc_len){
printf("Received screenshot with incorrect size.\n");
free(dataURL);
goto screenshot_finish;
}
if (!base64){
printf("Unable to decode received screenshot. Data not in dataURL format.\n");
free(dataURL);
goto screenshot_finish;
}
data->screenshot = base64_decode((unsigned char*)base64+1, strlen(base64+1), &data->N_screenshot);
if (!data->screenshot){
printf("An error occured while decoding the screenshot.\n");
}
data->r->status = REB_STATUS_PAUSED;
free(dataURL);
screenshot_finish:
data->need_copy = 0;
pthread_mutex_unlock(&(data->mutex));
fwrite(reb_server_header, 1, strlen(reb_server_header), stream);
fprintf(stream, "ok.\n");
}else{
reb_server_cerror(stream, "Unsupported URI.");
printf("URI: %s\n", uri);
}
fflush(stream);
fclose(stream);
close(childfd);
}
printf("Server shutting down...\n");
return PTHREAD_CANCELED;
#else
WSADATA wsa;
struct sockaddr_in server;
SOCKET clientS;
char method[BUFSIZE];
char uri[BUFSIZE];
char version[BUFSIZE];
if (WSAStartup(MAKEWORD(2, 2), &wsa) != 0) {
printf("Winsock startup failed");
exit(1);
}
data->socket = socket(AF_INET, SOCK_STREAM, 0);
if (data->socket == INVALID_SOCKET) {
printf("Socket error\n");
exit(1);
}
server.sin_family = AF_INET;
server.sin_port = htons(data->port);
InetPton(AF_INET, _T("0.0.0.0"), &server.sin_addr);
int ret_bind = bind(data->socket, (struct sockaddr*)&server, sizeof(server)); if (ret_bind) {
char error_msg[BUFSIZE];
snprintf(error_msg, BUFSIZE, "Error binding to port %d. Port might be in use.\n", data->port);
reb_simulation_error(r, error_msg);
data->ready = -1;
return 1;
}
int ret_listen = listen(data->socket, AF_INET);
if (ret_listen){
printf("Listen error\n");
exit(1);
}
printf("REBOUND Webserver listening on http://localhost:%d (not secure) ...\n",data->port);
while(1){
data->ready = 1;
clientS = accept(data->socket, NULL, NULL);
if (clientS == INVALID_SOCKET) { return 1;
}
int recN = 0;
char* recbuf = malloc(BUFSIZE);
int recbufN = 0;
while((recN = recv(clientS, recbuf+recbufN, BUFSIZE, 0))>0){
recbufN += recN;
if (recN<BUFSIZE) break;
recbuf = realloc(recbuf, recbufN+BUFSIZE);
}
sscanf(recbuf, "%s %s %s\n", method, uri, version);
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
reb_server_cerror(clientS, "Method not Implemented");
continue;
}
char* curLine = recbuf;
unsigned long content_length = 0;
while(curLine){
char* nextLine = strchr(curLine, '\n');
if (nextLine) *nextLine = '\0';
char cl[BUFSIZE];
int ni = sscanf(curLine, "Content-Length: %s\n", cl);
if (ni){
content_length = strtol(cl,NULL,10);
}
if (nextLine){
*nextLine = '\n';
curLine = nextLine+1;
}else{
break;
}
}
if (strcasecmp(method, "POST")) {
free(recbuf);
}
if (!strcasecmp(uri, "/simulation")) {
char* bufp = NULL;
size_t sizep;
data->need_copy = 1;
WaitForSingleObject(data->mutex, INFINITE);
reb_simulation_save_to_stream(r, &bufp,&sizep);
data->need_copy = 0;
ReleaseMutex(data->mutex);
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, bufp, sizep);
free(bufp);
}else if (!strncasecmp(uri, "/keyboard/",10)) {
int key = 0;
const char* ok = "ok.";
sscanf(uri, "/keyboard/%d", &key);
int skip_default_keys = 0;
data->need_copy = 1;
WaitForSingleObject(data->mutex, INFINITE);
if (r->key_callback){
skip_default_keys = r->key_callback(r, key);
}
data->need_copy = 0;
ReleaseMutex(data->mutex);
if (!skip_default_keys){
switch (key){
case 'Q':
data->r->status = REB_STATUS_USER;
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, ok, strlen(ok));
break;
case ' ':
if (data->r->status == REB_STATUS_PAUSED){
printf("Resume.\n");
data->r->status = REB_STATUS_RUNNING;
}else if (data->r->status == REB_STATUS_RUNNING){
printf("Pause.\n");
data->r->status = REB_STATUS_PAUSED;
}
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, ok, strlen(ok));
break;
case 264: if (data->r->status == REB_STATUS_PAUSED){
data->r->status = REB_STATUS_SINGLE_STEP;
printf("Step.\n");
}
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, ok, strlen(ok));
break;
case 267: if (data->r->status == REB_STATUS_PAUSED){
data->r->status = REB_STATUS_SINGLE_STEP - 50;
printf("50 step.\n");
}
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, ok, strlen(ok));
break;
default:
continue;
break;
}
}else{
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, ok, strlen(ok));
}
}else if (!strcasecmp(uri, "/") || !strcasecmp(uri, "/index.html") || !strcasecmp(uri, "/rebound.html")) {
FILE *f = fopen("rebound.html", "rb");
if (f){
fseek(f, 0, SEEK_END);
long fsize = ftell(f);
fseek(f, 0, SEEK_SET);
char *buf = malloc(fsize);
fread(buf, fsize, 1, f);
fclose(f);
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, buf, fsize);
free(buf);
}else{
reb_server_cerror(clientS, "rebound.html not found in current directory. Try `make rebound.html`.");
continue;
}
}else if (!strcasecmp(uri, "/favicon.ico")) {
sendBytes(clientS, reb_server_header_png, strlen(reb_server_header_png));
sendBytes(clientS, reb_favicon_png, reb_favicon_len);
}else if (!strcasecmp(uri, "/screenshot")) {
data->need_copy = 1;
WaitForSingleObject(data->mutex, INFINITE);
if (content_length==0){
printf("Received screenshot with size zero.");
goto screenshot_finish;
}
if (r->status != REB_STATUS_SCREENSHOT){
printf("Received screenshot but did not expect one.\n");
goto screenshot_finish;
}
if (data->screenshot) {
printf("Unable to receive screenshot as previous screenshot not freed.\n");
goto screenshot_finish;
}
char* dataURL = curLine;
int rc_len = strlen(dataURL)+1;
char* base64 = strchr(dataURL, ',');
if (content_length != rc_len){
printf("Received screenshot with incorrect size.\n");
goto screenshot_finish;
}
if (!base64){
printf("Unable to decode received screenshot. Data not in dataURL format.\n");
goto screenshot_finish;
}
data->screenshot = base64_decode((unsigned char*)base64+1, strlen(base64+1), &data->N_screenshot);
if (!data->screenshot){
printf("An error occured while decoding the screenshot.\n");
}
data->r->status = REB_STATUS_PAUSED;
screenshot_finish:
free(recbuf);
data->need_copy = 0;
ReleaseMutex(data->mutex);
const char* ok = "ok.";
sendBytes(clientS, reb_server_header, strlen(reb_server_header));
sendBytes(clientS, ok, strlen(ok));
}else{
reb_server_cerror(clientS, "Unsupported request.");
printf("URI: %s\n",uri);
continue;
}
closesocket(clientS);
}
WSACleanup();
return 0;
#endif }
#endif
int reb_simulation_start_server(struct reb_simulation* r, int port){
#ifdef SERVER
if (port){
if (r->server_data){
reb_simulation_error(r,"Server already started.");
return -1;
}
r->server_data = calloc(sizeof(struct reb_server_data),1);
r->server_data->r = r;
r->server_data->port = port;
#ifdef _WIN32
r->server_data->mutex = CreateMutex(NULL, FALSE, NULL);
HANDLE thread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)reb_server_start, r->server_data, 0, NULL);
#else
if (pthread_mutex_init(&(r->server_data->mutex), NULL)){
reb_simulation_error(r,"Mutex creation failed.");
return -1;
}
int ret_create = pthread_create(&(r->server_data->server_thread),NULL,reb_server_start,r->server_data);
if (ret_create){
reb_simulation_error(r, "Error creating server thread.");
return -1;
}
#endif int maxwait = 100;
while (r->server_data->ready==0 && maxwait){
usleep(10000);
maxwait--;
}
if (r->server_data->ready==0){
reb_simulation_warning(r, "Server did not start immediately. This might just take a little bit longer.");
}
return 0;
}else{
reb_simulation_error(r, "Cannot start server. Invalid port.");
return -1;
}
#else
#ifndef SERVERHIDEWARNING
reb_simulation_error(r, "REBOUND has been compiled without SERVER support.");
#endif return -1;
#endif }
void reb_simulation_stop_server(struct reb_simulation* r){
#ifdef SERVER
if (r==NULL) return;
if (r->server_data){
#ifdef _WIN32
closesocket(r->server_data->socket); #else
close(r->server_data->socket); int ret_cancel = pthread_cancel(r->server_data->server_thread);
if (ret_cancel==ESRCH){
printf("Did not find server thread while trying to cancel it.\n");
}
void* retval = 0;
pthread_join(r->server_data->server_thread, &retval);
if (retval!=PTHREAD_CANCELED){
printf("An error occured while cancelling server thread.\n");
}
#endif free(r->server_data);
r->server_data = NULL;
}
#endif }