bsnes/nall/http/server.hpp
Tim Allen c6fc15f8d2 Update to v101r18 release.
byuu says:

Changelog:

  - added 30 new PAL games to icarus (courtesy of Mikerochip)
  - new version of libco no longer requires mprotect nor W|X permissions
  - nall: default C compiler to -std=c11 instead of -std=c99
  - nall: use `-fno-strict-aliasing` during compilation
  - updated nall/certificates (hopefully for the last time)
  - updated nall/http to newer coding conventions
  - nall: improve handling of range() function

I didn't really work on higan at all, this is mostly just a release
because lots of other things have changed.

The most interesting is `-fno-strict-aliasing` ... basically, it joins
`-fwrapv` as being "stop the GCC developers from doing *really* evil
shit that could lead to security vulnerabilities or instabilities."

For the most part, it's a ~2% speed penalty for higan. Except for the
Sega Genesis, where it's a ~10% speedup. I have no idea how that's
possible, but clearly something's going very wrong with strict aliasing
on the Genesis core.

So ... it is what it is. If you need the performance for the non-Genesis
cores, you can turn it off in your builds. But I'm getting quite sick of
C++'s "surprises" and clever compiler developers, so I'm keeping it on
in all of my software going forward.
2016-09-14 21:55:53 +10:00

226 lines
6.4 KiB
C++

