uws-react-native 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -33,7 +33,7 @@ This repository does also contain the [example](https://github.com/RakaDoank/uws
33
33
 
34
34
  ## Compatibility
35
35
 
36
- `uws-react-native` is compatible only compatible for new architecture of React Native.
36
+ `uws-react-native` is only compatible for new architecture of React Native.
37
37
 
38
38
  ### Platform
39
39
 
@@ -53,6 +53,8 @@ There are some new APIs in uws-react-native. The most reason is because of the d
53
53
  - `onFullData`. This is an equivalent of `res.collectBody` from uWebSockets.js, but to set the max size, provide it through route method handler options
54
54
  - `onFullDataText`. Same as the `onFullData`, but provides the body data in raw text instead of ArrayBuffer. This is useful if you are using React Native 0.84 version or older because TextDecoder is natively supported only in React Native 0.85 version
55
55
 
56
+ ---
57
+
56
58
  ## Development & Research
57
59
 
58
60
  uws-react-native is still in heavy development and research. All the uWebSockets instances are not fully implemented yet.
@@ -73,7 +75,11 @@ Intentionally, we make the uWebSockets runs in another thread, therefore we have
73
75
 
74
76
  We have another issue because of the uWebSockets runs in another thread. From the JSI C++ side, we have to assume any JS function as a callback especially the route method handler is asynchronous. We cannot make a sync call to the JS function from an arbitrary thread to the JS thread, and it makes JS call to the uWebSockets runner is also late.
75
77
 
76
- For this case especially, we have to predefined two instances in the C++ uWebSockets route method handler because of the late call, and the late call would not be tolerated by the uWebSockets internally for some instances
78
+ There some topics you may to read regarding this threading research
79
+
80
+ #### Predefined Instances
81
+
82
+ For the threading case especially, we have to predefined two instances in the C++ uWebSockets route method handler because of the late call, and the late call would not be tolerated by the uWebSockets internally for some cases
77
83
 
78
84
  - `res.onAborted`
79
85
 
@@ -82,3 +88,9 @@ We have to predefined the `onAborted` callback in C++ side and prevent `res.end`
82
88
  - `res.onDataV2`
83
89
 
84
90
  For the `onData` and `onDataV2`, we have predefined it with a single `onDataV2` callback in C++ side. If this method is not predefined, uWebSockets will not collect any incoming body data. If a route method handler is sure that it doesn't expect any body data at all, a route method handler can disable the body reading through the third argument in `any`, `get`, `post`, and other route methods.
91
+
92
+ #### About Worker Thread
93
+
94
+ In theory, we can create another JavaScript runtime with [react-native-worklets](https://docs.swmansion.com/react-native-worklets/) and tie with our uWebSockets runner. It would solve a lot of late communication problem between uWebSockets runner and JavaScript thread that we embrace right now, but it also introduces new major issue, which is developer experience. It sounds like not a big problem, but it may bigger than you think.
95
+
96
+ Think of this sample case, you want to use uws-react-native server for a simple CRUD with a local database in an app. You probably know SQLite can be used in Android, iOS, macOS, even Windows app, with your own adapter or a known library that support SQLite integration in React Native such as [op-sqlite](https://github.com/OP-Engineering/op-sqlite), and [Expo SQLite](https://docs.expo.dev/versions/latest/sdk/sqlite). If you want to use that known library, this case would not works at all, because op-sqlite and/or Expo SQLite is tied to the default JavaScript runtime. Even, you cannot use any React Native non-JS-only libraries in arbitrary JavaScript thread that has created by react-native-worklets if the library you want to use is using the default JavaScript runtime. If you really want to achieve the goal of this case, you have to create your own library for the SQLite database by yourself and tie-up with the JavaScript runtime. Probably, in the future we would still provide that worker thread with react-native-worklets.
@@ -33,9 +33,9 @@ public:
33
33
  std::for_each(req->begin(),
34
34
  req->end(),
35
35
  [&rt_1, callback_ = std::move(callback)](const auto &item) {
36
- callback_.call(rt_1,
37
- facebook::jsi::String::createFromUtf8(rt_1, std::string(item.first)),
38
- facebook::jsi::String::createFromUtf8(rt_1, std::string(item.second)));
36
+ callback_.call(rt_1,
37
+ facebook::jsi::String::createFromAscii(rt_1, std::string(item.first)),
38
+ facebook::jsi::String::createFromAscii(rt_1, std::string(item.second)));
39
39
  });
40
40
  return facebook::jsi::Value::undefined();
41
41
  }));
