opal-up 0.0.4 → 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -23,65 +23,49 @@
23
23
  namespace uWS {
24
24
 
25
25
  struct BackPressure {
26
- std::string buffer;
27
- unsigned int pendingRemoval = 0;
28
- BackPressure(BackPressure &&other) {
29
- buffer = std::move(other.buffer);
30
- pendingRemoval = other.pendingRemoval;
31
- }
32
- BackPressure() = default;
33
- void append(const char *data, size_t length) {
34
- buffer.append(data, length);
35
- }
36
- void erase(unsigned int length) {
37
- pendingRemoval += length;
38
- /* Always erase a minimum of 1/32th the current backpressure */
39
- if (pendingRemoval > (buffer.length() >> 5)) {
40
- buffer.erase(0, pendingRemoval);
41
- pendingRemoval = 0;
42
- }
43
- }
44
- size_t length() {
45
- return buffer.length() - pendingRemoval;
46
- }
47
- void clear() {
48
- pendingRemoval = 0;
49
- buffer.clear();
50
- }
51
- void reserve(size_t length) {
52
- buffer.reserve(length + pendingRemoval);
53
- }
54
- void resize(size_t length) {
55
- buffer.resize(length + pendingRemoval);
56
- }
57
- const char *data() {
58
- return buffer.data() + pendingRemoval;
59
- }
60
- size_t size() {
61
- return length();
62
- }
63
- /* The total length, incuding pending removal */
64
- size_t totalLength() {
65
- return buffer.length();
26
+ std::string buffer;
27
+ unsigned int pendingRemoval = 0;
28
+ BackPressure(BackPressure &&other) {
29
+ buffer = std::move(other.buffer);
30
+ pendingRemoval = other.pendingRemoval;
31
+ }
32
+ BackPressure() = default;
33
+ void append(const char *data, size_t length) { buffer.append(data, length); }
34
+ void erase(unsigned int length) {
35
+ pendingRemoval += length;
36
+ /* Always erase a minimum of 1/32th the current backpressure */
37
+ if (pendingRemoval > (buffer.length() >> 5)) {
38
+ buffer.erase(0, pendingRemoval);
39
+ pendingRemoval = 0;
66
40
  }
41
+ }
42
+ size_t length() { return buffer.length() - pendingRemoval; }
43
+ void clear() {
44
+ pendingRemoval = 0;
45
+ buffer.clear();
46
+ }
47
+ void reserve(size_t length) { buffer.reserve(length + pendingRemoval); }
48
+ void resize(size_t length) { buffer.resize(length + pendingRemoval); }
49
+ const char *data() { return buffer.data() + pendingRemoval; }
50
+ size_t size() { return length(); }
51
+ /* The total length, incuding pending removal */
52
+ size_t totalLength() { return buffer.length(); }
67
53
  };
68
54
 
69
55
  /* Depending on how we want AsyncSocket to function, this will need to change */
70
56
 
71
- template <bool SSL>
72
- struct AsyncSocketData {
73
- /* This will do for now */
74
- BackPressure buffer;
57
+ template <bool SSL> struct AsyncSocketData {
58
+ /* This will do for now */
59
+ BackPressure buffer;
75
60
 
76
- /* Allow move constructing us */
77
- AsyncSocketData(BackPressure &&backpressure) : buffer(std::move(backpressure)) {
78
-
79
- }
61
+ /* Allow move constructing us */
62
+ AsyncSocketData(BackPressure &&backpressure)
63
+ : buffer(std::move(backpressure)) {}
80
64
 
81
- /* Or emppty */
82
- AsyncSocketData() = default;
65
+ /* Or emppty */
66
+ AsyncSocketData() = default;
83
67
  };
84
68
 
85
- }
69
+ } // namespace uWS
86
70
 
87
71
  #endif // UWS_ASYNCSOCKETDATA_H
@@ -21,63 +21,58 @@
21
21
  /* This filter has no false positives or collisions for the standard
22
22
  * and non-standard common request headers */
23
23
 
24
+ #include <bitset>
24
25
  #include <cstdint>
25
26
  #include <string_view>
26
- #include <bitset>
27
27
 
28
28
  namespace uWS {
29
29
 
30
30
  struct BloomFilter {
31
31
  private:
32
- std::bitset<256> filter;
33
- static inline uint32_t perfectHash(uint32_t features) {
34
- return features *= 1843993368;
35
- }
32
+ std::bitset<256> filter;
33
+ static inline uint32_t perfectHash(uint32_t features) {
34
+ return features *= 1843993368;
35
+ }
36
36
 
37
- union ScrambleArea {
38
- unsigned char p[4];
39
- uint32_t val;
40
- };
41
-
42
- ScrambleArea getFeatures(std::string_view key) {
43
- ScrambleArea s;
44
- s.p[0] = reinterpret_cast<const unsigned char&>(key[0]);
45
- s.p[1] = reinterpret_cast<const unsigned char&>(key[key.length() - 1]);
46
- s.p[2] = reinterpret_cast<const unsigned char&>(key[key.length() - 2]);
47
- s.p[3] = reinterpret_cast<const unsigned char&>(key[key.length() >> 1]);
48
- return s;
49
- }
37
+ union ScrambleArea {
38
+ unsigned char p[4];
39
+ uint32_t val;
40
+ };
41
+
42
+ ScrambleArea getFeatures(std::string_view key) {
43
+ ScrambleArea s;
44
+ s.p[0] = reinterpret_cast<const unsigned char &>(key[0]);
45
+ s.p[1] = reinterpret_cast<const unsigned char &>(key[key.length() - 1]);
46
+ s.p[2] = reinterpret_cast<const unsigned char &>(key[key.length() - 2]);
47
+ s.p[3] = reinterpret_cast<const unsigned char &>(key[key.length() >> 1]);
48
+ return s;
49
+ }
50
50
 
51
51
  public:
52
- bool mightHave(std::string_view key) {
53
- if (key.length() < 2) {
54
- return true;
55
- }
56
-
57
- ScrambleArea s = getFeatures(key);
58
- s.val = perfectHash(s.val);
59
- return filter[s.p[0]] &&
60
- filter[s.p[1]] &&
61
- filter[s.p[2]] &&
62
- filter[s.p[3]];
52
+ bool mightHave(std::string_view key) {
53
+ if (key.length() < 2) {
54
+ return true;
63
55
  }
64
56
 
65
- void add(std::string_view key) {
66
- if (key.length() >= 2) {
67
- ScrambleArea s = getFeatures(key);
68
- s.val = perfectHash(s.val);
69
- filter[s.p[0]] = 1;
70
- filter[s.p[1]] = 1;
71
- filter[s.p[2]] = 1;
72
- filter[s.p[3]] = 1;
73
- }
74
- }
57
+ ScrambleArea s = getFeatures(key);
58
+ s.val = perfectHash(s.val);
59
+ return filter[s.p[0]] && filter[s.p[1]] && filter[s.p[2]] && filter[s.p[3]];
60
+ }
75
61
 
76
- void reset() {
77
- filter.reset();
62
+ void add(std::string_view key) {
63
+ if (key.length() >= 2) {
64
+ ScrambleArea s = getFeatures(key);
65
+ s.val = perfectHash(s.val);
66
+ filter[s.p[0]] = 1;
67
+ filter[s.p[1]] = 1;
68
+ filter[s.p[2]] = 1;
69
+ filter[s.p[3]] = 1;
78
70
  }
71
+ }
72
+
73
+ void reset() { filter.reset(); }
79
74
  };
80
75
 
81
- }
76
+ } // namespace uWS
82
77
 
83
78
  #endif // UWS_BLOOMFILTER_H
@@ -20,217 +20,216 @@
20
20
 
21
21
  /* Independent chunked encoding parser, used by HttpParser. */
22
22
 
23
- #include <string>
24
- #include <cstring>
25
- #include <algorithm>
26
- #include <string_view>
27
23
  #include "MoveOnlyFunction.h"
24
+ #include <algorithm>
25
+ #include <cstring>
28
26
  #include <optional>
27
+ #include <string>
28
+ #include <string_view>
29
29
 
30
30
  namespace uWS {
31
31
 
32
- constexpr uint64_t STATE_HAS_SIZE = 1ull << (sizeof(uint64_t) * 8 - 1);//0x80000000;
33
- constexpr uint64_t STATE_IS_CHUNKED = 1ull << (sizeof(uint64_t) * 8 - 2);//0x40000000;
34
- constexpr uint64_t STATE_SIZE_MASK = ~(3ull << (sizeof(uint64_t) * 8 - 2));//0x3FFFFFFF;
35
- constexpr uint64_t STATE_IS_ERROR = ~0ull;//0xFFFFFFFF;
36
- constexpr uint64_t STATE_SIZE_OVERFLOW = 0x0Full << (sizeof(uint64_t) * 8 - 8);//0x0F000000;
32
+ constexpr uint64_t STATE_HAS_SIZE =
33
+ 1ull << (sizeof(uint64_t) * 8 - 1); // 0x80000000;
34
+ constexpr uint64_t STATE_IS_CHUNKED =
35
+ 1ull << (sizeof(uint64_t) * 8 - 2); // 0x40000000;
36
+ constexpr uint64_t STATE_SIZE_MASK =
37
+ ~(3ull << (sizeof(uint64_t) * 8 - 2)); // 0x3FFFFFFF;
38
+ constexpr uint64_t STATE_IS_ERROR = ~0ull; // 0xFFFFFFFF;
39
+ constexpr uint64_t STATE_SIZE_OVERFLOW =
40
+ 0x0Full << (sizeof(uint64_t) * 8 - 8); // 0x0F000000;
41
+
42
+ inline uint64_t chunkSize(uint64_t state) { return state & STATE_SIZE_MASK; }
43
+
44
+ /* Reads hex number until CR or out of data to consume. Updates state. Returns
45
+ * bytes consumed. */
46
+ inline void consumeHexNumber(std::string_view &data, uint64_t &state) {
47
+ /* Consume everything higher than 32 */
48
+ while (data.length() && data.data()[0] > 32) {
49
+
50
+ unsigned char digit = (unsigned char)data.data()[0];
51
+ if (digit >= 'a') {
52
+ digit = (unsigned char)(digit - ('a' - ':'));
53
+ } else if (digit >= 'A') {
54
+ digit = (unsigned char)(digit - ('A' - ':'));
55
+ }
56
+
57
+ unsigned int number = ((unsigned int)digit - (unsigned int)'0');
37
58
 
38
- inline uint64_t chunkSize(uint64_t state) {
39
- return state & STATE_SIZE_MASK;
59
+ if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
60
+ state = STATE_IS_ERROR;
61
+ return;
40
62
  }
41
63
 
42
- /* Reads hex number until CR or out of data to consume. Updates state. Returns bytes consumed. */
43
- inline void consumeHexNumber(std::string_view &data, uint64_t &state) {
44
- /* Consume everything higher than 32 */
45
- while (data.length() && data.data()[0] > 32) {
64
+ // extract state bits
65
+ uint64_t bits = /*state &*/ STATE_IS_CHUNKED;
66
+
67
+ state = (state & STATE_SIZE_MASK) * 16ull + number;
68
+
69
+ state |= bits;
70
+ data.remove_prefix(1);
71
+ }
72
+ /* Consume everything not /n */
73
+ while (data.length() && data.data()[0] != '\n') {
74
+ data.remove_prefix(1);
75
+ }
76
+ /* Now we stand on \n so consume it and enable size */
77
+ if (data.length()) {
78
+ state += 2; // include the two last /r/n
79
+ state |= STATE_HAS_SIZE | STATE_IS_CHUNKED;
80
+ data.remove_prefix(1);
81
+ }
82
+ }
46
83
 
47
- unsigned char digit = (unsigned char)data.data()[0];
48
- if (digit >= 'a') {
49
- digit = (unsigned char) (digit - ('a' - ':'));
50
- } else if (digit >= 'A') {
51
- digit = (unsigned char) (digit - ('A' - ':'));
52
- }
84
+ inline void decChunkSize(uint64_t &state, unsigned int by) {
53
85
 
54
- unsigned int number = ((unsigned int) digit - (unsigned int) '0');
86
+ // unsigned int bits = state & STATE_IS_CHUNKED;
55
87
 
56
- if (number > 16 || (chunkSize(state) & STATE_SIZE_OVERFLOW)) {
57
- state = STATE_IS_ERROR;
58
- return;
59
- }
88
+ state = (state & ~STATE_SIZE_MASK) | (chunkSize(state) - by);
60
89
 
61
- // extract state bits
62
- uint64_t bits = /*state &*/ STATE_IS_CHUNKED;
90
+ // state |= bits;
91
+ }
63
92
 
64
- state = (state & STATE_SIZE_MASK) * 16ull + number;
93
+ inline bool hasChunkSize(uint64_t state) { return state & STATE_HAS_SIZE; }
65
94
 
66
- state |= bits;
67
- data.remove_prefix(1);
68
- }
69
- /* Consume everything not /n */
70
- while (data.length() && data.data()[0] != '\n') {
71
- data.remove_prefix(1);
72
- }
73
- /* Now we stand on \n so consume it and enable size */
74
- if (data.length()) {
75
- state += 2; // include the two last /r/n
76
- state |= STATE_HAS_SIZE | STATE_IS_CHUNKED;
77
- data.remove_prefix(1);
78
- }
79
- }
95
+ /* Are we in the middle of parsing chunked encoding? */
96
+ inline bool isParsingChunkedEncoding(uint64_t state) {
97
+ return state & ~STATE_SIZE_MASK;
98
+ }
80
99
 
81
- inline void decChunkSize(uint64_t &state, unsigned int by) {
100
+ inline bool isParsingInvalidChunkedEncoding(uint64_t state) {
101
+ return state == STATE_IS_ERROR;
102
+ }
82
103
 
83
- //unsigned int bits = state & STATE_IS_CHUNKED;
104
+ /* Returns next chunk (empty or not), or if all data was consumed, nullopt is
105
+ * returned. */
106
+ static std::optional<std::string_view>
107
+ getNextChunk(std::string_view &data, uint64_t &state, bool trailer = false) {
84
108
 
85
- state = (state & ~STATE_SIZE_MASK) | (chunkSize(state) - by);
109
+ while (data.length()) {
86
110
 
87
- //state |= bits;
88
- }
111
+ // if in "drop trailer mode", just drop up to what we have as size
112
+ if (((state & STATE_IS_CHUNKED) == 0) && hasChunkSize(state) &&
113
+ chunkSize(state)) {
89
114
 
90
- inline bool hasChunkSize(uint64_t state) {
91
- return state & STATE_HAS_SIZE;
92
- }
115
+ // printf("Parsing trailer now\n");
93
116
 
94
- /* Are we in the middle of parsing chunked encoding? */
95
- inline bool isParsingChunkedEncoding(uint64_t state) {
96
- return state & ~STATE_SIZE_MASK;
97
- }
117
+ while (data.length() && chunkSize(state)) {
118
+ data.remove_prefix(1);
119
+ decChunkSize(state, 1);
98
120
 
99
- inline bool isParsingInvalidChunkedEncoding(uint64_t state) {
100
- return state == STATE_IS_ERROR;
101
- }
121
+ if (chunkSize(state) == 0) {
102
122
 
103
- /* Returns next chunk (empty or not), or if all data was consumed, nullopt is returned. */
104
- static std::optional<std::string_view> getNextChunk(std::string_view &data, uint64_t &state, bool trailer = false) {
105
-
106
- while (data.length()) {
107
-
108
- // if in "drop trailer mode", just drop up to what we have as size
109
- if (((state & STATE_IS_CHUNKED) == 0) && hasChunkSize(state) && chunkSize(state)) {
110
-
111
- //printf("Parsing trailer now\n");
112
-
113
- while(data.length() && chunkSize(state)) {
114
- data.remove_prefix(1);
115
- decChunkSize(state, 1);
116
-
117
- if (chunkSize(state) == 0) {
118
-
119
- /* This is an actual place where we need 0 as state */
120
- state = 0;
121
-
122
- /* The parser MUST stop consuming here */
123
- return std::nullopt;
124
- }
125
- }
126
- continue;
127
- }
128
-
129
- if (!hasChunkSize(state)) {
130
- consumeHexNumber(data, state);
131
- if (isParsingInvalidChunkedEncoding(state)) {
132
- return std::nullopt;
133
- }
134
- if (hasChunkSize(state) && chunkSize(state) == 2) {
135
-
136
- //printf("Setting state to trailer-parsing and emitting empty chunk\n");
137
-
138
- // set trailer state and increase size to 4
139
- if (trailer) {
140
- state = 4 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE;
141
- } else {
142
- state = 2 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE;
143
- }
144
-
145
- return std::string_view(nullptr, 0);
146
- }
147
- continue;
148
- }
149
-
150
- // do we have data to emit all?
151
- if (data.length() >= chunkSize(state)) {
152
- // emit all but 2 bytes then reset state to 0 and goto beginning
153
- // not fin
154
- std::string_view emitSoon;
155
- bool shouldEmit = false;
156
- if (chunkSize(state) > 2) {
157
- emitSoon = std::string_view(data.data(), chunkSize(state) - 2);
158
- shouldEmit = true;
159
- }
160
- data.remove_prefix(chunkSize(state));
161
- state = STATE_IS_CHUNKED;
162
- if (shouldEmit) {
163
- return emitSoon;
164
- }
165
- continue;
166
- } else {
167
- /* We will consume all our input data */
168
- std::string_view emitSoon;
169
- if (chunkSize(state) > 2) {
170
- uint64_t maximalAppEmit = chunkSize(state) - 2;
171
- if (data.length() > maximalAppEmit) {
172
- emitSoon = data.substr(0, maximalAppEmit);
173
- } else {
174
- //cb(data);
175
- emitSoon = data;
176
- }
177
- }
178
- decChunkSize(state, (unsigned int) data.length());
179
- state |= STATE_IS_CHUNKED;
180
- // new: decrease data by its size (bug)
181
- data.remove_prefix(data.length()); // ny bug fix för getNextChunk
182
- if (emitSoon.length()) {
183
- return emitSoon;
184
- } else {
185
- return std::nullopt;
186
- }
187
- }
188
- }
123
+ /* This is an actual place where we need 0 as state */
124
+ state = 0;
189
125
 
190
- return std::nullopt;
126
+ /* The parser MUST stop consuming here */
127
+ return std::nullopt;
128
+ }
129
+ }
130
+ continue;
191
131
  }
192
132
 
193
- /* This is really just a wrapper for convenience */
194
- struct ChunkIterator {
133
+ if (!hasChunkSize(state)) {
134
+ consumeHexNumber(data, state);
135
+ if (isParsingInvalidChunkedEncoding(state)) {
136
+ return std::nullopt;
137
+ }
138
+ if (hasChunkSize(state) && chunkSize(state) == 2) {
195
139
 
196
- std::string_view *data;
197
- std::optional<std::string_view> chunk;
198
- uint64_t *state;
199
- bool trailer;
140
+ // printf("Setting state to trailer-parsing and emitting empty
141
+ // chunk\n");
200
142
 
201
- ChunkIterator(std::string_view *data, uint64_t *state, bool trailer = false) : data(data), state(state), trailer(trailer) {
202
- chunk = uWS::getNextChunk(*data, *state, trailer);
143
+ // set trailer state and increase size to 4
144
+ if (trailer) {
145
+ state = 4 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE;
146
+ } else {
147
+ state = 2 /*| STATE_IS_CHUNKED*/ | STATE_HAS_SIZE;
203
148
  }
204
149
 
205
- ChunkIterator() {
150
+ return std::string_view(nullptr, 0);
151
+ }
152
+ continue;
153
+ }
206
154
 
155
+ // do we have data to emit all?
156
+ if (data.length() >= chunkSize(state)) {
157
+ // emit all but 2 bytes then reset state to 0 and goto beginning
158
+ // not fin
159
+ std::string_view emitSoon;
160
+ bool shouldEmit = false;
161
+ if (chunkSize(state) > 2) {
162
+ emitSoon = std::string_view(data.data(), chunkSize(state) - 2);
163
+ shouldEmit = true;
164
+ }
165
+ data.remove_prefix(chunkSize(state));
166
+ state = STATE_IS_CHUNKED;
167
+ if (shouldEmit) {
168
+ return emitSoon;
169
+ }
170
+ continue;
171
+ } else {
172
+ /* We will consume all our input data */
173
+ std::string_view emitSoon;
174
+ if (chunkSize(state) > 2) {
175
+ uint64_t maximalAppEmit = chunkSize(state) - 2;
176
+ if (data.length() > maximalAppEmit) {
177
+ emitSoon = data.substr(0, maximalAppEmit);
178
+ } else {
179
+ // cb(data);
180
+ emitSoon = data;
207
181
  }
182
+ }
183
+ decChunkSize(state, (unsigned int)data.length());
184
+ state |= STATE_IS_CHUNKED;
185
+ // new: decrease data by its size (bug)
186
+ data.remove_prefix(data.length()); // ny bug fix för getNextChunk
187
+ if (emitSoon.length()) {
188
+ return emitSoon;
189
+ } else {
190
+ return std::nullopt;
191
+ }
192
+ }
193
+ }
208
194
 
209
- ChunkIterator begin() {
210
- return *this;
211
- }
195
+ return std::nullopt;
196
+ }
212
197
 
213
- ChunkIterator end() {
214
- return ChunkIterator();
215
- }
198
+ /* This is really just a wrapper for convenience */
199
+ struct ChunkIterator {
216
200
 
217
- std::string_view operator*() {
218
- if (!chunk.has_value()) {
219
- std::abort();
220
- }
221
- return chunk.value();
222
- }
201
+ std::string_view *data;
202
+ std::optional<std::string_view> chunk;
203
+ uint64_t *state;
204
+ bool trailer;
223
205
 
224
- bool operator!=(const ChunkIterator &other) const {
225
- return other.chunk.has_value() != chunk.has_value();
226
- }
206
+ ChunkIterator(std::string_view *data, uint64_t *state, bool trailer = false)
207
+ : data(data), state(state), trailer(trailer) {
208
+ chunk = uWS::getNextChunk(*data, *state, trailer);
209
+ }
227
210
 
228
- ChunkIterator &operator++() {
229
- chunk = uWS::getNextChunk(*data, *state, trailer);
230
- return *this;
231
- }
211
+ ChunkIterator() {}
232
212
 
233
- };
234
- }
213
+ ChunkIterator begin() { return *this; }
214
+
215
+ ChunkIterator end() { return ChunkIterator(); }
216
+
217
+ std::string_view operator*() {
218
+ if (!chunk.has_value()) {
219
+ std::abort();
220
+ }
221
+ return chunk.value();
222
+ }
223
+
224
+ bool operator!=(const ChunkIterator &other) const {
225
+ return other.chunk.has_value() != chunk.has_value();
226
+ }
227
+
228
+ ChunkIterator &operator++() {
229
+ chunk = uWS::getNextChunk(*data, *state, trailer);
230
+ return *this;
231
+ }
232
+ };
233
+ } // namespace uWS
235
234
 
236
235
  #endif // UWS_CHUNKEDENCODING_H
@@ -4,33 +4,30 @@
4
4
 
5
5
  namespace uWS {
6
6
 
7
- struct WebSocketClientBehavior {
8
- MoveOnlyFunction<void()> open;
9
- MoveOnlyFunction<void()> message;
10
- MoveOnlyFunction<void()> close;
11
- //MoveOnlyFunction<void()> failed;
7
+ struct WebSocketClientBehavior {
8
+ MoveOnlyFunction<void()> open;
9
+ MoveOnlyFunction<void()> message;
10
+ MoveOnlyFunction<void()> close;
11
+ // MoveOnlyFunction<void()> failed;
12
+ };
12
13
 
13
- };
14
+ struct ClientApp {
14
15
 
15
- struct ClientApp {
16
+ WebSocketContext<0, false, int> *webSocketContext;
17
+ // behöver ett nytt http context med minimal klient, som slår om till den
18
+ // riktiga websocketcontext om samma storlek på httpsocket och websocket blir
19
+ // det enkel övergång
16
20
 
17
- WebSocketContext<0, false, int> *webSocketContext;
18
- // behöver ett nytt http context med minimal klient, som slår om till den riktiga websocketcontext
19
- // om samma storlek på httpsocket och websocket blir det enkel övergång
21
+ ClientApp(WebSocketClientBehavior &&behavior) {
22
+ // webSocketContext = WebSocketContext<0, false, int>::create();
23
+ }
20
24
 
21
- ClientApp(WebSocketClientBehavior &&behavior) {
22
- //webSocketContext = WebSocketContext<0, false, int>::create();
23
- }
25
+ ClientApp &&connect(std::string url, std::string protocol = "") {
24
26
 
25
- ClientApp &&connect(std::string url, std::string protocol = "") {
27
+ return std::move(*this);
28
+ }
26
29
 
27
- return std::move(*this);
28
- }
30
+ void run() {}
31
+ };
29
32
 
30
- void run() {
31
-
32
- }
33
-
34
- };
35
-
36
- }
33
+ } // namespace uWS