@@ -81,71 +81,113 @@ record HandlerConfig(
8181 throw new IllegalStateException ("at least one spec binding is required" );
8282 }
8383 requireNonNull (bodyMappers , "bodyMappers must not be null" );
84- ExceptionHandler exceptionHandler = handlerConfig .exceptionHandler ();
8584
8685 long t0 = System .currentTimeMillis ();
86+ ExceptionHandler exceptionHandler = handlerConfig .exceptionHandler ();
8787
8888 InetSocketAddress socketAddress =
8989 (bindAddress == null )
9090 ? new InetSocketAddress (port )
9191 : new InetSocketAddress (bindAddress , port );
92- if (sslContext != null ) {
93- HttpsServer https = HttpsServer .create (socketAddress , 0 );
94- https .setHttpsConfigurator (new TlsHttpsConfigurator (sslContext ));
95- this .httpServer = https ;
96- } else {
97- this .httpServer = HttpServer .create (socketAddress , 0 );
98- }
92+ this .httpServer = createHttpServer (socketAddress , sslContext );
9993 httpServer .setExecutor (newThreadPerTaskExecutor (ofVirtual ().name ("http-" , 0 ).factory ()));
10094
10195 ResponseRenderer renderer = new ResponseRenderer (bodyMappers );
96+ boolean anyBindingAtRoot =
97+ wireBindings (httpServer , bindings , bodyMappers , handlerConfig , exceptionHandler , renderer );
98+ wireExtras (httpServer , anyBindingAtRoot , handlerConfig .extras (), exceptionHandler , renderer );
99+
100+ httpServer .start ();
101+ this .shutdownTimeoutSeconds = shutdownTimeoutSeconds ;
102+ logStartup (t0 );
103+ }
104+
105+ private static HttpServer createHttpServer (InetSocketAddress addr , SSLContext sslContext )
106+ throws IOException {
107+ if (sslContext != null ) {
108+ HttpsServer https = HttpsServer .create (addr , 0 );
109+ https .setHttpsConfigurator (new TlsHttpsConfigurator (sslContext ));
110+ return https ;
111+ }
112+ return HttpServer .create (addr , 0 );
113+ }
102114
115+ @ SuppressWarnings ("java:S107" )
116+ private static boolean wireBindings (
117+ HttpServer httpServer ,
118+ List <SpecBinding > bindings ,
119+ Map <String , TypeMapper > bodyMappers ,
120+ HandlerConfig handlerConfig ,
121+ ExceptionHandler exceptionHandler ,
122+ ResponseRenderer renderer ) {
103123 boolean anyBindingAtRoot = false ;
104124 for (SpecBinding binding : bindings ) {
105125 String basePath = Optional .ofNullable (binding .spec ().basePath ()).orElse ("/" );
106126 anyBindingAtRoot |= "/" .equals (basePath );
107- Map <String , Operation > operationsById =
108- binding .spec ().operations ().stream ()
109- .collect (Collectors .toUnmodifiableMap (Operation ::operationId , op -> op ));
110- HttpContext ctx = httpServer .createContext (basePath );
111- ctx .getFilters ()
112- .add (
113- new RequestPreparationFilter (
114- binding .spec (),
115- binding .router (),
116- binding .validator (),
117- bodyMappers ,
118- exceptionHandler ,
119- renderer ,
120- handlerConfig .afterHooks ()));
121- ctx .getFilters ()
122- .add (
123- new SecurityFilter (
124- operationsById ,
125- binding .spec ().securitySchemes (),
126- binding .spec ().security (),
127- binding .securityValidators (),
128- handlerConfig .externalAuth ()));
129- ctx .setHandler (
130- new DispatchHandler (
131- binding .handlers (),
132- handlerConfig .interceptors (),
133- handlerConfig .decorators (),
134- renderer ));
135- }
136-
137- if (!anyBindingAtRoot ) {
138- ExtrasRouter extrasRouter = new ExtrasRouter (handlerConfig .extras (), renderer );
139- HttpContext extrasCtx = httpServer .createContext ("/" , extrasRouter );
140- extrasCtx .getFilters ().add (new ExceptionFilter (exceptionHandler , renderer ));
141- } else if (!handlerConfig .extras ().isEmpty ()) {
142- throw new IllegalStateException (
143- "extras cannot be registered when a binding owns basePath '/'" );
127+ wireBinding (
128+ httpServer , basePath , binding , bodyMappers , handlerConfig , exceptionHandler , renderer );
144129 }
145- httpServer .start ();
130+ return anyBindingAtRoot ;
131+ }
146132
147- this .shutdownTimeoutSeconds = shutdownTimeoutSeconds ;
133+ @ SuppressWarnings ("java:S107" )
134+ private static void wireBinding (
135+ HttpServer httpServer ,
136+ String basePath ,
137+ SpecBinding binding ,
138+ Map <String , TypeMapper > bodyMappers ,
139+ HandlerConfig handlerConfig ,
140+ ExceptionHandler exceptionHandler ,
141+ ResponseRenderer renderer ) {
142+ Map <String , Operation > operationsById =
143+ binding .spec ().operations ().stream ()
144+ .collect (Collectors .toUnmodifiableMap (Operation ::operationId , op -> op ));
145+ HttpContext ctx = httpServer .createContext (basePath );
146+ ctx .getFilters ()
147+ .add (
148+ new RequestPreparationFilter (
149+ binding .spec (),
150+ binding .router (),
151+ binding .validator (),
152+ bodyMappers ,
153+ exceptionHandler ,
154+ renderer ,
155+ handlerConfig .afterHooks ()));
156+ ctx .getFilters ()
157+ .add (
158+ new SecurityFilter (
159+ operationsById ,
160+ binding .spec ().securitySchemes (),
161+ binding .spec ().security (),
162+ binding .securityValidators (),
163+ handlerConfig .externalAuth ()));
164+ ctx .setHandler (
165+ new DispatchHandler (
166+ binding .handlers (),
167+ handlerConfig .interceptors (),
168+ handlerConfig .decorators (),
169+ renderer ));
170+ }
171+
172+ private static void wireExtras (
173+ HttpServer httpServer ,
174+ boolean anyBindingAtRoot ,
175+ Map <String , RequestHandler > extras ,
176+ ExceptionHandler exceptionHandler ,
177+ ResponseRenderer renderer ) {
178+ if (anyBindingAtRoot ) {
179+ if (!extras .isEmpty ()) {
180+ throw new IllegalStateException (
181+ "extras cannot be registered when a binding owns basePath '/'" );
182+ }
183+ return ;
184+ }
185+ ExtrasRouter extrasRouter = new ExtrasRouter (extras , renderer );
186+ HttpContext extrasCtx = httpServer .createContext ("/" , extrasRouter );
187+ extrasCtx .getFilters ().add (new ExceptionFilter (exceptionHandler , renderer ));
188+ }
148189
190+ private void logStartup (long t0 ) {
149191 String host = httpServer .getAddress ().getHostString ();
150192 String displayHost = host .contains (":" ) ? "[" + host + "]" : host ;
151193 LOG .info (
@@ -384,30 +426,62 @@ public Builder extraRoute(String path, RequestHandler handler) {
384426 }
385427
386428 public OpenApiServer build () throws IOException {
429+ List <SpecBinding > effectiveBindings = resolveBindings ();
430+ validateBindings (effectiveBindings );
431+ Map <String , String > seenBasePaths = rejectDuplicateBasePaths (effectiveBindings );
432+ rejectExtrasOnBasePath (seenBasePaths );
433+
434+ Map <String , TypeMapper > resolved = resolveBodyMappers (bodyMappers );
435+ ExceptionHandler effectiveExceptionHandler =
436+ exceptionHandler != null ? exceptionHandler : Handlers .defaultExceptionHandler ();
437+ HandlerConfig handlerConfig =
438+ new HandlerConfig (
439+ interceptors ,
440+ decorators ,
441+ effectiveExceptionHandler ,
442+ extras ,
443+ externalAuth ,
444+ List .copyOf (afterHooks ));
445+ int resolvedPort = resolvePort ();
446+ SSLContext sslContext =
447+ httpsCertChain != null ? PemSslContext .load (httpsCertChain , httpsPrivateKey ) : null ;
448+ return new OpenApiServer (
449+ effectiveBindings ,
450+ resolved ,
451+ handlerConfig ,
452+ resolvedPort ,
453+ bindAddress ,
454+ shutdownTimeoutSeconds ,
455+ sslContext );
456+ }
457+
458+ private List <SpecBinding > resolveBindings () {
387459 boolean usedLegacy = spec != null || handlers != null || !securityValidators .isEmpty ();
388460 boolean usedAddSpec = !bindings .isEmpty ();
389461 if (usedLegacy && usedAddSpec ) {
390462 throw new IllegalStateException (
391463 "use either spec()/handler()/securityValidator() or addSpec(), not both" );
392464 }
393- List <SpecBinding > effectiveBindings ;
394465 if (usedAddSpec ) {
395- effectiveBindings = List .copyOf (bindings );
396- } else {
397- requireNonNull (spec , "Spec must not be null" );
398- requireNonNull (handlers , "handlers must not be null" );
399- effectiveBindings = List .of (SpecBinding .of (spec , handlers , securityValidators ));
466+ return List .copyOf (bindings );
400467 }
468+ requireNonNull (spec , "Spec must not be null" );
469+ requireNonNull (handlers , "handlers must not be null" );
470+ return List .of (SpecBinding .of (spec , handlers , securityValidators ));
471+ }
401472
473+ private void validateBindings (List <SpecBinding > effectiveBindings ) {
402474 for (SpecBinding b : effectiveBindings ) {
403475 if (!externalAuth ) {
404476 validateSecurityWiring (b .spec (), b .securityValidators ());
405477 }
406478 validateHandlerWiring (b .spec (), b .handlers ());
407479 }
480+ }
408481
482+ private static Map <String , String > rejectDuplicateBasePaths (List <SpecBinding > bindings ) {
409483 Map <String , String > seenBasePaths = new LinkedHashMap <>();
410- for (SpecBinding b : effectiveBindings ) {
484+ for (SpecBinding b : bindings ) {
411485 String bp = Optional .ofNullable (b .spec ().basePath ()).orElse ("/" );
412486 String existingTitle = seenBasePaths .putIfAbsent (bp , b .spec ().info ().title ());
413487 if (existingTitle != null ) {
@@ -421,7 +495,10 @@ public OpenApiServer build() throws IOException {
421495 + "'" );
422496 }
423497 }
498+ return seenBasePaths ;
499+ }
424500
501+ private void rejectExtrasOnBasePath (Map <String , String > seenBasePaths ) {
425502 for (String path : extras .keySet ()) {
426503 if (seenBasePaths .containsKey (path )) {
427504 throw new IllegalStateException (
@@ -432,29 +509,6 @@ public OpenApiServer build() throws IOException {
432509 + "'" );
433510 }
434511 }
435-
436- Map <String , TypeMapper > resolved = resolveBodyMappers (bodyMappers );
437- ExceptionHandler effectiveExceptionHandler =
438- exceptionHandler != null ? exceptionHandler : Handlers .defaultExceptionHandler ();
439- HandlerConfig handlerConfig =
440- new HandlerConfig (
441- interceptors ,
442- decorators ,
443- effectiveExceptionHandler ,
444- extras ,
445- externalAuth ,
446- List .copyOf (afterHooks ));
447- int resolvedPort = resolvePort ();
448- SSLContext sslContext =
449- httpsCertChain != null ? PemSslContext .load (httpsCertChain , httpsPrivateKey ) : null ;
450- return new OpenApiServer (
451- effectiveBindings ,
452- resolved ,
453- handlerConfig ,
454- resolvedPort ,
455- bindAddress ,
456- shutdownTimeoutSeconds ,
457- sslContext );
458512 }
459513
460514 private int resolvePort () {
0 commit comments