@@ -8,10 +8,10 @@
8
8
 
9
9
  namespace uws_react_native {
10
10
 
11
- struct HttpResponseObjectOptions {
12
- bool disableBodyRead;
13
- unsigned long maxBodySize;
14
- };
11
+ // struct HttpResponseObjectOptions {
12
+ // bool disableBodyRead;
13
+ // unsigned long maxBodySize;
14
+ // };
15
15
 
16
16
  class HttpResponseObject : public facebook::jsi::Object {
17
17
 
@@ -52,63 +52,6 @@ private:
52
52
  bool isStopCollecting = false;
53
53
  } OnDataV2Assignee;
54
54
 
55
- void setChunk(std::string_view chunk,
56
- unsigned long maxRemainingBodyLength) {
57
- if(!this->OnDataV2Assignee.buffer) {
58
- this->OnDataV2Assignee.buffer = std::make_shared<std::vector<char>>();
59
- this->OnDataV2Assignee.buffer->reserve(maxRemainingBodyLength + chunk.size()); // preallocate with hint
60
- }
61
- this->OnDataV2Assignee.buffer->insert(this->OnDataV2Assignee.buffer->end(), chunk.begin(), chunk.end());
62
- // this->OnDataV2Assignee.chunk.append(chunk.data(), chunk.size());
63
- this->OnDataV2Assignee.maxRemainingBodyLength = maxRemainingBodyLength;
64
- }
65
-
66
- /**
67
- * Probably bad name,
68
- * it's used either for "onData", "onDataV2", or "onFullData", and combined for "onFullDataText"
69
- */
70
- void invokeOnDataHandler() {
71
- if(
72
- !this->OnDataV2Assignee.isCallbackForFullChunk ||
73
- this->OnDataV2Assignee.maxRemainingBodyLength == 0
74
- ) {
75
- /// HELP me the better way to pass JSI ArrayBuffer here.
76
- /// with faster buffer or anything
77
-
78
- /// I have tested that,
79
- /// when we captured the buffer by reference into the lambda,
80
- /// in the middle of stream, it is often giving inaccuracy of
81
- /// JS ArrayBuffer.byteLength in the `onDataV2` argument
82
- /// when it's compared to the maxRemainingBodyLength differentiation that captured by value.
83
- /// It's still accurate when it's finished.
84
-
85
- /// While capturing the buffer by value is often accurate.
86
- /// but is it slower?
87
-
88
- if(this->OnDataV2Assignee.callback) {
89
- this->OnDataV2Assignee.callback
90
- ->callWithPriority(facebook::react::SchedulerPriority::ImmediatePriority,
91
- [buffer = this->OnDataV2Assignee.buffer, maxRemainingBodyLength = this->OnDataV2Assignee.maxRemainingBodyLength](facebook::jsi::Runtime &rt, facebook::jsi::Function &cb) {
92
- auto mutableBuffer = CharsMutableBuffer(buffer);
93
-
94
- cb.call(rt,
95
- facebook::jsi::ArrayBuffer(rt, std::make_shared<CharsMutableBuffer>(std::move(mutableBuffer))),
96
- facebook::jsi::BigInt::fromUint64(rt, maxRemainingBodyLength));
97
- });
98
- }
99
-
100
- if(this->OnDataV2Assignee.callbackStr) {
101
- this->OnDataV2Assignee.callbackStr
102
- ->callWithPriority(facebook::react::SchedulerPriority::ImmediatePriority,
103
- [buffer = this->OnDataV2Assignee.buffer, maxRemainingBodyLength = this->OnDataV2Assignee.maxRemainingBodyLength](facebook::jsi::Runtime &rt, facebook::jsi::Function &cb) {
104
- cb.call(rt,
105
- std::string(buffer->begin(), buffer->end()),
106
- facebook::jsi::BigInt::fromUint64(rt, maxRemainingBodyLength));
107
- });
108
- }
109
- }
110
- }
111
-
112
55
  // void preEnd(facebook::jsi::Runtime &rt) const {
113
56
  // if(this->OnAbortedAssignee.alreadyAborted) {
114
57
  // /// Stated from uWebSockets
@@ -124,8 +67,8 @@ private:
124
67
  public:
125
68
  HttpResponseObject(facebook::jsi::Runtime &rt,
126
69
  uWS::HttpResponse<false> *res,
127
- std::shared_ptr<facebook::react::CallInvoker> &jsInvoker,
128
- std::optional<HttpResponseObjectOptions> &&options = HttpResponseObjectOptions{ .disableBodyRead = false, .maxBodySize = 0 }) : facebook::jsi::Object(rt) {
70
+ std::shared_ptr<facebook::react::CallInvoker> &jsInvoker
71
+ /* std::optional<HttpResponseObjectOptions> &&options = HttpResponseObjectOptions{ .disableBodyRead = false, .maxBodySize = 0 } */) : facebook::jsi::Object(rt) {
129
72
 
130
73
  this->setProperty(rt,
131
74
  "close",
@@ -377,7 +320,6 @@ public:
377
320
  this->OnDataV2Assignee.callback
378
321
  ->callWithPriority(facebook::react::SchedulerPriority::ImmediatePriority,
379
322
  [this](facebook::jsi::Runtime &rt, facebook::jsi::Function &cb) {
380
- // auto stringMutableBuffer = facebook::jsi::StringMutableBuffer(&this->OnDataV2Assignee.chunk);
381
323
  auto mutableBuffer = CharsMutableBuffer(this->OnDataV2Assignee.buffer.get());
382
324
  cb.call(rt,
383
325
  facebook::jsi::ArrayBuffer(rt, std::make_shared<CharsMutableBuffer>(std::move(mutableBuffer))));
@@ -558,65 +500,83 @@ public:
558
500
  return {rt_1, thisValue};
559
501
  }));
560
502
 
561
- /// We have to make JS call asynchronously because the uWebSockets app run at different thread.
562
- /// See the `uws_react_native::AppRunner`, and `facebook::react::AsyncCallback`.
563
- /// So this predefined `onAborted` assignment below is to tell that
564
- /// uWebSockets has to wait until JS call finished with the `res.end() or res.tryEnd()`.
565
- ///
566
- /// Stated from uWebSockets
567
- /// `Returning from a request handler without responding or attaching an onAborted handler is ill-use`
568
- ///
569
- /// I thought `onAborted` is just a callback or event listener.
570
- res->onAborted([this]() {
571
- this->OnAbortedAssignee.alreadyAborted = true;
572
-
573
- if(this->OnAbortedAssignee.callback) {
574
- this->OnAbortedAssignee.callback->call(facebook::jsi::Value::undefined());
575
- }
576
- });
577
-
578
- /// Sadly, we can't do late assignment to the onDataV2 and onData.
579
- /// uWebSockets will do nothing to our handler if we assign the lambda so late.
580
- /// So we have to predefined onDataV2 handler here, and save the chunk.
581
- if(!options->disableBodyRead) {
582
- res->onDataV2([this, maxBodySize = options->maxBodySize](auto chunk, auto maxRemainingBodyLength) {
583
- if(this->OnDataV2Assignee.isStopCollecting) {
584
- return;
585
- }
586
-
587
- if(maxBodySize > 0) {
588
- auto chunkSize = chunk.size();
589
- auto currentChunkSize = this->OnDataV2Assignee.buffer ? this->OnDataV2Assignee.buffer->size() : 0;
590
-
591
- /// First and possibly only chunk
592
- if(currentChunkSize == 0 && chunkSize > maxBodySize) {
593
- this->OnDataV2Assignee.isStopCollecting = true;
594
- /// set the first chunk
595
- this->setChunk(chunk, maxRemainingBodyLength);
596
- this->invokeOnDataHandler();
597
- return;
598
- }
599
-
600
- /// subsequent chunks overflow
601
- if(currentChunkSize > 0 && chunkSize > maxBodySize - currentChunkSize) {
602
- /// tell to JS that we already stop collecting chunk
603
- /// and invoke the JSI onData / onDataText / onDataV2 / onFullData / onFullDataText handler immediately
604
- this->OnDataV2Assignee.isStopCollecting = true;
605
- this->invokeOnDataHandler();
606
- return;
607
- }
608
- }
609
-
610
- this->setChunk(chunk, maxRemainingBodyLength);
611
- this->invokeOnDataHandler();
612
-
613
- /// About the invokeOnDataHandler,
614
- /// JS call may late
615
- /// it will invokes the handler once when user pass the handler.
616
- });
503
+ } // HttpResponseObject
504
+
505
+ void invokeOnAbortedHandler() {
506
+ this->OnAbortedAssignee.alreadyAborted = true;
507
+
508
+ if(this->OnAbortedAssignee.callback) {
509
+ this->OnAbortedAssignee.callback->call(facebook::jsi::Value::undefined());
617
510
  }
511
+ }
618
512
 
619
- } // HttpResponseObject
513
+ bool isStopCollectingData() const {
514
+ return this->OnDataV2Assignee.isStopCollecting;
515
+ }
516
+
517
+ void stopCollectingData() {
518
+ this->OnDataV2Assignee.isStopCollecting = true;
519
+ }
520
+
521
+ void updateBuffer(std::string_view chunk,
522
+ unsigned long maxRemainingBodyLength) {
523
+ if(!this->OnDataV2Assignee.buffer) {
524
+ this->OnDataV2Assignee.buffer = std::make_shared<std::vector<char>>();
525
+ this->OnDataV2Assignee.buffer->reserve(maxRemainingBodyLength + chunk.size()); // preallocate with hint
526
+ }
527
+ this->OnDataV2Assignee.buffer->insert(this->OnDataV2Assignee.buffer->end(), chunk.begin(), chunk.end());
528
+ this->OnDataV2Assignee.maxRemainingBodyLength = maxRemainingBodyLength;
529
+ }
530
+
531
+ size_t getBufferSize() const {
532
+ return this->OnDataV2Assignee.buffer->size();
533
+ }
534
+
535
+ /**
536
+ * Probably bad name,
537
+ * it's used either for "onData", "onDataV2", or "onFullData", and combined for "onFullDataText"
538
+ */
539
+ void invokeOnDataHandler() {
540
+ if(
541
+ !this->OnDataV2Assignee.isCallbackForFullChunk ||
542
+ this->OnDataV2Assignee.maxRemainingBodyLength == 0
543
+ ) {
544
+ /// HELP me the better way to pass JSI ArrayBuffer here.
545
+ /// with faster buffer or anything
546
+
547
+ /// I have tested that,
548
+ /// when we captured the buffer by reference into the lambda,
549
+ /// in the middle of stream, it is often giving inaccuracy of
550
+ /// JS ArrayBuffer.byteLength in the `onDataV2` argument
551
+ /// when it's compared to the maxRemainingBodyLength differentiation that captured by value.
552
+ /// It's still accurate when it's finished.
553
+
554
+ /// While capturing the buffer by value is often accurate.
555
+ /// but is it slower?
556
+
557
+ if(this->OnDataV2Assignee.callback) {
558
+ this->OnDataV2Assignee.callback
559
+ ->callWithPriority(facebook::react::SchedulerPriority::ImmediatePriority,
560
+ [buffer = this->OnDataV2Assignee.buffer, maxRemainingBodyLength = this->OnDataV2Assignee.maxRemainingBodyLength](facebook::jsi::Runtime &rt, facebook::jsi::Function &cb) {
561
+ auto mutableBuffer = CharsMutableBuffer(buffer);
562
+
563
+ cb.call(rt,
564
+ facebook::jsi::ArrayBuffer(rt, std::make_shared<CharsMutableBuffer>(std::move(mutableBuffer))),
565
+ facebook::jsi::BigInt::fromUint64(rt, maxRemainingBodyLength));
566
+ });
567
+ }
568
+
569
+ if(this->OnDataV2Assignee.callbackStr) {
570
+ this->OnDataV2Assignee.callbackStr
571
+ ->callWithPriority(facebook::react::SchedulerPriority::ImmediatePriority,
572
+ [buffer = this->OnDataV2Assignee.buffer, maxRemainingBodyLength = this->OnDataV2Assignee.maxRemainingBodyLength](facebook::jsi::Runtime &rt, facebook::jsi::Function &cb) {
573
+ cb.call(rt,
574
+ std::string(buffer->begin(), buffer->end()),
575
+ facebook::jsi::BigInt::fromUint64(rt, maxRemainingBodyLength));
576
+ });
577
+ }
578
+ }
579
+ }
620
580
 
621
581
  };
