Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move internal webserver to Core, add WebSocket stuff #11152

Merged
merged 8 commits into from
Jun 8, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,8 @@ add_library(native STATIC
ext/native/base/stringutil.h
ext/native/base/timeutil.cpp
ext/native/base/timeutil.h
ext/native/data/base64.cpp
ext/native/data/base64.h
ext/native/data/compression.cpp
ext/native/data/compression.h
ext/native/file/chunk_file.cpp
Expand Down Expand Up @@ -985,6 +987,8 @@ add_library(native STATIC
ext/native/net/sinks.h
ext/native/net/url.cpp
ext/native/net/url.h
ext/native/net/websocket_server.cpp
ext/native/net/websocket_server.h
ext/native/profiler/profiler.cpp
ext/native/profiler/profiler.h
ext/native/thin3d/thin3d.cpp
Expand Down Expand Up @@ -1389,6 +1393,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/HDRemaster.cpp
Core/HDRemaster.h
Core/ThreadEventQueue.h
Core/WebServer.cpp
Core/WebServer.h
Core/Debugger/Breakpoints.cpp
Core/Debugger/Breakpoints.h
Core/Debugger/DebugInterface.h
Expand Down
2 changes: 2 additions & 0 deletions Core/Core.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -511,6 +511,7 @@
<InlineFunctionExpansion Condition="'$(Configuration)|$(Platform)'=='Release|x64'">AnySuitable</InlineFunctionExpansion>
</ClCompile>
<ClCompile Include="WaveFile.cpp" />
<ClCompile Include="WebServer.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="..\ext\disarm.h" />
Expand Down Expand Up @@ -746,6 +747,7 @@
<ClInclude Include="Util\ppge_atlas.h" />
<ClInclude Include="..\ext\xxhash.h" />
<ClInclude Include="WaveFile.h" />
<ClInclude Include="WebServer.h" />
</ItemGroup>
<ItemGroup>
<None Include="..\android\jni\Android.mk" />
Expand Down
6 changes: 6 additions & 0 deletions Core/Core.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,9 @@
<ClCompile Include="Replay.cpp">
<Filter>Core</Filter>
</ClCompile>
<ClCompile Include="WebServer.cpp">
<Filter>Core</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<ClInclude Include="ELF\ElfReader.h">
Expand Down Expand Up @@ -1268,6 +1271,9 @@
<ClInclude Include="Replay.h">
<Filter>Core</Filter>
</ClInclude>
<ClInclude Include="WebServer.h">
<Filter>Core</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<None Include="CMakeLists.txt" />
Expand Down
278 changes: 278 additions & 0 deletions Core/WebServer.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
// Copyright (c) 2014- PPSSPP Project.

// This program 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, version 2.0 or later versions.

// This program 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

#include <algorithm>
#include <mutex>
#include <thread>
#include <unordered_map>
#include "base/stringutil.h"
#include "base/timeutil.h"
#include "file/fd_util.h"
#include "net/http_client.h"
#include "net/http_server.h"
#include "net/sinks.h"
#include "thread/threadutil.h"
#include "Common/FileUtil.h"
#include "Common/Log.h"
#include "Core/Config.h"
#include "Core/WebServer.h"

enum class ServerStatus {
STOPPED,
STARTING,
RUNNING,
STOPPING,
RESTARTING,
};

static const char *REPORT_HOSTNAME = "report.ppsspp.org";
static const int REPORT_PORT = 80;

static std::thread serverThread;
static ServerStatus serverStatus;
static std::mutex serverStatusLock;
static int serverFlags;

static void UpdateStatus(ServerStatus s) {
std::lock_guard<std::mutex> guard(serverStatusLock);
serverStatus = s;
}

static bool UpdateStatus(ServerStatus s, ServerStatus old) {
std::lock_guard<std::mutex> guard(serverStatusLock);
if (serverStatus == old) {
serverStatus = s;
return true;
}
return false;
}

static ServerStatus RetrieveStatus() {
std::lock_guard<std::mutex> guard(serverStatusLock);
return serverStatus;
}

// This reports the local IP address to report.ppsspp.org, which can then
// relay that address to a mobile device searching for the server.
static void RegisterServer(int port) {
http::Client http;
Buffer theVoid;

char resource4[1024] = {};
if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV4)) {
if (http.Connect()) {
std::string ip = fd_util::GetLocalIP(http.sock());
snprintf(resource4, sizeof(resource4) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port);

http.GET(resource4, &theVoid);
theVoid.Skip(theVoid.size());
http.Disconnect();
}
}

if (http.Resolve(REPORT_HOSTNAME, REPORT_PORT, net::DNSType::IPV6)) {
// We register both IPv4 and IPv6 in case the other client is using a different one.
if (resource4[0] != 0 && http.Connect()) {
http.GET(resource4, &theVoid);
theVoid.Skip(theVoid.size());
http.Disconnect();
}

// Currently, we're not using keepalive, so gotta reconnect...
if (http.Connect()) {
char resource6[1024] = {};
std::string ip = fd_util::GetLocalIP(http.sock());
snprintf(resource6, sizeof(resource6) - 1, "/match/update?local=%s&port=%d", ip.c_str(), port);

http.GET(resource6, &theVoid);
theVoid.Skip(theVoid.size());
http.Disconnect();
}
}
}

