11
11
"""This module contains the ASGI server for the restate framework."""
12
12
13
13
import asyncio
14
- from typing import Dict , Literal
14
+ from typing import Dict , TypedDict , Literal
15
15
import traceback
16
16
from restate .discovery import compute_discovery_json
17
17
from restate .endpoint import Endpoint
@@ -135,6 +135,33 @@ async def process_invocation_to_completion(vm: VMWrapper,
135
135
class LifeSpanNotImplemented (ValueError ):
136
136
"""Signal to the asgi server that we didn't implement lifespans"""
137
137
138
+
139
+ class ParsedPath (TypedDict ):
140
+ """Parsed path from the request."""
141
+ type : Literal ["invocation" , "health" , "discover" , "unknown" ]
142
+ service : str | None
143
+ handler : str | None
144
+
145
+ def parse_path (request : str ) -> ParsedPath :
146
+ """Parse the path from the request."""
147
+ # The following routes are possible
148
+ # $mountpoint/health
149
+ # $mountpoint/discover
150
+ # $mountpoint/invoke/:service/:handler
151
+ # as we don't know the mountpoint, we need to check the path carefully
152
+ fragments = request .rsplit ('/' , 4 )
153
+ # /invoke/:service/:handler
154
+ if len (fragments ) >= 3 and fragments [- 3 ] == 'invoke' :
155
+ return { "type" : "invocation" , "handler" : fragments [- 1 ], "service" : fragments [- 2 ] }
156
+ # /health
157
+ if fragments [- 1 ] == 'health' :
158
+ return { "type" : "health" , "service" : None , "handler" : None }
159
+ # /discover
160
+ if fragments [- 1 ] == 'discover' :
161
+ return { "type" : "discover" , "service" : None , "handler" : None }
162
+ # anything other than invoke is 404
163
+ return { "type" : "unknown" , "service" : None , "handler" : None }
164
+
138
165
def asgi_app (endpoint : Endpoint ):
139
166
"""Create an ASGI-3 app for the given endpoint."""
140
167
@@ -150,9 +177,10 @@ async def app(scope: Scope, receive: Receive, send: Send):
150
177
151
178
request_path = scope ['path' ]
152
179
assert isinstance (request_path , str )
180
+ request : ParsedPath = parse_path (request_path )
153
181
154
182
# Health check
155
- if request_path == '/ health' :
183
+ if request [ 'type' ] == 'health' :
156
184
await send_health_check (send )
157
185
return
158
186
@@ -168,16 +196,17 @@ async def app(scope: Scope, receive: Receive, send: Send):
168
196
return
169
197
170
198
# might be a discovery request
171
- if request_path == '/ discover' :
199
+ if request [ 'type' ] == 'discover' :
172
200
await send_discovery (scope , send , endpoint )
173
201
return
174
202
# anything other than invoke is 404
175
- if not request_path . startswith ( '/invoke/' ) :
203
+ if request [ 'type' ] == 'unknown' :
176
204
await send404 (send , receive )
177
205
return
178
- # path is of the form: /invoke/:service/:handler
179
- # strip "/invoke/" (= strlen 8) and split the service and handler
180
- service_name , handler_name = request_path [8 :].split ('/' )
206
+ assert request ['type' ] == 'invocation'
207
+ assert request ['service' ] is not None
208
+ assert request ['handler' ] is not None
209
+ service_name , handler_name = request ['service' ], request ['handler' ]
181
210
service = endpoint .services .get (service_name )
182
211
if not service :
183
212
await send404 (send , receive )
0 commit comments