opal-up 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE +209 -0
- data/README.md +97 -29
- data/bin/up_ruby +4 -0
- data/bin/up_ruby_cluster +4 -0
- data/ext/up_ext/App.h +606 -0
- data/ext/up_ext/AsyncSocket.h +355 -0
- data/ext/up_ext/AsyncSocketData.h +87 -0
- data/ext/up_ext/BloomFilter.h +83 -0
- data/ext/up_ext/ChunkedEncoding.h +236 -0
- data/ext/up_ext/ClientApp.h +36 -0
- data/ext/up_ext/HttpContext.h +502 -0
- data/ext/up_ext/HttpContextData.h +56 -0
- data/ext/up_ext/HttpErrors.h +53 -0
- data/ext/up_ext/HttpParser.h +680 -0
- data/ext/up_ext/HttpResponse.h +578 -0
- data/ext/up_ext/HttpResponseData.h +95 -0
- data/ext/up_ext/HttpRouter.h +380 -0
- data/ext/up_ext/Loop.h +204 -0
- data/ext/up_ext/LoopData.h +112 -0
- data/ext/up_ext/MoveOnlyFunction.h +377 -0
- data/ext/up_ext/PerMessageDeflate.h +315 -0
- data/ext/up_ext/ProxyParser.h +163 -0
- data/ext/up_ext/QueryParser.h +120 -0
- data/ext/up_ext/TopicTree.h +363 -0
- data/ext/up_ext/Utilities.h +66 -0
- data/ext/up_ext/WebSocket.h +381 -0
- data/ext/up_ext/WebSocketContext.h +434 -0
- data/ext/up_ext/WebSocketContextData.h +109 -0
- data/ext/up_ext/WebSocketData.h +86 -0
- data/ext/up_ext/WebSocketExtensions.h +256 -0
- data/ext/up_ext/WebSocketHandshake.h +145 -0
- data/ext/up_ext/WebSocketProtocol.h +506 -0
- data/ext/up_ext/bsd.c +767 -0
- data/ext/up_ext/bsd.h +109 -0
- data/ext/up_ext/context.c +524 -0
- data/ext/up_ext/epoll_kqueue.c +458 -0
- data/ext/up_ext/epoll_kqueue.h +67 -0
- data/ext/up_ext/extconf.rb +5 -0
- data/ext/up_ext/internal.h +224 -0
- data/ext/up_ext/libusockets.h +350 -0
- data/ext/up_ext/libuwebsockets.cpp +1344 -0
- data/ext/up_ext/libuwebsockets.h +396 -0
- data/ext/up_ext/loop.c +386 -0
- data/ext/up_ext/loop_data.h +38 -0
- data/ext/up_ext/socket.c +231 -0
- data/ext/up_ext/up_ext.c +930 -0
- data/lib/up/bun/rack_env.rb +1 -13
- data/lib/up/bun/server.rb +93 -19
- data/lib/up/cli.rb +3 -0
- data/lib/up/client.rb +68 -0
- data/lib/up/ruby/cluster.rb +39 -0
- data/lib/up/ruby/cluster_cli.rb +10 -0
- data/lib/up/{node → ruby}/rack_cluster.rb +5 -4
- data/lib/up/{node → ruby}/rack_server.rb +4 -4
- data/lib/up/ruby/server_cli.rb +10 -0
- data/lib/up/u_web_socket/cluster.rb +18 -3
- data/lib/up/u_web_socket/server.rb +108 -15
- data/lib/up/version.rb +1 -1
- metadata +72 -30
- data/.gitignore +0 -5
- data/Gemfile +0 -2
- data/bin/up_node +0 -12
- data/bin/up_node_cluster +0 -12
- data/example_rack_app/Gemfile +0 -3
- data/example_rack_app/config.ru +0 -6
- data/example_rack_app/rack_app.rb +0 -5
- data/example_roda_app/Gemfile +0 -6
- data/example_roda_app/config.ru +0 -6
- data/example_roda_app/roda_app.rb +0 -37
- data/example_sinatra_app/Gemfile +0 -6
- data/example_sinatra_app/config.ru +0 -6
- data/example_sinatra_app/sinatra_app.rb +0 -7
- data/lib/up/node/cluster.rb +0 -39
- data/lib/up/node/cluster_cli.rb +0 -15
- data/lib/up/node/rack_env.rb +0 -106
- data/lib/up/node/server.rb +0 -84
- data/lib/up/node/server_cli.rb +0 -15
- data/lib/up/u_web_socket/rack_env.rb +0 -101
- data/opal-up.gemspec +0 -27
- data/up_logo.svg +0 -256
@@ -0,0 +1,163 @@
|
|
1
|
+
/*
|
2
|
+
* Authored by Alex Hultman, 2018-2020.
|
3
|
+
* Intellectual property of third-party.
|
4
|
+
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
* you may not use this file except in compliance with the License.
|
7
|
+
* You may obtain a copy of the License at
|
8
|
+
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
* See the License for the specific language governing permissions and
|
15
|
+
* limitations under the License.
|
16
|
+
*/
|
17
|
+
|
18
|
+
/* This module implements The PROXY Protocol v2 */
|
19
|
+
|
20
|
+
#ifndef UWS_PROXY_PARSER_H
|
21
|
+
#define UWS_PROXY_PARSER_H
|
22
|
+
|
23
|
+
#ifdef UWS_WITH_PROXY
|
24
|
+
|
25
|
+
namespace uWS {
|
26
|
+
|
27
|
+
struct proxy_hdr_v2 {
|
28
|
+
uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */
|
29
|
+
uint8_t ver_cmd; /* protocol version and command */
|
30
|
+
uint8_t fam; /* protocol family and address */
|
31
|
+
uint16_t len; /* number of following bytes part of the header */
|
32
|
+
};
|
33
|
+
|
34
|
+
union proxy_addr {
|
35
|
+
struct { /* for TCP/UDP over IPv4, len = 12 */
|
36
|
+
uint32_t src_addr;
|
37
|
+
uint32_t dst_addr;
|
38
|
+
uint16_t src_port;
|
39
|
+
uint16_t dst_port;
|
40
|
+
} ipv4_addr;
|
41
|
+
struct { /* for TCP/UDP over IPv6, len = 36 */
|
42
|
+
uint8_t src_addr[16];
|
43
|
+
uint8_t dst_addr[16];
|
44
|
+
uint16_t src_port;
|
45
|
+
uint16_t dst_port;
|
46
|
+
} ipv6_addr;
|
47
|
+
};
|
48
|
+
|
49
|
+
/* Byte swap for little-endian systems */
|
50
|
+
/* Todo: This functions should be shared with the one in WebSocketProtocol.h! */
|
51
|
+
template <typename T>
|
52
|
+
T _cond_byte_swap(T value) {
|
53
|
+
uint32_t endian_test = 1;
|
54
|
+
if (*((char *)&endian_test)) {
|
55
|
+
union {
|
56
|
+
T i;
|
57
|
+
uint8_t b[sizeof(T)];
|
58
|
+
} src = { value }, dst;
|
59
|
+
|
60
|
+
for (unsigned int i = 0; i < sizeof(value); i++) {
|
61
|
+
dst.b[i] = src.b[sizeof(value) - 1 - i];
|
62
|
+
}
|
63
|
+
|
64
|
+
return dst.i;
|
65
|
+
}
|
66
|
+
return value;
|
67
|
+
}
|
68
|
+
|
69
|
+
struct ProxyParser {
|
70
|
+
private:
|
71
|
+
union proxy_addr addr;
|
72
|
+
|
73
|
+
/* Default family of 0 signals no proxy address */
|
74
|
+
uint8_t family = 0;
|
75
|
+
|
76
|
+
public:
|
77
|
+
/* Returns 4 or 16 bytes source address */
|
78
|
+
std::string_view getSourceAddress() {
|
79
|
+
|
80
|
+
// UNSPEC family and protocol
|
81
|
+
if (family == 0) {
|
82
|
+
return {};
|
83
|
+
}
|
84
|
+
|
85
|
+
if ((family & 0xf0) >> 4 == 1) {
|
86
|
+
/* Family 1 is INET4 */
|
87
|
+
return {(char *) &addr.ipv4_addr.src_addr, 4};
|
88
|
+
} else {
|
89
|
+
/* Family 2 is INET6 */
|
90
|
+
return {(char *) &addr.ipv6_addr.src_addr, 16};
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
/* Returns [done, consumed] where done = false on failure */
|
95
|
+
std::pair<bool, unsigned int> parse(std::string_view data) {
|
96
|
+
|
97
|
+
/* We require at least four bytes to determine protocol */
|
98
|
+
if (data.length() < 4) {
|
99
|
+
return {false, 0};
|
100
|
+
}
|
101
|
+
|
102
|
+
/* HTTP can never start with "\r\n\r\n", but PROXY always does */
|
103
|
+
if (memcmp(data.data(), "\r\n\r\n", 4)) {
|
104
|
+
/* This is HTTP, so be done */
|
105
|
+
return {true, 0};
|
106
|
+
}
|
107
|
+
|
108
|
+
/* We assume we are parsing PROXY V2 here */
|
109
|
+
|
110
|
+
/* We require 16 bytes here */
|
111
|
+
if (data.length() < 16) {
|
112
|
+
return {false, 0};
|
113
|
+
}
|
114
|
+
|
115
|
+
/* Header is 16 bytes */
|
116
|
+
struct proxy_hdr_v2 header;
|
117
|
+
memcpy(&header, data.data(), 16);
|
118
|
+
|
119
|
+
if (memcmp(header.sig, "\x0D\x0A\x0D\x0A\x00\x0D\x0A\x51\x55\x49\x54\x0A", 12)) {
|
120
|
+
/* This is not PROXY protocol at all */
|
121
|
+
return {false, 0};
|
122
|
+
}
|
123
|
+
|
124
|
+
/* We only support version 2 */
|
125
|
+
if ((header.ver_cmd & 0xf0) >> 4 != 2) {
|
126
|
+
return {false, 0};
|
127
|
+
}
|
128
|
+
|
129
|
+
//printf("Version: %d\n", (header.ver_cmd & 0xf0) >> 4);
|
130
|
+
//printf("Command: %d\n", (header.ver_cmd & 0x0f));
|
131
|
+
|
132
|
+
/* We get length in network byte order (todo: share this function with the rest) */
|
133
|
+
uint16_t hostLength = _cond_byte_swap<uint16_t>(header.len);
|
134
|
+
|
135
|
+
/* We must have all the data available */
|
136
|
+
if (data.length() < 16u + hostLength) {
|
137
|
+
return {false, 0};
|
138
|
+
}
|
139
|
+
|
140
|
+
/* Payload cannot be more than sizeof proxy_addr */
|
141
|
+
if (sizeof(proxy_addr) < hostLength) {
|
142
|
+
return {false, 0};
|
143
|
+
}
|
144
|
+
|
145
|
+
//printf("Family: %d\n", (header.fam & 0xf0) >> 4);
|
146
|
+
//printf("Transport: %d\n", (header.fam & 0x0f));
|
147
|
+
|
148
|
+
/* We have 0 family by default, and UNSPEC is 0 as well */
|
149
|
+
family = header.fam;
|
150
|
+
|
151
|
+
/* Copy payload */
|
152
|
+
memcpy(&addr, data.data() + 16, hostLength);
|
153
|
+
|
154
|
+
/* We consumed everything */
|
155
|
+
return {true, 16 + hostLength};
|
156
|
+
}
|
157
|
+
};
|
158
|
+
|
159
|
+
}
|
160
|
+
|
161
|
+
#endif
|
162
|
+
|
163
|
+
#endif // UWS_PROXY_PARSER_H
|
@@ -0,0 +1,120 @@
|
|
1
|
+
/*
|
2
|
+
* Authored by Alex Hultman, 2018-2020.
|
3
|
+
* Intellectual property of third-party.
|
4
|
+
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
* you may not use this file except in compliance with the License.
|
7
|
+
* You may obtain a copy of the License at
|
8
|
+
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
* See the License for the specific language governing permissions and
|
15
|
+
* limitations under the License.
|
16
|
+
*/
|
17
|
+
|
18
|
+
/* This module implements URI query parsing and retrieval of value given key */
|
19
|
+
|
20
|
+
#ifndef UWS_QUERYPARSER_H
|
21
|
+
#define UWS_QUERYPARSER_H
|
22
|
+
|
23
|
+
#include <string_view>
|
24
|
+
|
25
|
+
namespace uWS {
|
26
|
+
|
27
|
+
/* Takes raw query including initial '?' sign. Will inplace decode, so input will mutate */
|
28
|
+
static inline std::string_view getDecodedQueryValue(std::string_view key, std::string_view rawQuery) {
|
29
|
+
|
30
|
+
/* Can't have a value without a key */
|
31
|
+
if (!key.length()) {
|
32
|
+
return {};
|
33
|
+
}
|
34
|
+
|
35
|
+
/* Start with the whole querystring including initial '?' */
|
36
|
+
std::string_view queryString = rawQuery;
|
37
|
+
|
38
|
+
/* List of key, value could be cached for repeated fetches similar to how headers are, todo! */
|
39
|
+
while (queryString.length()) {
|
40
|
+
/* Find boundaries of this statement */
|
41
|
+
std::string_view statement = queryString.substr(1, queryString.find('&', 1) - 1);
|
42
|
+
|
43
|
+
/* Only bother if first char of key match (early exit) */
|
44
|
+
if (statement.length() && statement[0] == key[0]) {
|
45
|
+
/* Equal sign must be present and not in the end of statement */
|
46
|
+
auto equality = statement.find('=');
|
47
|
+
if (equality != std::string_view::npos) {
|
48
|
+
|
49
|
+
std::string_view statementKey = statement.substr(0, equality);
|
50
|
+
std::string_view statementValue = statement.substr(equality + 1);
|
51
|
+
|
52
|
+
/* String comparison */
|
53
|
+
if (key == statementKey) {
|
54
|
+
|
55
|
+
/* Decode value inplace, put null at end if before length of original */
|
56
|
+
char *in = (char *) statementValue.data();
|
57
|
+
|
58
|
+
/* Write offset */
|
59
|
+
unsigned int out = 0;
|
60
|
+
|
61
|
+
/* Walk over all chars until end or null char, decoding in place */
|
62
|
+
for (unsigned int i = 0; i < statementValue.length() && in[i]; i++) {
|
63
|
+
/* Only bother with '%' */
|
64
|
+
if (in[i] == '%') {
|
65
|
+
/* Do we have enough data for two bytes hex? */
|
66
|
+
if (i + 2 >= statementValue.length()) {
|
67
|
+
return {};
|
68
|
+
}
|
69
|
+
|
70
|
+
/* Two bytes hex */
|
71
|
+
int hex1 = in[i + 1] - '0';
|
72
|
+
if (hex1 > 9) {
|
73
|
+
hex1 &= 223;
|
74
|
+
hex1 -= 7;
|
75
|
+
}
|
76
|
+
|
77
|
+
int hex2 = in[i + 2] - '0';
|
78
|
+
if (hex2 > 9) {
|
79
|
+
hex2 &= 223;
|
80
|
+
hex2 -= 7;
|
81
|
+
}
|
82
|
+
|
83
|
+
*((unsigned char *) &in[out]) = (unsigned char) (hex1 * 16 + hex2);
|
84
|
+
i += 2;
|
85
|
+
} else {
|
86
|
+
/* Is this even a rule? */
|
87
|
+
if (in[i] == '+') {
|
88
|
+
in[out] = ' ';
|
89
|
+
} else {
|
90
|
+
in[out] = in[i];
|
91
|
+
}
|
92
|
+
}
|
93
|
+
|
94
|
+
/* We always only write one char */
|
95
|
+
out++;
|
96
|
+
}
|
97
|
+
|
98
|
+
/* If decoded string is shorter than original, put null char to stop next read */
|
99
|
+
if (out < statementValue.length()) {
|
100
|
+
in[out] = 0;
|
101
|
+
}
|
102
|
+
|
103
|
+
return statementValue.substr(0, out);
|
104
|
+
}
|
105
|
+
} else {
|
106
|
+
/* This querystring is invalid, cannot parse it */
|
107
|
+
return {nullptr, 0};
|
108
|
+
}
|
109
|
+
}
|
110
|
+
|
111
|
+
queryString.remove_prefix(statement.length() + 1);
|
112
|
+
}
|
113
|
+
|
114
|
+
/* Nothing found is given as nullptr, while empty string is given as some pointer to the given buffer */
|
115
|
+
return {nullptr, 0};
|
116
|
+
}
|
117
|
+
|
118
|
+
}
|
119
|
+
|
120
|
+
#endif
|
@@ -0,0 +1,363 @@
|
|
1
|
+
/*
|
2
|
+
* Authored by Alex Hultman, 2018-2021.
|
3
|
+
* Intellectual property of third-party.
|
4
|
+
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
6
|
+
* you may not use this file except in compliance with the License.
|
7
|
+
* You may obtain a copy of the License at
|
8
|
+
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
10
|
+
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
14
|
+
* See the License for the specific language governing permissions and
|
15
|
+
* limitations under the License.
|
16
|
+
*/
|
17
|
+
|
18
|
+
#ifndef UWS_TOPICTREE_H
|
19
|
+
#define UWS_TOPICTREE_H
|
20
|
+
|
21
|
+
#include <map>
|
22
|
+
#include <list>
|
23
|
+
#include <iostream>
|
24
|
+
#include <unordered_set>
|
25
|
+
#include <utility>
|
26
|
+
#include <memory>
|
27
|
+
#include <unordered_map>
|
28
|
+
#include <vector>
|
29
|
+
#include <string_view>
|
30
|
+
#include <functional>
|
31
|
+
#include <set>
|
32
|
+
#include <string>
|
33
|
+
|
34
|
+
namespace uWS {
|
35
|
+
|
36
|
+
struct Subscriber;
|
37
|
+
|
38
|
+
struct Topic : std::unordered_set<Subscriber *> {
|
39
|
+
|
40
|
+
Topic(std::string_view topic) : name(topic) {
|
41
|
+
|
42
|
+
}
|
43
|
+
|
44
|
+
std::string name;
|
45
|
+
};
|
46
|
+
|
47
|
+
struct Subscriber {
|
48
|
+
|
49
|
+
template <typename, typename> friend struct TopicTree;
|
50
|
+
|
51
|
+
private:
|
52
|
+
/* We use a factory */
|
53
|
+
Subscriber() = default;
|
54
|
+
|
55
|
+
/* State of prev, next does not matter unless we are needsDrainage() since we are not in the list */
|
56
|
+
Subscriber *prev, *next;
|
57
|
+
|
58
|
+
/* Any one subscriber can be part of at most 32 publishes before it needs a drain,
|
59
|
+
* or whatever encoding of runs or whatever we might do in the future */
|
60
|
+
uint16_t messageIndices[32];
|
61
|
+
|
62
|
+
/* This one matters the most, if it is 0 we are not in the list of drainableSubscribers */
|
63
|
+
unsigned char numMessageIndices = 0;
|
64
|
+
|
65
|
+
public:
|
66
|
+
|
67
|
+
/* We have a list of topics we subscribe to (read by WebSocket::iterateTopics) */
|
68
|
+
std::set<Topic *> topics;
|
69
|
+
|
70
|
+
/* User data */
|
71
|
+
void *user;
|
72
|
+
|
73
|
+
bool needsDrainage() {
|
74
|
+
return numMessageIndices;
|
75
|
+
}
|
76
|
+
};
|
77
|
+
|
78
|
+
template <typename T, typename B>
|
79
|
+
struct TopicTree {
|
80
|
+
|
81
|
+
enum IteratorFlags {
|
82
|
+
LAST = 1,
|
83
|
+
FIRST = 2
|
84
|
+
};
|
85
|
+
|
86
|
+
/* Whomever is iterating this topic is locked to not modify its own list */
|
87
|
+
Subscriber *iteratingSubscriber = nullptr;
|
88
|
+
|
89
|
+
private:
|
90
|
+
|
91
|
+
/* The drain callback must not publish, unsubscribe or subscribe.
|
92
|
+
* It must only cork, uncork, send, write */
|
93
|
+
std::function<bool(Subscriber *, T &, IteratorFlags)> cb;
|
94
|
+
|
95
|
+
/* The topics */
|
96
|
+
std::unordered_map<std::string_view, std::unique_ptr<Topic>> topics;
|
97
|
+
|
98
|
+
/* List of subscribers that needs drainage */
|
99
|
+
Subscriber *drainableSubscribers = nullptr;
|
100
|
+
|
101
|
+
/* Palette of outgoing messages, up to 64k */
|
102
|
+
std::vector<T> outgoingMessages;
|
103
|
+
|
104
|
+
void checkIteratingSubscriber(Subscriber *s) {
|
105
|
+
/* Notify user that they are doing something wrong here */
|
106
|
+
if (iteratingSubscriber == s) {
|
107
|
+
std::cerr << "Error: WebSocket must not subscribe or unsubscribe to topics while iterating its topics!" << std::endl;
|
108
|
+
std::terminate();
|
109
|
+
}
|
110
|
+
}
|
111
|
+
|
112
|
+
/* Warning: does NOT unlink from drainableSubscribers or modify next, prev. */
|
113
|
+
void drainImpl(Subscriber *s) {
|
114
|
+
/* Before we call cb we need to make sure this subscriber will not report needsDrainage()
|
115
|
+
* since WebSocket::send will call drain from within the cb in that case.*/
|
116
|
+
int numMessageIndices = s->numMessageIndices;
|
117
|
+
s->numMessageIndices = 0;
|
118
|
+
|
119
|
+
/* Then we emit cb */
|
120
|
+
for (int i = 0; i < numMessageIndices; i++) {
|
121
|
+
T &outgoingMessage = outgoingMessages[s->messageIndices[i]];
|
122
|
+
|
123
|
+
int flags = (i == numMessageIndices - 1) ? LAST : 0;
|
124
|
+
|
125
|
+
/* Returning true will stop drainage short (such as when backpressure is too high) */
|
126
|
+
if (cb(s, outgoingMessage, (IteratorFlags)(flags | (i == 0 ? FIRST : 0)))) {
|
127
|
+
break;
|
128
|
+
}
|
129
|
+
}
|
130
|
+
}
|
131
|
+
|
132
|
+
void unlinkDrainableSubscriber(Subscriber *s) {
|
133
|
+
if (s->prev) {
|
134
|
+
s->prev->next = s->next;
|
135
|
+
}
|
136
|
+
if (s->next) {
|
137
|
+
s->next->prev = s->prev;
|
138
|
+
}
|
139
|
+
/* If we are the head, then we also need to reset the head */
|
140
|
+
if (drainableSubscribers == s) {
|
141
|
+
drainableSubscribers = s->next;
|
142
|
+
}
|
143
|
+
}
|
144
|
+
|
145
|
+
public:
|
146
|
+
|
147
|
+
TopicTree(std::function<bool(Subscriber *, T &, IteratorFlags)> cb) : cb(cb) {
|
148
|
+
|
149
|
+
}
|
150
|
+
|
151
|
+
/* Returns nullptr if not found */
|
152
|
+
Topic *lookupTopic(std::string_view topic) {
|
153
|
+
auto it = topics.find(topic);
|
154
|
+
if (it == topics.end()) {
|
155
|
+
return nullptr;
|
156
|
+
}
|
157
|
+
return it->second.get();
|
158
|
+
}
|
159
|
+
|
160
|
+
/* Subscribe fails if we already are subscribed */
|
161
|
+
Topic *subscribe(Subscriber *s, std::string_view topic) {
|
162
|
+
/* Notify user that they are doing something wrong here */
|
163
|
+
checkIteratingSubscriber(s);
|
164
|
+
|
165
|
+
/* Lookup or create new topic */
|
166
|
+
Topic *topicPtr = lookupTopic(topic);
|
167
|
+
if (!topicPtr) {
|
168
|
+
Topic *newTopic = new Topic(topic);
|
169
|
+
topics.insert({std::string_view(newTopic->name.data(), newTopic->name.length()), std::unique_ptr<Topic>(newTopic)});
|
170
|
+
topicPtr = newTopic;
|
171
|
+
}
|
172
|
+
|
173
|
+
/* Insert us in topic, insert topic in us */
|
174
|
+
auto [it, inserted] = s->topics.insert(topicPtr);
|
175
|
+
if (!inserted) {
|
176
|
+
return nullptr;
|
177
|
+
}
|
178
|
+
topicPtr->insert(s);
|
179
|
+
|
180
|
+
/* Success */
|
181
|
+
return topicPtr;
|
182
|
+
}
|
183
|
+
|
184
|
+
/* Returns ok, last, newCount */
|
185
|
+
std::tuple<bool, bool, int> unsubscribe(Subscriber *s, std::string_view topic) {
|
186
|
+
/* Notify user that they are doing something wrong here */
|
187
|
+
checkIteratingSubscriber(s);
|
188
|
+
|
189
|
+
/* Lookup topic */
|
190
|
+
Topic *topicPtr = lookupTopic(topic);
|
191
|
+
if (!topicPtr) {
|
192
|
+
/* If the topic doesn't exist we are assumed to still be subscribers of something */
|
193
|
+
return {false, false, -1};
|
194
|
+
}
|
195
|
+
|
196
|
+
/* Erase from our list first */
|
197
|
+
if (s->topics.erase(topicPtr) == 0) {
|
198
|
+
return {false, false, -1};
|
199
|
+
}
|
200
|
+
|
201
|
+
/* Remove us from topic */
|
202
|
+
topicPtr->erase(s);
|
203
|
+
|
204
|
+
int newCount = topicPtr->size();
|
205
|
+
|
206
|
+
/* If there is no subscriber to this topic, remove it */
|
207
|
+
if (!topicPtr->size()) {
|
208
|
+
/* Unique_ptr deletes the topic */
|
209
|
+
topics.erase(topic);
|
210
|
+
}
|
211
|
+
|
212
|
+
/* If we don't hold any topics we are to be freed altogether */
|
213
|
+
return {true, s->topics.size() == 0, newCount};
|
214
|
+
}
|
215
|
+
|
216
|
+
/* Factory function for creating a Subscriber */
|
217
|
+
Subscriber *createSubscriber() {
|
218
|
+
return new Subscriber();
|
219
|
+
}
|
220
|
+
|
221
|
+
/* This is used to end a Subscriber, before freeing it */
|
222
|
+
void freeSubscriber(Subscriber *s) {
|
223
|
+
|
224
|
+
/* I guess we call this one even if we are not subscribers */
|
225
|
+
if (!s) {
|
226
|
+
return;
|
227
|
+
}
|
228
|
+
|
229
|
+
/* For all topics, unsubscribe */
|
230
|
+
for (Topic *topicPtr : s->topics) {
|
231
|
+
/* If we are the last subscriber, simply remove the whole topic */
|
232
|
+
if (topicPtr->size() == 1) {
|
233
|
+
topics.erase(topicPtr->name);
|
234
|
+
} else {
|
235
|
+
/* Otherwise just remove us */
|
236
|
+
topicPtr->erase(s);
|
237
|
+
}
|
238
|
+
}
|
239
|
+
|
240
|
+
/* We also need to unlink us */
|
241
|
+
if (s->needsDrainage()) {
|
242
|
+
unlinkDrainableSubscriber(s);
|
243
|
+
}
|
244
|
+
|
245
|
+
delete s;
|
246
|
+
}
|
247
|
+
|
248
|
+
/* Mainly used by WebSocket::send to drain one socket before sending */
|
249
|
+
void drain(Subscriber *s) {
|
250
|
+
/* The list is undefined and cannot be touched unless needsDrainage(). */
|
251
|
+
if (s->needsDrainage()) {
|
252
|
+
/* This function differs from drainImpl by properly unlinking
|
253
|
+
* the subscriber from drainableSubscribers. drainImpl does not. */
|
254
|
+
unlinkDrainableSubscriber(s);
|
255
|
+
|
256
|
+
/* This one always resets needsDrainage before it calls any cb's.
|
257
|
+
* Otherwise we would stackoverflow when sending after publish but before drain. */
|
258
|
+
drainImpl(s);
|
259
|
+
|
260
|
+
/* If we drained last subscriber, also clear outgoingMessages */
|
261
|
+
if (!drainableSubscribers) {
|
262
|
+
outgoingMessages.clear();
|
263
|
+
}
|
264
|
+
}
|
265
|
+
}
|
266
|
+
|
267
|
+
/* Called everytime we call send, to drain published messages so to sync outgoing messages */
|
268
|
+
void drain() {
|
269
|
+
if (drainableSubscribers) {
|
270
|
+
/* Drain one socket a time */
|
271
|
+
for (Subscriber *s = drainableSubscribers; s; s = s->next) {
|
272
|
+
/* Instead of unlinking every single subscriber, we just leave the list undefined
|
273
|
+
* and reset drainableSubscribers ptr below. */
|
274
|
+
drainImpl(s);
|
275
|
+
}
|
276
|
+
/* Drain always clears drainableSubscribers and outgoingMessages */
|
277
|
+
drainableSubscribers = nullptr;
|
278
|
+
outgoingMessages.clear();
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
/* Big messages bypass all buffering and land directly in backpressure */
|
283
|
+
template <typename F>
|
284
|
+
bool publishBig(Subscriber *sender, std::string_view topic, B &&bigMessage, F cb) {
|
285
|
+
/* Do we even have this topic? */
|
286
|
+
auto it = topics.find(topic);
|
287
|
+
if (it == topics.end()) {
|
288
|
+
return false;
|
289
|
+
}
|
290
|
+
|
291
|
+
/* For all subscribers in topic */
|
292
|
+
for (Subscriber *s : *it->second) {
|
293
|
+
|
294
|
+
/* If we are sender then ignore us */
|
295
|
+
if (sender != s) {
|
296
|
+
cb(s, bigMessage);
|
297
|
+
}
|
298
|
+
}
|
299
|
+
|
300
|
+
return true;
|
301
|
+
}
|
302
|
+
|
303
|
+
/* Linear in number of affected subscribers */
|
304
|
+
bool publish(Subscriber *sender, std::string_view topic, T &&message) {
|
305
|
+
/* Do we even have this topic? */
|
306
|
+
auto it = topics.find(topic);
|
307
|
+
if (it == topics.end()) {
|
308
|
+
return false;
|
309
|
+
}
|
310
|
+
|
311
|
+
/* If we have more than 65k messages we need to drain every socket. */
|
312
|
+
if (outgoingMessages.size() == UINT16_MAX) {
|
313
|
+
/* If there is a socket that is currently corked, this will be ugly as all sockets will drain
|
314
|
+
* to their own backpressure */
|
315
|
+
drain();
|
316
|
+
}
|
317
|
+
|
318
|
+
/* If nobody references this message, don't buffer it */
|
319
|
+
bool referencedMessage = false;
|
320
|
+
|
321
|
+
/* For all subscribers in topic */
|
322
|
+
for (Subscriber *s : *it->second) {
|
323
|
+
|
324
|
+
/* If we are sender then ignore us */
|
325
|
+
if (sender != s) {
|
326
|
+
|
327
|
+
/* At least one subscriber wants this message */
|
328
|
+
referencedMessage = true;
|
329
|
+
|
330
|
+
/* If we already have too many outgoing messages on this subscriber, drain it now */
|
331
|
+
if (s->numMessageIndices == 32) {
|
332
|
+
/* This one does not need to check needsDrainage here but still does. */
|
333
|
+
drain(s);
|
334
|
+
}
|
335
|
+
|
336
|
+
/* Finally we can continue */
|
337
|
+
s->messageIndices[s->numMessageIndices++] = (uint16_t)outgoingMessages.size();
|
338
|
+
/* First message adds subscriber to list of drainable subscribers */
|
339
|
+
if (s->numMessageIndices == 1) {
|
340
|
+
/* Insert us in the head of drainable subscribers */
|
341
|
+
s->next = drainableSubscribers;
|
342
|
+
s->prev = nullptr;
|
343
|
+
if (s->next) {
|
344
|
+
s->next->prev = s;
|
345
|
+
}
|
346
|
+
drainableSubscribers = s;
|
347
|
+
}
|
348
|
+
}
|
349
|
+
}
|
350
|
+
|
351
|
+
/* Push this message and return with success */
|
352
|
+
if (referencedMessage) {
|
353
|
+
outgoingMessages.emplace_back(message);
|
354
|
+
}
|
355
|
+
|
356
|
+
/* Success if someone wants it */
|
357
|
+
return referencedMessage;
|
358
|
+
}
|
359
|
+
};
|
360
|
+
|
361
|
+
}
|
362
|
+
|
363
|
+
#endif
|