static void RegisterDiscHandlers(http::Server *http, std::unordered_map<std::string, std::string> *paths) {
for (std::string filename : g_Config.recentIsos) {
#ifdef _WIN32
static const std::string sep = "\\/";
#else
static const std::string sep = "/";
#endif
size_t basepos = filename.find_last_of(sep);
std::string basename = "/" + (basepos == filename.npos ? filename : filename.substr(basepos + 1));

// Let's not serve directories, since they won't work. Only single files.
// Maybe can do PBPs and other files later. Would be neat to stream virtual disc filesystems.
if (endsWithNoCase(basename, ".cso") || endsWithNoCase(basename, ".iso")) {
(*paths)[ReplaceAll(basename, " ", "%20")] = filename;
}
}

auto handler = [paths](const http::Request &request) {
std::string filename = (*paths)[request.resource()];
s64 sz = File::GetFileSize(filename);

std::string range;
if (request.Method() == http::RequestHeader::HEAD) {
request.WriteHttpResponseHeader(200, sz, "application/octet-stream", "Accept-Ranges: bytes\r\n");
} else if (request.GetHeader("range", &range)) {
s64 begin = 0, last = 0;
if (sscanf(range.c_str(), "bytes=%lld-%lld", &begin, &last) != 2) {
request.WriteHttpResponseHeader(400, -1, "text/plain");
request.Out()->Push("Could not understand range request.");
return;
}

if (begin < 0 || begin > last || last >= sz) {
request.WriteHttpResponseHeader(416, -1, "text/plain");
request.Out()->Push("Range goes outside of file.");
return;
}

FILE *fp = File::OpenCFile(filename, "rb");
if (!fp || fseek(fp, begin, SEEK_SET) != 0) {
request.WriteHttpResponseHeader(500, -1, "text/plain");
request.Out()->Push("File access failed.");
if (fp) {
fclose(fp);
}
return;
}

s64 len = last - begin + 1;
char contentRange[1024];
sprintf(contentRange, "Content-Range: bytes %lld-%lld/%lld\r\n", begin, last, sz);
request.WriteHttpResponseHeader(206, len, "application/octet-stream", contentRange);

const size_t CHUNK_SIZE = 16 * 1024;
char *buf = new char[CHUNK_SIZE];
for (s64 pos = 0; pos < len; pos += CHUNK_SIZE) {
s64 chunklen = std::min(len - pos, (s64)CHUNK_SIZE);
fread(buf, chunklen, 1, fp);
request.Out()->Push(buf, chunklen);
}
fclose(fp);
delete [] buf;
request.Out()->Flush();
} else {
request.WriteHttpResponseHeader(418, -1, "text/plain");
request.Out()->Push("This server only supports range requests.");
}
};

for (auto pair : *paths) {
http->RegisterHandler(pair.first.c_str(), handler);
}
}

