@@ -12,7 +12,7 @@ your dev hub after doing the above.
12
12
13
13
2. Run this script at the terminal:
14
14
15
- echo "require('@cocalc/server/nats').default ()" | COCALC_MODE='single-user' DEBUG_CONSOLE=yes DEBUG=cocalc:* node
15
+ echo "require('@cocalc/server/nats/api ').initAPI ()" | COCALC_MODE='single-user' DEBUG_CONSOLE=yes DEBUG=cocalc:* node
16
16
17
17
18
18
3. Optional: start more servers -- requests get randomly routed to exactly one of them:
@@ -40,26 +40,18 @@ To view all requests (and replies) in realtime:
40
40
And remember to use the nats command, do "pnpm nats-cli" from cocalc/src.
41
41
*/
42
42
43
- import { JSONCodec } from "nats" ;
44
43
import getLogger from "@cocalc/backend/logger" ;
45
44
import { type HubApi , getUserId , transformArgs } from "@cocalc/nats/hub-api" ;
46
- import { getConnection } from "@cocalc/backend/nats" ;
45
+ import { getEnv } from "@cocalc/backend/nats" ;
47
46
import userIsInGroup from "@cocalc/server/accounts/is-in-group" ;
48
47
import { terminate as terminateDatabase } from "@cocalc/database/nats/changefeeds" ;
49
48
import { terminate as terminateChangefeedServer } from "@cocalc/nats/changefeed/server" ;
50
- import { Svcm } from "@nats-io/services" ;
51
49
import { terminate as terminateAuth } from "@cocalc/server/nats/auth" ;
52
50
import { terminate as terminateTieredStorage } from "@cocalc/server/nats/tiered-storage/api" ;
53
- import { respondMany } from "@cocalc/nats/service/many" ;
54
51
import { delay } from "awaiting" ;
55
- import { waitUntilConnected } from "@cocalc/nats/util" ;
56
-
57
- const MONITOR_INTERVAL = 30000 ;
58
52
59
53
const logger = getLogger ( "server:nats:api" ) ;
60
54
61
- const jc = JSONCodec ( ) ;
62
-
63
55
export function initAPI ( ) {
64
56
mainLoop ( ) ;
65
57
}
@@ -88,93 +80,68 @@ async function mainLoop() {
88
80
}
89
81
}
90
82
91
- async function serviceMonitor ( { nc, api, subject } ) {
92
- while ( ! terminate ) {
93
- logger . debug ( `serviceMonitor: waiting ${ MONITOR_INTERVAL } ms` ) ;
94
- await delay ( MONITOR_INTERVAL ) ;
95
- try {
96
- await waitUntilConnected ( ) ;
97
- await nc . request ( subject , jc . encode ( { name : "ping" } ) , {
98
- timeout : 7500 ,
99
- } ) ;
100
- logger . debug ( "serviceMonitor: ping succeeded" ) ;
101
- } catch ( err ) {
102
- logger . debug (
103
- `serviceMonitor: ping failed, so restarting service -- ${ err } ` ,
104
- ) ;
105
- api . stop ( ) ;
106
- return ;
107
- }
108
- }
109
- }
110
-
111
83
async function serve ( ) {
112
84
const subject = "hub.*.*.api" ;
113
85
logger . debug ( `initAPI -- subject='${ subject } ', options=` , {
114
86
queue : "0" ,
115
87
} ) ;
116
- const nc = await getConnection ( ) ;
117
- // @ts -ignore
118
- const svcm = new Svcm ( nc ) ;
119
-
120
- await waitUntilConnected ( ) ;
121
- const service = await svcm . add ( {
122
- name : "hub-server" ,
123
- version : "0.1.0" ,
124
- description : "CoCalc Hub Server" ,
125
- } ) ;
126
-
127
- const api = service . addEndpoint ( "api" , { subject } ) ;
128
- serviceMonitor ( { api, subject, nc } ) ;
129
- await listen ( { api, subject } ) ;
88
+ const { cn } = await getEnv ( ) ;
89
+ const api = await cn . subscribe ( subject ) ;
90
+ for await ( const mesg of api ) {
91
+ ( async ( ) => {
92
+ try {
93
+ await handleMessage ( { api, subject, mesg } ) ;
94
+ } catch ( err ) {
95
+ logger . debug ( `WARNING: unexpected error - ${ err } ` ) ;
96
+ }
97
+ } ) ( ) ;
98
+ }
130
99
}
131
100
132
- async function listen ( { api, subject } ) {
133
- for await ( const mesg of api ) {
134
- const request = jc . decode ( mesg . data ) ?? ( { } as any ) ;
135
- if ( request . name == "system.terminate" ) {
101
+ async function handleMessage ( { api, subject, mesg } ) {
102
+ const request = mesg . data ?? ( { } as any ) ;
103
+ if ( request . name == "system.terminate" ) {
104
+ // special hook so admin can terminate handling. This is useful for development.
105
+ const { account_id } = getUserId ( mesg . subject ) ;
106
+ if ( ! ( ! ! account_id && ( await userIsInGroup ( account_id , "admin" ) ) ) ) {
107
+ mesg . respond ( { error : "only admin can terminate" } ) ;
108
+ return ;
109
+ }
110
+ // TODO: could be part of handleApiRequest below, but done differently because
111
+ // one case halts this loop
112
+ const { service } = request . args [ 0 ] ?? { } ;
113
+ logger . debug ( `Terminate service '${ service } '` ) ;
114
+ if ( service == "db" ) {
115
+ terminateDatabase ( ) ;
116
+ mesg . respond ( { status : "terminated" , service } ) ;
117
+ return ;
118
+ } else if ( service == "auth" ) {
119
+ terminateAuth ( ) ;
120
+ mesg . respond ( { status : "terminated" , service } ) ;
121
+ return ;
122
+ } else if ( service == "tiered-storage" ) {
123
+ terminateTieredStorage ( ) ;
124
+ mesg . respond ( { status : "terminated" , service } ) ;
125
+ return ;
126
+ } else if ( service == "changefeeds" ) {
127
+ terminateChangefeedServer ( ) ;
128
+ mesg . respond ( { status : "terminated" , service } ) ;
129
+ return ;
130
+ } else if ( service == "api" ) {
136
131
// special hook so admin can terminate handling. This is useful for development.
137
- const { account_id } = getUserId ( mesg . subject ) ;
138
- if ( ! ( ! ! account_id && ( await userIsInGroup ( account_id , "admin" ) ) ) ) {
139
- mesg . respond ( jc . encode ( { error : "only admin can terminate" } ) ) ;
140
- continue ;
141
- }
142
- // TODO: could be part of handleApiRequest below, but done differently because
143
- // one case halts this loop
144
- const { service } = request . args [ 0 ] ?? { } ;
145
- logger . debug ( `Terminate service '${ service } '` ) ;
146
- if ( service == "db" ) {
147
- terminateDatabase ( ) ;
148
- mesg . respond ( jc . encode ( { status : "terminated" , service } ) ) ;
149
- continue ;
150
- } else if ( service == "auth" ) {
151
- terminateAuth ( ) ;
152
- mesg . respond ( jc . encode ( { status : "terminated" , service } ) ) ;
153
- continue ;
154
- } else if ( service == "tiered-storage" ) {
155
- terminateTieredStorage ( ) ;
156
- mesg . respond ( jc . encode ( { status : "terminated" , service } ) ) ;
157
- continue ;
158
- } else if ( service == "changefeeds" ) {
159
- terminateChangefeedServer ( ) ;
160
- mesg . respond ( jc . encode ( { status : "terminated" , service } ) ) ;
161
- continue ;
162
- } else if ( service == "api" ) {
163
- // special hook so admin can terminate handling. This is useful for development.
164
- console . warn ( "TERMINATING listening on " , subject ) ;
165
- logger . debug ( "TERMINATING listening on " , subject ) ;
166
- terminate = true ;
167
- mesg . respond ( jc . encode ( { status : "terminated" , service } ) ) ;
168
- api . stop ( ) ;
169
- return ;
170
- } else {
171
- mesg . respond ( jc . encode ( { error : `Unknown service ${ service } ` } ) ) ;
172
- }
132
+ console . warn ( "TERMINATING listening on " , subject ) ;
133
+ logger . debug ( "TERMINATING listening on " , subject ) ;
134
+ terminate = true ;
135
+ mesg . respond ( { status : "terminated" , service } ) ;
136
+ api . stop ( ) ;
137
+ return ;
173
138
} else {
174
- // we explicitly do NOT await this, since we want this hub server to handle
175
- // potentially many messages at once, not one at a time!
176
- handleApiRequest ( { request, mesg } ) ;
139
+ mesg . respond ( { error : `Unknown service ${ service } ` } ) ;
177
140
}
141
+ } else {
142
+ // we explicitly do NOT await this, since we want this hub server to handle
143
+ // potentially many messages at once, not one at a time!
144
+ handleApiRequest ( { request, mesg } ) ;
178
145
}
179
146
}
180
147
@@ -193,7 +160,7 @@ async function handleApiRequest({ request, mesg }) {
193
160
resp = { error : `${ err } ` } ;
194
161
}
195
162
try {
196
- await respondMany ( { mesg, data : jc . encode ( resp ) } ) ;
163
+ await mesg . respond ( resp ) ;
197
164
} catch ( err ) {
198
165
// there's nothing we can do here, e.g., maybe NATS just died.
199
166
logger . debug (
0 commit comments