#pragma once
#include <nall/service.hpp>
#include <nall/http/role.hpp>
namespace nall { namespace HTTP {
struct Server : Role, service {
inline auto open(uint port = 8080, const string& serviceName = "", const string& command = "") -> bool;
inline auto main(const function<Response (Request&)>& function = {}) -> void;
inline auto scan() -> string;
inline auto close() -> void;
~Server() { close(); }
private:
function<Response (Request&)> callback;
std::atomic<int> connections{0};
int fd4 = -1;
int fd6 = -1;
struct sockaddr_in addrin4 = {0};
struct sockaddr_in6 addrin6 = {0};
auto ipv4() const -> bool { return fd4 >= 0; }
auto ipv6() const -> bool { return fd6 >= 0; }
auto ipv4_close() -> void { if(fd4 >= 0) ::close(fd4); fd4 = -1; }
auto ipv6_close() -> void { if(fd6 >= 0) ::close(fd6); fd6 = -1; }
auto ipv4_scan() -> bool;
auto ipv6_scan() -> bool;
};
auto Server::open(uint port, const string& serviceName, const string& command) -> bool {
if(serviceName) {
if(!service::command(serviceName, command)) return false;
}
fd4 = socket(AF_INET, SOCK_STREAM, 0);
fd6 = socket(AF_INET6, SOCK_STREAM, 0);
if(!ipv4() && !ipv6()) return false;
{
#if defined(SO_RCVTIMEO)
if(settings.timeoutReceive) {
struct timeval rcvtimeo;
rcvtimeo.tv_sec = settings.timeoutReceive / 1000;
rcvtimeo.tv_usec = settings.timeoutReceive % 1000 * 1000;
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeo, sizeof(struct timeval));
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_RCVTIMEO, &rcvtimeo, sizeof(struct timeval));
}
#endif
#if defined(SO_SNDTIMEO)
if(settings.timeoutSend) {
struct timeval sndtimeo;
sndtimeo.tv_sec = settings.timeoutSend / 1000;
sndtimeo.tv_usec = settings.timeoutSend % 1000 * 1000;
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_SNDTIMEO, &sndtimeo, sizeof(struct timeval));
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_SNDTIMEO, &sndtimeo, sizeof(struct timeval));
}
#endif
#if defined(SO_NOSIGPIPE) //BSD, OSX
int nosigpipe = 1;
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(int));
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_NOSIGPIPE, &nosigpipe, sizeof(int));
#endif
#if defined(SO_REUSEADDR) //BSD, Linux, OSX
int reuseaddr = 1;
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int));
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(int));
#endif
#if defined(SO_REUSEPORT) //BSD, OSX
int reuseport = 1;
if(ipv4()) setsockopt(fd4, SOL_SOCKET, SO_REUSEPORT, &reuseport, sizeof(int));
if(ipv6()) setsockopt(fd6, SOL_SOCKET, SO_REUSEPORT, &reuseport, sizeof(int));
#endif
}
addrin4.sin_family = AF_INET;
addrin4.sin_addr.s_addr = htonl(INADDR_ANY);
addrin4.sin_port = htons(port);
addrin6.sin6_family = AF_INET6;
addrin6.sin6_addr = in6addr_any;
addrin6.sin6_port = htons(port);
if(bind(fd4, (struct sockaddr*)&addrin4, sizeof(addrin4)) < 0 || listen(fd4, SOMAXCONN) < 0) ipv4_close();
if(bind(fd6, (struct sockaddr*)&addrin6, sizeof(addrin6)) < 0 || listen(fd6, SOMAXCONN) < 0) ipv6_close();
return ipv4() || ipv6();
}
auto Server::main(const function<Response (Request&)>& function) -> void {
callback = function;
}
auto Server::scan() -> string {
if(auto command = service::receive()) return command;
if(connections >= settings.connectionLimit) return "busy";
if(ipv4() && ipv4_scan()) return "ok";
if(ipv6() && ipv6_scan()) return "ok";
return "idle";
}
auto Server::ipv4_scan() -> bool {
struct pollfd query = {0};
query.fd = fd4;
query.events = POLLIN;
poll(&query, 1, 0);
if(query.fd == fd4 && query.revents & POLLIN) {
++connections;
thread::create([&](uintptr) {
thread::detach();
int clientfd = -1;
struct sockaddr_in settings = {0};
socklen_t socklen = sizeof(sockaddr_in);
clientfd = accept(fd4, (struct sockaddr*)&settings, &socklen);
if(clientfd < 0) return;
uint32_t ip = ntohl(settings.sin_addr.s_addr);
Request request;
request._ipv6 = false;
request._ip = {
(uint8_t)(ip >> 24), ".",
(uint8_t)(ip >> 16), ".",
(uint8_t)(ip >> 8), ".",
(uint8_t)(ip >> 0)
};
if(download(clientfd, request) && callback) {
auto response = callback(request);
upload(clientfd, response);
} else {
upload(clientfd, Response()); //"501 Not Implemented"
}
::close(clientfd);
--connections;
}, 0, settings.threadStackSize);
return true;
}
return false;
}
auto Server::ipv6_scan() -> bool {
struct pollfd query = {0};
query.fd = fd6;
query.events = POLLIN;
poll(&query, 1, 0);
if(query.fd == fd6 && query.revents & POLLIN) {
++connections;
thread::create([&](uintptr) {
thread::detach();
int clientfd = -1;
struct sockaddr_in6 settings = {0};
socklen_t socklen = sizeof(sockaddr_in6);
clientfd = accept(fd6, (struct sockaddr*)&settings, &socklen);
if(clientfd < 0) return;
uint8_t* ip = settings.sin6_addr.s6_addr;
uint16_t ipSegment[8];
for(auto n : range(8)) ipSegment[n] = ip[n * 2 + 0] * 256 + ip[n * 2 + 1];
Request request;
request._ipv6 = true;
//RFC5952 IPv6 encoding: the first longest 2+ consecutive zero-sequence is compressed to "::"
int zeroOffset = -1;
int zeroLength = 0;
int zeroCounter = 0;
for(auto n : range(8)) {
uint16_t value = ipSegment[n];
if(value == 0) zeroCounter++;
if(zeroCounter > zeroLength) {
zeroLength = zeroCounter;
zeroOffset = 1 + n - zeroLength;
}
if(value != 0) zeroCounter = 0;
}
if(zeroLength == 1) zeroOffset = -1;
for(uint n = 0; n < 8;) {
if(n == zeroOffset) {
request._ip.append(n == 0 ? "::" : ":");
n += zeroLength;
} else {
uint16_t value = ipSegment[n];
request._ip.append(hex(value), n++ != 7 ? ":" : "");
}
}
if(download(clientfd, request) && callback) {
auto response = callback(request);
upload(clientfd, response);
} else {
upload(clientfd, Response()); //"501 Not Implemented"
}
::close(clientfd);
--connections;
}, 0, settings.threadStackSize);
return true;
}
return false;
}
auto Server::close() -> void {
ipv4_close();
ipv6_close();
}
}}