9
9
* https://github.com/restatedev/sdk-typescript/blob/main/LICENSE
10
10
*/
11
11
12
- import { on } from "events" ;
13
12
import stream from "stream" ;
14
13
import { pipeline , finished } from "stream/promises" ;
15
- import http2 from "http2" ;
14
+ import http2 , { Http2ServerRequest , Http2ServerResponse } from "http2" ;
16
15
import { parse as urlparse , Url } from "url" ;
17
16
import {
18
17
ProtocolMode ,
@@ -27,63 +26,9 @@ import { InvocationBuilder } from "../invocation";
27
26
import { StateMachine } from "../state_machine" ;
28
27
import { KeyedRouter , UnKeyedRouter } from "../types/router" ;
29
28
30
- /**
31
- * Creates a Restate entrypoint based on a HTTP2 server. The entrypoint will listen
32
- * for requests to the services at a specified port.
33
- *
34
- * This is the entrypoint to be used in most scenarios (standalone, Docker, Kubernetes, ...);
35
- * any deployments that forwards requests to a network endpoint. The prominent exception is
36
- * AWS Lambda, which uses the {@link restate_lambda_handler#lambdaApiGatewayHandler}
37
- * function to create an entry point.
38
- *
39
- * After creating this endpoint, register services on this entrypoint via {@link RestateServer.bindService }
40
- * and start it via {@link RestateServer.listen }.
41
- *
42
- * @example
43
- * A typical entry point would look like this:
44
- * ```
45
- * import * as restate from "@restatedev/restate-sdk";
46
- *
47
- * export const handler = restate
48
- * .createServer()
49
- * .bindService({
50
- * service: "MyService",
51
- * instance: new myService.MyServiceImpl(),
52
- * descriptor: myService.protoMetadata,
53
- * })
54
- * .listen(8000);
55
- * ```
56
- */
57
- export function createServer ( ) : RestateServer {
58
- return new RestateServer ( ) ;
59
- }
60
-
61
- /**
62
- * Restate entrypoint implementation for services. This server receives and
63
- * decodes the requests, streams events between the service and the Restate runtime,
64
- * and drives the durable execution of the service invocations.
65
- */
66
- export class RestateServer extends BaseRestateServer {
67
- constructor ( ) {
68
- super ( ProtocolMode . BIDI_STREAM ) ;
69
- }
70
-
71
- public bindKeyedRouter < M > (
72
- path : string ,
73
- router : KeyedRouter < M >
74
- ) : RestateServer {
75
- // Implementation note: This override if here mainly to change the return type to the more
76
- // concrete type RestateServer (from BaseRestateServer).
77
- super . bindRpcService ( path , router , true ) ;
78
- return this ;
79
- }
80
-
81
- public bindRouter < M > ( path : string , router : UnKeyedRouter < M > ) : RestateServer {
82
- // Implementation note: This override if here mainly to change the return type to the more
83
- // concrete type RestateServer (from BaseRestateServer).
84
- super . bindRpcService ( path , router , false ) ;
85
- return this ;
86
- }
29
+ export interface RestateServer {
30
+ // RestateServer is a http2 server handler that you can pass to http2.createServer.
31
+ ( request : Http2ServerRequest , response : Http2ServerResponse ) : void ;
87
32
88
33
/**
89
34
* Adds a gRPC service to be served from this endpoint.
@@ -121,12 +66,11 @@ export class RestateServer extends BaseRestateServer {
121
66
* @param serviceOpts The options describing the service to be bound. See above for a detailed description.
122
67
* @returns An instance of this RestateServer
123
68
*/
124
- public bindService ( serviceOpts : ServiceOpts ) : RestateServer {
125
- // Implementation note: This override if here mainly to change the return type to the more
126
- // concrete type RestateServer (from BaseRestateServer).
127
- super . bindService ( serviceOpts ) ;
128
- return this ;
129
- }
69
+ bindService ( serviceOpts : ServiceOpts ) : RestateServer ;
70
+
71
+ bindKeyedRouter < M > ( path : string , router : KeyedRouter < M > ) : RestateServer ;
72
+
73
+ bindRouter < M > ( path : string , router : UnKeyedRouter < M > ) : RestateServer ;
130
74
131
75
/**
132
76
* Starts the Restate server and listens at the given port.
@@ -137,23 +81,124 @@ export class RestateServer extends BaseRestateServer {
137
81
*
138
82
* This method's result promise never completes.
139
83
*
84
+ * This method is a shorthand for:
85
+ *
86
+ * @example
87
+ * ```
88
+ * const httpServer = http2.createServer(restateServer);
89
+ * httpServer.listen(port);
90
+ * ```
91
+ *
92
+ * If you need to manually control the server lifecycle, we suggest to manually instantiate the http2 server and use this object as request handler.
93
+ *
140
94
* @param port The port to listen at. May be undefined (see above).
141
95
*/
142
- public async listen ( port ?: number ) {
143
- // Infer the port if not specified, or default it
96
+ listen ( port ?: number ) : Promise < void > ;
97
+ }
98
+
99
+ /**
100
+ * Creates a Restate entrypoint based on a HTTP2 server. The entrypoint will listen
101
+ * for requests to the services at a specified port.
102
+ *
103
+ * This is the entrypoint to be used in most scenarios (standalone, Docker, Kubernetes, ...);
104
+ * any deployments that forwards requests to a network endpoint. The prominent exception is
105
+ * AWS Lambda, which uses the {@link restate_lambda_handler#lambdaApiGatewayHandler}
106
+ * function to create an entry point.
107
+ *
108
+ * After creating this endpoint, register services on this entrypoint via {@link RestateServer.bindService }
109
+ * and start it via {@link RestateServer.listen }.
110
+ *
111
+ * @example
112
+ * A typical entry point would look like this:
113
+ * ```
114
+ * import * as restate from "@restatedev/restate-sdk";
115
+ *
116
+ * export const handler = restate
117
+ * .createServer()
118
+ * .bindService({
119
+ * service: "MyService",
120
+ * instance: new myService.MyServiceImpl(),
121
+ * descriptor: myService.protoMetadata,
122
+ * })
123
+ * .listen(8000);
124
+ * ```
125
+ */
126
+ export function createServer ( ) : RestateServer {
127
+ // See https://stackoverflow.com/questions/16508435/implementing-typescript-interface-with-bare-function-signature-plus-other-fields/16508581#16508581
128
+ // for more details on how we implement the RestateServer interface.
129
+
130
+ const restateServerImpl = new RestateServerImpl ( ) ;
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ const instance : any = (
133
+ request : Http2ServerRequest ,
134
+ response : Http2ServerResponse
135
+ ) => {
136
+ restateServerImpl . acceptConnection ( request , response ) ;
137
+ } ;
138
+ instance . bindKeyedRouter = < M > ( path : string , router : UnKeyedRouter < M > ) => {
139
+ restateServerImpl . bindKeyedRouter ( path , router ) ;
140
+ return instance ;
141
+ } ;
142
+ instance . bindRouter = < M > ( path : string , router : UnKeyedRouter < M > ) => {
143
+ restateServerImpl . bindRouter ( path , router ) ;
144
+ return instance ;
145
+ } ;
146
+ instance . bindService = ( serviceOpts : ServiceOpts ) => {
147
+ restateServerImpl . bindService ( serviceOpts ) ;
148
+ return instance ;
149
+ } ;
150
+ instance . listen = ( port ?: number ) => {
144
151
const actualPort = port ?? parseInt ( process . env . PORT ?? "9080" ) ;
145
152
rlog . info ( `Listening on ${ actualPort } ...` ) ;
146
153
147
- for await ( const connection of incomingConnectionAtPort ( actualPort ) ) {
148
- this . handleConnection ( connection . url , connection . stream ) . catch ( ( e ) => {
149
- const error = ensureError ( e ) ;
150
- rlog . error (
151
- "Error while handling connection: " + ( error . stack ?? error . message )
152
- ) ;
153
- connection . stream . end ( ) ;
154
- connection . stream . destroy ( ) ;
155
- } ) ;
156
- }
154
+ const server = http2 . createServer ( instance ) ;
155
+ server . listen ( actualPort ) ;
156
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
157
+ return new Promise ( ( ) => { } ) ;
158
+ } ;
159
+
160
+ return < RestateServer > instance ;
161
+ }
162
+
163
+ class RestateServerImpl extends BaseRestateServer {
164
+ constructor ( ) {
165
+ super ( ProtocolMode . BIDI_STREAM ) ;
166
+ }
167
+
168
+ bindKeyedRouter < M > ( path : string , router : KeyedRouter < M > ) {
169
+ // Implementation note: This override if here mainly to change the return type to the more
170
+ // concrete type RestateServer (from BaseRestateServer).
171
+ super . bindRpcService ( path , router , true ) ;
172
+ }
173
+
174
+ bindRouter < M > ( path : string , router : UnKeyedRouter < M > ) {
175
+ // Implementation note: This override if here mainly to change the return type to the more
176
+ // concrete type RestateServer (from BaseRestateServer).
177
+ super . bindRpcService ( path , router , false ) ;
178
+ }
179
+
180
+ bindService ( serviceOpts : ServiceOpts ) {
181
+ // Implementation note: This override if here mainly to change the return type to the more
182
+ // concrete type RestateServer (from BaseRestateServer).
183
+ super . bindService ( serviceOpts ) ;
184
+ }
185
+
186
+ acceptConnection (
187
+ request : Http2ServerRequest ,
188
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
189
+ _response : Http2ServerResponse
190
+ ) {
191
+ const stream = request . stream ;
192
+ const url : Url = urlparse ( request . url ?? "/" ) ;
193
+
194
+ this . handleConnection ( url , stream ) . catch ( ( e ) => {
195
+ const error = ensureError ( e ) ;
196
+ rlog . error (
197
+ "Error while handling connection: " + ( error . stack ?? error . message )
198
+ ) ;
199
+ stream . end ( ) ;
200
+ stream . destroy ( ) ;
201
+ } ) ;
157
202
}
158
203
159
204
private async handleConnection (
@@ -190,25 +235,6 @@ export class RestateServer extends BaseRestateServer {
190
235
}
191
236
}
192
237
193
- async function * incomingConnectionAtPort ( port : number ) {
194
- const server = http2 . createServer ( ) ;
195
-
196
- server . on ( "error" , ( err ) =>
197
- rlog . error ( "Error in Restate service endpoint http2 server: " + err )
198
- ) ;
199
- server . listen ( port ) ;
200
-
201
- let connectionId = 1n ;
202
-
203
- for await ( const [ s , h ] of on ( server , "stream" ) ) {
204
- const stream = s as http2 . ServerHttp2Stream ;
205
- const headers = h as http2 . IncomingHttpHeaders ;
206
- const url : Url = urlparse ( headers [ ":path" ] ?? "/" ) ;
207
- connectionId ++ ;
208
- yield { connectionId, url, headers, stream } ;
209
- }
210
- }
211
-
212
238
async function respondDiscovery (
213
239
response : ServiceDiscoveryResponse ,
214
240
http2Stream : http2 . ServerHttp2Stream
0 commit comments