622
582
 
@@ -90,11 +90,10 @@ private:
90
90
  }
91
91
  }
92
92
 
93
- std::function<void (uWS::HttpResponse<false> *res, uWS::HttpRequest *req)> uwsRouteHandler = [pattern, disableBodyRead, maxBodySize, &rt, &jsInvoker, asyncCallback = facebook::react::AsyncCallback(rt, std::move(callback), jsInvoker)](uWS::HttpResponse<false> *res, uWS::HttpRequest *req) {
93
+ std::function<void (uWS::HttpResponse<false> *res, uWS::HttpRequest *req)> uwsRouteHandler = [disableBodyRead, maxBodySize, &rt, &jsInvoker, asyncCallback = facebook::react::AsyncCallback(rt, std::move(callback), jsInvoker)](uWS::HttpResponse<false> *res, uWS::HttpRequest *req) {
94
94
  auto httpResponseObject = std::make_shared<HttpResponseObject>(rt,
95
95
  res,
96
- jsInvoker,
97
- HttpResponseObjectOptions{ .disableBodyRead = disableBodyRead, .maxBodySize = maxBodySize });
96
+ jsInvoker);
98
97
 
99
98
  auto httpRequestObject = std::make_shared<HttpRequestObject>(rt, req);
100
99
 
@@ -103,6 +102,62 @@ private:
103
102
  *httpResponseObject,
104
103
  *httpRequestObject);
