spring_boot_starter_web 0.6.1
spring_boot_starter_web: ^0.6.1 copied to clipboard
Spring MVC for Dart: @RestController with @GetMapping and @PostMapping on dart:io, JSON bodies, @Valid, ProblemDetail errors, server-sent events, filters, CORS.
0.6.1 #
- feat(flutter): the desktop window from properties — title bar, initia… (#18)
0.6.0 #
Breaking #
- Properties renamed:
spring.servlet.multipart.*isspring.web.multipart.*;server.servlet.context-pathisserver.context-pathandserver.servlet.session.*isserver.session.*(there is no servlet container). An old name stops the start, naming the new one. The Boot 1 keyserver.context-path, which was read whenserver.servlet.context-pathwas not set, is now the key itself.
Changes #
server.request-timeout, a deadline for every request (no limit by default,0or less: none; an extension, Spring Boot has no such key): the filters and the handler run under aRequestDeadlineof that length, and when it passes before the answer starts the answer is a503 Service Unavailableproblem (AsyncRequestTimeoutException, a generic detail, logged as a warning at most every 10 seconds). What the handler does afterwards is dropped (no second answer, no unhandled error); a response already committed is left alone; a response that becomes a stream (server-sent events,SseEmitter, a streamed body) leaves the deadline when it starts, and a WebSocket handshake is not timed. Code the request calls can read the deadline (RequestDeadline.current), and an error that ends a request after its deadline, or aRequestDeadlineExceededException, is a 503 instead of a 500.AsyncRequestTimeoutExceptiontakes an optional detail.HandlerExceptionResolver, Spring's extension point for turning an error into an answer: a bean returns anErrorResponse(status, headers and problem) ornullto pass the error on. The resolvers are asked in@Orderafter the@ExceptionHandlermethods of the controller and of the@ControllerAdvicebeans (the application's handlers always win) and before the default 500; not for an error that is already anErrorResponseor that a filter throws; a resolver that throws is logged and skipped. A library maps its exceptions to HTTP with it: withspring_boot_starter_dataamong the dependencies the generator adds one, so anOptimisticLockingFailureExceptionand aDuplicateKeyExceptionare a 409 and anEmptyResultDataAccessExceptiona 404 (see the data starter).- The framework logs its main steps as Spring does, so
logging.level.root=debug(orlogging.level.web=debug) shows what happens:GET "/api/books", parameters={masked},Mapped to BookController#search(),Completed 200 OKandResolved [Type: detail]for the dispatcher;N mappings in 'requestMappingHandlerMapping'at debug and each mapping at trace; created, invalidated and re-keyed sessions (never their ids); the forwarded headers applied (scheme and host only); the parts and bytes of a multipart request. Parameters show asmasked, and headers (trace) as{masked}, unlessspring.mvc.log-request-details=true; even thenAuthorization,Cookie,Set-Cookieand any header or parameter whose name containstoken,secret,key,passwordorsessionstaymasked. Paths, parameter values and other client text go throughLogFormatUtils.formatValue: line breaks show as<EOL>. The query string is not logged in the request line (parameters are, when allowed), unlike Spring.
Fixes #
@RequestParamand@RequestHeaderon aMap(every query parameter or header) answered 500 when the map was typed (Map<String, String>,Map<String, List<String>>,Map<String, int>): the map is now built with the parameter's own key and value types; the same for a form read into aMapwith@RequestBody.
0.5.0 #
Breaking #
@CrossOrigin()withoutmethodsallows only the method the handler is mapped to, as in Spring (it allowedGET,HEADandPOST): a cross-originHEADto a@GetMappingneedsmethods: ['GET', 'HEAD'].- Every cross-origin request is checked as Spring's
DefaultCorsProcessorchecks it, its own method included: an allowed origin'sDELETE,PUT,HEADorOPTIONSthat the rules do not list is a 403 before the handler runs, with or without a pre-flight. - Only the bean named
corsConfigurationSourceis a CORS source, as in Spring Security (aCorsConfigurationSourcebean of another name is ignored, and several such beans no longer fail the start). It is no longer combined with@CrossOriginor theWebMvcConfigurermappings: where it has a configuration for a request, that configuration alone applies, with and without the security starter. Where it has none,@CrossOriginand the mappings apply. - Answers whose type the app didn't set have no
Content-Type(dart:io'stext/plain; charset=utf-8default is gone); 1xx, 204 and 304 answers have noContent-LengthorTransfer-Encoding, a 304 or 1xx noContent-Type, and a body written to them is dropped, as Tomcat does. *in the allowed origins together withallowCredentialsorallowPrivateNetwork, also inside a comma-separated value, stops the startup for a@CrossOriginand makes the requests a 500 for a mapping or a bean, as in Spring.- CORS origin patterns are compared with case (origins without), by
AllowedOrigins. - Every answer of a handler with CORS rules gets
Vary: Origin, Access-Control-Request-Method, Access-Control-Request-Headers, a CORS request or not, as Spring adds it; a handler's ownVary(of aResponseEntityor anErrorResponse) is merged with these names instead of replacing them. - A pre-flight to a mapped path without CORS rules, and one from the server's own origin, gets a 200 without CORS headers (was a 403), as in Spring; a pre-flight for a method no handler of the path maps is decided by the global rules alone (a 403 when they refuse it, never a 405).
- An
Origin, or a pre-flight's requested method or headers, with a character outside visible US-ASCII is a 403 (was a 500 with a stack trace). - A
ResponseEntitywith a status of 200 and anETagorLast-Modifiedanswers a conditionalGETorHEADwith a 304 (or a 412 forIf-Unmodified-Since), as Spring'sHttpEntityMethodProcessor. - A
@RequestBodyobject in a request without aContent-Typeis a 415, as Spring reads such a body asapplication/octet-stream(it was read as JSON); aContent-Typethat is not a media type is a 415 too. server.max-http-request-body-sizelimits every body,multipart/*ones included (the media type is checked before the body is read); only the multipart resolver reads uploads withspring.servlet.multipart.max-request-sizeinstead.- A multipart request may have at most 50 parts (
spring.servlet.multipart.max-part-count,-1: no limit; Tomcat's default in Boot 4); more is a 413. - The server answers a 400 before any filter, as Tomcat does, for an encoded
/,\or NUL in the path (%2F,%5C,%00), a.or..segment behind matrix parameters (/files/..;/secret), a%escape in the path or the query that is not UTF-8, and a malformed or missingHost(or an absolute target naming another host); a form with a malformed%escape is a 400 too. - WebSocket server sessions read at most 8 KB per message, as Tomcat in Spring (
server.websocket.max-text-message-sizeandmax-binary-message-size; above, the session closes with 1009), negotiate no permessage-deflate, and drop a client that takes nothing past 512 KB waiting or 10 seconds (server.websocket.send-buffer-size-limitandsend-time-limit; 4500), as Spring'sConcurrentWebSocketSessionDecorator. @ControllerAdvicebeans are tried in@Order, as in Spring: the first bean with a handler for the error answers it, even when a later bean has a more specific one.- A request body that sends nothing for
server.connection-timeout(60 s, Tomcat's default;0or less: no limit) ends with a 408, and idle keep-alive connections close after the same time. - At
server.servlet.session.max-sessionsthe in-memory repository removes every expired session and, when it is still full, refuses the new session with aTooManyActiveSessionsException(503), as WebFlux'sInMemoryWebSessionStoredoes; a live session is never pushed out for a new one. The constantsInMemoryHttpSessionRepository.loginAttribute,principalNameAttributeandprincipalKeyAttributeare removed. The security starter creates a session only at a login, so anonymous requests cannot fill the store.
Changes #
server.forward-headers-strategy(nativeorframework, Spring Boot's) addsForwardedHeaderFilter, first of all filters: behind a reverse proxy the request gets the scheme, host, port and client address ofForwarded(RFC 7239) orX-Forwarded-Proto,-Host,-Portand-For, so cookies getSecurebehind a TLS proxy and redirect URIs arehttps://. Only from the proxies ofserver.forward-headers.trusted-proxies(loopback and private networks by default, as Tomcat'sRemoteIpValve; Spring's filter believes every peer); the client's address is the first from the right that is not a trusted proxy; invalid values are ignored; the headers are removed for the rest of the chain.X-Forwarded-Prefixis not read.- The private directory of the uploads' temporary files is
rwx------on Linux too (dart:io'screateTempmade itrwxr-xr-xthere, readable by every user of the machine; on macOS it was already private). When it can't be made private, the upload fails. DispatcherHandler.corsConfigurationFor(request)gives the CORS rules of the mapping that would take a request, in the dispatcher's order (Spring'sHandlerMappingIntrospector): a WebSocket handshake gets thecorsConfigurationSourcebean's rules or none, never theWebMvcConfigurermappings.- A port in use fails the start, as in Spring Boot: the application waits for the server to listen before the
ApplicationStartedEvent, the runners and theApplicationReadyEvent,runAsyncthrows thePortInUseException, and the program ends with a non-zero exit code. - Graceful shutdown lets running requests finish within
spring.lifecycle.timeout-per-shutdown-phasebefore the beans are destroyed (the lifecycle processor now has that timeout too). - An upload that fails inside a part (a limit, a broken body, a client that went away) is answered (413 or 400) instead of hanging, and its temporary files are deleted at once.
- Uploads go to a private directory of the process (
0700, random file names), made again when the system cleans its temporary directory. @CrossOrigindefaults no longer widen the global CORS rules, as Spring's default markers:@CrossOrigin(methods: ['PUT'])under/api/**keeps the global origins and addsPUT.allowPrivateNetworkon@CrossOrigin,CorsConfigurationandCorsRegistrationanswers Chrome's Private Network Access pre-flights withAccess-Control-Allow-Private-Network: true;CorsConfiguration.validateAllowCredentials()andvalidateAllowPrivateNetwork()are Spring's checks.- CORS origins are checked by
AllowedOrigins: port lists (http://localhost:[3000,3001],[*]), comma-separated values and a trailing/, as in Spring. Differs from Spring: anOriginlonger than 1024 characters is never allowed (a pattern with several*froze the server on a largeOrigin). - A pre-flight is answered from the CORS rules of the first handler of its path and method, and an actual request from those of the handler that answers it, chosen with all its conditions. Differs from Spring when several handlers match a pre-flight equally: Spring allows it from any origin, here the other handlers' origins get a 403 (give such handlers the same rules).
- Event streams (
Stream,EventStream, NDJSON) are paused while an event is written, so a client that reads slowly slows the stream down instead of filling the server's memory; anSseEmitterwhose unawaited sends reach 1000 for a client that reads nothing fails the nextsendwith aStateErrorand ends. ResponseEntity<SseEmitter>,ResponseEntity<EventStream<T>>andResponseEntity<Stream<T>>write their events with the entity's status and headers.- Compression covers every response of
server.compression.mime-types(Spring Boot's list) of at leastserver.compression.min-response-size(2 KB), not only byte streams; never one with a strongETag; and every answer that could be compressed getsVary: Accept-Encoding, also for a client without gzip, as Tomcat. - A body the handler does not read (an upload answered with a 413 or 415) gets its answer first; then up to 2 MB are read and dropped, for at most 5 seconds, as Tomcat's
max-swallow-size, so the connection can serve the next request. - A handler that fails after the response was committed drops the connection without finishing the response, as Tomcat does (the response looked complete).
- A response header
dart:iocannot send (non-ASCII, a line break) is dropped and the error answered with a 500 problem (was an empty 200). - The server answers the WebSocket handshake itself: a missing, repeated or malformed
Sec-WebSocket-Keyis a 400, anotherSec-WebSocket-Versiona 426. - A 5xx answer an exception asks for (a
ResponseStatusException, the 503 of a refused session) is logged as a warning without a stack trace, at most once every 10 seconds for the same kind; an unexpected exception is logged with its stack trace. ServerPropertieshas the new settings:compressionMimeTypes,compressionMinResponseSize,connectionTimeout,webSocketMaxTextMessageSize,webSocketMaxBinaryMessageSize,webSocketSendBufferSizeLimit,webSocketSendTimeLimit.
0.4.3 #
- Flutter security (#15)
- feat: single sign-on with OAuth 2.0 / OpenID Connect (spring_boot_sta… (#11)
- docs: the READMEs and docs/ rewritten: what it is, why and when you need it, an example, then Spring (#10)
0.4.2 #
- Docs (#9)
0.4.1 #
- feat: guides replace the examples; optional parameters from Dart nullability; coloured logs
- feat: guides replace the examples; optional parameters from Dart nullability; coloured logs
0.4.0 #
HttpSession:request.getSession(), an in-memoryHttpSessionRepositorywith expiry and a limit (server.servlet.session.timeout,.max-sessions,.cookie.*;SameSite=Laxby default),changeSessionId(); the session cookie is written byHttpSessionFilter.@RequestParamreads the fields ofapplication/x-www-form-urlencodedbodies too, like Spring MVC (readFormParameters).- A request target starting with
//is no longer read as a host bydart:io: the path keeps its//(and is answered 404, or refused by the security starter's firewall).
0.3.2 #
- Initial version: Spring MVC for Dart.
@RestControllerclasses with@GetMapping,@PostMapping, ... ondart:io'sHttpServer(server.*properties), path variables, request parameters, headers and cookies, JSON bodies through theObjectMapper,@Validbodies,ResponseEntity, errors asProblemDetailwith@ExceptionHandlerand@RestControllerAdvice, server-sent events (EventStream,SseEmitter), filters, interceptors, CORS, WebSocket endpoints ofspring_boot_starter_websocketon the same server;MockMvcandspringBootTest(webEnvironment:)inspring_boot_starter_test.