swh:1:snp:122bde0cb0e54f3d002c308e151c63f07e45e6be
Tip revision: 4e94bc42eebcd34c3c4c56447064b0ea65a1c811 authored by Ruth-Huang6012 on 19 March 2024, 13:25:49 UTC
Fixed bug in integrator_whfast512.c (#760)
Fixed bug in integrator_whfast512.c (#760)
Tip revision: 4e94bc4
server.c
/**
* @file server.c
* @brief Opens a webserver to allow for platform independent visualization.
* @author Hanno Rein <hanno@hanno-rein.de>
* @details These functions provide real time visualizations
* using OpenGL. Part of the code is by Dave O'Hallaron, Carnegie Mellon (tiny.c).
*
* @section LICENSE
* Copyright (c) 2023 Hanno Rein, Dave O'Hallaron, Carnegie Mellon
*
* This file is part of rebound.
*
* rebound is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* rebound 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with rebound. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "rebound.h"
#ifdef SERVER
#include <stdio.h>
#ifdef _MSC_VER
//not #if defined(_WIN32) || defined(_WIN64) because we have strncasecmp in mingw
#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 // _WIN32
#include <unistd.h>
#include <errno.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/mman.h>
#include <netinet/in.h>
#endif // _WIN32
#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"
//"Access-Control-Allow-Origin: *\n"
//"Cross-Origin-Opener-Policy: cross-origin\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) {
// optionally use select() to wait for the
// socket to have more space to write before
// calling send() again...
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 // _WIN32
#ifdef _WIN32
static void reb_server_cerror(SOCKET clientS, char cause[]){
#else //_WIN32
static void reb_server_cerror(FILE *stream, char *cause){
#endif //_WIN32
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); // close the Client Socket now that our Work is Complete.
#else //_WIN32
fwrite(buf, 1, strlen(buf), stream);
#endif //_WIN32
free(buf);
}
static const unsigned char base64_table[65] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/**
* base64_decode - Base64 decode
* @src: Data to be decoded
* @len: Length of the data to be decoded
* @out_len: Pointer to output length variable
* Returns: Allocated buffer of out_len bytes of decoded data,
* or %NULL on failure
*
* Caller is responsible for freeing the returned buffer.
*
* Source: https://web.mit.edu/freebsd/head/contrib/wpa/src/utils/base64.c
*/
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 {
/* Invalid padding */
free(out);
return NULL;
}
break;
}
}
}
*out_len = pos - out;
return out;
}
#ifndef _WIN32
void* reb_server_start(void* args){
#else //_WIN32
DWORD WINAPI reb_server_start(void* args){
#endif // _WIN32
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);
/* variables for connection management */
int childfd; /* child socket */
unsigned int clientlen; /* byte size of client's address */
int optval; /* flag value for setsockopt */
struct sockaddr_in serveraddr; /* server's addr */
struct sockaddr_in clientaddr; /* client addr */
/* variables for connection I/O */
FILE *stream; /* stream version of childfd */
char buf[BUFSIZE]; /* message buffer */
char method[BUFSIZE]; /* request method */
char uri[BUFSIZE]; /* request uri */
char version[BUFSIZE]; /* request method */
/* open socket descriptor */
data->socket = socket(AF_INET, SOCK_STREAM, 0);
if (data->socket < 0)
reb_exit("ERROR opening socket");
/* allows us to restart server immediately */
optval = 1;
setsockopt(data->socket, SOL_SOCKET, SO_REUSEADDR,
(const void *)&optval , sizeof(int));
/* bind port to socket */
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;
}
/* get us ready to accept connection requests */
if (listen(data->socket, 5) < 0) /* allow 5 requests to queue up */
reb_exit("ERROR on listen");
printf("REBOUND Webserver listening on http://localhost:%d ...\n",data->port);
/*
* main loop: wait for a connection request, parse HTTP,
* serve requested content, close connection.
*/
clientlen = sizeof(clientaddr);
while (1) {
/* wait for a connection request */
data->ready = 1;
childfd = accept(data->socket, (struct sockaddr *) &clientaddr, &clientlen);
if (childfd < 0) { // Accept will fail if main thread is closing socket.
return PTHREAD_CANCELED;
}
/* open the child socket descriptor as a stream */
if ((stream = fdopen(childfd, "r+")) == NULL)
reb_exit("ERROR on fdopen");
/* get the HTTP request line */
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);
/* only support the GET method */
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
reb_server_cerror(stream, "Only GET+POST are implemented.");
fclose(stream);
close(childfd);
continue;
}
/* read (and ignore) the HTTP headers */
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: // arrow down
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: // page down
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:
// reb_server_cerror(stream, "Unsupported key received.");
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);
}
/* clean up */
fflush(stream);
fclose(stream);
close(childfd);
}
printf("Server shutting down...\n");
return PTHREAD_CANCELED;
#else // _WIN32
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)); // binding the Host Address and Port Number
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 ...\n",data->port);
while(1){
data->ready = 1;
clientS = accept(data->socket, NULL, NULL);
if (clientS == INVALID_SOCKET) { // Accept will fail if main thread is closing socket.
return 1;
}
// Receive entire request.
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);
}
// Get method and uri
sscanf(recbuf, "%s %s %s\n", method, uri, version);
if (strcasecmp(method, "GET") && strcasecmp(method, "POST")) {
reb_server_cerror(clientS, "Method not Implemented");
continue;
}
/* read (and ignore) the HTTP headers */
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;
}
}
// Only post data is needed, otherwise free here
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: // down arrow
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: // page down
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:
// reb_server_cerror(clientS, "Unknown key received.");
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; // Memory!
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 // _WIN32
}
#endif // SERVER
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 // _WIN32
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 // _WIN32
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 // SERVER
#ifndef SERVERHIDEWARNING
reb_simulation_error(r, "REBOUND has been compiled without SERVER support.");
#endif // SERVERHIDEWARNING
return -1;
#endif // SERVER
}
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); // Will cause thread to exit.
#else // _WIN32
close(r->server_data->socket); // Will cause thread to exit.
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 // _WIN32
free(r->server_data);
r->server_data = NULL;
}
#endif //SERVER
}