Seamless SSL/non-SSL swapping, new Context-centric interface
This commit is contained in:
@@ -1,23 +1,28 @@
|
||||
// this is roughly the interfaces I plan for (with changes and additional helpers added with time)
|
||||
// much speaks for a header-only or header-mostly implementation now that uSockets is properly isolating its internal headers
|
||||
|
||||
#include "uWS.h"
|
||||
#include "Context.h"
|
||||
|
||||
int main() {
|
||||
|
||||
std::cout << "HttpSocket size: " << sizeof(HttpSocket::Data) << std::endl;
|
||||
std::cout << "HttpSocket size: " << sizeof(HttpSocket<true>::Data) << std::endl;
|
||||
char *buffer = new char[512];
|
||||
|
||||
// either use this, or use onHttpRoute but not both
|
||||
uWS::defaultHub.onHttpRequest([buffer](HttpSocket *s, HttpRequest *req) {
|
||||
// SSL options are given via uSockets structure
|
||||
us_ssl_socket_context_options options = {};
|
||||
options.key_file_name = "/home/alexhultman/uWebSockets/misc/ssl/key.pem";
|
||||
options.cert_file_name = "/home/alexhultman/uWebSockets/misc/ssl/cert.pem";
|
||||
options.passphrase = "1234";
|
||||
|
||||
uWS::SSLContext(options).onHttpRequest([buffer](auto *s, HttpRequest *req) {
|
||||
|
||||
if (req->getUrl() == "/") {
|
||||
s->writeStatus(200)->writeHeader("Server", "µWebSockets v0.15")->end(buffer, 512);
|
||||
s->writeStatus(200)->writeHeader("Hello", "World")->end(buffer, 512);
|
||||
} else {
|
||||
std::cout << "Got HTTP request at URL: " << req->getUrl() << std::endl;
|
||||
}
|
||||
|
||||
}).listen("localhost", 3000, 0).run();
|
||||
}).listen("localhost", 3000, 0);
|
||||
|
||||
// todo: important swapping to SSL should be .secureListen(same interfaces) and work out of the box!
|
||||
uWS::defaultHub.run();
|
||||
}
|
||||
|
||||
+175
@@ -1,6 +1,181 @@
|
||||
#ifndef CONTEXT_H
|
||||
#define CONTEXT_H
|
||||
|
||||
#include "Hub.h"
|
||||
|
||||
#include "libusockets.h"
|
||||
#include <type_traits>
|
||||
|
||||
// represents a mix of two us_socket contexts either in ssl or not (one for http, one for websocket)
|
||||
|
||||
// if you want to share the same openssl context between the two us socket ssl contexts? how?
|
||||
|
||||
#include "Http.h"
|
||||
|
||||
//#define SWAP_F [](auto a, auto b){ if constexpr(SSL) return a; else return b; }
|
||||
|
||||
namespace uWS {
|
||||
|
||||
// holds 4 protocols: server http, client http, server websocket, client websocket (for either SSL or not)
|
||||
template <bool SSL>
|
||||
class ContextBase {
|
||||
|
||||
protected:
|
||||
|
||||
template <class A, class B>
|
||||
static typename std::conditional<SSL, A, B>::type *static_dispatch(A *a, B *b) {
|
||||
if constexpr(SSL) {
|
||||
return a;
|
||||
} else {
|
||||
return b;
|
||||
}
|
||||
}
|
||||
|
||||
typedef typename std::conditional<SSL, us_ssl_socket_context, us_socket_context>::type SOCKET_CONTEXT_TYPE;
|
||||
|
||||
struct Data {
|
||||
Data() {
|
||||
|
||||
|
||||
}
|
||||
|
||||
std::function<void(HttpSocket<SSL> *)> onHttpConnection;
|
||||
std::function<void(HttpSocket<SSL> *)> onHttpDisconnection;
|
||||
std::function<void(HttpSocket<SSL> *, HttpRequest *)> onHttpRequest;
|
||||
} *data;
|
||||
|
||||
// server protocols
|
||||
SOCKET_CONTEXT_TYPE *httpServerContext;
|
||||
SOCKET_CONTEXT_TYPE *webSocketServerContext;
|
||||
|
||||
|
||||
|
||||
// client protocols
|
||||
|
||||
public:
|
||||
|
||||
// the shared constructor
|
||||
void init(us_loop *loop) {
|
||||
|
||||
new (data = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(httpServerContext)) Data();
|
||||
|
||||
// register shims
|
||||
static_dispatch(us_ssl_socket_context_on_open, us_socket_context_on_open)(httpServerContext, [](auto *s) {
|
||||
Data *data = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
||||
|
||||
|
||||
// here we need to construct a HTTP socket on the ext!
|
||||
|
||||
|
||||
|
||||
// we always give pointers to us_socket?
|
||||
// same mistake as before?
|
||||
|
||||
if (!data->onHttpConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: this is VERY tricky to keep bug-free!
|
||||
// we could give the ext here, and skip all bugs?
|
||||
data->onHttpConnection((HttpSocket<SSL> *) s);
|
||||
});
|
||||
|
||||
static_dispatch(us_ssl_socket_context_on_close, us_socket_context_on_close)(httpServerContext, [](auto *s) {
|
||||
Data *data = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
||||
|
||||
|
||||
// we always give pointers to us_socket?
|
||||
// same mistake as before?
|
||||
|
||||
if (!data->onHttpDisconnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: this is VERY tricky to keep bug-free!
|
||||
// we could give the ext here, and skip all bugs?
|
||||
data->onHttpDisconnection((HttpSocket<SSL> *) s);
|
||||
});
|
||||
|
||||
static_dispatch(us_ssl_socket_context_on_data, us_socket_context_on_data)(httpServerContext, [](auto *s, char *data, int length) {
|
||||
|
||||
Data *contextData = (Data *) static_dispatch(us_ssl_socket_context_ext, us_socket_context_ext)(static_dispatch(us_ssl_socket_get_context, us_socket_get_context)(s));
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
// a HttpSocket is basically the Http state and everything needed for it, we use it to parse the data and it knows about its context
|
||||
|
||||
//
|
||||
|
||||
HttpRequest req(data, length);
|
||||
if (req.isComplete()) {
|
||||
contextData->onHttpRequest((HttpSocket<SSL> *) s, &req);
|
||||
} else {
|
||||
std::cout << "Got chunked HTTP headers!" << std::endl;
|
||||
}
|
||||
|
||||
// here we run the HTTP parser on this data
|
||||
|
||||
|
||||
|
||||
// we always give pointers to us_socket?
|
||||
// same mistake as before?
|
||||
|
||||
// note: this is VERY tricky to keep bug-free!
|
||||
// we could give the ext here, and skip all bugs?
|
||||
//data->((HttpSocket *) s);
|
||||
});
|
||||
}
|
||||
|
||||
// for server
|
||||
void listen(const char *host, int port, int options) {
|
||||
static_dispatch(us_ssl_socket_context_listen, us_socket_context_listen)(httpServerContext, host, port, options, sizeof(HttpSocket<SSL>));
|
||||
}
|
||||
|
||||
// for server
|
||||
ContextBase &onHttpRequest(decltype(Data::onHttpRequest) handler) {
|
||||
data->onHttpRequest = handler;
|
||||
|
||||
return *this;
|
||||
}
|
||||
|
||||
// for client and server
|
||||
ContextBase &onWebSocketConnection() {
|
||||
return *this;
|
||||
}
|
||||
|
||||
~ContextBase() {
|
||||
}
|
||||
};
|
||||
|
||||
class Context : public ContextBase<false> {
|
||||
|
||||
public:
|
||||
Context() {
|
||||
|
||||
// we create the context ourselves here
|
||||
httpServerContext = us_create_socket_context(defaultHub.loop, sizeof(Data));
|
||||
|
||||
// init from base
|
||||
init(defaultHub.loop);
|
||||
}
|
||||
};
|
||||
|
||||
class SSLContext : public ContextBase<true> {
|
||||
|
||||
public:
|
||||
// denna finns i barnet SSLContext!
|
||||
SSLContext(us_ssl_socket_context_options options) {
|
||||
// we create the context ourselves here
|
||||
httpServerContext = us_create_ssl_socket_context(defaultHub.loop, sizeof(Data), options);
|
||||
|
||||
// init from base
|
||||
init(defaultHub.loop);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // CONTEXT_H
|
||||
|
||||
+10
-2
@@ -71,6 +71,7 @@ struct HttpRequest {
|
||||
|
||||
|
||||
// HttpSocket is an alias for us_socket
|
||||
template <bool SSL>
|
||||
struct HttpSocket {
|
||||
|
||||
// chunked response will be tricky with this buffering scheme
|
||||
@@ -115,14 +116,21 @@ struct HttpSocket {
|
||||
return this;
|
||||
}
|
||||
|
||||
// this depends on SSL!
|
||||
void end(char *data, int length) {
|
||||
us_socket *s = (us_socket *) this;
|
||||
//us_ssl_socket *s = (us_ssl_socket *) this;
|
||||
|
||||
char *corkBuffer = getCorkBuffer();
|
||||
memcpy(corkBuffer + corkOffset, data, length);
|
||||
corkOffset += length;
|
||||
|
||||
us_socket_write(s, corkBuffer, corkOffset, 0);
|
||||
|
||||
if constexpr(SSL) {
|
||||
us_ssl_socket_write((us_ssl_socket *) this, corkBuffer, corkOffset);
|
||||
} else {
|
||||
us_socket_write((us_socket *) this, corkBuffer, corkOffset, 0);
|
||||
}
|
||||
|
||||
corkOffset = 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,130 +16,38 @@ int corkOffset = 0;
|
||||
|
||||
struct Hub;
|
||||
|
||||
// maybe a Context is both TCP and SSL in one?
|
||||
template <bool isServer, bool isSSL>
|
||||
struct Context {
|
||||
// Hub::listen has to create the socket context (with SSL options) via first time init
|
||||
// what if you want to listen to another port with another SSL cert?
|
||||
// every listen has to correspond to a listen socket and a socket context, created for each call to listen
|
||||
// same with connect?
|
||||
|
||||
Hub &hub;
|
||||
// just remove Context altogether and just let Hub have all of this stuff
|
||||
// it really only needs to hold the listen socket and the socket context wrapped as ListenToken instead
|
||||
// ListenToken.close()
|
||||
|
||||
struct Data {
|
||||
Data() {
|
||||
// ListenContext?
|
||||
|
||||
|
||||
}
|
||||
// instead of Context, have some kind of token for Listening and allow it to be stopped but not cloesed (you stop listening but keep sockets alive)
|
||||
// what if you then want to listen again? you don't want to create a new context that time?
|
||||
|
||||
std::function<void(HttpSocket *)> onHttpConnection;
|
||||
std::function<void(HttpSocket *)> onHttpDisconnection;
|
||||
std::function<void(HttpSocket *, HttpRequest *)> onHttpRequest;
|
||||
} *data;
|
||||
// imagine listening, then stopping, then continuing - maybe support PAUSING a listen socket without closing it? that way you listen once, pause listening, etc
|
||||
// this way we can solve the accept issue with listen-pause and listen timers?
|
||||
|
||||
us_socket_context *httpContext;
|
||||
|
||||
Context(Hub &hub) : hub(hub) {
|
||||
|
||||
}
|
||||
|
||||
// one for each? ssl and non-ssl?
|
||||
void init(us_loop *loop) {
|
||||
httpContext = us_create_socket_context(loop, sizeof(Data));
|
||||
|
||||
new (data = (Data *) us_socket_context_ext(httpContext)) Data();
|
||||
|
||||
// register shims
|
||||
us_socket_context_on_open(httpContext, [](us_socket *s) {
|
||||
Data *data = (Data *) us_socket_context_ext(us_socket_get_context(s));
|
||||
// ListenToken.pause(), .resume(), .close()
|
||||
|
||||
|
||||
// here we need to construct a HTTP socket on the ext!
|
||||
// eller så bara ändrar man gränssnittet så att man explicit skapar ett context?
|
||||
|
||||
|
||||
// createContext(ssl options eller inga options avgör om SSL eller inte)
|
||||
// eller bara sslContext(options).onHttplalala.listen().Hub().run();
|
||||
|
||||
// we always give pointers to us_socket?
|
||||
// same mistake as before?
|
||||
// hubben är underförstådd och du agerar egentligen alltid på ett context som antingen är ssl eller inte
|
||||
|
||||
if (!data->onHttpConnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: this is VERY tricky to keep bug-free!
|
||||
// we could give the ext here, and skip all bugs?
|
||||
data->onHttpConnection((HttpSocket *) s);
|
||||
});
|
||||
|
||||
us_socket_context_on_close(httpContext, [](us_socket *s) {
|
||||
Data *data = (Data *) us_socket_context_ext(us_socket_get_context(s));
|
||||
|
||||
// we always give pointers to us_socket?
|
||||
// same mistake as before?
|
||||
|
||||
if (!data->onHttpDisconnection) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: this is VERY tricky to keep bug-free!
|
||||
// we could give the ext here, and skip all bugs?
|
||||
data->onHttpDisconnection((HttpSocket *) s);
|
||||
});
|
||||
|
||||
us_socket_context_on_data(httpContext, [](us_socket *s, char *data, int length) {
|
||||
Data *contextData = (Data *) us_socket_context_ext(us_socket_get_context(s));
|
||||
|
||||
|
||||
|
||||
|
||||
// a HttpSocket is basically the Http state and everything needed for it, we use it to parse the data and it knows about its context
|
||||
|
||||
//
|
||||
|
||||
HttpRequest req(data, length);
|
||||
if (req.isComplete()) {
|
||||
contextData->onHttpRequest((HttpSocket *) s, &req);
|
||||
} else {
|
||||
std::cout << "Got chunked HTTP headers!" << std::endl;
|
||||
}
|
||||
|
||||
// here we run the HTTP parser on this data
|
||||
|
||||
|
||||
|
||||
// we always give pointers to us_socket?
|
||||
// same mistake as before?
|
||||
|
||||
// note: this is VERY tricky to keep bug-free!
|
||||
// we could give the ext here, and skip all bugs?
|
||||
//data->((HttpSocket *) s);
|
||||
});
|
||||
}
|
||||
|
||||
void onHttpConnection(decltype(Data::onHttpConnection) handler) {
|
||||
data->onHttpConnection = handler;
|
||||
}
|
||||
void onHttpDisconnection(decltype(Data::onHttpDisconnection) handler) {
|
||||
data->onHttpDisconnection = handler;
|
||||
}
|
||||
Hub &onHttpRequest(decltype(Data::onHttpRequest) handler) {
|
||||
data->onHttpRequest = handler;
|
||||
|
||||
return hub;
|
||||
}
|
||||
|
||||
// this should only be enabled if we are server context!
|
||||
|
||||
// should return Loop, not hub? or simply make it so that ALL contexts stem from some Hub? Hub is essentually the Loop abstraction? could work
|
||||
Hub &listen(const char *host, int port, int options) {
|
||||
us_socket_context_listen(httpContext, host, port, options, sizeof(HttpSocket));
|
||||
|
||||
return hub;
|
||||
}
|
||||
};
|
||||
|
||||
// are we SSL or regular hub?
|
||||
struct Hub : Context<true, false> {
|
||||
struct Hub {
|
||||
us_loop *loop;
|
||||
|
||||
using Context::onHttpConnection;
|
||||
using Context<true, false>::listen;
|
||||
|
||||
struct Data {
|
||||
Data() {
|
||||
|
||||
@@ -160,10 +68,8 @@ struct Hub : Context<true, false> {
|
||||
|
||||
}
|
||||
|
||||
Hub() : loop(us_create_loop(wakeupCb, preCb, postCb, sizeof(Data))), Context<true, false>(*this) {
|
||||
Hub() : loop(us_create_loop(wakeupCb, preCb, postCb, sizeof(Data))) {
|
||||
new (data = (Data *) us_loop_ext(loop)) Data();
|
||||
|
||||
Context<true, false>::init(loop);
|
||||
}
|
||||
|
||||
void run() {
|
||||
|
||||
+1
-1
Submodule uSockets updated: ad3ee4c3b5...88ff2b1f35
Reference in New Issue
Block a user