dox_core 3.1.2
dox_core: ^3.1.2 copied to clipboard
A perfect solution for your web backend development with dart that offers a wide range of features to help you build powerful and scalable web applications.
3.1.2 #
- Uses dox_validation 3.1.2, so
@Describe('...')can be used on DTO fields.
3.1.1 #
Cache().flexibletakes a nullstale: keep the last good value until a refresh replaces it.- New:
Hash.sha256(value)gives the SHA-256 hex of a value, to store API keys and access tokens as a hash (not for passwords). - New:
ValidationException.issueskeeps every issue of a failed DTO or schema check (path, rule code, message, params). AnExceptionHandlercan use it for another 422 shape, like[{field, message, rule}]. - New:
RequestBodyandBodyKindare exported, soreq.requestBody.kindcan tell invalid JSON (BodyKind.invalidJson) from no body, without importing a file oflib/http.
3.1.0 #
- Behavior change: without Redis,
RateLimitcounts are now shared by all isolates. Before, each isolate counted alone, so the real limit wasmaxtimes the number of isolates. Nowmaxholds for the whole server. An app with more than one isolate gets a lower real limit than before. Raisemaxif you relied on the old number. - Security: new check of the
Originheader on a WebSocket upgrade. UseRoute.websocket(allowedOrigins: [...])for one route, orAppConfig.webSocketAllowedOriginsfor all of them. It is off by default, so apps do not change. A WebSocket route with no check prints one warning on stderr at start. - Security fix:
back()only follows aRefererof the same host. A referer of another site goes to the fallback. Before, it was an open redirect. - Fix: a multipart body with no boundary or no
Content-Dispositiongets 400, not 500. - Fix: the headers of
abort()are kept on routes that have a next middleware. - Fix: a mounted handler that throws now gives 500. Before, the client could hang.
- Fix: a download name with quotes or non-ASCII letters now gets a safe
Content-Disposition. - New: one warning on stderr when CORS has origin
*together withcredentials: true. - New:
Crypt.encryptString/Crypt.decryptString(andCrypt.encrypt/Crypt.decryptfor JSON values) use theAPP_KEYof the app. Old keys inpreviousKeysstill decrypt. - New:
Hash.check, the same asHash.verify, with the Laravel name. - New:
Cache().many(keys)andCache().putMany(values, duration:)on the facade. The store already had them. - New:
MethodOverride(header: true)also reads theX-HTTP-Method-Overrideheader on a POST. It is off by default, so apps do not change. - Fix:
Route().setStatics(Route().statics)emptied the static folders. It cleared the list before it copied it. - Fix: after
%decoding, the.wherecheck now lets.match a new line. Before,.where('page', '.*')did not match/docs/a%0Abany more. req.pathIs(pattern)cleans each pattern once, not on every call.- Docs:
Route.viewis the WebDAVVIEWmethod. It does not render a template like LaravelRoute::view.Route.wheresays the value is checked again after decoding. - Security fix:
.wherenow checks the param again after%decoding. Before, it checked only the text as it is in the URL. So.where('name', '[^/]+')let/nos/..%2F..%2Fetc%2Fpasswdthrough, and the controller got../../etc/passwd. Now a decoded value that does not match the regex makes the route not match, like any other miss (the next route, the fallback or 404). Params with no%and routes without.wheredo not change. - Security fix:
req.pathIs()now cleans the path like the router does. Before, it only dropped the/at the start. So/admin/and/admin//reached the/adminroute, butpathIs('admin')was false, and a middleware guard on it was skipped. Now//counts as one/, and a/at the end does not matter, in the path and in the pattern. A path that matched before still matches. - Fix: with more than one isolate,
Route.staticFilesadded beforestartServer()and outside aRouterworked only in the main isolate. A file like/app.cssgot a 404 at random. Now the static folders go to every isolate, like routes and fallbacks. - Fix: a route with an optional param, like
/greet/{name?}, now shows in the OpenAPI docs as both/greetand/greet/{name}. Before, only/greet/{name}was there, because the?was removed beforedox_openapicould see it. - Fix:
req.session.reflash()now keeps the flash data when it is the first use of the session in a request. Before, it kept nothing, because the old flash keys were read before the session was loaded. - Docs:
hasdoes not read dot keys (onlyinput,filled,exists,missing,booleanandintegerdo).Route.redirectdoes not fill{id}in the target and drops the query string. A GET to a POST-only path gets the fallback if a fallback covers the path, else 405. A fallback added afterstartServer()does not reach the other isolates. - With
MethodOverride, a POST to a path with no route for any method gets 404 at once. Before, Dox read the whole body first. - Fix: with
MethodOverride, a POST with_method=PUTto a path with no PUT route now says "PUT is not allowed" in the 405 message. Before, it said "POST is not allowed". - The router calls
Route()once per route lookup, not twice. - The warning for a middleware that returns
nullreadsAPP_ENVonce, not on every such request. - Fix:
Crypt.decryptof a value made withCrypt.encryptString(text that is not JSON) gave a 500 error with aFormatException. Now it throwsDecryptException, a 400 error. - Fix: in the OpenAPI docs, the short path of an optional route, like
/greetfor/greet/{name?}, listed a 404 "Not found" answer. It has no param, so no record can be missing. Now only/greet/{name}lists the 404. - Fix: a
Route.fallbackin a group with a param in its prefix, like{tenant}, now matches, andreq.paramgives the param. Before, the prefix was compared as plain text, so the fallback never ran. - Fix:
.where('id', '^[0-9]+$')now matches. A leading^and a trailing$are dropped. Before, they ended up inside the route regex, so the route never matched. req.pathIs()andreq.routeIs()build the regex of a pattern once, not on every call.- Fix:
req.pathIs()gives false for a path with a bare%or a bad code like%zz. Before, it threw an error and the client got 500. req.boolean()no longer makes a new set of true words on every call.- New:
Route.match(methods, path, handler)adds one route for each method in the list.Route.any(path, handler)adds one for GET, POST, PUT, PATCH and DELETE. OPTIONS is left out, so the CORS preflight answer still works. - New:
Route.redirect(from, to, status:)andRoute.permanentRedirect(from, to). 302 and 301. - New:
.where(param, regex)on a route. The route matches only when the param text matches the regex. Else the next route is tried. Routes without it match the same as before. - New:
Route.fallback(handler)answers a GET or HEAD request that matches no route and no static file. It runs its middleware, so in a router the global middleware runs too. It follows the group prefix and domain. The built-in 404 and 405 do not change. - New: in development (
APP_ENVisdevelopmentorlocal), Dox prints a warning to stderr when a middleware returnsnull. That ends the request with an empty 200, and the controller does not run. Nothing else changes. - New request helpers on
DoxRequest:input(key, defaultValue)takes a default. A dot key likeuser.nameoritems.0.idreads inside maps and lists. A key that is in the input as it is still wins, so old code works the same.filled(key): the key is there and not empty.exists(key): the key is there, with any value.missing(key): the key is not there.hasdoes not change.except(keys),boolean(key),integer(key, defaultValue).file(key)andhasFile(key)for uploads.bearerToken()reads the token of anAuthorization: Bearerheader.path(),fullUrl(),pathIs(pattern)androuteIs(pattern). A*in the pattern matches any text. It ispathIs, notis, becauseisis a Dart keyword.
- Docs: the
abortexample now usesreq.auth?.id().req.auth?.iddoes not exist. - New:
MethodOverridemiddleware. Add it toAppConfig.globalMiddleware, and a POST request with a_methodfield ofPUT,PATCHorDELETEgoes to the route of that method. So a plain HTML form can reachRoute.put,Route.patch,Route.deleteandRoute.resourceroutes.req.methodgives the new method.CsrfMiddlewarestill checks the request. Other_methodvalues and other request methods are ignored. It is off when the middleware is not there. - Fix:
res.header(name, value)now replaces a header with the same name in any case, likewithHeadersdoes. Before,header('Cache-Control', ..)aftercache()sent twoCache-Controlvalues. - Break: removed the unused
middlewareAndControllerHandlerandgetDoxRequesthelpers. - Fix:
res.withHeaders(map)now adds the headers to the ones set before it. Before, it dropped them all, also theContent-Dispositionof a download. A key in the map replaces the header with the same name, in any case (cache-controlandCache-Controlare one header). - New:
SessionMiddleware().fromCookie(value)loads a session from its cookie value, with no request. Use it for a Socket.IO login. Give the value as the browser sent it (still encrypted). It runs the same checks as the middleware: a changed cookie, a cookie of another name, a cookie made with another APP_KEY, an expired session or an unknown session id gives an empty session. Changes to it are not saved. - New:
DoxCookie.decrypt(name, value)decrypts a cookie value likereq.cookie(name)does.req.cookienow uses it, with no change in what it accepts. - New:
package:dox_core/command.dartwith theCommandbase class. A package or an app adds its owndoxcommands with it. Name a library with a top-levelcommandslist in thedox: commands:key of pubspec.yaml, and thedoxCLI finds it. It is a separate library, so it does not clash with otherCommandclasses (like the one ofpackage:args). - New:
MigrationFileinpackage:dox_core/command.dart. A command writes a migration intodb/migrationwith it, with the same file name asdox make:migration. It does not write a second migration with the same name. - Fix:
Route.mountmatches whole path parts only. A mount at/adminnow gets/admin,/admin/and/admin/..., but no longer/administratoror/admin-login. Only requests that went to the wrong handler change. A prefix that ends in/(like Socket.IO's/socket.io/) works as before. - Fix:
//counts as one/when a path is matched to a mount prefix. So/admin//secure/xgoes to a mount at/admin/secure. The handler still gets the path as it came. - Change: when two mount prefixes match, the longest one wins. Before, the first one added won. Apps with one mount, or with mounts that do not overlap, do not change.
- New:
Route.mount(prefix, handler, middleware: true)runsAppConfig.globalMiddleware(security headers, CSRF, rate limit) before the raw handler. A middleware that gives a response stops the request.RequestTimeoutdoes not run for a mount. When two mount prefixes match, the longest one wins.terminatesteps run once the answer is sent. The global CORS config does not apply to a mount. The default is still no middleware, so Socket.IO does not change. RequestFile.headnow keeps the first 128 KB of a big upload (on disk), and all bytes of a small upload (in memory, no copy). Before, it kept 32 bytes. A JPEG keeps its image size after the photo metadata, so@Dimensionsandv.file().dimensions()need these bytes.- New:
Route.staticFiles(prefix, folder, maxAge:)serves a folder (CSS, JS, images) for GET and HEAD requests that match no route. Routes always win..., dot files and folders get 404. Files getLast-Modified,304, Range support, the global CORS headers and an optionalCache-Control. The prefix followsRoute.group, the router prefix andRoute.domain. - New:
Route.staticFilessends a weakETag(from the file size and change time). A matchingIf-None-Matchgets304with no body.If-None-Matchwins overIf-Modified-Since. - New:
Route.staticFilessends files in a.well-knownfolder at the top of the folder (ACME challenges,security.txt). Every other dot name, also inside.well-known, still gets404. - Fix: a file response with
If-Rangenow uses theRangeonly whenIf-Rangeis the file'sLast-Modifieddate or its strongETag. Else it sends the whole file with200, so a resumed download does not mix two versions of a file. - Fix: a
Range,If-Modified-SinceorIf-None-Matchheader sent on two lines no longer gives500. - Fix: a static file
304has the file's content type, nottext/plain. - New:
AppConfig.idleTimeout. A keep-alive connection with no new request for this long is closed. Default 30 seconds, as before. Behind a load balancer, set it longer than the balancer's idle timeout (eg. 65 seconds for AWS ALB). Needs dox_http_native 3.0.3. - New:
AppConfig.maxConnections. The most connections one isolate holds open at once. Must be 1 or more (0 or less throws anArgumentErrorwhen the server starts). When it is full, new clients wait in the OS listen queue until a connection closes. WebSocket connections and connections that are still closing count too. The queue holds up to 1024 clients, and the OS can make it smaller (somaxconn; 128 by default on macOS). When the queue is full, new clients wait and then time out. On Linux each new client goes to one isolate (SO_REUSEPORT), so it can wait for a full isolate even when others have room. Default no limit, as before. Needs dox_http_native 3.0.3. - New:
Route.websocket(maxMessageSize:). A bigger message closes the socket with 1009. Default 16 MB, as before.
3.0.2 #
- Change: a Redis cache store with no
prefixgets<app name>_cache:fromAPP_NAME(likemy_shop_cache:), so two apps on one Redis do not share keys or flush each other's cache. Existing Redis cache keys are not found once after the update. Rate limit counters, cache locks andcachedriver sessions start fresh, so users with acachedriver session log in again. Set'prefix': 'dox_cache:'on the store to keep the old keys. - New: the
Cache()facade hasflexibleandtouch, andremember/rememberForevertakedecodeandlock. - Perf: the route binder does not load a value again when a middleware already bound it, like
Can(param: 'post')of dox_auth. - New:
RequestIdmiddleware. Each request gets an id in theX-Request-Idheader and inreq.requestId. A safe id from the client is kept.LogMiddlewarelines haverequest_id, andExceptionHandler.reportcan readExceptionHandler.requestId. - New:
ETagmiddleware. GET and HEAD answers with a body in memory get a weakETag. A matchingIf-None-Matchgets304 Not Modifiedwith no body. - New: signed urls.
Route.signedUrl(name, params:, query:, expires:, absolute:, baseUrl:)adds asignature(HMAC-SHA256 with thedox:signed-urlkey) and an optionalexpires. TheValidateSignaturemiddleware answers403for a bad or expired url. Old keys fromAppConfig.previousKeysstill work. - New:
RequestTimeout(Duration)middleware andRoute.get(...).timeout(Duration). A slow route answers503with the coderequest_timeout. The late result is dropped. - New:
IDoxTerminableMiddleware. Itsterminate(req, res)runs after the answer is sent. An error in it is reported, and never changes the answer. Routes without one do not pay for it. - Fix: a middleware item with no
handlestep at the start of a route list ended the request with an empty answer. It is now skipped.
3.0.1 #
- Perf:
AESEncryptor(encrypted cookies, the cookie session, CSRF) now runs AES-GCM in Rust (AesGcmof dox_http_native 3.0.2), with the AES instructions of the CPU. One session cookie took about 116 µs to encrypt and 95 µs to decrypt, now about 1.7 µs and 0.9 µs. The output is the same, so cookies made before still open. - Removed the
pointycastledependency. HKDF now uses HMAC-SHA256 ofpackage:crypto(from the Dart team). The keys are the same. - New: a path that has routes, but not for the request method, answers
405 Method Not Allowedwith anAllowheader (likeAllow: GET, POST, HEAD). Before, it was404. - New:
abort(code, [message, headers])andHttpStatusException(code)for any status. Common codes get a defaulterrorCodeandmessage. Theheadersare sent with the answer. - New:
ForbiddenHttpException(403) andMethodNotAllowedHttpException(405). - New: bracket keys in form bodies and query strings nest, like in Laravel.
tags[]=a&tags[]=bgivesreq.input('tags') == ['a', 'b'], andaddress[city]=xgives a map. A repeated key with no brackets still keeps its last value. - New:
AppConfig(previousKeys: [...]). Encrypted cookies and cookie sessions made with an oldAPP_KEYcan still be read, so a new key does not log out every user.AESEncryptor.decodehas a newpreviousSecretsoption. - Fix: path params are now URL-decoded.
/users/a%20bgives'a b', not'a%20b'. A URL fromRoute.url()now gives back the same value. A param that is not valid UTF-8 (like%ff) gives 404. - Fix: 5xx HTTP exceptions (like
InternalErrorExceptionandQueryException) are now sent toExceptionHandler.report. Before, everyIHttpExceptionwas skipped, so real server errors never reached the logs or Sentry. 4xx exceptions are still not reported. - Fix:
req.headersjoins repeated header values with,. Before, twoAcceptvalues became one string with no separator. - Fix: a
Rangeheader with a number too big for an int gave 200 with an empty body. A start that is too big now gives 416, and a suffix that is too big gives the whole file. - Fix:
req.methodis now the request method. A HEAD request on a GET route saidGET. - Fix: on a route with no params, a write to
req.paramstayed for the next requests to that route. Each request now has its own map.
3.0.0 #
- The first stable release.
3.0.0-rc.1 #
- Breaking: Dox no longer uses
APP_KEYas it is. It makes one key per purpose from it with HKDF-SHA256 (RFC 5869):dox:cookiefor cookies and the cookie session,dox:encryptforAESEncryptor, and more. See the newKeyDerivationclass. Existing encrypted cookies, sessions and tokens become invalid after this version. Users must log in again. AESEncryptor.encodeanddecodetake apurposelabel. The default isdox:encrypt. The secret can now have any length.- Fix:
Hash.makeandHash.makeAsyncwith a password over 72 bytes threw the bcrypt error, and its text held the password. So the password could end up in the logs. Dox now throws its ownArgumentErrorwith no password in the text. - New:
Hash.equals(a, b)compares two secrets in constant time.CsrfMiddlewareuses it. - Fix: the
cookiesession driver did not check the save time in the cookie. A copied cookie stayed valid untilAPP_KEYchanged. A session older thanlifetimeis now a new empty session. - Breaking:
req.authis now its own field on the request, not a value in the input. Before, a query or body fielddox_authentication_instancemadereq.auththrow a 500.AUTH_REQUEST_KEYis removed. UseAuth().setUser(req, user)or setreq.auth. - Fix:
APP_ENV=testingstopped the app on a badAPP_KEY. It is now treated liketest: only a warning. Loggeris now exported frompackage:dox_core/dox_core.dart.- The HTTP exceptions (
NotFoundHttpExceptionand the others) take adynamic message, likeIHttpException. A map message is sent as JSON as it is, likeNotFoundHttpException(message: {'error': 'not_found'}). - Security: the server now sends
X-XSS-Protection: 0, not1; mode=block. OWASP says the old browser filter should be off.SecurityHeadershas a newxssProtectionoption. Anulloption (or anullvalue inextra) now removes the header, also a server default likeX-Frame-Options. - Security:
CsrfMiddlewareno longer reads_tokenfrom the query string. It reads only the body and theX-CSRF-TOKEN/X-XSRF-TOKENheaders. - Doc comments: fixed the examples of
Cache.remember,Route.domain,Route.middlewareandRoute.disabledCors.
3.0.0-alpha.17 #
- Version bump only, to match the other packages.
3.0.0-alpha.16 #
- Version bump only, to match the other packages.
3.0.0-alpha.15 #
- Change: the startup hook is
DoxProvider. Writeclass X extends DoxProvider.setup()runs once in every isolate at start.shutdown()is optional and runs once in every isolate when the app stops. List providers inAppConfig.providers, or add them withDox().addProvider()andDox().addProviders(). The container method isc.provider<T>().
3.0.0-alpha.14 #
- Fix:
AUTH_REQUEST_KEYis now only indox_core, so importingdox_coreanddox_authtogether no longer gives an "ambiguous import" error. - Fix:
response(x).header('Content-Type', 'image/svg+xml')failed with "More than one value for header content-type". The defaulttext/plainstayed next to the new type. Nowheader()replaces the content type, the same ascontentType(). The name can be in any case. - Fix: when sending the error answer failed too, the client got no answer and waited until the timeout. Now Dox reports the error and sends a plain 500. When the head is already sent, it closes the response.
- New:
.env.testing. When the app runs its tests (dart test, or a realAPP_ENV=testing), Dox reads.env.testingon top of.env. Its keys win over.env. A real environment variable still wins over both. It works forEnv.get()and the typedAppEnv.load(). Use it for a test database:DB_NAME=my_app_test. NewEnv.isTesting, andEnv().load(testing: true). - New:
DoxTestClient.withSession()gives a test client that works like a browser. It keeps the cookies of every response, removes deleted or expired ones, and sends theXSRF-TOKENcookie back inX-XSRF-TOKENonPOST,PUT,PATCHandDELETE. It does not follow redirects.client.cookiesgives the kept cookies. The normal client does not change. - Fix:
OpenApiConfig(enabled: null)now treats an emptyAPP_ENVasdevelopment, the same default asappEnvof the typed env. Before,/docswas off whenAPP_ENVwas not set (for example in CI). SetAPP_ENV=productionin production. - Change: an empty
APP_ENVmeans production everywhere in Dox:/docsis off, the 500 error text is hidden and a badAPP_KEYstops the app. SetAPP_ENV=developmentin your.envand in CI. TheappEnvdefault of the typed env in the docs and examples is nowproductiontoo (wasdevelopment). - New:
@Securityon a middleware class sets the login scheme of its routes in the OpenAPI docs.OpenApiConfig.securityByMiddlewarestill wins over it. - Change: a middleware class name that ends with
AuthMiddleware(and has no@Security) now gets abearerAuthscheme with nobearerFormat. Before, the docs called every such token a JWT.
3.0.0-alpha.13 #
- New:
OpenApiConfig(include: ['/api/*'], exclude: ['/api/internal/*'])picks which routes go in the OpenAPI docs by path. A*at the end matches the rest of the path.
3.0.0-alpha.12 #
- Breaking:
dox_coreno longer depends ondox_storageanddox_socketio. Add the package you use to your app.- Storage:
Storage(),StorageConfigandStorageDiskFactorymoved todox_storage.AppConfig.storageis removed: extendStorageServicefromdox_storage(class AppStorageService extends StorageService { AppStorageService() : super(storageConfig); }).Route.files()is nowStorage.route().Dox().storageis nowStorage.manager.RequestFile.store()needsimport 'package:dox_storage/dox_storage.dart'. - Socket.IO:
SocketIoConfigmoved todox_socketio.AppConfig.socketIois removed: extendSocketIoServicefromdox_socketio.Dox().socketIois nowSocketIoService.server. dox_coreno longer exportsDisk,DriveManager,FileNotFoundExceptionandStorageException. Import them fromdox_storage.StreamFileandDownloadableFilestay indox_core.
- Storage:
- Fix: the OpenAPI docs could give a route the docs of another controller with the same method name. The docs now pass the controller class of
(C c) => c.methodandRoute.get<C>routes to the registry. - New:
ControllerAction.controllergives the controller class.
3.0.0-alpha.11 #
- New: middleware that runs after the controller. Implement
IDoxNextMiddleware(handle(DoxRequest req, Next next)), or use a function(DoxRequest req, Next next).await next()runs the rest of the middleware and the controller and gives aDoxResponse, so the code after it can change the status, headers and cookies. A thrown error comes back as its error response, so the after part still runs. Before-only middleware work as before. A route with nonextmiddleware takes the same fast path as before. - New:
DoxResponse.statusandDoxResponse.headersgetters. - New:
redirect(url, status: 302)andback(req, fallback: '/')helpers. - New: sessions.
SessionMiddlewaregives each requestreq.sessionwithget,put,has,forget,all,flush,pull,flash,reflash,keep,regenerate,invalidateandtoken.AppConfig(session: SessionConfig(...))picks thecookiedriver (all data in a cookie, encrypted with AES-GCM, 4 KB limit) or thecachedriver (a random id in the cookie, the data in adox_cachestore likeredis). The cookie isHttpOnlyandSameSite=Laxby default. The cookie driver reads the session on first use. The session is saved, and the cookie sent, only when it changed. - New:
CsrfMiddleware.POST,PUT,PATCHandDELETErequests must send the session token in_token,X-CSRF-TOKENorX-XSRF-TOKEN, else419(TokenMismatchException). It sets anXSRF-TOKENcookie that axios and Inertia send back for you.except:skips paths like/api/*. - New: a validation error on a request with a session that does not want JSON (a form post, an Inertia request) flashes
errorsand the old input (never password fields), and redirects back. JSON requests still get422. Read them withreq.session.errorsandreq.session.old('email').req.session.flashErrors(...)does the same by hand. Newreq.expectsJson()andreq.hasSession. - New:
DoxCookie(sameSite: SameSite.lax). - New:
TestResponse.cookiesandDoxTestClient.withCookies(...), to go on with a session in tests.
3.0.0-alpha.10 #
- Version bump only, to match the other packages.
3.0.0-alpha.9 #
- New: a real IoC container.
IocContainer(Dox().ioc) hasget<T>(),has<T>(),singleton<T>(),factory<T>(),instance<T>(),override<T>()andclearOverrides(). Each isolate has its own container: a singleton is one instance per isolate, made on first use. - New:
AppConfig(container: registerContainer)with the function fromlib/container.g.dart(made bydox_builder). Every isolate runs it. The routers and services it finds are registered for you, soAppConfig.routersandAppConfig.servicescan stay empty. The ones you list there still work and come first. - New: typed routes.
Route.get<UserController>('users', (UserController c) => c.index)(also post, put, patch, delete and the others). The controller comes from the container, once per isolate, at start.(UserController c) => c.indexwithout<UserController>works too.Route.resource('users', UserController)takes a controller class. - New: a controller parameter whose type is in the container gets it from the container.
- New: route model binding for
@DoxModelclasses. A controller parameter of typeUsergets theUserof the{user}param (or the next param), found by its primary key. No row answers 404.Route.bindstill wins. - Breaking: the old
IocContainermethodsregister,registerByName,registerSingletonandgetByNameare removed. Usefactory,singletonandget. - New: named routes.
Route.get('/users/{id}', c).name('users.show'), thenroute('users.show', params: {'id': 5})gives/users/5.query:adds a query string.Route.group(..., name: 'admin.')puts a prefix in front of the names inside. Resource routes are namedblogs.index...blogs.destroy;Route.resource(..., name:)picks another base name. A name used twice stops the app at start. An unknown name, a missing param or an extra param throws anArgumentError. - New: route model binding without
dart:mirrors.Route.bind<User>('user', (String id) => User().find(id))runs before the controller of every route with a{user}param. The controller gets it as aUser userparameter, or withreq.bound<User>('user'). A binder that givesnullanswers 404. - New:
ExceptionHandler, the one place that turns an error into a response. Extend it and setAppConfig(exceptionHandler: ...).report(error, stackTrace)logs,render(error, req)picks the response. The default answers are the same as before. A URL with no route goes through it too. - Breaking:
AppConfig.errorHandleranddefaultErrorHandlerare removed. OverrideExceptionHandler.reportinstead. It gets the same errors: every error that is not anIHttpException, also errors outside a request. A thrown value that is not anExceptionorError(like aString) is now reported too. responseHandlerstill works and still runs afterrender.- New:
RateLimitmiddleware.RateLimit(max: 60, per: Duration(minutes: 1), key: (DoxRequest req) => req.ip()). One request too many gets429withRetry-After. Every answer hasX-RateLimit-LimitandX-RateLimit-Remaining. The counts live in Redis when the default cache store is Redis (shared by all isolates and servers), else in memory. Fixed window. NewTooManyRequestsException. - New:
SecurityHeadersmiddleware. It addsX-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-PolicyandCross-Origin-Opener-Policy, andStrict-Transport-Securityon https requests only. Each one can be changed or turned off withnull.Content-Security-Policyis off by default. - New:
Route.health('/health', checks: {...}). It answers 200{"status": "ok"}, or 503 when a check throws, returnsfalseor times out.HealthCheck.cache()checks the cache. The error text is in the answer only indevelopmentandlocal. - New:
package:dox_core/testing.dartwithDoxTestClient.DoxTestClient.start(appConfig)runs the app in the test on a free port.get,post,put,patch,delete,send,withHeadersandwithToken.TestResponsehasstatus,body,json,headersandexpectStatus,expectJson,expectBody,expectHeader. - dox_core now depends on
matcher(for theexpectmethods ofTestResponse).
3.0.0-alpha.8 #
- Uses dox_http_native 3.0.0-alpha.8: pipelined requests are about 2.8 times faster (1 core: 172k to 480k req/s). Normal requests are as fast as before.
- Security: every file response (
File,Storage().stream(),Storage().download(),Route.files) sendsX-Content-Type-Options: nosniff. Before, aFileresponse and a raw download did not. - New:
Route.files(path, download: true)sends every file as a download (Content-Disposition: attachment). Use it for user uploads. html, svg, xml and javascript files are still always sent as a download in a sandbox. - Fix: a trusted proxy
X-Forwarded-Forhop with a port (1.2.3.4:5678,[::1]:5678) is now read without the port. Before, it was not a valid ip, soreq.ip()could be wrong. A bare IPv6 like::1still works.
3.0.0-alpha.7 #
- Breaking:
dox_websocketis gone. Usedox_socketiofor realtime.Dox().websocketandDox().setWebsocket()are removed. - Breaking:
Route.websocket(path, handler)is now a raw WebSocket route.handlergets the openWebSocketand theDoxRequest. Middleware runs first. A request that is not a WebSocket upgrade gets 400. dox_http_nativenow lives in its own repo (dartondox/dox-http-native). Dox uses it from pub.dev (^3.0.0-alpha.7). Its build hook downloads a ready-built library, so you do not need Rust.- Perf: faster route matching. A plain route is found with one map lookup, also when a param route was added before it (as long as that param route can not match the same path). Param routes are grouped by their first path segment, so a request only tries the routes that can match. The first matching route still wins.
- Perf: the request handler reads
HostandContent-Typeonce per request.Hostis read only when the app has aRoute.domain()route.HttpBody.read()takes an optionalcontentType. - Perf: on the native server, a JSON or form body is used as it is, with no stream in between. The size limit is still checked.
- Perf: JSON responses are encoded in 8 KB chunks, not 256 bytes.
- Perf: fewer objects per request.
RouteData.params,DoxRequest.paramandDoxRequest.methodare made on first use.RouteData.domainLoweris made once. CORS header lists are joined once. - Perf: one Future less per async request (
then(onError:)instead ofthen().onError()). - Perf: multipart parsing makes its regexes once, and no longer builds a
Future.anyper chunk. - Perf:
LogMiddlewarelowercases theredactwords once, not per key. - Perf: an error response reads
APP_ENVand lowercases it only when the value changed. - Perf:
IocContainercallsT.toString()once per call. - Breaking: the
dart:ioserver is gone. Every app runs on the native server (dox_http_native), also an app with WebSocket or Socket.IO routes.AppConfig.server, theServerBackendenum and theDOX_SERVERenv var are removed.DoxServer.usesNative()and theisolateIdoption ofDoxServer.listen()are removed. - Breaking: when the native library does not load, Dox stops at start with an error. It says why (eg. an ABI mismatch) and how to fix it (install Rust so the build hook can build it, or rebuild your
lib_pathlibrary). Before, Dox logged a warning and useddart:io. - On a graceful stop (SIGTERM,
Dox().stop()), open WebSockets get close code 1001. - Perf:
AESEncryptorkeeps the key bytes of the last secret and writes the output into one buffer. Decode reads the nonce and text without copies. The output format is the same. - New:
Hash.makeAsync()andHash.verifyAsync(). They run bcrypt in another isolate, so a request does not block the server. - New:
Hash.rounds(default 10) sets the bcrypt cost.Hash.make()also takesrounds:. - Security: the app does not start when
APP_KEYis empty, not exactly 32 characters, or one of the old public sample keys from.env.example. With such a key anyone can sign tokens and cookies. InAPP_ENVdevelopment,localortestDox only logs a warning. A longer key (eg. 48 or 64 characters) is refused too, because AES needs a key of 16, 24 or 32 bytes and every encrypted cookie failed with it. Before, only an empty key inproductionstopped the app. Rundox key:generatefor a new key. - Security: a multipart part header with many backslashes (
name="\\\\...) no longer blocks a server isolate for hours (ReDoS in the quoted string regex). - Security: a
StreamFileresponse (Storage().stream(),Route.files()) with an html, xml, svg or javascript type is sent withContent-Disposition: attachmentandContent-Security-Policy: sandbox. EveryStreamFilegetsX-Content-Type-Options: nosniff. Before, an uploadedx.htmlorx.svgwas shown inline, and its script ran on the app origin (stored XSS). - Security: a
Route.domain()route only matches when theHostheader matches. Before, a request with noHostmatched every domain route. AHostwith a port (admin.app.com:8080) now matchesRoute.domain('admin.app.com'). - Security: an encrypted cookie is bound to its name (AES-GCM associated data). A value made for
localegivesnullwhen it is sent asuser_id.AESEncryptor.encode()/decode()take a newassociatedDataoption. Encrypted cookies made by older versions no longer decrypt, soreq.cookie()givesnullfor them once. Users may need to log in again. - Security:
DoxCookieisHttpOnlyby default.secureisnullby default, which setsSecurewhen the request came over https (req.scheme(), seetrustedProxies). SethttpOnly: falseorsecure: falseto change it. - Security: the native server checks
maxBodySizeandmaxMultipartSizeon their own. Before, Rust let any body up to the bigger limit (100 MB) through, so a JSON body could take 100 MB of RAM before Dart said 413. - New
AppConfig.bodyTimeout(default 120 seconds). On the native server, a client that does not send the whole body in this time gets 408. It stops slow clients that hold memory and connections.
3.0.0-alpha.6 #
- Security:
Storage().putRequestFile()andRequestFile.store()no longer trust the client file name when the bytes are a known format that does not match it. Before, a valid png sent asa.htmlpassed@Image/@Mimesand was saved as.html, soRoute.files()served it astext/htmlon the app origin (stored XSS). Now it is saved with the extension of the bytes (.png). - Security: new upload file names use a random uuid (v4), not a time-based one (v1). A v1 name shows the upload time and the server, and the next name is easy to guess.
- Security:
trustedProxies: ['*']trusts the direct peer only.req.ip()is the right-mostX-Forwarded-Forhop, the one the proxy added. Before,*also trusted every hop in the header, soreq.ip()was the left-most value, which the client sets (X-Forwarded-For: 6.6.6.6). That broke IP rate limits and allow lists, also behind the Fly.io proxy of the deploy guide. - Security:
LogMiddlewarewrites[REDACTED]for body fields and headers whose name haspassword,passwd,secret,token,authorization,cookie,api_key,api-keyorapikeyin it (upper or lower case). Before, the login body (with the password) and, withwithHeader: true, theAuthorizationheader went to the log as plain text. Change the list withLogMiddleware(redact: [...]). - Security: an error that is not an
IHttpExceptionanswers500with{"code": "server_error", "message": "Server Error"}. The error text is sent only whenAPP_ENVisdevelopmentorlocal. Before, the client always goterror.toString(), which can hold SQL, table names, file paths or database hosts.errorHandlerstill gets the full error. - Graceful shutdown closes the cache stores of
Cache()(eg. Redis), in every isolate, after theDoxShutdownservices. Your app needs no extra code. - The
ORMServiceof a new project implementsDoxShutdownand callsSqlQueryBuilder.close(), so the database connections close on shutdown too. In an older project, add this tolib/services/orm_service.dart:class ORMService implements DoxService, DoxShutdown { // setup() as before @override Future<void> shutdown() async { await SqlQueryBuilder.close(); } }
3.0.0-alpha.5 #
- Fix: CORS
origincan be a list. Dox compares the requestOriginwith the list and sends back only the origin that matches, or no header. Before, it joined the list with commas, and browsers rejected it. A list also sendsVary: Origin. - Breaking:
AESEncryptornow uses AES-GCM with a random nonce for each message. Before, it used AES-CTR with an all-zero nonce and no message check. Old encrypted cookies and values can not be read. - Breaking:
AESEncryptor.decodethrowsDecryptException(400) when the text was changed or the key is wrong. Before, it printed an error and returned''. req.cookie()returnsnullfor an encrypted cookie that can not be decrypted.- Fix:
Hash.verifyreturnsfalsefor a malformed hash. Before, it threw. AESEncryptorandDecryptExceptionare now exported frompackage:dox_core/dox_core.dart.- Fix: in
.env,KEY=value # notegave the valuevalue # note. A#after a space now starts a comment. A#inside quotes, or with no space before it, is kept. - Fix: the
.envparser removed every quote character inside a value. Now only the quotes around the value are removed. - Fix:
HEADrequests got 404, alsoHEAD /docs. AHEADrequest now runs theGETroute of the same path. The answer has the status and headers ofGET, with no body. - New: Dox listens on
PORTwhenAPP_PORTis not set. Cloud Run, Fly, Render and Heroku setPORT.APP_PORTstill wins. - New:
AppConfig.hostand the env varAPP_HOSTpick the network interface to listen on, like127.0.0.1. The default is the same as before: all interfaces (::, IPv6 and IPv4). - Fix: the start log always said
http://127.0.0.1:<port>. It now shows the real host and port, likehttp://[::]:3000. - Removed the unused
sprintfdependency. - New: graceful shutdown. On
SIGTERMorSIGINTevery isolate stops taking new connections and lets open requests finish, up toAppConfig.shutdownTimeout(default 8 seconds). Then Dox runsDoxShutdownservices and exits with code 0. Before, open requests were cut. A second signal stops at once. - New:
DoxShutdown. A service that also implements it getsshutdown()in every isolate on stop, to close connections. - New:
await Dox().stop()stops the server gracefully in every isolate, without exit. - Fix: an
s3storage disk withoutbucket,keyorsecretfailed withNull is not a subtype of String. Now it fails with a clear message that names the missing setting. - Fix: a URL with no route now answers like a thrown
NotFoundHttpException. The body is JSON ({"code": "not_found", "message": "GET /x not found"}), and the appresponseHandlerruns for it, so apps can format it. - Fix: the error answer of an
IHttpExceptionnow has itserrorCode. A text message gives{"code": "<errorCode>", "message": "<text>"}as JSON. Before, the body was plain text anderrorCodewas never sent. AMapmessage (like validation errors) is still sent as it is. - Fix: the
Loggernow readsLoggerConfig.level. Lines below that level are not printed. Levels from low to high:debug,info(andsuccess),warn,error(anddanger). - Fix:
Route.group('blogs')withRoute.get('all')made/blogsall. Path parts now join with one/. This also works for nested groups,Route.prefixandRoute.resource. - Fix:
Route.resourcehid every error from the controller. Now it only skips methods the controller does not have.
3.0.0-alpha.4 #
- Version bump only, to match the other packages.
3.0.0-alpha.3 #
- New:
Cache().getRaw(key)gives the cached value as JSON text, with no decode. Send it as a JSON response with no extra encode step. - New Redis cache option
pool_size(seedox_cache).
3.0.0-alpha.2 #
- New default server:
dox_http_native(Rust hyper parses HTTP, your code stays in Dart). Hello world goes from about 41k to about 144k req/s on one isolate. Setserver: ServerBackend.dartIoinAppConfig(orDOX_SERVER=dartio) to usedart:io. Dox usesdart:ioby itself, with one warning line, when the native library does not load, and for apps with WebSocket or Socket.IO routes. - Faster requests: a simple
GEThello world went from about 31k to about 40k req/s on one isolate (plaindart:iois about 46k). A request with no body and sync middleware and controller now runs with noFuture.req.ip(), the query,req.all()and cookies are read on first use only. Reading the client ip was the biggest cost, because it asks the socket. Clean paths skip the two regex calls, and plain routes are found with a map lookup. - Fix: a
@Dtobody with aRequestFilefield shows asmultipart/form-datain the OpenAPI docs. - Fix:
req.ip()gave::ffff:127.0.0.1for IPv4 clients. Now it gives127.0.0.1. The same goes forX-Forwarded-Forhops. - Needs Dart 3.13 or newer.
- All dependencies updated to their latest versions.
- Removed the
encryptpackage (not maintained, and it blockedpointycastle4). AES for cookies and the file cache now usespointycastle4. The output is the same, so old cookies and cache files still decode. - Fix:
Env.get()of a missing key with no default gave the text"null". A missingAPP_KEYbecame the key"null", which anyone can guess. NowStringgives'',String?givesnull, andint/num/double/boolthrow a clear error. New:Env.get<bool>(). - Startup stops with an error when
APP_KEYis empty andAPP_ENV=production. Other environments log a warning. - New: typed input with DTOs. A controller asks for a
@Dtoclass by type:store(CreateUser body). Dox reads the body (or the query forGET), checks it, and answers422with{"items.1.quantity": "..."}when it is wrong.await req.validate<T>()does the same inside a method. See the Validation guide. - New:
AppConfig(dtos: dtos)(fromlib/dtos.g.dart) andAppConfig(validationMessages:)for other languages.req.localeis the firstAccept-Language. - New: typed controller parameters.
DoxRequestanywhere, one@Dto, andString,int,double,num,boolpath params by position. A path param that does not fit its type answers404. The types are read once at start; an unknown type stops the server with a clear error. - New: optional path params
{id?}. - New: query strings and form data with repeated keys (
tags=a&tags=b) and bracket keys (address[city],items[0][name]) for DTOs. - A DTO answers
400for a body that is not valid JSON, and415for a body type it can not read. RequestFileimplementsFileInputand keeps its first bytes, so@Mimesand@Imagecheck the real file type.- Breaking: removed
req.validate(Map),req.validateAsync(),FormRequest,AppConfig(formRequests:),DoxValidator,IocContainer.registerRequest()andprocessInputMapper(). Use a DTO.req.validateSchema(schema)stays for input you only know at runtime. - Breaking: a controller with more path params than the route has
{...}params stops the start. Before, the extra ones got''. - Fix: a typed path param like
int idgave a 500 (it was always aString). - A
ValidationExceptionfromdox_validation(for exampleschema.parse()in a controller) answers 422. dox_coreexportsdox_validation(not itsValidationException), with the DTO annotations.dox_ormalso hasSchema, andpackage:uuidhasUuid: usehideor a prefix in a file that needs both.- Breaking:
Cache()runs ondox_cache.AppConfig.cacheis adox_cacheCacheConfig(defaultStore:, stores:)with option maps.CacheDriverInterfaceandFileCacheDriverare removed. Values must be JSON types.putwithout a duration keeps the value forever (was 1 hour). New:add,pull,increment,decrement,remember,rememberForever,lock,tags. - Breaking:
Storage()runs ondox_storage.AppConfig.fileStorageis nowAppConfig.storage(StorageConfig(defaultDisk:, disks:)with option maps forlocal,s3,memory).StorageDriverInterfaceandLocalStorageDriverare removed. An unknown disk name throws. Storage().stream()anddownload()stream from any disk (S3 too), withContent-Length. New:url,signedUrl,use()for the fullDisk.- New
Route.files('files')serves local signed URLs (403 for a bad signature). New catch-all route param{path*}. - OpenAPI:
AppConfig(openApi: OpenApiConfig(...))adds/docs,/openapi.jsonand/openapi.yaml. On by default only whenAPP_ENVis development or local. Bodies come from the routebody:option or a DTO.Dox().writeOpenApi(dir)writes the files. RouteData.preMiddlewarenow holds the group and router middleware.- Socket.IO:
AppConfig(socketIo: SocketIoConfig(enabled: true, ...))serves/socket.io/on the app port, before the router.Route.mount(prefix, handler)is the raw hook. Off by default. - No extra work per request when OpenAPI or Socket.IO is off.
- OpenAPI docs are automatic.
dart run build_runner buildwriteslib/openapi.g.dart. Pass it asOpenApiConfig(registry: openApiRegistry). Each route then gets its summary and description (from the///comment), tags,Class.methodoperation id, typed path params, request (a@Dtoclass) and answer schema (from the return type), plus401,404,422and500. - A middleware whose class name ends with
AuthMiddlewareaddsbearerAuthto the docs, with no config. - A
GETorDELETErequest class becomes query params in the docs. dox_coreexportsOpenApi,OpenApiRegistry,OpenApiOperation,OpenApiRouteBindingandResponseDocofdox_openapi.- The OpenAPI JSON and YAML are encoded once and kept, not on each request.
- New
EnvReaderandEnvExceptionfor the typed env class (@DoxEnv()). The real environment wins over.env, the same asEnv.get().Env.get()stays for dynamic keys. - OpenAPI:
/docsuses Scalar by default.OpenApiConfig(ui: DocsUi.swagger)gives Swagger UI. NewOpenApiConfigoptions:scalar: ScalarOptions(...)(theme, dark mode, download button, proxy) andpageTitle.
3.0.0-alpha.1 #
- Version 3 of Dox. All Dox packages now share one version.
- Needs Dart 3.6 or newer.
- See the release notes for all main changes.
- Faster: streamed bodies, all CPU cores, faster router.
uuid4 andmime2.
2.0.0 #
- Change validation {attribute} to {field}
- Restructure response handler and middleware
- Restructure app configuration
- Extract websocket as separate package
dox-websocket - Type improvement on usage of auth package
- Bug fix on multithread with services
- Support storage class for file storage
- Add more unit/integration test coverage
- Fix bug for websocket running on multiple isolates
- Update cache config setting on app config
- Added support for multi-thread http server which is 10x faster on concurrency request
- Added support for services (i.e database, redis) to run on each isolate/multi-thread
- Added cache class that run file driver as default
- Added support for custom cache drivers, i.e redis, memcached
- Added
JSON.stringify()andJSON.parse()that support DateTime to encode - Added support for DateTime object on http response/return data
- Added support of size and bytes information on uploaded
RequestFile - Bug fixed on multipart form data file store
- Removed database config option in app config.
- Moved ioc container from
Global.ioctoDox().ioc - Improvement on routes
- Rename Handler interface to ResponseHandlerInterface
1.0.6 #
- Remove third party dot env package and replace with own
Envclass
1.0.5 #
- Modify request auth getter to function to support type injection
1.0.4 #
- Added interfaces/classes for authentication
- Bug fixed on router prefix
- Bug fixed on cookie return type
String?
1.0.3 #
- Ignore error on missing method of resource route
- Added single quote rule in linter
1.0.2 #
- Bug fixed method not found on resource route
1.0.1 #
- Added missing types on functions and arguments
- Added linter rules
1.0.0 #
- First stable release
1.0.0-alpha.2 #
- Support domain routing
- Improvement group routing
- Support middleware routing
- Added app level middleware support
- Added function's self documentation
- Websocket improvement and support multiple path
- Support serializer
1.0.0-alpha.1 #
- Added support for validation
- Added support for form data for file uploading
- Improve error handling
1.0.0-alpha.0 #
- Refactor the code
- Added test cases
- Added support for global middleware
- Added support to throw error exception in response and add support to handle via response handler
- Rename BaseHttpException to HttpException
- Bug fixed on cookie response
0.0.17 #
- Separate Query builder from core
0.0.16 #
- Replace with dox query builder
- Added feature to auto encode List
- Added resource routes
- Added
req.input()in DoxRequest - Added support CORS
- Added More Response options on DoxResponse
- Added
Hash.make('password')for password encryption - Added Support websocket