@@ -69,11 +69,28 @@ def __init__(
6969 app_id : Optional [str ],
7070 logger : Optional [Logger ] = None ,
7171 skip_auth : bool = False ,
72+ server_factory : Optional [Callable [[FastAPI ], uvicorn .Server ]] = None ,
7273 ):
74+ """
75+ Args:
76+ app_id: Optional Microsoft App ID.
77+ logger: Optional logger.
78+ skip_auth: Whether to skip JWT validation.
79+ server_factory: Optional function that takes an ASGI app
80+ and returns a configured `uvicorn.Server`.
81+ Example:
82+ ```python
83+ def custom_server_factory(app: FastAPI) -> uvicorn.Server:
84+ return uvicorn.Server(config=uvicorn.Config(app, host="0.0.0.0", port=8000))
85+
86+
87+ http_plugin = HttpPlugin(app_id="your-app-id", server_factory=custom_server_factory)
88+ ```
89+ """
7390 super ().__init__ ()
7491 self .logger = logger or ConsoleLogger ().create_logger ("@teams/http-plugin" )
75- self ._server : Optional [uvicorn .Server ] = None
7692 self ._port : Optional [int ] = None
93+ self ._server : Optional [uvicorn .Server ] = None
7794 self ._on_ready_callback : Optional [Callable [[], Awaitable [None ]]] = None
7895 self ._on_stopped_callback : Optional [Callable [[], Awaitable [None ]]] = None
7996
@@ -104,6 +121,14 @@ async def combined_lifespan(app: Starlette):
104121
105122 self .app = FastAPI (lifespan = combined_lifespan )
106123
124+ # Create uvicorn server if user provides custom factory method
125+ if server_factory :
126+ self ._server = server_factory (self .app )
127+ if self ._server .config .app is not self .app :
128+ raise ValueError (
129+ "server_factory must return a uvicorn.Server configured with the provided FastAPI app instance."
130+ )
131+
107132 # Add JWT validation middleware
108133 if app_id and not skip_auth :
109134 jwt_middleware = create_jwt_validation_middleware (
@@ -144,14 +169,21 @@ def on_stopped_callback(self, callback: Optional[Callable[[], Awaitable[None]]])
144169
145170 async def on_start (self , event : PluginStartEvent ) -> None :
146171 """Start the HTTP server."""
147- port = event .port
148172 self ._port = event .port
149173
150174 try :
151- config = uvicorn .Config (app = self .app , host = "0.0.0.0" , port = port , log_level = "info" )
152- self ._server = uvicorn .Server (config )
175+ if self ._server and self ._server .config .port != self ._port :
176+ self .logger .warning (
177+ "Using port configured by server factory: %d, but plugin start event has port %d." ,
178+ self ._server .config .port ,
179+ self ._port ,
180+ )
181+ self ._port = self ._server .config .port
182+ else :
183+ config = uvicorn .Config (app = self .app , host = "0.0.0.0" , port = self ._port , log_level = "info" )
184+ self ._server = uvicorn .Server (config )
153185
154- self .logger .info ("Starting HTTP server on port %d" , port )
186+ self .logger .info ("Starting HTTP server on port %d" , self . _port )
155187
156188 # The lifespan handler will call the callback when the server is ready
157189 await self ._server .serve ()
0 commit comments