mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-09-04 11:45:18 +02:00
NewThreadExecutor::Run pushed a std::thread per connection and only ever joined them in the destructor, so a server leaked a joinable thread object for every connection it had ever served. Measured with 60 connect/disconnect cycles against the debugger: handle count +60 before, +1 after. Each worker now flags itself done as its last act, and Run() reaps the finished ones first. Only the accept thread calls Run(), so the flag is the only thing that needs to be atomic. Note this doesn't bound how many connections can be in flight at once - it just stops the finished ones from piling up. Separately, a received close code was echoed straight back. RFC 6455 7.4.1 reserves 1004, 1005, 1006 and 1015 for describing how a connection ended locally, so they must never go on the wire - echoing one back would be our protocol violation rather than the client's. Send PROTOCOL_ERROR when they give us something we can't repeat.
148 lines
3.6 KiB
C++
148 lines
3.6 KiB
C++
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <functional>
|
|
#include <map>
|
|
#include <memory>
|
|
#include <thread>
|
|
#include <vector>
|
|
|
|
#include "Common/Net/HTTPHeaders.h"
|
|
#include "Common/Net/Resolve.h"
|
|
|
|
class NewThreadExecutor {
|
|
public:
|
|
~NewThreadExecutor();
|
|
void Run(std::function<void()> func);
|
|
|
|
private:
|
|
// Reap threads that have finished. Only called from the thread that calls Run().
|
|
void Prune();
|
|
|
|
struct Worker {
|
|
std::thread thread;
|
|
// Set by the worker as its last act, read by whoever calls Run() next.
|
|
std::shared_ptr<std::atomic<bool>> done;
|
|
};
|
|
std::vector<Worker> workers_;
|
|
};
|
|
|
|
namespace net {
|
|
|
|
class InputSink;
|
|
class OutputSink;
|
|
|
|
} // namespace net
|
|
|
|
namespace http {
|
|
|
|
class ServerRequest {
|
|
public:
|
|
ServerRequest(int fd);
|
|
~ServerRequest();
|
|
|
|
std::string_view resource() const {
|
|
return header_.resource;
|
|
}
|
|
|
|
RequestHeader::Method Method() const {
|
|
return header_.method;
|
|
}
|
|
|
|
const RequestHeader &Header() const {
|
|
return header_;
|
|
}
|
|
|
|
bool GetParamValue(const char *param_name, std::string *value) const {
|
|
return header_.GetParamValue(param_name, value);
|
|
}
|
|
// Use lowercase.
|
|
bool GetHeader(const char *name, std::string *value) const {
|
|
return header_.GetOther(name, value);
|
|
}
|
|
|
|
net::InputSink *In() const { return in_; }
|
|
net::OutputSink *Out() const { return out_; }
|
|
|
|
// TODO: Remove, in favor of PartialWrite and friends.
|
|
int fd() const { return fd_; }
|
|
|
|
void WritePartial() const;
|
|
void Write();
|
|
void Close();
|
|
|
|
bool IsOK() const { return fd_ > 0; }
|
|
|
|
// If size is negative, no Content-Length: line is written.
|
|
void WriteHttpResponseHeader(const char *ver, int status, int64_t size = -1, const char *mimeType = nullptr, const char *otherHeaders = nullptr) const;
|
|
|
|
private:
|
|
net::InputSink *in_;
|
|
net::OutputSink *out_;
|
|
RequestHeader header_;
|
|
int fd_;
|
|
};
|
|
|
|
// Register handlers on this class to serve stuff.
|
|
class Server {
|
|
public:
|
|
// Takes ownership.
|
|
Server(NewThreadExecutor *executor);
|
|
virtual ~Server();
|
|
|
|
typedef std::function<void(const ServerRequest &)> UrlHandlerFunc;
|
|
typedef std::map<std::string, UrlHandlerFunc, std::less<>> UrlHandlerMap;
|
|
|
|
// Runs forever, serving request. If you want to do something else than serve pages,
|
|
// better put this on a thread. Returns false if failed to start serving, never
|
|
// returns if successful.
|
|
bool Run(int port);
|
|
// May run for (significantly) longer than timeout, but won't wait longer than that
|
|
// for a new connection to handle.
|
|
bool RunSlice(double timeout);
|
|
bool Listen(int port, const char *reason, net::DNSType type = net::DNSType::ANY);
|
|
void Stop();
|
|
|
|
void RegisterHandler(const char *url_path, UrlHandlerFunc handler);
|
|
void SetFallbackHandler(UrlHandlerFunc handler);
|
|
|
|
// If you want to customize things at a lower level than just a simple path handler,
|
|
// then inherit and override this. Implementations should forward to HandleRequestDefault
|
|
// if they don't recognize the url.
|
|
virtual void HandleRequest(const ServerRequest &request);
|
|
|
|
int ListenerSocket() const {
|
|
return listenerSock_;
|
|
}
|
|
int Port() const {
|
|
return port_;
|
|
}
|
|
const std::string &LocalAddress() const {
|
|
return localAddress_;
|
|
}
|
|
|
|
private:
|
|
bool Listen6(int port, bool ipv6_only, const char *reason);
|
|
bool Listen4(int port, const char *reason);
|
|
|
|
void HandleConnection(int conn_fd);
|
|
|
|
// Things like default 404, etc.
|
|
void HandleRequestDefault(const ServerRequest &request);
|
|
|
|
// Neat built-in handlers that are tied to the server.
|
|
void HandleListing(const ServerRequest &request);
|
|
void Handle404(const ServerRequest &request);
|
|
|
|
int listenerSock_;
|
|
int port_ = 0;
|
|
std::string localAddress_;
|
|
|
|
UrlHandlerMap handlers_;
|
|
UrlHandlerFunc fallback_;
|
|
|
|
NewThreadExecutor *executor_;
|
|
};
|
|
|
|
} // namespace http
|