105
104
  });
105
+
106
+ /// We have to make JS call asynchronously because the uWebSockets app run at different thread.
107
+ /// See the `react_native_uws::AppRunner`, and `facebook::react::AsyncCallback`.
108
+ /// So this predefined `onAborted` assignment below is to tell that
109
+ /// uWebSockets has to wait until JS call finished with the `res.end() or res.tryEnd()`.
110
+ ///
111
+ /// Stated from uWebSockets
112
+ /// `Returning from a request handler without responding or attaching an onAborted handler is ill-use`
113
+ ///
114
+ /// I thought `onAborted` is just a callback or event listener.
115
+ res->onAborted([httpResponseObject]() {
116
+ httpResponseObject->invokeOnAbortedHandler();
117
+ });
118
+
119
+ /// Sadly, we can't do late assignment to the onDataV2 and onData.
120
+ /// uWebSockets will do nothing to our handler if we assign the lambda so late.
121
+ /// So we have to predefined onDataV2 handler here, and save the chunk.
122
+ if(!disableBodyRead) {
123
+ res->onDataV2([httpResponseObject, maxBodySize](auto chunk, auto maxRemainingBodyLength) {
124
+ if(httpResponseObject->isStopCollectingData()) {
125
+ return;
126
+ }
127
+
128
+ if(maxBodySize > 0) {
129
+ auto chunkSize = chunk.size();
130
+ auto currentChunkSize = httpResponseObject->getBufferSize();
131
+
132
+ /// First and possibly only chunk
133
+ if(currentChunkSize == 0 && chunkSize > maxBodySize) {
134
+ httpResponseObject->stopCollectingData();
135
+ /// set the first chunk
136
+ httpResponseObject->updateBuffer(chunk, maxRemainingBodyLength);
137
+ httpResponseObject->invokeOnDataHandler();
138
+ /// Don't worry,
139
+ /// JS call may late
140
+ /// it will invokes the handler once when user pass the handler.
141
+ return;
142
+ }
143
+
144
+ /// subsequent chunks overflow
145
+ if(currentChunkSize > 0 && chunkSize > maxBodySize - currentChunkSize) {
146
+ /// tell to JS that we already stop collecting chunk
147
+ /// and invoke the JSI onData / onDataText / onDataV2 / onFullData / onFullDataText handler immediately
148
+ httpResponseObject->stopCollectingData();
149
+ httpResponseObject->invokeOnDataHandler();
150
+ /// Don't worry,
151
+ /// JS call may late
152
+ /// it will invokes the handler once when user pass the handler.
153
+ return;
154
+ }
155
+ }
156
+
157
+ httpResponseObject->updateBuffer(chunk, maxRemainingBodyLength);
158
+ httpResponseObject->invokeOnDataHandler();
159
+ });
160
+ }
106
161
  };
107
162
 
108
163
  if(method == UwsRouteMethod::ANY) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uws-react-native",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "The port of uWebSockets library for React Native",
5
5
  "license": "MIT",
6
6
  "author": "RakaDoank <rakaaudira@gmail.com> (https://github.com/RakaDoank)",