static void ExecuteWebServer() {
setCurrentThreadName("HTTPServer");

auto http = new http::Server(new threading::NewThreadExecutor());
std::unordered_map<std::string, std::string> discPaths;

if (serverFlags & (int)WebServerFlags::DISCS) {
RegisterDiscHandlers(http, &discPaths);
}

if (!http->Listen(g_Config.iRemoteISOPort)) {
if (!http->Listen(0)) {
ERROR_LOG(FILESYS, "Unable to listen on any port");
UpdateStatus(ServerStatus::STOPPED);
return;
}
}
UpdateStatus(ServerStatus::RUNNING);

g_Config.iRemoteISOPort = http->Port();
RegisterServer(http->Port());
double lastRegister = real_time_now();
while (RetrieveStatus() == ServerStatus::RUNNING) {
http->RunSlice(1.0);

double now = real_time_now();
if (now > lastRegister + 540.0) {
RegisterServer(http->Port());
lastRegister = now;
}
}

http->Stop();

// Move to STARTING to lock flags/STOPPING.
if (UpdateStatus(ServerStatus::STARTING, ServerStatus::RESTARTING)) {
ExecuteWebServer();
} else {
UpdateStatus(ServerStatus::STOPPED);
}
}

bool StartWebServer(WebServerFlags flags) {
std::lock_guard<std::mutex> guard(serverStatusLock);
switch (serverStatus) {
case ServerStatus::RUNNING:
case ServerStatus::RESTARTING:
if ((serverFlags & (int)flags) == (int)flags) {
// Already running those flags.
return false;
}
serverStatus = ServerStatus::RESTARTING;
serverFlags |= (int)flags;
return true;

case ServerStatus::STOPPED:
serverStatus = ServerStatus::STARTING;
serverFlags = (int)flags;
serverThread = std::thread(&ExecuteWebServer);
serverThread.detach();
return true;

default:
return false;
}
}

bool StopWebServer(WebServerFlags flags) {
std::lock_guard<std::mutex> guard(serverStatusLock);
if (serverStatus != ServerStatus::RUNNING && serverStatus != ServerStatus::RESTARTING) {
return false;
}

serverFlags &= ~(int)flags;
if (serverFlags == 0) {
serverStatus = ServerStatus::STOPPING;
} else {
serverStatus = ServerStatus::RESTARTING;
}
return true;
}

bool WebServerStopping(WebServerFlags flags) {
std::lock_guard<std::mutex> guard(serverStatusLock);
if (serverStatus == ServerStatus::RESTARTING) {
return (serverFlags & (int)flags) == 0;
}
return serverStatus == ServerStatus::STOPPING;
}

bool WebServerStopped(WebServerFlags flags) {
std::lock_guard<std::mutex> guard(serverStatusLock);
if (serverStatus == ServerStatus::RUNNING) {
return (serverFlags & (int)flags) == 0;
}
return serverStatus == ServerStatus::STOPPED;
}
27 changes: 27 additions & 0 deletions Core/WebServer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// Copyright (c) 2014- PPSSPP Project.

// This program 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, version 2.0 or later versions.

// This program 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.

enum class WebServerFlags {
DISCS = 1,

ALL = 1,
};

bool StartWebServer(WebServerFlags flags);
bool StopWebServer(WebServerFlags flags);
bool WebServerStopping(WebServerFlags flags);
bool WebServerStopped(WebServerFlags flags);
4 changes: 3 additions & 1 deletion UI/NativeApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
#include "Core/HLE/sceUsbGps.h"
#include "Core/Util/GameManager.h"
#include "Core/Util/AudioFormat.h"
#include "Core/WebServer.h"
#include "GPU/GPUInterface.h"

#include "ui_atlas.h"
Expand Down Expand Up @@ -587,7 +588,8 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch
}

if (g_Config.bRemoteShareOnStartup) {
StartRemoteISOSharing();
// TODO: Separate config options.
StartWebServer(WebServerFlags::ALL);
}

std::string sysName = System_GetProperty(SYSPROP_NAME);
Expand Down
Loading