Skip to content

Commit ad90a44

Browse files
committed
Merge branch 'dev-0.10'
2 parents 88e996e + e2a5384 commit ad90a44

File tree

21 files changed

+810
-313
lines changed

21 files changed

+810
-313
lines changed

.nvmrc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
v18
1+
v20.10.0

README.md

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ and [old][Old Architecture] RN architectures.
6666
- [Enabling Alias module]
6767
- [Enabling Rewrite module]
6868
- [Enabling WebDAV module]
69+
- [Connecting to an Active Server in the Native Layer]
6970
- [API Reference](#api-reference)
7071
- [Server] — Represents a server instance.
7172
- [constructor()] — Creates a new [Server] instance.
@@ -89,7 +90,9 @@ and [old][Old Architecture] RN architectures.
8990
- [extractBundledAssets()] — Extracts bundled assets into a regular folder
9091
(Android-specific).
9192
- [getActiveServer()] — Gets currently active, starting, or stopping
92-
server instance, if any.
93+
server instance, if any, according to the TS layer data.
94+
- [getActiveServerId()] — Gets ID of the currently active, starting, or
95+
stopping server instance, if any, according to the Native layer data.
9396
- [resolveAssetsPath()] — Resolves relative paths for bundled assets.
9497
- [ERROR_LOG_FILE] — Location of the error log file.
9598
- [STATES] — Enumerates possible states of [Server] instance.
@@ -485,6 +488,50 @@ routes when you create [Server] instance, using `extraConfig` option.
485488
`,
486489
```
487490
491+
### Connecting to an Active Server in the Native Layer
492+
[Connecting to an Active Server in the Native Layer]: #connecting-to-an-active-server-in-the-native-layer
493+
494+
When this library is used the regular way, the [Lighttpd] server in the native
495+
layer is launched when the [.start()] method of a [Server] instance is triggered
496+
on the JavaScript (TypeScript) side, and the native server is terminated when
497+
the [.stop()] method is called on the JS side. In the JS layer we hold most of
498+
the server-related information (`hostname`, `port`, `fileDir`, _etc._),
499+
and take care of the high-level server control (_i.e._ the optional
500+
pause / resume of the server when the app enters background / foreground).
501+
If JS engine is restarted (or just related JS modules are reloaded) the regular
502+
course of action is to explictly terminate the active server just before it,
503+
and to re-create, and re-launch it afterwards. If it is not done, the [Lighttpd]
504+
server will remain active in the native layer across the JS engine restart,
505+
and it won't be possible to launch a new server instance after the restart,
506+
as the library only supports at most one active [Lighttpd] server, and it
507+
throws an error if the server launch command arrives to the native layer while
508+
[Lighttpd] server is already active.
509+
510+
However, in response to
511+
[the ticket #95](https://github.com/birdofpreyru/react-native-static-server/issues/95)
512+
we provide a way to reuse an active native server across JS engine restarts,
513+
without restarting the server. To do so you:
514+
- Use [getActiveServerId()] method to check whether the native server is active
515+
(if so, this method resolves to a non-_null_ ID).
516+
- Create a new [Server] instance passing into its [constructor()] that server ID
517+
as the `id` option, and [STATES]`.ACTIVE` as the `state` option. These options
518+
(usually omitted when creating a regular [Server] instance) ensure that
519+
the created [Server] instance is able to communicate with the already running
520+
native server, and to correctly handle subsequent [.stop()] and [.start()]
521+
calls. Beside these, it is up-to-you to set all other options to the values
522+
you need (_i.e._ setting `id`, and `state` just «connects»
523+
the newly created [Server] instance to the active native server, but it
524+
does not restore any other information about the server — you should
525+
restore or redefine it the way you see fit).
526+
527+
Note, this way it is possible to create multiple [Server] instances connected
528+
to the same active native server. As they have the same `id`, they all will
529+
represent the same server, thus calling [.stop()] and [.start()] commands
530+
on any of them will operate the same server, and update the states of all
531+
these JS server instances, without triggering the error related to
532+
the «at most one active server a time» (though, it has not been
533+
carefully tested yet).
534+
488535
## API Reference
489536
### Server
490537
[Server]: #server
@@ -551,6 +598,12 @@ within `options` argument:
551598
special `hostname` values to ask the library to automatically select
552599
appropriate non-local address._
553600
601+
- `id` — **number** — Optional. Allows to enforce a specific ID,
602+
used to communicate with the server instance within the Native layer, thus
603+
it allows to re-connect to an existing native server instance.
604+
See «[Connecting to an Active Server in the Native Layer]»
605+
for details. By default, an `id` is selected by the library.
606+
554607
- `nonLocal` — **boolean** — Optional. By default, if `hostname`
555608
option was not provided, the server starts at the "`127.0.0.1`" (loopback)
556609
address, and it is only accessible within the host app.
@@ -565,6 +618,15 @@ within `options` argument:
565618
- `port` — **number** — Optional. The port at which to start the server.
566619
If 0 (default) an available port will be automatically selected.
567620
621+
- `state` — [STATES] — Optional. Allows to enforce the initial
622+
server state value, which is necessary [when connecting to an existing
623+
native server instance][Connecting to an Active Server in the Native Layer].
624+
Note, it only influence the logic behind subsequent [.start()] and [.stop()]
625+
calls, _i.e._ the constructor does not attempt to put the server in this
626+
state, nor does it check the value is consistent with the active server,
627+
if any, in the native layer. By default, the state is initialized
628+
to `STATES.INACTIVE`.
629+
568630
- `stopInBackground` — **boolean** — Optional.
569631
570632
By default, the server continues to work as usual when its host app enters
@@ -795,17 +857,43 @@ This is an Android-specific function; it does nothing on other platforms.
795857
796858
### getActiveServer()
797859
[getActiveServer()]: #getactiveserver
798-
```js
860+
```ts
799861
import {getActiveServer} from '@dr.pogodin/react-native-static-server';
800862
801-
getActiveServer(): Server;
863+
getActiveServer(): Server | undefined;
802864
```
803865
Returns currently active, starting, or stopping [Server] instance, if any exist
804866
in the app. It does not return, however, any inactive server instance which has
805867
been stopped automatically because of `stopInBackground` option, when the app
806868
entered background, and might be automatically started in future if the app
807869
enters foreground again prior to an explicit [.stop()] call for that instance.
808870
871+
**NOTE:** The result of this function is based on the TypeScript layer data
872+
(that's why it is synchronous), in contrast to the [getActiveServerId()]
873+
function below, which calls into the Native layer, and returns ID of the active
874+
server based on that.
875+
876+
### getActiveServerId()
877+
[getActiveServerId()]: #getactiveserverid
878+
```ts
879+
import {getActiveServerId} from '@dr.pogodin/react-native-static-server';
880+
881+
getActiveServerId(): Promise<number | null>;
882+
```
883+
Returns ID of the currently active, starting, or stopping server instance,
884+
if any exist in the Native layer.
885+
886+
This function is provided in response to
887+
[the ticket #95](https://github.com/birdofpreyru/react-native-static-server/issues/95),
888+
to allow &laquo;[Connecting to an Active Server in the Native Layer]&raquo;.
889+
The ID returned by this function can be passed in into [Server] instance
890+
[constructor()] to create server instance communicating to the existing native
891+
layer server.
892+
893+
**NOTE:** It is different from [getActiveServer()] function above, which
894+
returns the acurrently active, starting, or stopping [Server] instance based on
895+
TypeScript layer data.
896+
809897
### resolveAssetsPath()
810898
[resolveAssetsPath()]: #resolveassetspath
811899
```ts

android/src/main/java/com/drpogodin/reactnativestaticserver/ReactNativeStaticServerModule.kt

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ class ReactNativeStaticServerModule internal constructor(context: ReactApplicati
3434
return constants
3535
}
3636

37+
@ReactMethod
38+
override fun getActiveServerId(promise: Promise) {
39+
promise.resolve(server?.id)
40+
}
41+
3742
@ReactMethod
3843
override fun getLocalIpAddress(promise: Promise) {
3944
try {
@@ -65,9 +70,9 @@ class ReactNativeStaticServerModule internal constructor(context: ReactApplicati
6570
@ReactMethod
6671
override fun start(
6772
id: Double, // Server ID for backward communication with JS layer.
68-
configPath: String?,
69-
errlogPath: String?,
70-
promise: Promise?
73+
configPath: String,
74+
errlogPath: String,
75+
promise: Promise
7176
) {
7277
Log.i(LOGTAG, "Starting...")
7378
try {
@@ -90,10 +95,8 @@ class ReactNativeStaticServerModule internal constructor(context: ReactApplicati
9095
pendingPromise = promise
9196
val emitter: DeviceEventManagerModule.RCTDeviceEventEmitter = getReactApplicationContext()
9297
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
93-
server = Server(
94-
configPath,
95-
errlogPath
96-
) { signal, details ->
98+
99+
server = Server(id, configPath, errlogPath) { signal, details ->
97100
if (signal !== Server.LAUNCHED) server = null
98101
if (pendingPromise == null) {
99102
val event = Arguments.createMap()

android/src/main/java/com/lighttpd/Server.java

Lines changed: 0 additions & 102 deletions
This file was deleted.
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package com.lighttpd
2+
3+
import android.util.Log
4+
import com.drpogodin.reactnativestaticserver.Errors
5+
import java.util.function.BiConsumer
6+
7+
/**
8+
* Java interface for native Lighttpd server running in a dedicated Thread.
9+
* Use Thread methods to operate the server:
10+
* .start() - To launch it;
11+
* .isActive() - To check its current status;
12+
* .interrupt() - To gracefully terminate it.
13+
* Also, `signalConsumer` callback provided to Server instance upon construction
14+
* will provide you with server state change Signals.
15+
*
16+
* As Java Thread instances may be executed only once, to restart the server
17+
* you should create and launch a new instance of Server object.
18+
*
19+
* BEWARE: With the current Lighttpd implementation,
20+
* and the way it is integrated into this library, it is not safe to run
21+
* multiple server instances in parallel! Be sure the previous server instance,
22+
* if any, has terminated or crashed before launching a new one!
23+
*/
24+
class Server(
25+
var id: Double,
26+
var configPath: String,
27+
var errlogPath: String,
28+
private val signalConsumer: BiConsumer<String, String?>
29+
) : Thread() {
30+
override fun interrupt() {
31+
Log.i(LOGTAG, "Server.interrupt() triggered")
32+
gracefulShutdown()
33+
// No need to call super.interrupt() here, the native this.shutdown()
34+
// method will set within the native layer necessary flags that will
35+
// cause graceful termination of the thread.
36+
}
37+
38+
private external fun gracefulShutdown()
39+
external fun launch(configPath: String, errlogPath: String): Int
40+
override fun run() {
41+
Log.i(LOGTAG, "Server.run() triggered")
42+
if (activeServer != null) {
43+
val msg = "Another Server instance is active"
44+
Log.e(LOGTAG, msg)
45+
signalConsumer.accept(CRASHED, msg)
46+
return
47+
}
48+
try {
49+
activeServer = this
50+
val res = launch(configPath, errlogPath)
51+
if (res != 0) {
52+
throw Exception("Native server exited with status $res")
53+
}
54+
55+
// NOTE: It MUST BE set "null" prior to sending out TERMINATED or CRASHED
56+
// signals.
57+
activeServer = null
58+
Log.i(LOGTAG, "Server terminated gracefully")
59+
signalConsumer.accept(TERMINATED, null)
60+
} catch (error: Exception) {
61+
activeServer = null
62+
Log.e(LOGTAG, "Server crashed", error)
63+
signalConsumer.accept(CRASHED, error.message)
64+
}
65+
}
66+
67+
companion object {
68+
init {
69+
System.loadLibrary("lighttpd")
70+
}
71+
72+
// NOTE: Tried to use enum, but was not able to make it work with JNI.
73+
const val CRASHED = "CRASHED"
74+
const val LAUNCHED = "LAUNCHED"
75+
const val TERMINATED = "TERMINATED"
76+
private var activeServer: Server? = null
77+
private const val LOGTAG = Errors.LOGTAG
78+
79+
// NOTE: @JvmStatic annotation is needed to make this function
80+
// visible via JNI in C code.
81+
@JvmStatic fun onLaunchedCallback() {
82+
activeServer!!.signalConsumer.accept(LAUNCHED, null)
83+
}
84+
}
85+
}

0 commit comments

Comments
 (0)