├── .gitattributes ├── .gitignore ├── AUTHORS ├── CHANGELOG.md ├── LICENSE ├── example ├── app.yaml ├── client │ ├── .gitignore │ ├── pubspec.yaml │ ├── static │ │ ├── bootstrap │ │ │ ├── bootstrap.css │ │ │ └── bootstrap.min.css │ │ ├── css │ │ │ ├── bootflat-extensions.css │ │ │ ├── bootflat-square.css │ │ │ ├── bootflat.css │ │ │ ├── bootstrap.css │ │ │ ├── bootstrap.min.css │ │ │ ├── font-awesome.css │ │ │ ├── font-awesome.min.css │ │ │ └── style.css │ │ ├── fonts │ │ │ ├── FontAwesome.otf │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ └── fontawesome-webfont.woff │ │ └── img │ │ │ ├── check_flat │ │ │ └── default.png │ │ │ └── dart_force_logo.jpg │ └── web │ │ ├── client.dart │ │ └── index.html └── server │ ├── advice │ └── text_advice.dart │ ├── controllers │ ├── about_controller.dart │ ├── count_controller.dart │ ├── error_controller.dart │ ├── login_controller.dart │ ├── path_controller.dart │ ├── post_controller.dart │ ├── redirect_controller.dart │ ├── rest │ │ ├── api_controller.dart │ │ ├── just_a_rest_controller.dart │ │ ├── readme.md │ │ └── response_body_controller.dart │ ├── secure_controller.dart │ ├── security │ │ └── session_strategy.dart │ └── status_controller.dart │ ├── interceptors │ └── random_interceptor.dart │ ├── pubspec.yaml │ ├── server.dart │ ├── test │ └── count_controller_test.dart │ └── views │ ├── about.html │ ├── count.html │ ├── error.html │ ├── form.html │ ├── info.html │ ├── locale.html │ ├── login.html │ ├── notfound.html │ ├── number.html │ ├── pathvar.html │ └── requestparam.html ├── lib ├── annotations │ ├── helpers.dart │ └── metadata.dart ├── converters │ ├── abstract_message_converter.dart │ ├── csv_message_converter.dart │ ├── http_message_converter.dart │ ├── json_http_message_converter.dart │ └── text_http_message_converter.dart ├── error │ ├── handler_exception_resolver.dart │ └── simple_exception_resolver.dart ├── force_mvc.dart ├── http │ ├── http_headers_wrapper.dart │ ├── http_input_message.dart │ ├── http_message.dart │ ├── http_message_regulator.dart │ ├── http_method.dart │ ├── http_output_message.dart │ ├── invalid_mime_type_error.dart │ ├── media_type.dart │ └── mime_type.dart ├── i18n │ ├── accept_header_locale_resolver.dart │ ├── cookie_locale_resolver.dart │ ├── default_locale_resolver.dart │ ├── fixed_locale_resolver.dart │ └── locale_resolver.dart ├── manager │ └── cookie_holder_manager.dart ├── render │ ├── mustache_render.dart │ └── view_render.dart ├── security │ ├── no_security_strategy.dart │ ├── security_context_holder.dart │ └── security_strategy.dart ├── server │ ├── force_request.dart │ ├── handler_interceptor.dart │ ├── http_request_streamer.dart │ ├── interceptors_collection.dart │ ├── model.dart │ ├── mvc_typedefs.dart │ ├── path_analyzer.dart │ ├── registry.dart │ ├── request_method.dart │ ├── response_hooks.dart │ ├── serving_assistent.dart │ ├── serving_files.dart │ ├── simple_web_server.dart │ └── web_application.dart ├── test.dart └── utils │ └── mime_type_utils.dart ├── pubspec.yaml ├── readme.md ├── resources └── dart_force_logo.jpg └── test ├── locale_resolver_test.dart ├── locale_test.dart └── path_test.dart /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dart-forcemvc2.iml 2 | # Dart excludes 3 | packages 4 | .packages 5 | .project 6 | .children 7 | *.js 8 | pubspec.lock 9 | .idea 10 | /dart-forcemvc.iml 11 | /.settings/ 12 | .pub 13 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Originally written by: 2 | Joris Hermans - http://github.com/jorishermans 3 | 4 | With contributions by: 5 | Robert Åkerblom-Andersson - https://github.com/Scorpiion 6 | Domenico Monaco - https://github.com/kiuz 7 | Nicolas Garnier - https://github.com/nicolasgarnier 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog ### 2 | 3 | This file contains highlights of what changes on each version of the forcemvc package. 4 | 5 | #### Pub version 0.8.2 & version 0.8.5 #### 6 | 7 | - Update version of wired to v0.4.5 8 | - improve @ResponseBody & httpmessageconverter logic 9 | 10 | #### Pub version 0.8.1 #### 11 | 12 | - Introducing @RestController 13 | - improve @ResponseBody 14 | - solve path variables with dash, issue #32 15 | 16 | #### Pub version 0.8.0 #### 17 | 18 | - Updating dependencies on logging 19 | - Clean up code 20 | - introduce @ResponseBody 21 | - HttpMessageConverters and HttpMessageRegulator are also available in this release with a JsonHttpMessageConverter implementation 22 | 23 | #### Pub version 0.7.3 #### 24 | 25 | - Introducing a new annotation @ResponseStatus, so we can indicate status code of the response 26 | 27 | #### Pub version 0.7.2 #### 28 | 29 | - Adopt code to async/await 30 | - Extract locale to an other dependency 31 | 32 | #### Pub version 0.7.1+1 #### 33 | 34 | - Improve the flow of client serving 35 | - Add more logging 36 | 37 | #### Pub version 0.7.1 #### 38 | 39 | - Allow completely handling the HttpResponse 40 | 41 | #### Pub version 0.7.0 #### 42 | 43 | - update forcemvc to wired 4.3 44 | - add web config and an easy change towards core components with di 45 | * SecurityContextHolder 46 | * HandlerExceptionResolver 47 | * LocaleResolver 48 | * ServingAssistent 49 | 50 | #### Pub version 0.6.1+1 #### 51 | 52 | - add improvement on static handling internally 53 | 54 | #### Pub version 0.6.1 #### 55 | 56 | - remove start page and use this instead! 57 | 58 | app.static("/", "index.html"); 59 | 60 | #### Pub Version 0.6.0+1 #### 61 | 62 | - Tighter version constraint on exported package 63 | 64 | #### Pub Version 0.6.0 #### 65 | 66 | - WebServer becomes WebApplication dart class 67 | - The .on method becomes .use method 68 | - app.static will skip the view rendering logic, ideal for angular and polymer applications 69 | - simplify code on some parts 70 | 71 | #### Pub Version 0.5.10 #### 72 | 73 | - Extending the possibilities of a Rest interface. 74 | 75 | #### Pub Version 0.5.9+1 #### 76 | 77 | - Fix a bug so that @Authentication works, and takes up BASIC as a role. 78 | 79 | #### Pub Version 0.5.9 #### 80 | 81 | - Improve the startup functionality, by adding startup fallback mechanism 82 | 83 | #### Pub Version 0.5.8 #### 84 | 85 | - Make the default value to startpage empty. 86 | It is better to use: 87 | 88 | app.use('/', (req, model) { 89 | return "index"; 90 | }); 91 | 92 | then startPage: "index.html" 93 | 94 | Be aware that then it becomes a template and that when you use polymer or angular you need to use another delimiter. 95 | 96 | #### Pub version 0.5.7+1 #### 97 | 98 | - Deprecate the method on, it is too confusing with the on method of dart force, the method 'use' is now the prefered way. 99 | - Fixing a crash when the startPage is not found. 100 | 101 | #### Pub version 0.5.7 #### 102 | 103 | - Changing the main class of WebServer to WebApplication. 104 | - Making WebServer Deprecated 105 | 106 | #### Pub version 0.5.6+1 #### 107 | 108 | - Fix issue serving without pub serve 109 | - put more control in accept_header_locale 110 | 111 | #### Pub version 0.5.6 #### 112 | 113 | - Apply pub serve trick also to view render logic. 114 | - Fallbacks / error handling when a resource is not been found. 115 | 116 | #### Pub version 0.5.5 #### 117 | 118 | - Add locale resolver mechanics into the framework 119 | - Easy use of the locale in the controller 120 | 121 | #### Pub version 0.5.4 #### 122 | 123 | - Add required boolean into @RequestParam(required: true) 124 | - Pub serve trick, issue 23 is been solved. 125 | 126 | #### Pub version 0.5.3+1 #### 127 | 128 | - Small adjustments so dart force can also work on appengine. 129 | 130 | #### Pub version 0.5.3 #### 131 | 132 | - Add better 404 handling 133 | - Serving the Client files from their URL relative to the script's location 134 | - Make dart-forcemvc WebServer compatible with App Engine Dart 135 | 136 | #### Pub version 0.5.2+1 #### 137 | 138 | - Fixing 'Replace only the first occurrence of /build' 139 | 140 | #### Pub version 0.5.2 #### 141 | 142 | - package 'Force IT' is been renamed to 'wired' 143 | 144 | #### Pub version 0.5.1 #### 145 | 146 | - Add Type checking in @ExceptionHandler 147 | 148 | #### Pub version 0.5.0+1 #### 149 | 150 | - Add getAuthentication in MVCAnnotationHelper 151 | 152 | #### Pub version 0.5.0 #### 153 | 154 | - Add PreauthorizeRoles and PreauthorizeFunc Annotations 155 | - Add possibility to define authorization roles 156 | - Define and extend the way security works in forcemvc 157 | - Upgrade to the latest version of 'force it' 158 | 159 | #### Pub version 0.4.0+1 & 0.4.0+2 #### 160 | 161 | - Adding MVCAnnotationHelper, with the method hasAuthentication(obj) to the package 162 | 163 | #### Pub version 0.4.0 #### 164 | 165 | - Remove parentheses from annotations. 166 | - Update to the latest version (0.2.0) of force_it 167 | - Add example folder to the git repository 168 | 169 | #### Pub version 0.3.8 #### 170 | 171 | - Introducing @ControllerAdvice so that you can make methods with @ModelAttribute & @ExceptionHandler that has inpact to all the controller 172 | 173 | #### Pub version 0.3.7 #### 174 | 175 | - General error handling with a HandlerExceptionResolver 176 | - Introducing the annotation @ExceptionHandler so you can use that in your @Controller classes when an error is happening. 177 | 178 | #### Pub version 0.3.6+1 #### 179 | 180 | - First steps in error handling 181 | 182 | #### Pub version 0.3.6 #### 183 | 184 | - Adding startSecure method, so you can start your webserver over SSL. 185 | 186 | #### Pub version 0.3.5+1 #### 187 | 188 | - Update package configuration 189 | 190 | #### Pub version 0.3.5 #### 191 | 192 | - Provide a class MockForceRequest to mock more easily ForceRequest in unittests 193 | - Upgrade of the unittest package and introducing of the mock package 194 | 195 | #### Pub version 0.3.4+1 & 0.3.4+2 & 0.3.4+3 #### 196 | 197 | - update rules for mustache rendering the view 198 | 199 | #### Pub version 0.3.4 #### 200 | 201 | - Add @RequestMapping on an object level possible 202 | 203 | #### Pub version 0.3.3+1 #### 204 | 205 | - Make @RequestParam working without a value 206 | 207 | #### Pub version 0.3.3 #### 208 | 209 | - Error logging when the server don't start 210 | - Add responseHooks into the web_server 211 | - Using variable name of the annotated @RequestParam when value is empty 212 | 213 | #### Pub version 0.3.2+1 & 0.3.2+2 #### 214 | 215 | - improve serving code of the start page. 216 | 217 | #### Pub version 0.3.2 #### 218 | 219 | - load values into your webserver from yaml files 220 | - cors 221 | - small improvement on the model object, it allows you to put anything in it 222 | 223 | #### Pub version 0.3.1 #### 224 | 225 | Fix a problem with serving dart files. 226 | 227 | #### Pub version 0.3.0+1 #### 228 | 229 | Fix a bug with passing by the startPage. 230 | 231 | #### Pub version 0.3.0 #### 232 | 233 | Add 'force it' package into this new version of the forcemvc so you can use @Autowired in the @Controller classes. 234 | 235 | #### Pub version 0.2.4 #### 236 | 237 | Add easier bootstrapping of the logging functionality serverside. 238 | 239 | #### Pub version 0.2.3 #### 240 | 241 | Implemented changes to the security api. Add an optional parameter {data} in the security strategy checkAuthorization class. 242 | 243 | #### Pub version 0.2.2 #### 244 | 245 | Refactoring of the use of webSockets, also provide httpRequest in the flow, so you get access to httpsession. 246 | 247 | #### Pub version 0.2.1+2 #### 248 | 249 | Make the security rules more general. 250 | 251 | #### Pub version 0.2.1+1 #### 252 | 253 | Make the securityContextHolder object in webserver public accessible. 254 | 255 | #### Pub version 0.2.1 #### 256 | 257 | New structure! 258 | 259 | #### Pub version 0.2.0 #### 260 | 261 | Adding security and authentication functionality 262 | 263 | #### Pub version 0.1.13+5 #### 264 | 265 | Move transformation method to the view renderer! 266 | 267 | #### Pub version 0.1.13+4 #### 268 | 269 | More logging! 270 | 271 | #### Pub version 0.1.13+1 & 0.1.13+2 #### 272 | 273 | Adding slashes in src="...", it will become src="/..." when needed of the template rendering is been performed! 274 | 275 | #### Pub version 0.1.13 #### 276 | 277 | Depend upon mustache4dart and not upon mustache anymore! 278 | 279 | #### Pub version 0.1.12+2 #### 280 | 281 | Small improvement on the view rendering part! 282 | 283 | #### Pub version 0.1.12+1 #### 284 | 285 | Changed ../build/ folder to ../build/web/ 286 | 287 | #### Pub version 0.1.12 #### 288 | 289 | Check also view, template files in the build folder, so after pub build in the web folder! 290 | 291 | #### Pub version 0.1.11+3 #### 292 | 293 | Better logging for view rendering! 294 | 295 | #### Pub version 0.1.11+2 #### 296 | 297 | Implement getPostRawData, so you can get the raw posted data! 298 | 299 | #### Pub version 0.1.11+1 #### 300 | 301 | Adapt getPostData to disable jsonifying the data! 302 | 303 | #### Pub version 0.1.11 #### 304 | 305 | Adding asynchronous requests to the possibilities now! 306 | 307 | #### Pub version 0.1.10 #### 308 | 309 | Fixing in @RequestMapping, the method part! 310 | 311 | #### Pub version 0.1.9+4 #### 312 | 313 | Adding redirect functionality. 314 | 315 | #### Pub version 0.1.9+3 #### 316 | 317 | Removing dependency on Uuid. 318 | 319 | #### Pub version 0.1.9+2 #### 320 | 321 | Add HttpSession & HttpHeaders in possible arguments in a controller method 322 | 323 | #### Pub version 0.1.9+1 #### 324 | 325 | Solve an issue with the code, please update! 326 | 327 | #### Pub version 0.1.9 #### 328 | 329 | Adding @RequestParam, so you can add this annotation to the controller class if you want to have easy access to querystring parameters. 330 | 331 | #### Pub version 0.1.8+3 #### 332 | 333 | Make static folder configurable and some small improvements on the regex expression. 334 | 335 | #### Pub version 0.1.8+2 #### 336 | 337 | Static folder for serving static files to the client! 338 | 339 | #### Pub version 0.1.8+1 #### 340 | 341 | Improved accesability of path variables. 342 | 343 | #### Pub version 0.1.8 #### 344 | 345 | New ways of view rendering and getting the templates. 346 | 347 | #### Pub version 0.1.7+2 & 0.1.7+3 #### 348 | 349 | Cleanup code webserver. Solving an issue! 350 | 351 | #### Pub version 0.1.7+1 #### 352 | 353 | Updated this buildPath: '../build/web' to get it working in Dart 1.2 354 | 355 | #### Pub version 0.1.7 #### 356 | 357 | Extend ForceRequest with getPostData and getPostParams to support post methods. 358 | 359 | #### Pub version 0.1.6 #### 360 | 361 | Adding path variables to the mvc part. 362 | 363 | #### Pub version 0.1.5 #### 364 | 365 | Serving .dart files much easier, putting it in the framework itself. 366 | 367 | #### Pub version 0.1.4 #### 368 | 369 | Adding interceptors to the game, so you can write interceptor classes to intercept a request. 370 | 371 | #### Pub version 0.1.3 #### 372 | 373 | Updating dependency on force mirrors, it solves a bug with invocation. 374 | Controller scanning, so you don't need to register a class, just annotate it with @Controller. 375 | 376 | #### Pub version 0.1.2+4 & 0.1.2+5 #### 377 | 378 | Adding an improvement of force mirrors invoke. 379 | 380 | #### Pub version 0.1.2+3 #### 381 | 382 | Solving issue with dependency management 383 | 384 | #### Pub version 0.1.2+2 #### 385 | 386 | Add dependency to forcemirrors. 387 | 388 | #### Pub version 0.1.2+1 #### 389 | 390 | Adding tests for mirrorhelpers and refactor code to improve annotation handling. 391 | 392 | #### Pub version 0.1.2 #### 393 | 394 | Adding the ModelAttribute annotation into the mvc framework. 395 | 396 | #### Pub version 0.1.0 #### 397 | 398 | Adding an abstraction ForceRequest, a wrapper around httprequest. 399 | 400 | #### Pub version 0.1.0 #### 401 | 402 | Adding renderer and model to the mvc part of it. 403 | Adding mustache as a new dependency for the rendering part. 404 | 405 | #### Pub version 0.0.6 & 0.0.7 #### 406 | 407 | Fixing issue with annotations of RequestMapping 408 | 409 | #### Pub version 0.0.5 #### 410 | 411 | Introducing Simple Web Server 412 | 413 | #### Pub version 0.0.5 #### 414 | 415 | Introduction of the model 416 | 417 | #### Pub version 0.0.4 #### 418 | 419 | Update version of uuid 420 | 421 | #### Pub version 0.0.3 #### 422 | 423 | Adding RequestMethod class into the project 424 | 425 | #### Pub version 0.0.2 #### 426 | 427 | Adding documentation and solving issues. 428 | 429 | #### Pub version 0.0.1 #### 430 | 431 | Setup of the project, moved basic_server.dart to this package and make it usable in the dart force main package. 432 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Joris Hermans 2 | 3 | Joris: https://twitter.com/jorishermans 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. -------------------------------------------------------------------------------- /example/app.yaml: -------------------------------------------------------------------------------- 1 | name: Force MVC Example 2 | 3 | server: 4 | - url: / 5 | 6 | client: 7 | - url: /web 8 | match: preregex 9 | dir: web 10 | 11 | - url: /static 12 | match: preregex 13 | dir: static 14 | -------------------------------------------------------------------------------- /example/client/.gitignore: -------------------------------------------------------------------------------- 1 | /build/** 2 | /build/ 3 | -------------------------------------------------------------------------------- /example/client/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: client 2 | dependencies: 3 | browser: any 4 | 5 | -------------------------------------------------------------------------------- /example/client/static/css/bootflat-extensions.css: -------------------------------------------------------------------------------- 1 | /* 2 | Bootflat 1.0.1 3 | Designed & Built by flathemes, http://www.flathemes.com 4 | Licensed under MIT License, http://opensource.org/licenses/mit-license.html 5 | 6 | Thanks for supporting our website and enjoy! 7 | */ 8 | /*------------------------------------*\ 9 | $bubble 10 | \*------------------------------------*/ 11 | .bubble-body { 12 | position: relative; 13 | padding: 3px; 14 | background-color: #ecf0f1; 15 | border-radius: 3px; 16 | overflow: visible; 17 | } 18 | .pull-left ~ .bubble-body { 19 | margin-left: 60px; 20 | } 21 | .pull-right ~ .bubble-body { 22 | margin-right: 60px; 23 | } 24 | .bubble-body .bubble-inner { 25 | min-height: 32px; 26 | border: 1px solid #d3d7d7; 27 | background-color: #fbfbfb; 28 | } 29 | .bubble-body .bubble-heading { 30 | padding: 0 10px; 31 | border-bottom: 1px solid #d3d7d7; 32 | background-color: #f4f4f4; 33 | font-size: 12px; 34 | font-weight: bold; 35 | color: #222; 36 | overflow: hidden; 37 | white-space: nowrap; 38 | text-overflow: ellipsis; 39 | height: 33px; 40 | line-height: 33px; 41 | } 42 | .bubble-body .bubble-content { 43 | padding: 10px; 44 | font-size: 13px; 45 | overflow: auto; 46 | width: 100%; 47 | line-height: 1.7; 48 | } 49 | .bubble-body .bubble-inner { 50 | margin-bottom: 0; 51 | -webkit-border-radius: 0; 52 | -moz-border-radius: 0; 53 | border-radius: 0; 54 | } 55 | .bubble-body .bubble-inner .bubble { 56 | margin: 0 10px; 57 | padding-top: 10px; 58 | border-top: 1px solid #ecf0f1; 59 | } 60 | .bubble-body .bubble-inner .bubble .bubble { 61 | margin: 0; 62 | } 63 | .bubble-body .bubble-inner .bubble-body:before, 64 | .bubble-body .bubble-inner .bubble-body:after { 65 | display: none; 66 | } 67 | .bubble-body .bubble-inner .bubble-body, 68 | .bubble-body .bubble-inner .bubble-inner { 69 | padding: 0; 70 | border: none; 71 | background-color: transparent; 72 | } 73 | .bubble-body .bubble-inner .bubble-inner .bubble-heading { 74 | padding: 0; 75 | border-bottom: none; 76 | background-color: transparent; 77 | height: auto; 78 | line-height: normal; 79 | } 80 | .bubble-body .bubble-inner .bubble-inner .bubble-content { 81 | padding: 0; 82 | font-size: 13px; 83 | overflow: auto; 84 | width: 100%; 85 | line-height: 1.5; 86 | } 87 | .bubble-arrow-left:before, 88 | .bubble-arrow-right:after { 89 | position: absolute; 90 | top: 15px; 91 | content: ""; 92 | display: block; 93 | height: 0; 94 | width: 0; 95 | border-width: 10px; 96 | border-style: solid; 97 | } 98 | .bubble-arrow-left:before { 99 | border-color: transparent #ecf0f1 transparent transparent; 100 | left: -20px; 101 | } 102 | .bubble-arrow-right:after { 103 | border-color: transparent transparent transparent #ecf0f1; 104 | right: -20px; 105 | } 106 | /*------------------------------------*\ 107 | $breadcrumb-arrow 108 | \*------------------------------------*/ 109 | .breadcrumb-arrow { 110 | padding: 0; 111 | list-style:none; 112 | background-color: #ecf0f1; 113 | height:36px; 114 | line-height: 36px; 115 | } 116 | .breadcrumb-arrow li:first-child a { 117 | border-top-left-radius: 4px; 118 | border-bottom-left-radius: 4px; 119 | } 120 | .breadcrumb-arrow li, 121 | .breadcrumb-arrow li a, 122 | .breadcrumb-arrow li span{ 123 | display:-moz-inline-box; 124 | display:inline-table; 125 | display:inline-block; 126 | zoom:1; 127 | *display:inline; 128 | vertical-align:top; 129 | } 130 | .breadcrumb-arrow li:not(:first-child) { 131 | margin-left: -5px; 132 | } 133 | .breadcrumb-arrow li + li:before { 134 | padding: 0; 135 | content: ""; 136 | } 137 | .breadcrumb-arrow li span { 138 | padding: 0 10px; 139 | } 140 | .breadcrumb-arrow li a, 141 | .breadcrumb-arrow li:not(:first-child) span { 142 | padding:0 10px 0 25px; 143 | height:35px; 144 | line-height:35px; 145 | } 146 | .breadcrumb-arrow li:first-child a { 147 | padding: 0 10px; 148 | } 149 | .breadcrumb-arrow li a { 150 | position:relative; 151 | border:1px solid #3da8e3; 152 | color:#fff; 153 | background-color:#3da8e3; 154 | text-decoration:none; 155 | } 156 | .breadcrumb-arrow li [class^="icon-"], 157 | .breadcrumb-arrow ul li [class*=" icon-"] { 158 | top: 0; 159 | } 160 | .breadcrumb-arrow-arrow li:first-child a { 161 | padding-left:10px; 162 | } 163 | .breadcrumb-arrow li a:before, 164 | .breadcrumb-arrow li a:after { 165 | position:absolute; 166 | top:0; 167 | content:''; 168 | width: 0; 169 | height: 0; 170 | border-top: 17px solid transparent; 171 | border-bottom: 17px solid transparent; 172 | } 173 | .breadcrumb-arrow li a:before { 174 | right: -10px; 175 | border-left-width: 10px; 176 | border-left-style:solid; 177 | border-left-color:#3da8e3; 178 | z-index:3; 179 | } 180 | .breadcrumb-arrow li a:after{ 181 | right: -11px; 182 | border-left: 10px solid #2980b9; 183 | z-index:2; 184 | } 185 | .breadcrumb-arrow li a:hover, 186 | .breadcrumb-arrow li a:focus { 187 | background-color:#3598ce; 188 | border: 1px solid #3598ce; 189 | } 190 | .breadcrumb-arrow li a:hover:before, 191 | .breadcrumb-arrow li a:focus:before { 192 | border-left-color: #3598ce; 193 | } 194 | .breadcrumb-arrow li a:active { 195 | background-color:#2980b9; 196 | border: 1px solid #2980b9; 197 | } 198 | .breadcrumb-arrow li a:active:before, 199 | .breadcrumb-arrow li a:active:after { 200 | border-left-color:#2980b9; 201 | } 202 | .breadcrumb-arrow li span{ 203 | color:#bdc3c7; 204 | } 205 | /*------------------------------------*\ 206 | $nav-tabs-panel 207 | \*------------------------------------*/ 208 | .nav-tabs-panel, 209 | .nav-tabs-panel.nav-justified { 210 | margin-bottom: 15px; 211 | border-bottom: 1px solid #2986b9; 212 | background-color: #ecf0f1; 213 | } 214 | .nav-tabs-panel .tab-default, 215 | .nav-tabs-panel.nav-justified .tab-default { 216 | margin-right: 0; 217 | padding: 11px 15px; 218 | border-bottom: none; 219 | color: #292929; 220 | } 221 | .nav-tabs-panel.nav-justified .active .tab-default, 222 | .nav-tabs-panel.nav-justified .active .tab-default:hover, 223 | .nav-tabs-panel.nav-justified .active .tab-default:focus, 224 | .nav-tabs-panel .active .tab-default, 225 | .nav-tabs-panel .active .tab-default:hover, 226 | .nav-tabs-panel .active .tab-default:focus { 227 | border-color: transparent transparent #2986b9 transparent; 228 | border-bottom-style:solid; 229 | border-width: 0 0 3px 0; 230 | color: #fff; 231 | background-color: #3da8e3; 232 | -webkit-border-radius: 0; 233 | -moz-border-radius: 0; 234 | border-radius: 0; 235 | } 236 | .nav-tabs-panel li a:hover, 237 | .nav-tabs-panel li a:focus { 238 | border-color: transparent transparent transparent; 239 | background-color: transparent; 240 | } 241 | .nav-tabs-panel .open .dropdown-toggle, 242 | .nav-tabs-panel li.dropdown.open.active a:hover, 243 | .nav-tabs-panel li.dropdown.open.active a:focus { 244 | color: #292929; 245 | background-color: transparent; 246 | border-color: transparent; 247 | } 248 | .nav-tabs-panel .dropdown-toggle .caret, 249 | .nav-tabs-panel .dropdown-toggle:hover .caret, 250 | .nav-tabs-panel .dropdown-toggle:focus .caret, 251 | .nav-tabs-panel li.dropdown.open .caret, 252 | .nav-tabs-panel li.dropdown.open.active .caret, 253 | .nav-tabs-panel li.dropdown.open a:hover .caret, 254 | .nav-tabs-panel li.dropdown.open a:focus .caret { 255 | border-top-color: #292929; 256 | border-bottom-color: #292929; 257 | } 258 | .nav-tabs-panel .active .dropdown-toggle .caret { 259 | border-top-color: #fff; 260 | border-bottom-color: #fff; 261 | } 262 | .nav-tabs-panel .dropdown-menu { 263 | margin-top: 1px; 264 | } 265 | .nav-tabs-panel .dropdown-menu li a { 266 | background-color: transparent; 267 | } 268 | .nav-tabs-panel .dropdown-menu li.active a { 269 | background-color: #2986b9; 270 | } 271 | .nav-tabs-panel .dropdown-menu li a:hover, 272 | .nav-tabs-panel .dropdown-menu li a:focus { 273 | background-color: #2986b9; 274 | } 275 | /*------------------------------------*\ 276 | $tabs-below 277 | \*------------------------------------*/ 278 | .tabs-below .nav-tabs-panel { 279 | margin-top: 15px; 280 | margin-bottom: 0; 281 | border-top: 1px solid #2986b9; 282 | border-bottom:none; 283 | } 284 | .tabs-below .nav-tabs-panel li { 285 | margin-top: 0; 286 | } 287 | .tabs-below .nav-tabs-panel li a:hover, 288 | .tabs-below .nav-tabs-panel li a:focus { 289 | border-top-color: transparent; 290 | } 291 | .tabs-below .nav-tabs-panel .active .tab-default, 292 | .tabs-below .nav-tabs-panel .active .tab-default:hover, 293 | .tabs-below .nav-tabs-panel .active .tab-default:focus { 294 | border-bottom-color: #2986b9; 295 | } 296 | .tabs-below .nav-tabs-panel .dropdown-menu { 297 | -webkit-border-radius: 4px 4px 0 0; 298 | -moz-border-radius: 4px 4px 0 0; 299 | border-radius: 4px 4px 0 0; 300 | } 301 | /*------------------------------------*\ 302 | $tabs-left and $tabs-right 303 | \*------------------------------------*/ 304 | .tabs-left .nav-tabs-panel, 305 | .tabs-right .nav-tabs-panel { 306 | position: relative; 307 | border-bottom: none; 308 | z-index: 20; 309 | } 310 | .tabs-left .nav-tabs-panel li, 311 | .tabs-right .nav-tabs-panel li { 312 | float: none; 313 | } 314 | .tabs-left .nav-tabs-panel li .tab-default, 315 | .tabs-right .nav-tabs-panel li .tab-default { 316 | min-width: 39px; 317 | margin-bottom:0; 318 | } 319 | .tabs-left .nav-tabs-panel li .tab-default:hover, 320 | .tabs-left .nav-tabs-panel li .tab-default:focus, 321 | .tabs-right .nav-tabs-panel li .tab-default:hover, 322 | .tabs-right .nav-tabs-panel li .tab-default:focus { 323 | border-color: transparent; 324 | } 325 | .tabs-left .nav-tabs-panel { 326 | float: left; 327 | margin-right: 15px; 328 | border-right: 1px solid #2986b9; 329 | } 330 | .tabs-left .nav-tabs-panel li a { 331 | margin-right: 0; 332 | } 333 | .tabs-left .nav-tabs-panel .active .tab-default, 334 | .tabs-left .nav-tabs-panel .active .tab-default:hover, 335 | .tabs-left .nav-tabs-panel .active .tab-default:focus { 336 | border-color: transparent transparent transparent #2986b9; 337 | border-style: solid; 338 | border-width: 0 0 0 3px; 339 | } 340 | .tabs-right .nav-tabs-panel { 341 | float: right; 342 | margin-left: 15px; 343 | border-left: 1px solid #2986b9; 344 | } 345 | .tabs-right .nav-tabs-panel li a { 346 | margin-left: 0; 347 | } 348 | .tabs-right .nav-tabs-panel .active .tab-default, 349 | .tabs-right .nav-tabs-panel .active .tab-default:hover, 350 | .tabs-right .nav-tabs-panel .active .tab-default:focus { 351 | border-color: transparent #2986b9 transparent transparent; 352 | border-style: solid; 353 | border-width: 0 3px 0 0; 354 | } 355 | 356 | 357 | -------------------------------------------------------------------------------- /example/client/static/css/bootflat-square.css: -------------------------------------------------------------------------------- 1 | /* 2 | Bootflat 1.0.1 3 | Designed & Built by flathemes, http://www.flathemes.com 4 | Licensed under MIT License, http://opensource.org/licenses/mit-license.html 5 | 6 | Thanks for supporting our website and enjoy! 7 | */ 8 | /*------------------------------------*\ 9 | $default-square 10 | \*------------------------------------*/ 11 | .img-thumbnail-square, 12 | .btn-square, 13 | .btn-group-square .btn, 14 | .btn-group-square .dropdown-menu, 15 | .btn-group-square .btn, 16 | .btn-group-square .btn:first-child:not(:last-child), 17 | .btn-group-square .btn:last-child:not(:first-child), 18 | .table-bordered-square, 19 | .table-bordered-square tr:first-child th:first-child, 20 | .table-bordered-square tr:first-child th:last-child, 21 | .table-bordered-square tr:last-child td:first-child, 22 | .table-bordered-square tr:last-child td:last-child, 23 | .input-group-square .btn, 24 | .input-group-square .dropdown-menu, 25 | .input-group-square .form-control, 26 | .input-group-square .input-group-addon, 27 | .dropdown-menu-square, 28 | .dropdown-menu-square .dropdown-submenu .dropdown-menu, 29 | .form-square .form-control, 30 | .form-square .btn, 31 | .form-control-square, 32 | .label-square, 33 | .badge-square, 34 | .alert-square, 35 | .alert-square .btn, 36 | .progress-square, 37 | .breadcrumb-square, 38 | .tooltip-square .tooltip-inner, 39 | .popover-square, 40 | .nav-list-panel-square, 41 | .nav-tabs-square li > a, 42 | .tabs-below .nav-tabs-square li > a, 43 | .tabs-right .nav-tabs-square li > a, 44 | .tabs-left .nav-tabs-square li > a, 45 | .nav-tabs-square .dropdown-menu, 46 | .tabs-below .nav-tabs-square .dropdown-menu, 47 | .nav-pills-square li a, 48 | .nav-pills-square .dropdown-menu, 49 | .navbar-square, 50 | .navbar-square .dropdown-menu, 51 | .pagination-square li:first-child a, 52 | .pagination-square li:first-child span, 53 | .pagination-square li:last-child a, 54 | .pagination-square li:last-child span, 55 | .pager-square li a:hover, 56 | .pager-square li a:focus, 57 | .panel-group-square .panel, 58 | .panel-group-square .panel-heading, 59 | .panel-group-square .panel-body, 60 | /*------------------------------------*\ 61 | $extend-square 62 | \*------------------------------------*/ 63 | .breadcrumb-arrow-square li:first-child a { 64 | -webkit-border-radius: 0; 65 | -moz-border-radius: 0; 66 | border-radius: 0; 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /example/client/static/css/font-awesome.min.css: -------------------------------------------------------------------------------- 1 | @font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.0.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.0.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.0.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.0.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.0.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font-family:FontAwesome;font-style:normal;font-weight:normal;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale} 2 | .fa-lg{font-size:1.3333333333333333em;line-height:.75em;vertical-align:-15%} 3 | .fa-2x{font-size:2em} 4 | .fa-3x{font-size:3em} 5 | .fa-4x{font-size:4em} 6 | .fa-5x{font-size:5em} 7 | .fa-fw{width:1.2857142857142858em;text-align:center} 8 | .fa-ul{padding-left:0;margin-left:2.142857142857143em;list-style-type:none}.fa-ul>li{position:relative} 9 | .fa-li{position:absolute;left:-2.142857142857143em;width:2.142857142857143em;top:.14285714285714285em;text-align:center}.fa-li.fa-lg{left:-1.8571428571428572em} 10 | .fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em} 11 | .pull-right{float:right} 12 | .pull-left{float:left} 13 | .fa.pull-left{margin-right:.3em} 14 | .fa.pull-right{margin-left:.3em} 15 | .fa-spin{-webkit-animation:spin 2s infinite linear;-moz-animation:spin 2s infinite linear;-o-animation:spin 2s infinite linear;animation:spin 2s infinite linear} 16 | @-moz-keyframes spin{0%{-moz-transform:rotate(0deg)} 100%{-moz-transform:rotate(359deg)}}@-webkit-keyframes spin{0%{-webkit-transform:rotate(0deg)} 100%{-webkit-transform:rotate(359deg)}}@-o-keyframes spin{0%{-o-transform:rotate(0deg)} 100%{-o-transform:rotate(359deg)}}@-ms-keyframes spin{0%{-ms-transform:rotate(0deg)} 100%{-ms-transform:rotate(359deg)}}@keyframes spin{0%{transform:rotate(0deg)} 100%{transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-moz-transform:rotate(90deg);-ms-transform:rotate(90deg);-o-transform:rotate(90deg);transform:rotate(90deg)} 17 | .fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-moz-transform:rotate(180deg);-ms-transform:rotate(180deg);-o-transform:rotate(180deg);transform:rotate(180deg)} 18 | .fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-moz-transform:rotate(270deg);-ms-transform:rotate(270deg);-o-transform:rotate(270deg);transform:rotate(270deg)} 19 | .fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-moz-transform:scale(-1, 1);-ms-transform:scale(-1, 1);-o-transform:scale(-1, 1);transform:scale(-1, 1)} 20 | .fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-moz-transform:scale(1, -1);-ms-transform:scale(1, -1);-o-transform:scale(1, -1);transform:scale(1, -1)} 21 | .fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle} 22 | .fa-stack-1x,.fa-stack-2x{position:absolute;width:100%;text-align:center} 23 | .fa-stack-1x{line-height:inherit} 24 | .fa-stack-2x{font-size:2em} 25 | .fa-inverse{color:#fff} 26 | .fa-glass:before{content:"\f000"} 27 | .fa-music:before{content:"\f001"} 28 | .fa-search:before{content:"\f002"} 29 | .fa-envelope-o:before{content:"\f003"} 30 | .fa-heart:before{content:"\f004"} 31 | .fa-star:before{content:"\f005"} 32 | .fa-star-o:before{content:"\f006"} 33 | .fa-user:before{content:"\f007"} 34 | .fa-film:before{content:"\f008"} 35 | .fa-th-large:before{content:"\f009"} 36 | .fa-th:before{content:"\f00a"} 37 | .fa-th-list:before{content:"\f00b"} 38 | .fa-check:before{content:"\f00c"} 39 | .fa-times:before{content:"\f00d"} 40 | .fa-search-plus:before{content:"\f00e"} 41 | .fa-search-minus:before{content:"\f010"} 42 | .fa-power-off:before{content:"\f011"} 43 | .fa-signal:before{content:"\f012"} 44 | .fa-gear:before,.fa-cog:before{content:"\f013"} 45 | .fa-trash-o:before{content:"\f014"} 46 | .fa-home:before{content:"\f015"} 47 | .fa-file-o:before{content:"\f016"} 48 | .fa-clock-o:before{content:"\f017"} 49 | .fa-road:before{content:"\f018"} 50 | .fa-download:before{content:"\f019"} 51 | .fa-arrow-circle-o-down:before{content:"\f01a"} 52 | .fa-arrow-circle-o-up:before{content:"\f01b"} 53 | .fa-inbox:before{content:"\f01c"} 54 | .fa-play-circle-o:before{content:"\f01d"} 55 | .fa-rotate-right:before,.fa-repeat:before{content:"\f01e"} 56 | .fa-refresh:before{content:"\f021"} 57 | .fa-list-alt:before{content:"\f022"} 58 | .fa-lock:before{content:"\f023"} 59 | .fa-flag:before{content:"\f024"} 60 | .fa-headphones:before{content:"\f025"} 61 | .fa-volume-off:before{content:"\f026"} 62 | .fa-volume-down:before{content:"\f027"} 63 | .fa-volume-up:before{content:"\f028"} 64 | .fa-qrcode:before{content:"\f029"} 65 | .fa-barcode:before{content:"\f02a"} 66 | .fa-tag:before{content:"\f02b"} 67 | .fa-tags:before{content:"\f02c"} 68 | .fa-book:before{content:"\f02d"} 69 | .fa-bookmark:before{content:"\f02e"} 70 | .fa-print:before{content:"\f02f"} 71 | .fa-camera:before{content:"\f030"} 72 | .fa-font:before{content:"\f031"} 73 | .fa-bold:before{content:"\f032"} 74 | .fa-italic:before{content:"\f033"} 75 | .fa-text-height:before{content:"\f034"} 76 | .fa-text-width:before{content:"\f035"} 77 | .fa-align-left:before{content:"\f036"} 78 | .fa-align-center:before{content:"\f037"} 79 | .fa-align-right:before{content:"\f038"} 80 | .fa-align-justify:before{content:"\f039"} 81 | .fa-list:before{content:"\f03a"} 82 | .fa-dedent:before,.fa-outdent:before{content:"\f03b"} 83 | .fa-indent:before{content:"\f03c"} 84 | .fa-video-camera:before{content:"\f03d"} 85 | .fa-picture-o:before{content:"\f03e"} 86 | .fa-pencil:before{content:"\f040"} 87 | .fa-map-marker:before{content:"\f041"} 88 | .fa-adjust:before{content:"\f042"} 89 | .fa-tint:before{content:"\f043"} 90 | .fa-edit:before,.fa-pencil-square-o:before{content:"\f044"} 91 | .fa-share-square-o:before{content:"\f045"} 92 | .fa-check-square-o:before{content:"\f046"} 93 | .fa-move:before{content:"\f047"} 94 | .fa-step-backward:before{content:"\f048"} 95 | .fa-fast-backward:before{content:"\f049"} 96 | .fa-backward:before{content:"\f04a"} 97 | .fa-play:before{content:"\f04b"} 98 | .fa-pause:before{content:"\f04c"} 99 | .fa-stop:before{content:"\f04d"} 100 | .fa-forward:before{content:"\f04e"} 101 | .fa-fast-forward:before{content:"\f050"} 102 | .fa-step-forward:before{content:"\f051"} 103 | .fa-eject:before{content:"\f052"} 104 | .fa-chevron-left:before{content:"\f053"} 105 | .fa-chevron-right:before{content:"\f054"} 106 | .fa-plus-circle:before{content:"\f055"} 107 | .fa-minus-circle:before{content:"\f056"} 108 | .fa-times-circle:before{content:"\f057"} 109 | .fa-check-circle:before{content:"\f058"} 110 | .fa-question-circle:before{content:"\f059"} 111 | .fa-info-circle:before{content:"\f05a"} 112 | .fa-crosshairs:before{content:"\f05b"} 113 | .fa-times-circle-o:before{content:"\f05c"} 114 | .fa-check-circle-o:before{content:"\f05d"} 115 | .fa-ban:before{content:"\f05e"} 116 | .fa-arrow-left:before{content:"\f060"} 117 | .fa-arrow-right:before{content:"\f061"} 118 | .fa-arrow-up:before{content:"\f062"} 119 | .fa-arrow-down:before{content:"\f063"} 120 | .fa-mail-forward:before,.fa-share:before{content:"\f064"} 121 | .fa-resize-full:before{content:"\f065"} 122 | .fa-resize-small:before{content:"\f066"} 123 | .fa-plus:before{content:"\f067"} 124 | .fa-minus:before{content:"\f068"} 125 | .fa-asterisk:before{content:"\f069"} 126 | .fa-exclamation-circle:before{content:"\f06a"} 127 | .fa-gift:before{content:"\f06b"} 128 | .fa-leaf:before{content:"\f06c"} 129 | .fa-fire:before{content:"\f06d"} 130 | .fa-eye:before{content:"\f06e"} 131 | .fa-eye-slash:before{content:"\f070"} 132 | .fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"} 133 | .fa-plane:before{content:"\f072"} 134 | .fa-calendar:before{content:"\f073"} 135 | .fa-random:before{content:"\f074"} 136 | .fa-comment:before{content:"\f075"} 137 | .fa-magnet:before{content:"\f076"} 138 | .fa-chevron-up:before{content:"\f077"} 139 | .fa-chevron-down:before{content:"\f078"} 140 | .fa-retweet:before{content:"\f079"} 141 | .fa-shopping-cart:before{content:"\f07a"} 142 | .fa-folder:before{content:"\f07b"} 143 | .fa-folder-open:before{content:"\f07c"} 144 | .fa-resize-vertical:before{content:"\f07d"} 145 | .fa-resize-horizontal:before{content:"\f07e"} 146 | .fa-bar-chart-o:before{content:"\f080"} 147 | .fa-twitter-square:before{content:"\f081"} 148 | .fa-facebook-square:before{content:"\f082"} 149 | .fa-camera-retro:before{content:"\f083"} 150 | .fa-key:before{content:"\f084"} 151 | .fa-gears:before,.fa-cogs:before{content:"\f085"} 152 | .fa-comments:before{content:"\f086"} 153 | .fa-thumbs-o-up:before{content:"\f087"} 154 | .fa-thumbs-o-down:before{content:"\f088"} 155 | .fa-star-half:before{content:"\f089"} 156 | .fa-heart-o:before{content:"\f08a"} 157 | .fa-sign-out:before{content:"\f08b"} 158 | .fa-linkedin-square:before{content:"\f08c"} 159 | .fa-thumb-tack:before{content:"\f08d"} 160 | .fa-external-link:before{content:"\f08e"} 161 | .fa-sign-in:before{content:"\f090"} 162 | .fa-trophy:before{content:"\f091"} 163 | .fa-github-square:before{content:"\f092"} 164 | .fa-upload:before{content:"\f093"} 165 | .fa-lemon-o:before{content:"\f094"} 166 | .fa-phone:before{content:"\f095"} 167 | .fa-square-o:before{content:"\f096"} 168 | .fa-bookmark-o:before{content:"\f097"} 169 | .fa-phone-square:before{content:"\f098"} 170 | .fa-twitter:before{content:"\f099"} 171 | .fa-facebook:before{content:"\f09a"} 172 | .fa-github:before{content:"\f09b"} 173 | .fa-unlock:before{content:"\f09c"} 174 | .fa-credit-card:before{content:"\f09d"} 175 | .fa-rss:before{content:"\f09e"} 176 | .fa-hdd:before{content:"\f0a0"} 177 | .fa-bullhorn:before{content:"\f0a1"} 178 | .fa-bell:before{content:"\f0f3"} 179 | .fa-certificate:before{content:"\f0a3"} 180 | .fa-hand-o-right:before{content:"\f0a4"} 181 | .fa-hand-o-left:before{content:"\f0a5"} 182 | .fa-hand-o-up:before{content:"\f0a6"} 183 | .fa-hand-o-down:before{content:"\f0a7"} 184 | .fa-arrow-circle-left:before{content:"\f0a8"} 185 | .fa-arrow-circle-right:before{content:"\f0a9"} 186 | .fa-arrow-circle-up:before{content:"\f0aa"} 187 | .fa-arrow-circle-down:before{content:"\f0ab"} 188 | .fa-globe:before{content:"\f0ac"} 189 | .fa-wrench:before{content:"\f0ad"} 190 | .fa-tasks:before{content:"\f0ae"} 191 | .fa-filter:before{content:"\f0b0"} 192 | .fa-briefcase:before{content:"\f0b1"} 193 | .fa-fullscreen:before{content:"\f0b2"} 194 | .fa-group:before{content:"\f0c0"} 195 | .fa-chain:before,.fa-link:before{content:"\f0c1"} 196 | .fa-cloud:before{content:"\f0c2"} 197 | .fa-flask:before{content:"\f0c3"} 198 | .fa-cut:before,.fa-scissors:before{content:"\f0c4"} 199 | .fa-copy:before,.fa-files-o:before{content:"\f0c5"} 200 | .fa-paperclip:before{content:"\f0c6"} 201 | .fa-save:before,.fa-floppy-o:before{content:"\f0c7"} 202 | .fa-square:before{content:"\f0c8"} 203 | .fa-reorder:before{content:"\f0c9"} 204 | .fa-list-ul:before{content:"\f0ca"} 205 | .fa-list-ol:before{content:"\f0cb"} 206 | .fa-strikethrough:before{content:"\f0cc"} 207 | .fa-underline:before{content:"\f0cd"} 208 | .fa-table:before{content:"\f0ce"} 209 | .fa-magic:before{content:"\f0d0"} 210 | .fa-truck:before{content:"\f0d1"} 211 | .fa-pinterest:before{content:"\f0d2"} 212 | .fa-pinterest-square:before{content:"\f0d3"} 213 | .fa-google-plus-square:before{content:"\f0d4"} 214 | .fa-google-plus:before{content:"\f0d5"} 215 | .fa-money:before{content:"\f0d6"} 216 | .fa-caret-down:before{content:"\f0d7"} 217 | .fa-caret-up:before{content:"\f0d8"} 218 | .fa-caret-left:before{content:"\f0d9"} 219 | .fa-caret-right:before{content:"\f0da"} 220 | .fa-columns:before{content:"\f0db"} 221 | .fa-unsorted:before,.fa-sort:before{content:"\f0dc"} 222 | .fa-sort-down:before,.fa-sort-asc:before{content:"\f0dd"} 223 | .fa-sort-up:before,.fa-sort-desc:before{content:"\f0de"} 224 | .fa-envelope:before{content:"\f0e0"} 225 | .fa-linkedin:before{content:"\f0e1"} 226 | .fa-rotate-left:before,.fa-undo:before{content:"\f0e2"} 227 | .fa-legal:before,.fa-gavel:before{content:"\f0e3"} 228 | .fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"} 229 | .fa-comment-o:before{content:"\f0e5"} 230 | .fa-comments-o:before{content:"\f0e6"} 231 | .fa-flash:before,.fa-bolt:before{content:"\f0e7"} 232 | .fa-sitemap:before{content:"\f0e8"} 233 | .fa-umbrella:before{content:"\f0e9"} 234 | .fa-paste:before,.fa-clipboard:before{content:"\f0ea"} 235 | .fa-lightbulb-o:before{content:"\f0eb"} 236 | .fa-exchange:before{content:"\f0ec"} 237 | .fa-cloud-download:before{content:"\f0ed"} 238 | .fa-cloud-upload:before{content:"\f0ee"} 239 | .fa-user-md:before{content:"\f0f0"} 240 | .fa-stethoscope:before{content:"\f0f1"} 241 | .fa-suitcase:before{content:"\f0f2"} 242 | .fa-bell-o:before{content:"\f0a2"} 243 | .fa-coffee:before{content:"\f0f4"} 244 | .fa-cutlery:before{content:"\f0f5"} 245 | .fa-file-text-o:before{content:"\f0f6"} 246 | .fa-building:before{content:"\f0f7"} 247 | .fa-hospital:before{content:"\f0f8"} 248 | .fa-ambulance:before{content:"\f0f9"} 249 | .fa-medkit:before{content:"\f0fa"} 250 | .fa-fighter-jet:before{content:"\f0fb"} 251 | .fa-beer:before{content:"\f0fc"} 252 | .fa-h-square:before{content:"\f0fd"} 253 | .fa-plus-square:before{content:"\f0fe"} 254 | .fa-angle-double-left:before{content:"\f100"} 255 | .fa-angle-double-right:before{content:"\f101"} 256 | .fa-angle-double-up:before{content:"\f102"} 257 | .fa-angle-double-down:before{content:"\f103"} 258 | .fa-angle-left:before{content:"\f104"} 259 | .fa-angle-right:before{content:"\f105"} 260 | .fa-angle-up:before{content:"\f106"} 261 | .fa-angle-down:before{content:"\f107"} 262 | .fa-desktop:before{content:"\f108"} 263 | .fa-laptop:before{content:"\f109"} 264 | .fa-tablet:before{content:"\f10a"} 265 | .fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"} 266 | .fa-circle-o:before{content:"\f10c"} 267 | .fa-quote-left:before{content:"\f10d"} 268 | .fa-quote-right:before{content:"\f10e"} 269 | .fa-spinner:before{content:"\f110"} 270 | .fa-circle:before{content:"\f111"} 271 | .fa-mail-reply:before,.fa-reply:before{content:"\f112"} 272 | .fa-github-alt:before{content:"\f113"} 273 | .fa-folder-o:before{content:"\f114"} 274 | .fa-folder-open-o:before{content:"\f115"} 275 | .fa-expand-o:before{content:"\f116"} 276 | .fa-collapse-o:before{content:"\f117"} 277 | .fa-smile-o:before{content:"\f118"} 278 | .fa-frown-o:before{content:"\f119"} 279 | .fa-meh-o:before{content:"\f11a"} 280 | .fa-gamepad:before{content:"\f11b"} 281 | .fa-keyboard-o:before{content:"\f11c"} 282 | .fa-flag-o:before{content:"\f11d"} 283 | .fa-flag-checkered:before{content:"\f11e"} 284 | .fa-terminal:before{content:"\f120"} 285 | .fa-code:before{content:"\f121"} 286 | .fa-reply-all:before{content:"\f122"} 287 | .fa-mail-reply-all:before{content:"\f122"} 288 | .fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"} 289 | .fa-location-arrow:before{content:"\f124"} 290 | .fa-crop:before{content:"\f125"} 291 | .fa-code-fork:before{content:"\f126"} 292 | .fa-unlink:before,.fa-chain-broken:before{content:"\f127"} 293 | .fa-question:before{content:"\f128"} 294 | .fa-info:before{content:"\f129"} 295 | .fa-exclamation:before{content:"\f12a"} 296 | .fa-superscript:before{content:"\f12b"} 297 | .fa-subscript:before{content:"\f12c"} 298 | .fa-eraser:before{content:"\f12d"} 299 | .fa-puzzle-piece:before{content:"\f12e"} 300 | .fa-microphone:before{content:"\f130"} 301 | .fa-microphone-slash:before{content:"\f131"} 302 | .fa-shield:before{content:"\f132"} 303 | .fa-calendar-o:before{content:"\f133"} 304 | .fa-fire-extinguisher:before{content:"\f134"} 305 | .fa-rocket:before{content:"\f135"} 306 | .fa-maxcdn:before{content:"\f136"} 307 | .fa-chevron-circle-left:before{content:"\f137"} 308 | .fa-chevron-circle-right:before{content:"\f138"} 309 | .fa-chevron-circle-up:before{content:"\f139"} 310 | .fa-chevron-circle-down:before{content:"\f13a"} 311 | .fa-html5:before{content:"\f13b"} 312 | .fa-css3:before{content:"\f13c"} 313 | .fa-anchor:before{content:"\f13d"} 314 | .fa-unlock-o:before{content:"\f13e"} 315 | .fa-bullseye:before{content:"\f140"} 316 | .fa-ellipsis-horizontal:before{content:"\f141"} 317 | .fa-ellipsis-vertical:before{content:"\f142"} 318 | .fa-rss-square:before{content:"\f143"} 319 | .fa-play-circle:before{content:"\f144"} 320 | .fa-ticket:before{content:"\f145"} 321 | .fa-minus-square:before{content:"\f146"} 322 | .fa-minus-square-o:before{content:"\f147"} 323 | .fa-level-up:before{content:"\f148"} 324 | .fa-level-down:before{content:"\f149"} 325 | .fa-check-square:before{content:"\f14a"} 326 | .fa-pencil-square:before{content:"\f14b"} 327 | .fa-external-link-square:before{content:"\f14c"} 328 | .fa-share-square:before{content:"\f14d"} 329 | .fa-compass:before{content:"\f14e"} 330 | .fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"} 331 | .fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"} 332 | .fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"} 333 | .fa-euro:before,.fa-eur:before{content:"\f153"} 334 | .fa-gbp:before{content:"\f154"} 335 | .fa-dollar:before,.fa-usd:before{content:"\f155"} 336 | .fa-rupee:before,.fa-inr:before{content:"\f156"} 337 | .fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"} 338 | .fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"} 339 | .fa-won:before,.fa-krw:before{content:"\f159"} 340 | .fa-bitcoin:before,.fa-btc:before{content:"\f15a"} 341 | .fa-file:before{content:"\f15b"} 342 | .fa-file-text:before{content:"\f15c"} 343 | .fa-sort-alpha-asc:before{content:"\f15d"} 344 | .fa-sort-alpha-desc:before{content:"\f15e"} 345 | .fa-sort-amount-asc:before{content:"\f160"} 346 | .fa-sort-amount-desc:before{content:"\f161"} 347 | .fa-sort-numeric-asc:before{content:"\f162"} 348 | .fa-sort-numeric-desc:before{content:"\f163"} 349 | .fa-thumbs-up:before{content:"\f164"} 350 | .fa-thumbs-down:before{content:"\f165"} 351 | .fa-youtube-square:before{content:"\f166"} 352 | .fa-youtube:before{content:"\f167"} 353 | .fa-xing:before{content:"\f168"} 354 | .fa-xing-square:before{content:"\f169"} 355 | .fa-youtube-play:before{content:"\f16a"} 356 | .fa-dropbox:before{content:"\f16b"} 357 | .fa-stack-overflow:before{content:"\f16c"} 358 | .fa-instagram:before{content:"\f16d"} 359 | .fa-flickr:before{content:"\f16e"} 360 | .fa-adn:before{content:"\f170"} 361 | .fa-bitbucket:before{content:"\f171"} 362 | .fa-bitbucket-square:before{content:"\f172"} 363 | .fa-tumblr:before{content:"\f173"} 364 | .fa-tumblr-square:before{content:"\f174"} 365 | .fa-long-arrow-down:before{content:"\f175"} 366 | .fa-long-arrow-up:before{content:"\f176"} 367 | .fa-long-arrow-left:before{content:"\f177"} 368 | .fa-long-arrow-right:before{content:"\f178"} 369 | .fa-apple:before{content:"\f179"} 370 | .fa-windows:before{content:"\f17a"} 371 | .fa-android:before{content:"\f17b"} 372 | .fa-linux:before{content:"\f17c"} 373 | .fa-dribbble:before{content:"\f17d"} 374 | .fa-skype:before{content:"\f17e"} 375 | .fa-foursquare:before{content:"\f180"} 376 | .fa-trello:before{content:"\f181"} 377 | .fa-female:before{content:"\f182"} 378 | .fa-male:before{content:"\f183"} 379 | .fa-gittip:before{content:"\f184"} 380 | .fa-sun-o:before{content:"\f185"} 381 | .fa-moon-o:before{content:"\f186"} 382 | .fa-archive:before{content:"\f187"} 383 | .fa-bug:before{content:"\f188"} 384 | .fa-vk:before{content:"\f189"} 385 | .fa-weibo:before{content:"\f18a"} 386 | .fa-renren:before{content:"\f18b"} 387 | .fa-pagelines:before{content:"\f18c"} 388 | .fa-stack-exchange:before{content:"\f18d"} 389 | .fa-arrow-circle-o-right:before{content:"\f18e"} 390 | .fa-arrow-circle-o-left:before{content:"\f190"} 391 | .fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"} 392 | .fa-dot-circle-o:before{content:"\f192"} 393 | .fa-wheelchair:before{content:"\f193"} 394 | .fa-vimeo-square:before{content:"\f194"} 395 | .fa-turkish-lira:before,.fa-try:before{content:"\f195"} 396 | -------------------------------------------------------------------------------- /example/client/static/css/style.css: -------------------------------------------------------------------------------- 1 | 2 | body { 3 | background-color: #F8F8F8; 4 | font-family: 'Open Sans', sans-serif; 5 | font-size: 14px; 6 | font-weight: normal; 7 | line-height: 1.2em; 8 | margin: 15px; 9 | } 10 | 11 | h1, p { 12 | color: #333; 13 | } 14 | 15 | #sample_container_id { 16 | width: 100%; 17 | height: 400px; 18 | position: relative; 19 | border: 1px solid #ccc; 20 | background-color: #fff; 21 | } 22 | 23 | #sample_text_id { 24 | font-size: 24pt; 25 | text-align: center; 26 | margin-top: 140px; 27 | } 28 | 29 | #context-wrapper { 30 | min-height: 400px; 31 | } 32 | 33 | #context { 34 | position: absolute; 35 | bottom: 0; 36 | } 37 | 38 | .box { 39 | border: 1px solid #d8d8d8; 40 | -moz-border-radius: 3px; 41 | -webkit-border-radius: 3px; 42 | border-radius: 3px; 43 | background-color: #fff; 44 | border-bottom-width: 2px; 45 | margin: 10px 0 20px 0; 46 | padding: 20px 20px; 47 | } 48 | 49 | .mheight { 50 | min-height: 200px; 51 | } 52 | 53 | .small { 54 | font-size: 10pt; 55 | color: #aaa; 56 | } 57 | 58 | -------------------------------------------------------------------------------- /example/client/static/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-forcemvc/f1199f185783c0caca699ed0b6f18113feb7d256/example/client/static/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /example/client/static/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-forcemvc/f1199f185783c0caca699ed0b6f18113feb7d256/example/client/static/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /example/client/static/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-forcemvc/f1199f185783c0caca699ed0b6f18113feb7d256/example/client/static/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /example/client/static/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-forcemvc/f1199f185783c0caca699ed0b6f18113feb7d256/example/client/static/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /example/client/static/img/check_flat/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-forcemvc/f1199f185783c0caca699ed0b6f18113feb7d256/example/client/static/img/check_flat/default.png -------------------------------------------------------------------------------- /example/client/static/img/dart_force_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-forcemvc/f1199f185783c0caca699ed0b6f18113feb7d256/example/client/static/img/dart_force_logo.jpg -------------------------------------------------------------------------------- /example/client/web/client.dart: -------------------------------------------------------------------------------- 1 | import 'dart:html'; 2 | 3 | // This is a client side dart script needed by dart force in order to 4 | // bootstrap the client side of the application (?) 5 | 6 | void main() { 7 | DivElement statusElement = querySelector('#status'); 8 | statusElement.innerHtml = "Js / dart is up and running! Cool it should work like this!"; 9 | } 10 | -------------------------------------------------------------------------------- /example/client/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | force mvc example 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 27 | 28 | 29 | 30 |
31 |
32 |

Force mvc examples

33 |

Force mvc is part of the dart force framework. It is a server side implementation with concepts of spring mvc. It uses annotations like @RequestMapping, @Controller, @ModelAttribute, interceptors, ... more to come.

34 |

Learn more »

35 |
36 |
37 |
38 | 39 |
40 |
41 |

Normal page

42 |

This page just will count the visits in memory on the server

43 |

View example »

44 |
45 |
46 |

JSON

47 |

Force mvc also allows REST endpoints that can return JSON.

48 |

View example »

49 |
50 |
51 |

Path Variables

52 |

You can use path variables in the url to make it more dynamic! In this example it is /var/{var1}/.

53 |

View example »

54 |
55 |
56 |

Security

57 |

You can also add authentication into the controller, test it out!

58 |

View example »

59 |
60 | 61 |
62 |

63 |
64 |
65 | This application is an example of the dart force mvc part of dart force framework! 66 |
67 |
68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /example/server/advice/text_advice.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @ControllerAdvice 4 | class TextAdvice { 5 | 6 | @ModelAttribute("text") 7 | String addText() { 8 | return "just some text"; 9 | } 10 | 11 | @ExceptionHandler() 12 | String errorPage() { 13 | return "error"; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /example/server/controllers/about_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | class AboutController { 5 | 6 | @Value("name") 7 | String name; 8 | 9 | @Value("description") 10 | String description; 11 | 12 | @RequestMapping(value: "/test/about/") 13 | String aboutPage(req, Locale locale, Model model) { 14 | model.addAttribute("name", name); 15 | model.addAttribute("description", description); 16 | model.addAttribute("locale", locale.toString()); 17 | return "about"; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /example/server/controllers/count_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | class CountController { 5 | int count = 0; 6 | 7 | CountController() { 8 | count = 1; 9 | } 10 | 11 | @ModelAttribute("datetime") 12 | String addDateTime() { 13 | DateTime now = new DateTime.now(); 14 | return now.toString(); 15 | } 16 | 17 | @RequestMapping(value: "/count") 18 | String countMethod(req, Model model) { 19 | count++; 20 | model.addAttribute("count", "$count"); 21 | return "count"; 22 | } 23 | 24 | @RequestMapping(value: "/json") 25 | void countJson(req, Model model) { 26 | model.addAttribute("count", "$count"); 27 | model.addAttribute("bla", "hallo"); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /example/server/controllers/error_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | class DoorLockedError extends StateError { 4 | DoorLockedError(String msg) : super(msg); 5 | } 6 | 7 | @Controller 8 | class ErrorController { 9 | 10 | @RequestMapping(value: "/error/thrown/door/") 11 | String doorlocked(req, Model model) { 12 | throw new DoorLockedError("something is looked!"); 13 | } 14 | 15 | @RequestMapping(value: "/error/thrown/all/") 16 | String error(req, Model model) { 17 | throw new Error(); 18 | } 19 | 20 | @ExceptionHandler(type: DoorLockedError) 21 | String doorLockedError(req, Model model) { 22 | model.addAttribute("explanation", "This is a specific error!"); 23 | return "error"; 24 | } 25 | 26 | @ExceptionHandler() 27 | String error_catch(req, Model model) { 28 | model.addAttribute("explanation", "Some error!"); 29 | return "error"; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/server/controllers/login_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | class LoginController { 5 | 6 | @RequestMapping(value: "/login/") 7 | String form(req, Model model, @RequestParam(value: "referer", defaultValue: "/") var referer) { 8 | model.addAttribute("referer", referer); 9 | return "login"; 10 | } 11 | 12 | @RequestMapping(value: "/login/", method: "POST") 13 | Future countMethod(ForceRequest req, HttpSession session, Model model) { 14 | req.getPostParams().then((map) { 15 | if ( map["user"]=="admin" && map["pwd"]=="admin123" ) { 16 | model.addAttribute("user", map["user"]); 17 | 18 | session["user"] = "admin"; 19 | session["role"] = "ADMIN"; 20 | 21 | var referer = map["referer"]; 22 | req.async("redirect:$referer"); 23 | } else { 24 | model.addAttribute("error", "not ok"); 25 | model.addAttribute("referer", map["referer"]); 26 | req.async("login"); 27 | } 28 | }); 29 | model.addAttribute("status", "ok"); 30 | 31 | return req.asyncFuture; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /example/server/controllers/path_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | class PathController { 5 | 6 | @RequestMapping(value: "/var/{var1}/") 7 | String variable(Model model, var1) { 8 | model.addAttribute("variable", var1); 9 | return "pathvar"; 10 | } 11 | 12 | @RequestMapping(value: "/path/{var1}/") 13 | String multivariable(Model model, @PathVariable("var1") variable) { 14 | model.addAttribute("variable", variable); 15 | return "pathvar"; 16 | } 17 | 18 | @RequestMapping(value: "/qs/") 19 | String querystring(Model model, 20 | @RequestParam(value:"var", defaultValue: "what?") variable) { 21 | model.addAttribute("variable", variable); 22 | return "requestparam"; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/server/controllers/post_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | class PostController { 5 | 6 | @RequestMapping(value: "/form/") 7 | String form(req, Model model) { 8 | return "form"; 9 | } 10 | 11 | @RequestMapping(value: "/post/", method: "POST") 12 | Future countMethod(req, Model model) async { 13 | var map = await req.getPostParams(); 14 | model.addAttribute("email", map["email"]); 15 | model.addAttribute("status", "ok"); 16 | 17 | return map; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /example/server/controllers/redirect_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | @RequestMapping(value: "/redirect") 5 | class RedirectController { 6 | 7 | int redirect = 0; 8 | 9 | @RequestMapping(value: "/start") 10 | String redirectMethod(req, Model model) { 11 | redirect++; 12 | return "redirect:/redirect/viewable/"; 13 | } 14 | 15 | @RequestMapping(value: "/viewable/") 16 | String countMethod(req, Model model) { 17 | model.addAttribute("number", "$redirect"); 18 | model.addAttribute("name", "redirect"); 19 | return "number"; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /example/server/controllers/rest/api_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | class Book extends Object with Jsonify { 4 | Book(this.author, this.title); 5 | String author; 6 | String title; 7 | } 8 | 9 | @Controller 10 | @RequestMapping(value: "/api") 11 | class ApiController { 12 | int count = 0; 13 | 14 | ApiController() { 15 | count = 1; 16 | } 17 | 18 | @ModelAttribute("datetime") 19 | String addDateTime() { 20 | DateTime now = new DateTime.now(); 21 | return now.toString(); 22 | } 23 | 24 | @RequestMapping(value: "/count") 25 | void countJson(req, Model model) { 26 | count++; 27 | model.addAttribute("count", "$count"); 28 | model.addAttribute("bla", "hallo"); 29 | } 30 | 31 | @RequestMapping(value: "/map") 32 | Map mapJson() { 33 | Map map = new Map(); 34 | map["count"] = "$count"; 35 | map["bla"] = "hallo"; 36 | return map; 37 | } 38 | 39 | @RequestMapping(value: "/list") 40 | List lists() { 41 | List books = new List(); 42 | books.add("just a list"); 43 | books.add("another entry"); 44 | return books; 45 | } 46 | 47 | @RequestMapping(value: "/books") 48 | List books() { 49 | List books = new List(); 50 | books.add(new Book("JK Rowling", "Harry Potter")); 51 | books.add(new Book("Tolkin", "Hobbit")); 52 | return books; 53 | } 54 | 55 | @RequestMapping(value: '/book/1') 56 | @ResponseBody 57 | Book book() { 58 | return new Book("JK Rowling", "Harry Potter"); 59 | } 60 | 61 | @RequestMapping(value: "/book", method: RequestMethod.POST) 62 | Future post(req, Model model) { 63 | model.addAttribute("post", "ok"); 64 | 65 | req.getPostParams().then((data) { 66 | req.async(data); 67 | }); 68 | return req.asyncFuture; 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /example/server/controllers/rest/just_a_rest_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @RestController 4 | @RequestMapping(value: "/rest") 5 | class JustARestController { 6 | int count = 0; 7 | 8 | JustARestController() { 9 | count = 1; 10 | } 11 | 12 | @RequestMapping(value: "/count") 13 | int countJson(req, Model model) { 14 | count++; 15 | return count; 16 | } 17 | 18 | @RequestMapping(value: "/map") 19 | Map mapJson() { 20 | Map map = new Map(); 21 | map["count"] = "$count"; 22 | map["bla"] = "hallo"; 23 | return map; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /example/server/controllers/rest/readme.md: -------------------------------------------------------------------------------- 1 | #### Define Rest API with ForceMVC #### 2 | 3 | You can very easily define a rest api with ForceMVC. 4 | 5 | First of all you define your @Controller class. 6 | 7 | ```dart 8 | @Controller 9 | class RestController { 10 | 11 | } 12 | ``` 13 | 14 | Now we want to define an entry point in our rest api, so we add a method. 15 | Our api has the path /api/books, all your rest methods can be grouped under /api. 16 | 17 | ```dart 18 | @Controller 19 | @RequestMapping(value: "/api") 20 | class RestController { 21 | 22 | @RequestMapping(value: "/books") 23 | List books() { 24 | ... 25 | } 26 | } 27 | ``` 28 | 29 | This is how our books object looks like. 30 | 31 | ```dart 32 | class Book { 33 | Book(this.author, this.title); 34 | String author; 35 | String title; 36 | } 37 | ``` 38 | 39 | But be aware that this will not work! 40 | 41 | You need to jsonify it, you can do this by adding a method 'Map toJson()'. 42 | Or just use our mixin Jsonify. 43 | 44 | ```dart 45 | class Book extends Object with Jsonify { 46 | Book(this.author, this.title); 47 | String author; 48 | String title; 49 | } 50 | ``` 51 | 52 | It is always better to write your own Json mapper or you can use packages in pub like exportable. 53 | This way you have more control how your object will be transformed to json! 54 | 55 | ```dart 56 | class Book { 57 | Book(this.author, this.title); 58 | String author; 59 | String title; 60 | 61 | Map toJson() { 62 | Map map = new Map(); 63 | map["author"] = author; 64 | map["title"] = title; 65 | return map; 66 | } 67 | } 68 | ``` 69 | 70 | Now you can return book types and it is possible for them to be transformed to a json object. 71 | 72 | ```dart 73 | @Controller 74 | @RequestMapping(value: "/api") 75 | class RestController { 76 | 77 | @RequestMapping(value: "/books") 78 | List books() { 79 | List books = new List(); 80 | books.add(new Book("JK Rowling", "Harry Potter")); 81 | books.add(new Book("Tolkin", "Hobbit")); 82 | return books; 83 | } 84 | } 85 | ``` 86 | 87 | Now you can go to http://localhost:8080/api/books and you will see the following outcome. 88 | 89 | ```json 90 | [{ 91 | "author": "JK Rowling", 92 | "title": "Harry Potter" 93 | }, 94 | { 95 | "author": "Tolkin", 96 | "title": "Hobbit" 97 | }] 98 | ``` 99 | 100 | #### More #### 101 | 102 | ForceMVC has the same principle as spring mvc. 103 | So if you want to add one object to all your rest calls you can do that as follow. 104 | 105 | ```dart 106 | @ModelAttribute("datetime") 107 | String addDateTime() { 108 | DateTime now = new DateTime.now(); 109 | return now.toString(); 110 | } 111 | ``` 112 | 113 | This will always add a dateTime object in your rest api as a result to your browser. So if we add this to our RestController ... 114 | 115 | We will get the following outcome. 116 | ```json 117 | [[{ 118 | "author": "JK Rowling", 119 | "title": "Harry Potter" 120 | }, 121 | { 122 | "author": "Tolkin", 123 | "title": "Hobbit" 124 | }], 125 | { 126 | "datetime": "2014-11-29 18:28:05.801" 127 | } 128 | ] 129 | ``` 130 | 131 | You can also define the http method type on a rest call by doing the following. 132 | ```dart 133 | @RequestMapping(value: "/post", method: RequestMethod.POST) 134 | void post(req, Model model) { 135 | model.addAttribute("post", "ok"); 136 | 137 | req.getPostData().then((data) { 138 | 139 | }); 140 | } 141 | ``` 142 | -------------------------------------------------------------------------------- /example/server/controllers/rest/response_body_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | @RequestMapping(value: "/rb") 5 | class ResponseBodyController { 6 | int count = 0; 7 | 8 | ResponseBodyController() { 9 | count = 1; 10 | } 11 | 12 | @ModelAttribute("datetime") 13 | String addDateTime() { 14 | DateTime now = new DateTime.now(); 15 | return now.toString(); 16 | } 17 | 18 | @RequestMapping(value: "/count") 19 | @ResponseBody 20 | int countJson(req, Model model) { 21 | count++; 22 | return count; 23 | } 24 | 25 | @RequestMapping(value: "/map") 26 | @ResponseBody 27 | Map mapJson() { 28 | Map map = new Map(); 29 | map["count"] = "$count"; 30 | map["bla"] = "hallo"; 31 | return map; 32 | } 33 | 34 | @RequestMapping(value: "/list") 35 | @ResponseBody 36 | List lists() { 37 | List books = new List(); 38 | books.add("just a list"); 39 | books.add("another entry"); 40 | return books; 41 | } 42 | 43 | @RequestMapping(value: "/books") 44 | @ResponseBody 45 | List books() { 46 | List books = new List(); 47 | books.add(new Book("JK Rowling", "Harry Potter")); 48 | books.add(new Book("Tolkin", "Hobbit")); 49 | return books; 50 | } 51 | 52 | @RequestMapping(value: '/book/1') 53 | @ResponseBody 54 | Book book() { 55 | return new Book("JK Rowling", "Harry Potter"); 56 | } 57 | 58 | @RequestMapping(value: "/book", method: RequestMethod.POST) 59 | Future post(req, Model model) { 60 | model.addAttribute("post", "ok"); 61 | 62 | req.getPostParams().then((data) { 63 | req.async(data); 64 | }); 65 | return req.asyncFuture; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /example/server/controllers/secure_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | class User { 4 | int id; 5 | String name; 6 | String role; 7 | String password; 8 | } 9 | 10 | bool hasAdminRole(user, [List methodArguments]) => user.role == "ADMIN"; 11 | 12 | @Controller 13 | @PreAuthorizeRoles(const ["ADMIN"]) 14 | @PreAuthorizeIf(hasAdminRole) // Not yet implemented 15 | class AdminController { 16 | 17 | int redirect = 0; 18 | 19 | @RequestMapping(value: "/admin/info/") 20 | String variable(req, Model model) { 21 | return "info"; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /example/server/controllers/security/session_strategy.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | class SessionStrategy extends SecurityStrategy { 4 | 5 | bool checkAuthorization(HttpRequest req, List roles, data) { 6 | HttpSession session = req.session; 7 | return (session["role"]!=null && roles.any((r) => r == session["role"])); 8 | } 9 | 10 | Uri getRedirectUri(HttpRequest req) { 11 | var referer = req.uri.toString(); 12 | return Uri.parse("/login/?referer=$referer"); 13 | } 14 | } -------------------------------------------------------------------------------- /example/server/controllers/status_controller.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | @Controller 4 | class StatusController { 5 | 6 | @RequestMapping(value: "/status/404") 7 | @ResponseStatus(HttpStatus.NOT_FOUND) 8 | void form(req, Model model) { 9 | model.addAttribute("status", "404 not found"); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /example/server/interceptors/random_interceptor.dart: -------------------------------------------------------------------------------- 1 | part of example_forcedart; 2 | 3 | class RandomInterceptor implements HandlerInterceptor { 4 | 5 | bool preHandle(ForceRequest req, Model model, Object handler) { 6 | print("preHandle req ..."); 7 | return true; 8 | } 9 | 10 | void postHandle(ForceRequest req, Model model, Object handler) { 11 | print("postHandle req ..."); 12 | var rng = new Random(); 13 | var rnd = rng.nextInt(100); 14 | model.addAttribute("random", "$rnd"); 15 | } 16 | 17 | void afterCompletion(ForceRequest req, Model model, Object handler) { 18 | print("after completion ..."); 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /example/server/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: dartforcemvcexample 2 | description: example of the dart force mvc framework 3 | dependencies: 4 | forcemvc: 5 | path: ../../ 6 | http: '>=0.9.0 <0.10.0' 7 | http_server: '>=0.9.0 <0.10.0' 8 | logging: '>=0.9.0 <0.10.0' 9 | dev_dependencies: 10 | unittest: '>=0.11.0 <0.11.1' 11 | -------------------------------------------------------------------------------- /example/server/server.dart: -------------------------------------------------------------------------------- 1 | library example_forcedart; 2 | 3 | import 'dart:io'; 4 | import 'dart:async'; 5 | import 'dart:math'; 6 | import 'package:logging/logging.dart'; 7 | import 'package:forcemvc/force_mvc.dart'; 8 | import 'package:wired/wired.dart'; 9 | 10 | part 'controllers/post_controller.dart'; 11 | part 'controllers/login_controller.dart'; 12 | part 'controllers/redirect_controller.dart'; 13 | part 'controllers/path_controller.dart'; 14 | part 'controllers/count_controller.dart'; 15 | part 'controllers/secure_controller.dart'; 16 | part 'controllers/about_controller.dart'; 17 | part 'controllers/error_controller.dart'; 18 | part 'controllers/status_controller.dart'; 19 | 20 | part 'controllers/rest/just_a_rest_controller.dart'; 21 | part 'controllers/rest/api_controller.dart'; 22 | part 'controllers/rest/response_body_controller.dart'; 23 | 24 | part 'advice/text_advice.dart'; 25 | 26 | part 'interceptors/random_interceptor.dart'; 27 | part 'controllers/security/session_strategy.dart'; 28 | 29 | 30 | void main() { 31 | // Setup what port to listen to 32 | var portEnv = Platform.environment['PORT']; 33 | var port = portEnv == null ? 8080 : int.parse(portEnv); 34 | var serveClient = portEnv == null ? true : false; 35 | 36 | // Create a force server 37 | WebApplication webApp = new WebApplication(host: "127.0.0.1", 38 | port: port, 39 | staticFiles: '../client/static/', 40 | clientFiles: '../client/build/web/', 41 | clientServe: serveClient, 42 | views: "views/"); 43 | // register yaml files 44 | webApp.loadValues("../app.yaml"); 45 | // server.loadValues("pubspec.yaml"); 46 | webApp.notFound((ForceRequest req, Model model) { 47 | return "notfound"; 48 | }); 49 | 50 | // Set up logger. 51 | webApp.setupConsoleLog(Level.FINEST); 52 | 53 | // Setup session strategy 54 | webApp.strategy = new SessionStrategy(); 55 | 56 | // Serve the view called index as default 57 | webApp.static("/", "index.html"); 58 | 59 | // Start serving force with a randomPortFallback 60 | webApp.start(fallback: randomPortFallback); 61 | } 62 | 63 | @Config 64 | class OwnConfig { 65 | 66 | @Bean 67 | LocaleResolver localeResolver() { 68 | return new FixedLocaleResolver(Locale.ITALY); 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /example/server/test/count_controller_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:unittest/unittest.dart'; 2 | import 'package:forcemvc/force_mvc.dart'; 3 | import 'package:forcemvc/test.dart'; 4 | import '../server.dart'; 5 | 6 | main() { 7 | // First tests! 8 | var countController = new CountController(); 9 | 10 | Model model = new Model(); 11 | MockForceRequest req = new MockForceRequest(); 12 | 13 | test('testing the count controller', () { 14 | var view = countController.countMethod(req, model); 15 | expect(view, "count"); 16 | expect(model.getData()["count"], "2"); 17 | }); 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /example/server/views/about.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
26 |
27 |
{{name}}
28 |
{{description}}
29 |
{{locale}}
30 |

31 |
32 |
33 | This application is an example of the dart force mvc part of dart force framework! 34 |
35 |
36 |
37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /example/server/views/count.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
26 |
27 |
{{count}} page counts (refresh to see it going up!) on {{datetime}}
28 |

29 |
30 |
31 | This application is an example of the dart force mvc part of dart force framework! 32 |
33 |
34 | You can find the sourcecode of this example here. 35 |
36 |
37 |
38 | 39 | 40 | -------------------------------------------------------------------------------- /example/server/views/error.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Error

25 |
what now?
26 |
27 | 28 |
Some error happened!
29 |
{{explanation}}
30 |

31 |
32 |
33 | This application is an example of the dart force mvc part of dart force framework! 34 |
35 |
36 |
37 | 38 | -------------------------------------------------------------------------------- /example/server/views/form.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
form example
26 |
27 |
28 |
29 | 30 | 31 |
32 |
33 | 34 | 35 |
36 |
37 | 40 |
41 | 42 |
43 | 44 |

45 |
46 |
47 | This application is an example of the dart force mvc part of dart force framework! 48 |
49 |
50 |
51 | 52 | 53 | -------------------------------------------------------------------------------- /example/server/views/info.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC authentication example

25 |
26 |
27 |
restricted for admins to watch
28 |

29 |
30 |
31 | This application is an example of the dart force mvc part of dart force framework! 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /example/server/views/locale.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
26 |
27 |
{{name}}
28 |
{{locale}}
29 |

30 |
31 |
32 | This application is an example of the dart force mvc part of dart force framework! 33 |
34 |
35 |
36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /example/server/views/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
26 |
Please login (admin - admin123)
27 |
28 |
29 | 30 |
31 | 32 | 33 |
34 |
35 | 36 | 37 |
38 | 39 |
40 | 41 |

42 |
43 |
44 | This application is an example of the dart force mvc part of dart force framework! 45 |
46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /example/server/views/notfound.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Page not found

25 |
26 |
27 |
Oeps, I don't know that path!
28 |

29 |
30 |
31 | This application is an example of the dart force mvc part of dart force framework! 32 |
33 |
34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/server/views/number.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
26 |
27 |
{{number}} numbers for {{name}}
28 |

29 |
30 |
31 | This application is an example of the dart force mvc part of dart force framework! 32 |
33 |
34 |
35 | 36 | 37 | -------------------------------------------------------------------------------- /example/server/views/pathvar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
26 |
27 |
{{variable}} is a pathvariable in the url!
28 |

29 |
30 |
31 | This application is an example of the dart force mvc part of dart force framework! 32 |
33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /example/server/views/requestparam.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 21 | 22 |
23 |
24 |

Force MVC example

25 |
26 |
27 |
{{variable}} is a request variable in the url!
28 |

29 |
30 |
31 | This application is an example of the dart force mvc part of dart force framework! 32 |
33 |
34 |
35 | 36 | -------------------------------------------------------------------------------- /lib/annotations/helpers.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class MVCAnnotationHelper { 4 | static bool hasAuthentication(Object obj) { 5 | AnnotationScanner<_Authentication> annoChecker = new AnnotationScanner<_Authentication>(); 6 | return annoChecker.isOn(obj); 7 | } 8 | 9 | static _Authentication getAuthentication(Object obj) { 10 | AnnotationScanner<_Authentication> annoChecker = new AnnotationScanner<_Authentication>(); 11 | return annoChecker.instanceFrom(obj); 12 | } 13 | } -------------------------------------------------------------------------------- /lib/annotations/metadata.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * Annotation that will be used when the methods of the class needing authentication 5 | * 6 | */ 7 | const Authentication = const _Authentication(); 8 | 9 | class _Authentication { 10 | 11 | const _Authentication(); 12 | 13 | List get roles => ["BASIC"]; 14 | } 15 | 16 | class PreAuthorizeRoles { 17 | final List roles; 18 | 19 | const PreAuthorizeRoles(this.roles); 20 | } 21 | 22 | typedef bool PreAuthorizeFunc(user, [List methodArguments]); 23 | 24 | class PreAuthorizeIf { 25 | final PreAuthorizeFunc preAuthorizeFunc; 26 | 27 | const PreAuthorizeIf(this.preAuthorizeFunc); 28 | } 29 | 30 | /** 31 | * Annotation that will be used to indicate that a class is a controller 32 | * 33 | */ 34 | const Controller = const _Controller(); 35 | 36 | class _Controller { 37 | 38 | const _Controller(); 39 | 40 | } 41 | 42 | /** 43 | * Annotation that will be used to indicate that a class is a controller 44 | * and all methods have ResponseBody annotation 45 | * 46 | */ 47 | const RestController = const _RestController(); 48 | 49 | class _RestController { 50 | 51 | const _RestController(); 52 | 53 | } 54 | 55 | /** 56 | * Annotation that will be used to indicate that a class is a controller adviser 57 | * 58 | */ 59 | const ControllerAdvice = const _ControllerAdvice(); 60 | 61 | class _ControllerAdvice { 62 | 63 | const _ControllerAdvice(); 64 | 65 | } 66 | 67 | /** 68 | * Annotation that will be used to indicate a method that will be called when an exception happens 69 | * 70 | */ 71 | class ExceptionHandler { 72 | 73 | final Type type; 74 | 75 | const ExceptionHandler({this.type}); 76 | 77 | } 78 | 79 | /** 80 | * Annotation that will indicate that a method or a field needs to be added to the Model. 81 | * 82 | * You can use it like this 83 | * 84 | * @ModelAttribute("someValue") 85 | * String someName() { 86 | * return mv.getValue(); 87 | * } 88 | * 89 | */ 90 | class ModelAttribute { 91 | 92 | final String value; 93 | const ModelAttribute(this.value); 94 | 95 | String toString() => "$value"; 96 | 97 | } 98 | 99 | /** 100 | * You can also use the annotation @PathVariable("name") to match the pathvariable, like below: 101 | * 102 | * @RequestMapping(value: "/var/{var1}/", method: "GET") 103 | * String multivariable(req, Model model, @PathVariable("var1") variable) {} 104 | * 105 | * */ 106 | 107 | class PathVariable { 108 | 109 | final String value; 110 | const PathVariable(this.value); 111 | 112 | String toString() => "$value"; 113 | 114 | } 115 | 116 | /** 117 | * Annotation for mapping web requests onto specific handler classes and/or 118 | * handler methods. Provides a consistent style between the different 119 | * environments, with the semantics adapting to the concrete environment. 120 | * 121 | * Look at the following example, how you can use this: 122 | * 123 | * @RequestMapping(value: "/someurl", method: "GET") 124 | * void index(ForceRequest req, Model model) 125 | * 126 | * */ 127 | 128 | class RequestMapping { 129 | 130 | final String value; 131 | final String method; 132 | const RequestMapping({this.value: "", this.method:"GET"}); 133 | 134 | String toString() => "$value -> $method"; 135 | 136 | } 137 | 138 | /** 139 | * Annotation which indicates that a method parameter should be bound to a web 140 | * request parameter. 141 | * 142 | * */ 143 | class RequestParam { 144 | 145 | final String value; 146 | final String defaultValue; 147 | final bool required; 148 | const RequestParam({this.value: "", this.defaultValue: "", this.required: false}); 149 | 150 | String toString() => "$value -> - $defaultValue ($required)"; 151 | 152 | } 153 | 154 | /** 155 | * 156 | * Annotation for assigning the status code of the http response 157 | * 158 | **/ 159 | class ResponseStatus { 160 | 161 | final int value; 162 | 163 | const ResponseStatus(this.value); 164 | 165 | String toString() => "Response statuscode: $value"; 166 | 167 | } 168 | 169 | /** 170 | * Annotation that will be used to set the response type as a json object 171 | * 172 | */ 173 | const ResponseBody = const _ResponseBody(); 174 | 175 | class _ResponseBody { 176 | 177 | const _ResponseBody(); 178 | 179 | } 180 | -------------------------------------------------------------------------------- /lib/converters/abstract_message_converter.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * Abstract base class for most HttpMessageConverter implementations. 5 | */ 6 | abstract class AbstractHttpMessageConverter implements HttpMessageConverter { 7 | 8 | List supportedMediaTypes = new List(); 9 | 10 | var defaultCharset; 11 | 12 | /** 13 | * Set the list of MediaType objects supported by this converter. 14 | */ 15 | void setSupportedMediaTypes(List supportedMediaTypes) { 16 | // Assert.notEmpty(supportedMediaTypes, "'supportedMediaTypes' must not be empty"); 17 | this.supportedMediaTypes = supportedMediaTypes; 18 | } 19 | 20 | List getSupportedMediaTypes() { 21 | return new List.unmodifiable(this.supportedMediaTypes); 22 | } 23 | 24 | /** 25 | * Returns true if any of the #setSupportedMediaTypes(List) 26 | * supported media types MediaType#includes(MediaType) include the 27 | * given media type. 28 | * @param mediaType the media type to read, can be null if not specified. 29 | * Typically the value of a Content-Type header. 30 | * @return true if the supported media types include the media type, 31 | * or if the media type is null 32 | */ 33 | bool canRead(MediaType mediaType) { 34 | if (mediaType == null) { 35 | return true; 36 | } 37 | for (MediaType supportedMediaType in getSupportedMediaTypes()) { 38 | if (supportedMediaType.includes(mediaType)) { 39 | return true; 40 | } 41 | } 42 | return false; 43 | } 44 | 45 | /** 46 | * Returns true if the given media type includes any of the 47 | * #setSupportedMediaTypes(List) supported media types. 48 | * @param mediaType the media type to write, can be null if not specified. 49 | * Typically the value of an Accept header. 50 | * @return true if the supported media types are compatible with the media type, 51 | * or if the media type is null 52 | */ 53 | bool canWrite(MediaType mediaType) { 54 | if (mediaType == null || MediaType.ALL == mediaType) { 55 | return true; 56 | } 57 | for (MediaType supportedMediaType in getSupportedMediaTypes()) { 58 | if (supportedMediaType.isCompatibleWith(mediaType)) { 59 | return true; 60 | } 61 | } 62 | return false; 63 | } 64 | 65 | /** 66 | * This implementation simple delegates to #readInternal(HttpInputMessage). 67 | * Future implementations might add some default behavior, however. 68 | */ 69 | T read(HttpInputMessage inputMessage) { 70 | return readInternal(inputMessage); 71 | } 72 | 73 | /** 74 | * This implementation sets the default headers by calling #addDefaultHeaders, 75 | * and then calls #writeInternal. 76 | */ 77 | void write(final T t, MediaType contentType, HttpOutputMessage outputMessage) { 78 | final HttpHeadersWrapper headers = outputMessage.getResponseHeaders(); 79 | addDefaultHeaders(headers, t, contentType); 80 | writeInternal(t, outputMessage); 81 | } 82 | 83 | /** 84 | * Add default headers to the output message. 85 | * This implementation delegates to #getDefaultContentType(Object) if a content 86 | * type was not provided, set if necessary the default character set, calls 87 | * #getContentLength, and sets the corresponding headers. 88 | */ 89 | void addDefaultHeaders(HttpHeadersWrapper headers, T t, MediaType contentType) { 90 | MediaType contentTypeToUse = contentType; 91 | if (contentType == null || contentType.isWildcardType() || contentType.isWildcardSubtype()) { 92 | contentTypeToUse = getDefaultContentType(t); 93 | } 94 | else if (MediaType.APPLICATION_OCTET_STREAM == contentType) { 95 | MediaType mediaType = getDefaultContentType(t); 96 | contentTypeToUse = (mediaType != null ? mediaType : contentTypeToUse); 97 | } 98 | if (contentTypeToUse != null) { 99 | headers.setContentType(contentTypeToUse); 100 | } 101 | if (headers.getContentLength() == null) { 102 | var contentLength = getContentLength(t, headers.getContentType()); 103 | if (contentLength != null) { 104 | headers.setContentLength(contentLength); 105 | } 106 | } 107 | } 108 | 109 | /** 110 | * Returns the default content type for the given type. Called when #write 111 | * is invoked without a specified content type parameter. 112 | * By default, this returns the first element of the 113 | * #setSupportedMediaTypes(List) supportedMediaTypes property, if any. 114 | * Can be overridden in subclasses. 115 | * @param t the type to return the content type for 116 | * @return the content type, or null if not known 117 | */ 118 | MediaType getDefaultContentType(T t) { 119 | List mediaTypes = getSupportedMediaTypes(); 120 | return (!mediaTypes.isEmpty ? mediaTypes.first : null); 121 | } 122 | 123 | /** 124 | * Returns the content length for the given type. 125 | * By default, this returns null, meaning that the content length is unknown. 126 | * Can be overridden in subclasses. 127 | * @param t the type to return the content length for 128 | * @return the content length, or null if not known 129 | */ 130 | num getContentLength(T t, MediaType contentType) { 131 | return null; 132 | } 133 | 134 | /** 135 | * Abstract template method that reads the actual object. Invoked from {@link #read}. 136 | * @param clazz the type of object to return 137 | * @param inputMessage the HTTP input message to read from 138 | * @return the converted object 139 | */ 140 | T readInternal(HttpInputMessage inputMessage); 141 | 142 | /** 143 | * Abstract template method that writes the actual body. Invoked from #write. 144 | * @param t the object to write to the output message 145 | * @param outputMessage the HTTP output message to write to 146 | */ 147 | void writeInternal(T t, HttpOutputMessage outputMessage); 148 | 149 | } 150 | -------------------------------------------------------------------------------- /lib/converters/csv_message_converter.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class CsvMessageConverter extends AbstractHttpMessageConverter { 4 | 5 | CsvMessageConverter() { 6 | setSupportedMediaTypes([MediaType.TEXT_CSV]); 7 | } 8 | 9 | List readInternal(HttpInputMessage inputMessage) { return null; } 10 | 11 | void writeInternal(List list, HttpOutputMessage outputMessage) { 12 | // write things to the response ... outputMessage.getBody(). 13 | String name, outputFile = ""; 14 | 15 | for (var obj in list) { 16 | InstanceMirror myClassInstanceMirror = reflect(obj); 17 | ClassMirror classMirror = myClassInstanceMirror.type; 18 | 19 | String className = MirrorSystem.getName(classMirror.simpleName); 20 | className = className.toLowerCase(); 21 | name = "${className}.csv"; 22 | 23 | for (var v in classMirror.declarations.values) { 24 | if (v is VariableMirror) { 25 | var value = myClassInstanceMirror.getField(v.simpleName); 26 | outputFile = "${outputFile}${value.reflectee};"; 27 | } 28 | } 29 | outputFile = "$outputFile \n"; 30 | } 31 | try { 32 | outputMessage.getResponseHeaders().add("Content-Disposition", "attachment; filename=\"$name\""); 33 | } catch (e) { 34 | print(e); 35 | } 36 | outputMessage.getOutputBody().write(outputFile); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /lib/converters/http_message_converter.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | abstract class HttpMessageConverter { 4 | 5 | bool canRead(MediaType mediaType) { return false; } 6 | 7 | bool canWrite(MediaType mediaType) { return false; } 8 | 9 | List getSupportedMediaTypes() { return new List(); } 10 | 11 | T read(HttpInputMessage inputMessage) { return null; } 12 | 13 | void write(T t, MediaType contentType, HttpOutputMessage outputMessage) {} 14 | 15 | } 16 | -------------------------------------------------------------------------------- /lib/converters/json_http_message_converter.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class JsonHttpMessageConverter extends AbstractHttpMessageConverter { 4 | 5 | JsonHttpMessageConverter() { 6 | setSupportedMediaTypes([MediaType.APPLICATION_JSON]); 7 | } 8 | 9 | List getSupportedMediaTypes() { return [MediaType.APPLICATION_JSON]; } 10 | 11 | T readInternal(HttpInputMessage inputMessage) { return null; } 12 | 13 | void writeInternal(T t, HttpOutputMessage outputMessage) { 14 | // write things to the response ... outputMessage.getBody(). 15 | print('Convert this to JSON'); 16 | 17 | String data = JSON.encode(t); 18 | outputMessage.getOutputBody().write(data); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /lib/converters/text_http_message_converter.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class TextHttpMessageConverter extends HttpMessageConverter { 4 | 5 | canRead(MediaType mediaType) { 6 | return mediaType.hasSame(MediaType.TEXT_PLAIN) || mediaType.hasSame(MediaType.TEXT_HTML); 7 | } 8 | 9 | bool canWrite(MediaType mediaType) { 10 | return mediaType.hasSame(MediaType.TEXT_PLAIN) || mediaType.hasSame(MediaType.TEXT_HTML); 11 | } 12 | 13 | List getSupportedMediaTypes() { return [MediaType.TEXT_PLAIN_VALUE, MediaType.TEXT_HTML_VALUE]; } 14 | 15 | T read(HttpInputMessage inputMessage) { return null; } 16 | 17 | void write(T t, MediaType contentType, HttpOutputMessage outputMessage) { 18 | // write things to the response ... outputMessage.getBody(). 19 | try { 20 | String text = t.toString(); 21 | outputMessage.getOutputBody().write(text); 22 | } catch(exception) { 23 | outputMessage.getOutputBody().write("N/P"); 24 | } 25 | 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /lib/error/handler_exception_resolver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * Abstract class to be implemented by objects than can resolve exceptions thrown 5 | * during handler mapping or execution, in the typical case to error views. 6 | * 7 | */ 8 | 9 | abstract class HandlerExceptionResolver { 10 | 11 | /** 12 | * Try to resolve the given exception that got thrown during on handler execution, 13 | * returning a String that the viewname or return null if you want to return a json representation. 14 | * @param request current Force Request, an encapsulated HttpRequest 15 | * @param ex the exception that got thrown during handler execution 16 | * @return a corresponding String that represents the viewname of your template 17 | */ 18 | String resolveException(ForceRequest request, Model model, Exception ex); 19 | 20 | /** 21 | * Try to resolve the given exception that got thrown during on handler error, 22 | * an error is different from an exception, it is an error that happens on runtime. 23 | * returning a String that the viewname or return null if you want to return a json representation. 24 | * @param request current Force Request, an encapsulated HttpRequest 25 | * @param ex the exception that got thrown during handler execution 26 | * @return a corresponding String that represents the viewname of your template 27 | */ 28 | 29 | String resolveError(ForceRequest request, Model model, Error er); 30 | 31 | } -------------------------------------------------------------------------------- /lib/error/simple_exception_resolver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * A simple dummy implementation for the handling of a exception 5 | */ 6 | 7 | class SimpleExceptionResolver extends HandlerExceptionResolver { 8 | 9 | static final String DEFAULT_EXCEPTION_ATTRIBUTE = "exception"; 10 | static final String DEFAULT_STACKTRACE_ATTRIBUTE = "stacktrace"; 11 | static final String DEFAULT_ERROR_ATTRIBUTE = "error"; 12 | 13 | /** 14 | * A simple implementation of the given exception that got thrown during on handler execution. 15 | * @param request current Force Request, an encapsulated HttpRequest 16 | * @param ex the exception that got thrown during handler execution 17 | * @return a corresponding String that represents the viewname of your template or null when a json response is required 18 | */ 19 | String resolveException(ForceRequest request, Model model, Exception ex) { 20 | model.addAttribute(DEFAULT_EXCEPTION_ATTRIBUTE, ex.toString()); 21 | return null; 22 | } 23 | 24 | /** 25 | * A simple implementation of the given exception that got thrown during on handler error. 26 | * @param request current Force Request, an encapsulated HttpRequest 27 | * @param ex the exception that got thrown during handler execution 28 | * @return a corresponding String that represents the viewname of your template or null when a json response is required 29 | */ 30 | String resolveError(ForceRequest request, Model model, Error er) { 31 | model.addAttribute(DEFAULT_ERROR_ATTRIBUTE, er.toString()); 32 | model.addAttribute(DEFAULT_STACKTRACE_ATTRIBUTE, er.stackTrace.toString()); 33 | return null; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /lib/force_mvc.dart: -------------------------------------------------------------------------------- 1 | library dart_force_mvc_lib; 2 | 3 | import 'dart:async'; 4 | import 'dart:mirrors'; 5 | import 'dart:convert'; 6 | import 'dart:io'; 7 | import 'dart:math'; 8 | import 'dart:collection'; 9 | 10 | import 'package:http_server/http_server.dart' as http_server; 11 | import 'package:route/server.dart' show Router, UrlPattern; 12 | import 'package:logging/logging.dart' show Logger, Level, LogRecord; 13 | 14 | import 'package:path/path.dart' show normalize; 15 | 16 | import 'package:mustache4dart/mustache4dart.dart'; 17 | 18 | import 'package:mirrorme/mirrorme.dart'; 19 | import 'package:wired/wired.dart'; 20 | 21 | import 'package:locale/locale.dart'; 22 | export 'package:locale/locale.dart'; 23 | 24 | export 'package:jsonify/jsonify.dart'; 25 | 26 | part 'server/web_application.dart'; 27 | part 'server/simple_web_server.dart'; 28 | part 'server/mvc_typedefs.dart'; 29 | part 'server/request_method.dart'; 30 | part 'server/model.dart'; 31 | part 'server/force_request.dart'; 32 | part 'server/registry.dart'; 33 | part 'server/handler_interceptor.dart'; 34 | part 'server/interceptors_collection.dart'; 35 | part 'server/path_analyzer.dart'; 36 | part 'server/serving_files.dart'; 37 | part 'server/response_hooks.dart'; 38 | part 'server/http_request_streamer.dart'; 39 | part 'server/serving_assistent.dart'; 40 | 41 | part 'i18n/locale_resolver.dart'; 42 | part 'i18n/accept_header_locale_resolver.dart'; 43 | part 'i18n/default_locale_resolver.dart'; 44 | part 'i18n/fixed_locale_resolver.dart'; 45 | part 'i18n/cookie_locale_resolver.dart'; 46 | 47 | part 'manager/cookie_holder_manager.dart'; 48 | 49 | // security 50 | part 'security/security_context_holder.dart'; 51 | part 'security/security_strategy.dart'; 52 | part 'security/no_security_strategy.dart'; 53 | 54 | // render 55 | part 'render/view_render.dart'; 56 | part 'render/mustache_render.dart'; 57 | 58 | part 'annotations/metadata.dart'; 59 | part 'annotations/helpers.dart'; 60 | 61 | // exception handling 62 | part 'error/handler_exception_resolver.dart'; 63 | part 'error/simple_exception_resolver.dart'; 64 | 65 | // converters 66 | part 'converters/http_message_converter.dart'; 67 | part 'converters/json_http_message_converter.dart'; 68 | part 'converters/text_http_message_converter.dart'; 69 | part 'converters/csv_message_converter.dart'; 70 | part 'converters/abstract_message_converter.dart'; 71 | 72 | // converters 73 | part 'http/media_type.dart'; 74 | part 'http/mime_type.dart'; 75 | part 'http/invalid_mime_type_error.dart'; 76 | part 'http/http_message.dart'; 77 | part 'http/http_input_message.dart'; 78 | part 'http/http_output_message.dart'; 79 | part 'http/http_message_regulator.dart'; 80 | // part 'http/http_headers.dart'; 81 | part 'http/http_headers_wrapper.dart'; 82 | part 'http/http_method.dart'; 83 | 84 | part 'utils/mime_type_utils.dart'; 85 | -------------------------------------------------------------------------------- /lib/http/http_headers_wrapper.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class HttpHeadersWrapper { 4 | 5 | static final String CONTENT_DISPOSITION = "Content-Disposition"; 6 | 7 | HttpHeaders httpHeaders; 8 | 9 | HttpHeadersWrapper(this.httpHeaders); 10 | 11 | /** 12 | * Set the list of acceptable MediaType, 13 | * as specified by the Accept Header. 14 | */ 15 | void setAccept(List acceptableMediaTypes) { 16 | this.set(HttpHeaders.ACCEPT, MediaType.toStringify(acceptableMediaTypes)); 17 | } 18 | 19 | /** 20 | * Return the list of acceptable MediaType media types, 21 | * as specified by the Accept header. 22 | * Returns an empty list when the acceptable media types are unspecified. 23 | */ 24 | List getAccept() { 25 | String value = getFirst(HttpHeaders.ACCEPT); 26 | List result = (value != null ? MediaType.parseMediaTypes(value) : new List()); 27 | 28 | return result; 29 | } 30 | 31 | /** 32 | * Set the MediaType media type of the body, 33 | * as specified by the Content-Type header. 34 | */ 35 | void setContentType(MediaType mediaType) { 36 | // assert(!mediaType.isWildcardType()); 37 | // assert(!mediaType.isWildcardSubtype()); 38 | 39 | this.set(HttpHeaders.CONTENT_TYPE, mediaType.toString()); 40 | } 41 | 42 | /** 43 | * Return the MediaType media type of the body, as specified 44 | * by the Content-Type header. 45 | * Returns null when the content-type is unknown. 46 | */ 47 | MediaType getContentType() { 48 | String value = getFirst(HttpHeaders.CONTENT_TYPE); 49 | return (hasLength(value) ? new MediaType.parseMediaType(value) : null); 50 | } 51 | 52 | /** 53 | * Set the MediaType media type of the body, 54 | * as specified by the Content-Type header. 55 | */ 56 | void setContentLength(length) { 57 | this.set(HttpHeaders.CONTENT_LENGTH, length); 58 | } 59 | 60 | /** 61 | * Return the MediaType media type of the body, as specified 62 | * by the Content-Type header. 63 | * Returns null when the content-type is unknown. 64 | */ 65 | dynamic getContentLength() { 66 | return getFirst(HttpHeaders.CONTENT_LENGTH); 67 | } 68 | 69 | 70 | /** 71 | * Set the (new) value of the Content-Disposition header 72 | * for form-data. 73 | */ 74 | void setContentDispositionFormData(String name, String filename) { 75 | assert(name != null); 76 | 77 | String builder = "form-data; name=\""; 78 | builder = "$builder$name\""; 79 | if (filename != null) { 80 | builder = "$builder; filename=\""; 81 | builder = "$builder$filename\""; 82 | } 83 | this.set(CONTENT_DISPOSITION, builder); 84 | } 85 | 86 | bool hasLength(String value) { 87 | return value != null && value.length > 0; 88 | } 89 | 90 | String getFirst(name) { 91 | return this.httpHeaders.value(name); 92 | } 93 | 94 | void set(name, value) { 95 | this.httpHeaders.set(name, value); 96 | } 97 | 98 | void add(name, value) { 99 | this.httpHeaders.add(name, value); 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /lib/http/http_input_message.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | /** 3 | * Represents an HTTP input message, consisting of headers 4 | * and a readable body. 5 | * 6 | * Typically implemented by an HTTP request handle on the server side, 7 | * or an HTTP response handle on the client side. 8 | * 9 | */ 10 | abstract class HttpInputMessage extends HttpMessage { 11 | 12 | /** 13 | * Return the headers of this message. 14 | * @return a corresponding HttpHeaders object (never null) 15 | */ 16 | HttpHeadersWrapper getRequestHeaders() { return null; } 17 | 18 | /** 19 | * Return the body of the message as an stream. 20 | */ 21 | Stream getBody() { return null; } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /lib/http/http_message.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | /** 3 | * Represents the base interface for HTTP request and response messages. 4 | * Consists of HttpHeaders, retrievable via #getHeaders(). 5 | * 6 | * @author Joris Hermans 7 | * @since 0.8.0 8 | */ 9 | abstract class HttpMessage { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /lib/http/http_message_regulator.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class HttpMessageRegulator { 4 | 5 | List messageConverters = new List(); 6 | 7 | HttpMessageRegulator() { 8 | add(new JsonHttpMessageConverter()); 9 | add(new CsvMessageConverter()); 10 | } 11 | 12 | add(HttpMessageConverter messageConverter) { 13 | messageConverters.add(messageConverter); 14 | } 15 | 16 | loopOverMessageConverters(ForceRequest req, Object obj) { 17 | List mediaTypes = req.getRequestHeaders().getAccept(); 18 | for (MediaType mediaType in mediaTypes) { 19 | for (HttpMessageConverter messageConverter in messageConverters) { 20 | if (messageConverter.canWrite(mediaType)) { 21 | messageConverter.write(obj, mediaType, req); 22 | } 23 | } 24 | } 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /lib/http/http_method.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | enum HttpMethod { 4 | 5 | GET, HEAD, POST, PUT, PATCH, DELETE, OPTIONS, TRACE 6 | 7 | } 8 | 9 | class HttpMethodUtils { 10 | final Map mappings = new HashMap(); 11 | 12 | HttpMethodUtils() { 13 | mappings["GET"] = HttpMethod.GET; 14 | mappings["HEAD"] = HttpMethod.HEAD; 15 | mappings["POST"] = HttpMethod.POST; 16 | mappings["PUT"] = HttpMethod.PUT; 17 | mappings["PATCH"] = HttpMethod.PATCH; 18 | mappings["DELETE"] = HttpMethod.DELETE; 19 | mappings["OPTIONS"] = HttpMethod.OPTIONS; 20 | mappings["TRACE"] = HttpMethod.TRACE; 21 | } 22 | 23 | 24 | /** 25 | * Resolve the given method value to an [HttpMethod]. 26 | * @param method the method value as a String 27 | * @return the corresponding [HttpMethod], or [null] if not found 28 | */ 29 | HttpMethod resolve(String method) { 30 | return (method != null ? mappings[method] : null); 31 | } 32 | 33 | 34 | /** 35 | * Determine whether this [HttpMethod] matches the given 36 | * method value. 37 | * @param method the method value as a String 38 | * @return true if it matches, false otherwise 39 | */ 40 | bool matches(HttpMethod http_method, String method) { 41 | return (http_method == resolve(method)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/http/http_output_message.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | /** 3 | * Represents an HTTP output message, consisting of headers 4 | * and a writable body. 5 | * 6 | * Typically implemented by an HTTP request handle on the client side, 7 | * or an HTTP response handle on the server side. 8 | * 9 | * @author Joris Hermans 10 | * @since 0.8.0 11 | */ 12 | abstract class HttpOutputMessage extends HttpMessage { 13 | 14 | /** 15 | * Return the headers of this message. 16 | * @return a corresponding HttpHeaders object (never null) 17 | */ 18 | HttpHeadersWrapper getResponseHeaders() { return null; } 19 | 20 | /** 21 | * Return the body of the message as an output stream. 22 | */ 23 | IOSink getOutputBody(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /lib/http/invalid_mime_type_error.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class InvalidMimeTypeError extends StateError { 4 | InvalidMimeTypeError(String type, String msg) : super(msg); 5 | } 6 | -------------------------------------------------------------------------------- /lib/http/media_type.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * A sub-class of MimeType that adds support for quality parameters as defined 5 | * in the HTTP specification. 6 | * 7 | * @author Joris Hermans 8 | * 9 | */ 10 | class MediaType extends MimeType { 11 | 12 | /** 13 | * constant media type that includes all media ranges (i.e. "*/*"). 14 | */ 15 | static final MediaType ALL = new MediaType.parseMediaType(ALL_VALUE); 16 | 17 | /** 18 | * A String equivalent of {@link MediaType#ALL}. 19 | */ 20 | static final String ALL_VALUE = "*/*"; 21 | 22 | /** 23 | * constant media type for {@code application/atom+xml}. 24 | */ 25 | static final MediaType APPLICATION_ATOM_XML = new MediaType.parseMediaType(APPLICATION_ATOM_XML_VALUE); 26 | 27 | /** 28 | * A String equivalent of {@link MediaType#APPLICATION_ATOM_XML}. 29 | */ 30 | static final String APPLICATION_ATOM_XML_VALUE = "application/atom+xml"; 31 | 32 | /** 33 | * constant media type for {@code application/x-www-form-urlencoded}. 34 | * */ 35 | static final MediaType APPLICATION_FORM_URLENCODED = new MediaType.parseMediaType(APPLICATION_FORM_URLENCODED_VALUE); 36 | 37 | /** 38 | * A String equivalent of {@link MediaType#APPLICATION_FORM_URLENCODED}. 39 | */ 40 | static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded"; 41 | 42 | /** 43 | * constant media type for {@code application/json}. 44 | * @see #APPLICATION_JSON_UTF8 45 | */ 46 | static final MediaType APPLICATION_JSON = new MediaType.parseMediaType(APPLICATION_JSON_VALUE); 47 | 48 | /** 49 | * A String equivalent of {@link MediaType#APPLICATION_JSON}. 50 | * @see #APPLICATION_JSON_UTF8_VALUE 51 | */ 52 | static final String APPLICATION_JSON_VALUE = "application/json"; 53 | 54 | /** 55 | * constant media type for {@code application/json;charset=UTF-8}. 56 | */ 57 | static final MediaType APPLICATION_JSON_UTF8 = new MediaType.parseMediaType(APPLICATION_JSON_UTF8_VALUE); 58 | 59 | /** 60 | * A String equivalent of {@link MediaType#APPLICATION_JSON_UTF8}. 61 | */ 62 | static final String APPLICATION_JSON_UTF8_VALUE = APPLICATION_JSON_VALUE + ";charset=UTF-8"; 63 | 64 | /** 65 | * constant media type for {@code application/octet-stream}. 66 | * */ 67 | static final MediaType APPLICATION_OCTET_STREAM = new MediaType.parseMediaType(APPLICATION_OCTET_STREAM_VALUE); 68 | 69 | /** 70 | * A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}. 71 | */ 72 | static final String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream"; 73 | 74 | /** 75 | * constant media type for {@code application/xhtml+xml}. 76 | * */ 77 | static final MediaType APPLICATION_XHTML_XML = new MediaType.parseMediaType(APPLICATION_XHTML_XML_VALUE); 78 | 79 | /** 80 | * A String equivalent of {@link MediaType#APPLICATION_XHTML_XML}. 81 | */ 82 | static final String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml"; 83 | 84 | /** 85 | * constant media type for {@code application/xml}. 86 | */ 87 | static final MediaType APPLICATION_XML = new MediaType.parseMediaType(APPLICATION_XML_VALUE); 88 | 89 | /** 90 | * A Text CSV mediatype. 91 | */ 92 | static final String TEXT_CSV_VALUE = "text/csv"; 93 | 94 | /** 95 | * constant media type for {@code text/csv}. 96 | */ 97 | static final MediaType TEXT_CSV = new MediaType.parseMediaType(TEXT_CSV_VALUE); 98 | 99 | 100 | /** 101 | * A String equivalent of {@link MediaType#APPLICATION_XML}. 102 | */ 103 | static final String APPLICATION_XML_VALUE = "application/xml"; 104 | 105 | /** 106 | * constant media type for {@code image/gif}. 107 | */ 108 | static final MediaType IMAGE_GIF = new MediaType.parseMediaType(IMAGE_GIF_VALUE); 109 | 110 | /** 111 | * A String equivalent of {@link MediaType#IMAGE_GIF}. 112 | */ 113 | static final String IMAGE_GIF_VALUE = "image/gif"; 114 | 115 | /** 116 | * constant media type for {@code image/jpeg}. 117 | */ 118 | static final MediaType IMAGE_JPEG = new MediaType.parseMediaType(IMAGE_JPEG_VALUE); 119 | 120 | /** 121 | * A String equivalent of {@link MediaType#IMAGE_JPEG}. 122 | */ 123 | static final String IMAGE_JPEG_VALUE = "image/jpeg"; 124 | 125 | /** 126 | * Public constant media type for {@code image/png}. 127 | */ 128 | static final MediaType IMAGE_PNG = new MediaType.parseMediaType(IMAGE_PNG_VALUE); 129 | 130 | /** 131 | * A String equivalent of {@link MediaType#IMAGE_PNG}. 132 | */ 133 | static final String IMAGE_PNG_VALUE = "image/png"; 134 | 135 | /** 136 | * Public constant media type for {@code image/webp}. 137 | */ 138 | static final MediaType IMAGE_WEBP = new MediaType.parseMediaType(IMAGE_WEBP_VALUE); 139 | 140 | /** 141 | * A String equivalent of {@link MediaType#IMAGE_WEBP}. 142 | */ 143 | static final String IMAGE_WEBP_VALUE = "image/webp"; 144 | 145 | /** 146 | * Public constant media type for {@code multipart/form-data}. 147 | * */ 148 | static final MediaType MULTIPART_FORM_DATA = new MediaType.parseMediaType(MULTIPART_FORM_DATA_VALUE); 149 | 150 | /** 151 | * A String equivalent of {@link MediaType#MULTIPART_FORM_DATA}. 152 | */ 153 | static final String MULTIPART_FORM_DATA_VALUE = "multipart/form-data"; 154 | 155 | /** 156 | * Public constant media type for {@code text/html}. 157 | * */ 158 | static final MediaType TEXT_HTML = new MediaType.parseMediaType(TEXT_HTML_VALUE); 159 | 160 | /** 161 | * A String equivalent of {@link MediaType#TEXT_HTML}. 162 | */ 163 | static final String TEXT_HTML_VALUE = "text/html"; 164 | 165 | /** 166 | * Public constant media type for {@code text/plain}. 167 | * */ 168 | static final MediaType TEXT_PLAIN = new MediaType.parseMediaType(TEXT_PLAIN_VALUE); 169 | 170 | /** 171 | * A String equivalent of {@link MediaType#TEXT_PLAIN}. 172 | */ 173 | static final String TEXT_PLAIN_VALUE = "text/plain"; 174 | 175 | /** 176 | * Public constant media type for {@code text/xml}. 177 | * */ 178 | static final MediaType TEXT_XML = new MediaType.parseMediaType(TEXT_XML_VALUE); 179 | 180 | /** 181 | * A String equivalent of {@link MediaType#TEXT_XML}. 182 | */ 183 | static final String TEXT_XML_VALUE = "text/xml"; 184 | 185 | 186 | static final String PARAM_QUALITY_FACTOR = "q"; 187 | 188 | MediaType(String type, {String subtype, String charset, Map parameters}) : super(type, subtype: subtype, charset: charset, parameters: parameters); 189 | 190 | factory MediaType.parseMediaType(String mediaType) { 191 | MimeType type; 192 | type = MimeTypeUtils.parseMimeType(mediaType); 193 | return new MediaType(type.getType(), subtype: type.getSubtype(), parameters: type.getParameters()); 194 | } 195 | 196 | bool includes(MediaType other) { 197 | return super.includes(other); 198 | } 199 | 200 | bool isCompatibleWith(MediaType other) { 201 | return super.isCompatibleWith(other); 202 | } 203 | 204 | bool hasSame(MediaType other) { 205 | return this.getType() == other.getType() 206 | && this.getSubtype() == other.getSubtype(); 207 | } 208 | 209 | /* 210 | * Return a replica of this instance with the quality value of the given MediaType. 211 | * @return the same instance if the given MediaType doesn't have a quality value, or a new one otherwise 212 | */ 213 | MediaType copyQualityValue(MediaType mediaType) { 214 | if (!mediaType.getParameters().containsKey(PARAM_QUALITY_FACTOR)) { 215 | return this; 216 | } 217 | Map params = new LinkedHashMap(); 218 | params.addAll(getParameters()); 219 | params[PARAM_QUALITY_FACTOR] = mediaType.getParameters()[PARAM_QUALITY_FACTOR]; 220 | return new MediaType(this.getType(), parameters: params); 221 | } 222 | 223 | /* 224 | * Return a replica of this instance with its quality value removed. 225 | * @return the same instance if the media type doesn't contain a quality value, or a new one otherwise 226 | */ 227 | MediaType removeQualityValue() { 228 | if (!getParameters().containsKey(PARAM_QUALITY_FACTOR)) { 229 | return this; 230 | } 231 | Map params = new LinkedHashMap(); 232 | params.addAll(getParameters()); 233 | params.remove(PARAM_QUALITY_FACTOR); 234 | return new MediaType(this.getType(), parameters: params); 235 | } 236 | 237 | /* 238 | * Parse the given, comma-separated string into a list of MediaType objects. 239 | * This method can be used to parse an Accept or Content-Type header. 240 | * @param mediaTypes the string to parse 241 | * @return the list of media types 242 | * @throws IllegalArgumentException if the string cannot be parsed 243 | */ 244 | static List parseMediaTypes(String mediaTypes) { 245 | var tokens = mediaTypes.split(","); 246 | List result = new List(); 247 | for (String token in tokens) { 248 | result.add(new MediaType.parseMediaType(token)); 249 | } 250 | return result; 251 | } 252 | 253 | /** 254 | * Return a string representation of the given list of MediaType objects. 255 | * This method can be used to for an Accept or Content-Type header. 256 | * @param mediaTypes the media types to create a string representation for 257 | * @return the string representation 258 | */ 259 | static String toStringify(Iterable mediaTypes) { 260 | return MimeTypeUtils.toStringify(mediaTypes); 261 | } 262 | } 263 | -------------------------------------------------------------------------------- /lib/http/mime_type.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * Represents a MIME Type, as originally defined in RFC 2046 and subsequently used in 5 | * other Internet protocols including HTTP. 6 | * 7 | * This class, however, does not contain support for the q-parameters used 8 | * in HTTP content negotiation. Those can be found in the sub-class 9 | * MediaType in the http folder. 10 | */ 11 | class MimeType { 12 | 13 | static final String WILDCARD_TYPE = "*"; 14 | 15 | // static final BitSet TOKEN; 16 | 17 | static final String PARAM_CHARSET = "charset"; 18 | 19 | String type; 20 | 21 | String subtype; 22 | 23 | Map parameters; 24 | 25 | String charset; 26 | 27 | MimeType._(); 28 | 29 | /** 30 | * Create a new Mimetype for the given primary type and subtype and charset and parameters. 31 | */ 32 | MimeType(String type, {String subtype, String charset, Map parameters}) { 33 | // Assert.hasLength(type, "type must not be empty"); 34 | // Assert.hasLength(subtype, "subtype must not be empty"); 35 | // checkToken(type); 36 | // checkToken(subtype); 37 | this.type = type.toLowerCase(); 38 | this.subtype = subtype.toLowerCase(); 39 | this.charset = charset; 40 | 41 | if (parameters != null && parameters.length > 0) { 42 | Map map = new LinkedHashMap(); 43 | for (var attribute in parameters.keys) { 44 | String value = parameters[attribute]; 45 | checkParameters(attribute, value); 46 | map[attribute] = value; 47 | } 48 | this.parameters = map; 49 | } 50 | else { 51 | this.parameters = new LinkedHashMap(); 52 | } 53 | } 54 | 55 | void checkParameters(String attribute, String value) { 56 | // Assert.hasLength(attribute, "parameter attribute must not be empty"); 57 | // Assert.hasLength(value, "parameter value must not be empty"); 58 | // checkToken(attribute); 59 | if (PARAM_CHARSET == attribute) { 60 | value = unquote(value); 61 | } 62 | } 63 | 64 | bool isQuotedString(String s) { 65 | if (s.length < 2) { 66 | return false; 67 | } 68 | else { 69 | return ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'"))); 70 | } 71 | } 72 | 73 | String unquote(String s) { 74 | if (s == null) { 75 | return null; 76 | } 77 | return isQuotedString(s) ? s.substring(1, s.length - 1) : s; 78 | } 79 | 80 | /** 81 | * Indicates whether the getType is the wildcard character 82 | * or not. 83 | */ 84 | bool isWildcardType() { 85 | return WILDCARD_TYPE == getType(); 86 | } 87 | 88 | /** 89 | * Indicates whether the subtype is the wildcard 90 | * character or the wildcard character followed by a suffix. 91 | * @return whether the subtype is a wildcard 92 | */ 93 | bool isWildcardSubtype() { 94 | return WILDCARD_TYPE == (getSubtype()) || getSubtype().startsWith("*+"); 95 | } 96 | 97 | /** 98 | * Indicates whether this media type is concrete, i.e. whether neither the type 99 | * nor the subtype is a wildcard character. 100 | * @return whether this media type is concrete 101 | */ 102 | bool isConcrete() { 103 | return !isWildcardType() && !isWildcardSubtype(); 104 | } 105 | 106 | /** 107 | * Return the primary type. 108 | */ 109 | String getType() { 110 | return this.type; 111 | } 112 | 113 | /** 114 | * Return the subtype. 115 | */ 116 | String getSubtype() { 117 | return this.subtype; 118 | } 119 | 120 | /** 121 | * Return the character set, as indicated by a charset parameter, if any. 122 | * @return the character set, or null if not available 123 | */ 124 | String getCharSet() { 125 | String charSet = getParameter(PARAM_CHARSET); 126 | return (charSet != null ? unquote(charSet) : null); 127 | } 128 | 129 | /** 130 | * Return a generic parameter value, given a parameter name. 131 | * @param name the parameter name 132 | * @return the parameter value, or null if not present 133 | */ 134 | String getParameter(String name) { 135 | return this.parameters[name]; 136 | } 137 | 138 | /* 139 | * Return all generic parameter values. 140 | * @return a read-only map (possibly empty, never null) 141 | */ 142 | Map getParameters() { 143 | return this.parameters; 144 | } 145 | 146 | /** 147 | * Indicate whether this MediaType includes the given media type. 148 | * For instance, text includes text/plain and text/html, 149 | * and application+xml includes application/soap+xml, etc. This 150 | * method is not symmetric. 151 | * @param other the reference media type with which to compare 152 | * @return true if this media type includes the given media type; 153 | * false otherwise 154 | */ 155 | bool includes(MimeType other) { 156 | if (other == null) { 157 | return false; 158 | } 159 | if (this.isWildcardType()) { 160 | // includes anything 161 | return true; 162 | } 163 | else if (getType() == other.getType()) { 164 | if (getSubtype() == other.getSubtype()) { 165 | return true; 166 | } 167 | if (this.isWildcardSubtype()) { 168 | // wildcard with suffix, e.g. application/*+xml 169 | int thisPlusIdx = getSubtype().indexOf('+'); 170 | if (thisPlusIdx == -1) { 171 | return true; 172 | } 173 | else { 174 | // application/*+xml includes application/soap+xml 175 | int otherPlusIdx = other.getSubtype().indexOf('+'); 176 | if (otherPlusIdx != -1) { 177 | String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx); 178 | String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1); 179 | String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1); 180 | if (thisSubtypeSuffix == otherSubtypeSuffix && WILDCARD_TYPE == thisSubtypeNoSuffix) { 181 | return true; 182 | } 183 | } 184 | } 185 | } 186 | } 187 | return false; 188 | } 189 | 190 | bool isCompatibleWith(MimeType other) { 191 | if (other == null) { 192 | return false; 193 | } 194 | if (isWildcardType() || other.isWildcardType()) { 195 | return true; 196 | } 197 | else if (getType() == other.getType()) { 198 | if (getSubtype() == other.getSubtype()) { 199 | return true; 200 | } 201 | // wildcard with suffix? e.g. application/*+xml 202 | if (this.isWildcardSubtype() || other.isWildcardSubtype()) { 203 | 204 | int thisPlusIdx = getSubtype().indexOf('+'); 205 | int otherPlusIdx = other.getSubtype().indexOf('+'); 206 | 207 | if (thisPlusIdx == -1 && otherPlusIdx == -1) { 208 | return true; 209 | } 210 | else if (thisPlusIdx != -1 && otherPlusIdx != -1) { 211 | String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx); 212 | String otherSubtypeNoSuffix = other.getSubtype().substring(0, otherPlusIdx); 213 | 214 | String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1); 215 | String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1); 216 | 217 | if (thisSubtypeSuffix == otherSubtypeSuffix && 218 | (WILDCARD_TYPE == thisSubtypeNoSuffix || WILDCARD_TYPE == otherSubtypeNoSuffix)) { 219 | return true; 220 | } 221 | } 222 | } 223 | } 224 | return false; 225 | } 226 | 227 | String toString() { 228 | // return "${type}/${subtype} -> ${parameters}"; 229 | return "${type}/${subtype}"; 230 | } 231 | 232 | /** 233 | * Parse the given String value into a MimeType object, 234 | * with this method name following the 'valueOf' naming convention 235 | * @see MimeTypeUtils#parseMimeType(String) 236 | */ 237 | static MimeType valueOf(String value) { 238 | return MimeTypeUtils.parseMimeType(value); 239 | } 240 | 241 | } 242 | -------------------------------------------------------------------------------- /lib/i18n/accept_header_locale_resolver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * Implementation of LocaleResolver that simply uses the primary locale 5 | * specified in the "accept-language" header of the HTTP request (that is, 6 | * the locale sent by the client browser, normally that of the client's OS). 7 | */ 8 | class AcceptHeaderLocaleResolver implements LocaleResolver { 9 | 10 | Locale resolveLocale(ForceRequest request) { 11 | List locales = new List(); 12 | List values = request.header(HttpHeaders.ACCEPT_LANGUAGE); 13 | if (values!=null && values.isNotEmpty) { 14 | values.forEach((value) { 15 | locales.add(resolveLocaleWithHeader(value)); 16 | }); 17 | } 18 | return locales.isNotEmpty? locales[0] : Locale.defaultLocale; 19 | } 20 | 21 | void setLocale(ForceRequest request, Locale locale) { 22 | throw new UnsupportedError( 23 | "Cannot change HTTP accept header - use a different locale resolution strategy"); 24 | } 25 | 26 | Locale resolveLocaleWithHeader(String accept_header) { 27 | List locales = new List(); 28 | for (String str in accept_header.split(",")){ 29 | List arr = str.trim().replaceAll("-", "_").split(";"); 30 | 31 | //Parse the locale 32 | Locale locale = null; 33 | List l = arr[0].split("_"); 34 | switch(l.length){ 35 | case 2: locale = new Locale(l[0], l[1]); break; 36 | case 3: locale = new Locale(l[0], l[1], variant: l[2]); break; 37 | default: locale = new Locale(l[0], ""); break; 38 | } 39 | 40 | //Parse the q-value 41 | // not been used 42 | /*double q = 1.0; 43 | for (String s in arr){ 44 | s = s.trim(); 45 | if (s.startsWith("q=")){ 46 | q = double.parse(s.substring(2).trim()); 47 | break; 48 | } 49 | }*/ 50 | 51 | locales.add(locale); 52 | } 53 | return locales.isNotEmpty? locales[0] : null; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/i18n/cookie_locale_resolver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * {@link LocaleResolver} implementation that uses a cookie sent back to the user 5 | * in case of a custom setting, with a fallback to the specified default locale 6 | * or the request's accept-header locale. 7 | * 8 | * This is particularly useful for stateless applications without user sessions. 9 | */ 10 | class CookieLocaleResolver extends AbstractLocaleResolver { 11 | 12 | /** 13 | * The default cookie name used if none is explicitly set. 14 | */ 15 | final String DEFAULT_COOKIE_NAME = "FORCE.LOCALE"; 16 | 17 | Locale defaultLocale; 18 | 19 | CookieHolderManager cookieManager = new CookieHolderManager(); 20 | 21 | CookieLocaleResolver() { 22 | cookieManager.cookieName = DEFAULT_COOKIE_NAME; 23 | } 24 | 25 | /** 26 | * Set a fixed Locale that this resolver will return if no cookie found. 27 | */ 28 | void setDefaultLocale(Locale defaultLocale) { 29 | this.defaultLocale = defaultLocale; 30 | } 31 | 32 | /** 33 | * Return the fixed Locale that this resolver will return if no cookie found, 34 | * if any. 35 | */ 36 | Locale getDefaultLocale() { 37 | return this.defaultLocale; 38 | } 39 | 40 | 41 | Locale resolveLocale(ForceRequest request) { 42 | // Retrieve and parse cookie value. 43 | Cookie cookie = cookieManager.getCookie(request.request); 44 | if (cookie != null) { 45 | // ... todo for see implementation 46 | Locale locale = Locale.parseString(cookie.value); //StringUtils.parseLocaleString(cookie.value); How will I parse the cookie to a locale 47 | 48 | if (locale != null) { 49 | return locale; 50 | } 51 | } 52 | 53 | return determineDefaultLocale(request); 54 | } 55 | 56 | void setLocale(ForceRequest request, Locale locale) { 57 | if (locale != null) { 58 | cookieManager.addCookie(request.request.response, locale.toString()); 59 | } else { 60 | // Set request attribute to fallback locale and remove cookie. 61 | cookieManager.removeCookie(request.request.response); 62 | } 63 | } 64 | 65 | /** 66 | * Determine the default locale for the given request, 67 | * Called if no locale cookie has been found. 68 | * 69 | * The default implementation returns the specified default locale, 70 | * if any, else falls back to the request's accept-header locale. 71 | * @param request the request to resolve the locale for 72 | * @return the default locale 73 | */ 74 | Locale determineDefaultLocale(ForceRequest request) { 75 | Locale defaultLocale = getDefaultLocale(); 76 | if (defaultLocale == null) { 77 | defaultLocale = Locale.defaultLocale; 78 | } 79 | return defaultLocale; 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lib/i18n/default_locale_resolver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | abstract class AbstractLocaleResolver implements LocaleResolver { 4 | 5 | Locale _defaultLocale; 6 | 7 | 8 | /** 9 | * Set a default Locale that this resolver will return if no other locale found. 10 | */ 11 | void setDefaultLocale(Locale defaultLocale) { 12 | this._defaultLocale = defaultLocale; 13 | } 14 | 15 | /** 16 | * Return the default Locale that this resolver is supposed to fall back to, if any. 17 | */ 18 | Locale getDefaultLocale() { 19 | return this._defaultLocale; 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /lib/i18n/fixed_locale_resolver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * Always returns a default locale, implementation of a locale resolver 5 | */ 6 | class FixedLocaleResolver extends AbstractLocaleResolver { 7 | 8 | /** 9 | * Create a FixedLocaleResolver that exposes the given locale. 10 | * @param locale the locale to expose 11 | */ 12 | FixedLocaleResolver(Locale locale) { 13 | setDefaultLocale(locale); 14 | } 15 | 16 | Locale resolveLocale(ForceRequest request) { 17 | Locale locale = getDefaultLocale(); 18 | if (locale == null) { 19 | locale = Locale.defaultLocale; 20 | } 21 | return locale; 22 | } 23 | 24 | void setLocale(ForceRequest request, Locale locale) { 25 | throw new UnsupportedError( 26 | "Cannot change fixed locale - use a different locale resolution strategy"); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /lib/i18n/locale_resolver.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | /** 4 | * Astract class for web-based locale resolution strategies that allows for 5 | * both locale resolution via the request and locale modification via 6 | * request and response. 7 | * 8 | *

This interface allows for implementations based on request, session, 9 | * cookies, etc. The default implementation is AcceptHeaderLocaleResolver, 10 | * simply using the request's locale provided by the respective HTTP header. 11 | * 12 | */ 13 | abstract class LocaleResolver { 14 | 15 | /** 16 | * Resolve the current locale via the given request. 17 | * Should return a default locale as fallback in any case. 18 | * @param request the request to resolve the locale for 19 | * @return the current locale (never null) 20 | */ 21 | Locale resolveLocale(ForceRequest request); 22 | 23 | /** 24 | * Set the current locale to the given one. 25 | * @param request the request to be used for locale modification 26 | * @param locale the new locale, or null to clear the locale 27 | * @throws UnsupportedOperationException if the LocaleResolver implementation 28 | * does not support dynamic changing of the theme 29 | */ 30 | void setLocale(ForceRequest request, Locale locale); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /lib/manager/cookie_holder_manager.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class CookieHolderManager { 4 | 5 | static final Logger logger = new Logger('CookieManager'); 6 | 7 | /** 8 | * Default path that cookies will be visible to: "/", i.e. the entire server. 9 | */ 10 | static final String DEFAULT_COOKIE_PATH = "/"; 11 | 12 | String cookiePath = DEFAULT_COOKIE_PATH; 13 | 14 | int cookieMaxAge; 15 | bool cookieSecure; 16 | String cookieName; 17 | String cookieDomain; 18 | 19 | 20 | /** 21 | * Add a cookie with the given value to the response, 22 | * using the cookie descriptor settings of this generator. 23 | * 24 | * Delegates to createCookie for cookie creation. 25 | * @param response the HTTP response to add the cookie to 26 | * @param cookieValue the value of the cookie to add 27 | */ 28 | void addCookie(HttpResponse response, String cookieValue) { 29 | Cookie cookie = createCookie(cookieValue); 30 | int maxAge = cookieMaxAge; 31 | if (maxAge != null) { 32 | cookie.maxAge = maxAge; 33 | } 34 | if (cookieSecure) { 35 | cookie.secure = true; 36 | } 37 | response.cookies.add(cookie); 38 | logger.log(Level.INFO, "Added cookie with name [${cookieName}] and value [${cookieValue}]"); 39 | } 40 | 41 | /** 42 | * Remove the cookie that this generator describes from the response. 43 | * Will generate a cookie with empty value and max age 0. 44 | * 45 | * Delegates to createCookie for cookie creation. 46 | * @param response the HTTP response to remove the cookie from 47 | */ 48 | void removeCookie(HttpResponse response) { 49 | Cookie cookie = createCookie(""); 50 | cookie.maxAge = 0; 51 | response.cookies.add(cookie); 52 | logger.log(Level.INFO, "Removed cookie with name [${cookieName}]"); 53 | } 54 | 55 | /** 56 | * Create a cookie with the given value, using the cookie descriptor 57 | * settings of this manager (except for "cookieMaxAge"). 58 | * 59 | * @param cookieValue the value of the cookie to crate 60 | * @return the cookie 61 | */ 62 | Cookie createCookie(String cookieValue) { 63 | Cookie cookie = new Cookie(cookieName, cookieValue); 64 | if (cookieDomain != null) { 65 | cookie.domain = cookieDomain; 66 | } 67 | cookie.path = cookiePath; 68 | return cookie; 69 | } 70 | 71 | /** 72 | * Getting the cookie according to the request and the cookie name. 73 | * 74 | * @param request the HTTP request to retrieve the cookie from 75 | * @return the cookie 76 | */ 77 | Cookie getCookie(HttpRequest request) { 78 | List cookies = request.cookies; 79 | 80 | if (cookies != null) { 81 | for (Cookie cookie in cookies) { 82 | if (cookieName == cookie.name) { 83 | return cookie; 84 | } 85 | } 86 | } 87 | return null; 88 | } 89 | } -------------------------------------------------------------------------------- /lib/render/mustache_render.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class MustacheRender extends ForceViewRender { 4 | Delimiter delimiter = new Delimiter('{{', '}}'); 5 | 6 | MustacheRender(ServingAssistent servingAssistent, [views = "../views/", clientFiles = "../build/web/", clientServe = true]) : super(servingAssistent, views, clientFiles, clientServe); 7 | 8 | String _render_impl(String template, model) { 9 | var output = render(_reviewTemplate(template), model, delimiter: delimiter); 10 | return _transform(output); 11 | } 12 | 13 | String _reviewTemplate(String result) { 14 | result = result.replaceAll("${delimiter.opening}&", "${delimiter.opening}&"); 15 | return result; 16 | } 17 | 18 | String _transform(String result) { 19 | result = result.replaceAll("src=\"", "src=\"/"); 20 | result = result.replaceAll("src=\"/http:/", "src=\"http:/"); 21 | result = result.replaceAll("src=\"/../", "src=\"../"); 22 | result = result.replaceAll("src='", "src='/"); 23 | result = result.replaceAll("src='/http:/", "src='http:/"); 24 | result = result.replaceAll("src='/../", "src='../"); 25 | result = result.replaceAll("src='//", "src='/"); 26 | result = result.replaceAll("src=\"//", "src=\"/"); 27 | return result; 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /lib/render/view_render.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | abstract class ForceViewRender { 4 | final Logger log = new Logger('ForceViewRender'); 5 | String views; 6 | String clientFiles; 7 | bool clientServe; 8 | 9 | ServingAssistent servingAssistent; 10 | 11 | ForceViewRender(this.servingAssistent, [this.views, this.clientFiles, this.clientServe]) { 12 | // Check so that we have a server side views directory exists 13 | views = Platform.script.resolve(views).toFilePath(); 14 | 15 | _exists(views); 16 | 17 | // If we should serve client files, check that pub build has been run 18 | // or use the pub serve tric 19 | if(clientServe == true) { 20 | clientFiles = Platform.script.resolve(clientFiles).toFilePath(); 21 | 22 | _exists(clientFiles); 23 | } 24 | } 25 | 26 | void _exists(dir) { 27 | try { 28 | if (!new Directory(dir).existsSync()) { 29 | log.severe("The '$dir' directory was not found."); 30 | } 31 | } on FileSystemException { 32 | log.severe("The '$dir' directory was not found."); 33 | } 34 | } 35 | 36 | Future render(String view, model) async { 37 | var viewUri = new Uri.file(views).resolve("$view.html"); 38 | var file = new File(viewUri.toFilePath()); 39 | var result = ""; 40 | 41 | if (file.existsSync()) { 42 | result = await _readFile(file, model); 43 | } else { 44 | if (servingAssistent!=null) { 45 | try { 46 | Stream> inputStream = await servingAssistent.read(clientFiles, "$view.html"); 47 | var template = await inputStream.transform(UTF8.decoder).first; 48 | result = _render_impl(template, model); 49 | } catch(e) { 50 | log.severe("The '$view' can't be resolved."); 51 | } 52 | } else { 53 | viewUri = new Uri.file(clientFiles).resolve("$view.html"); 54 | file = new File(viewUri.toFilePath()); 55 | if (file.existsSync()) { 56 | result = await _readFile(file, model); 57 | } 58 | } 59 | } 60 | 61 | return result; 62 | } 63 | 64 | Future _readFile(File file, model) async { 65 | var data = await file.readAsBytes(); 66 | var template = new String.fromCharCodes(data); 67 | return _render_impl(template, model); 68 | } 69 | 70 | String _render_impl(String template, model); 71 | } -------------------------------------------------------------------------------- /lib/security/no_security_strategy.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class NoSecurityStrategy extends SecurityStrategy { 4 | 5 | bool checkAuthorization(HttpRequest obj, List roles, data) => true; 6 | 7 | Uri getRedirectUri(HttpRequest req) { 8 | return Uri.parse("/"); 9 | } 10 | } -------------------------------------------------------------------------------- /lib/security/security_context_holder.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class SecurityContextHolder { 4 | 5 | SecurityStrategy strategy; 6 | 7 | SecurityContextHolder(this.strategy); 8 | 9 | bool checkAuthorization(HttpRequest req, List roles, {data: null}) { 10 | return this.strategy.checkAuthorization(req, roles, data); 11 | } 12 | 13 | Uri redirectUri(HttpRequest req) => this.strategy.getRedirectUri(req); 14 | } -------------------------------------------------------------------------------- /lib/security/security_strategy.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | abstract class SecurityStrategy { 4 | 5 | bool checkAuthorization(T req, List roles, data); 6 | 7 | Uri getRedirectUri(HttpRequest req); 8 | 9 | } -------------------------------------------------------------------------------- /lib/server/force_request.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class ForceRequest implements HttpInputMessage, HttpOutputMessage { 4 | 5 | HttpRequest request; 6 | Map path_variables; 7 | Completer _asyncCallCompleter; 8 | Locale locale; 9 | 10 | ForceRequest._(); 11 | 12 | ForceRequest(this.request) { 13 | path_variables = new Map(); 14 | _asyncCallCompleter = new Completer(); 15 | } 16 | 17 | List header(String name) => request.headers[name.toLowerCase()]; 18 | 19 | bool accepts(String type) => 20 | request.headers['accept'].where((name) => name.split(',').indexOf(type) ).length > 0; 21 | 22 | bool isMime(String type) => 23 | request.headers['content-type'].where((value) => value == type).isNotEmpty; 24 | 25 | bool get isForwarded => request.headers['x-forwarded-host'] != null; 26 | 27 | List get cookies => request.cookies.map((Cookie cookie) { 28 | cookie.name = Uri.decodeQueryComponent(cookie.name); 29 | cookie.value = Uri.decodeQueryComponent(cookie.value); 30 | return cookie; 31 | }); 32 | 33 | void statusCode(int statusCode) { 34 | request.response.statusCode = statusCode; 35 | } 36 | 37 | // HTTPInputMessage 38 | Stream getBody() { 39 | return this.request.transform(const AsciiDecoder()); 40 | } 41 | 42 | IOSink getOutputBody() { 43 | return request.response; 44 | } 45 | 46 | HttpHeadersWrapper getResponseHeaders() { 47 | return new HttpHeadersWrapper(this.request.response.headers); 48 | } 49 | 50 | HttpHeadersWrapper getRequestHeaders() { 51 | return new HttpHeadersWrapper(this.request.headers); 52 | } 53 | 54 | // All about getting post data 55 | Future getPostData({ bool usejson: true }) { 56 | Completer completer = new Completer(); 57 | this.request.listen((List buffer) { 58 | // Return the data back to the client. 59 | String dataOnAString = new String.fromCharCodes(buffer); 60 | 61 | var package = usejson ? JSON.decode(dataOnAString) : dataOnAString; 62 | completer.complete(package); 63 | }); 64 | return completer.future; 65 | } 66 | 67 | Future> getPostRawData() { 68 | Completer c = new Completer(); 69 | this.getBody().listen((content) { 70 | c.complete(content); 71 | }); 72 | return c.future; 73 | } 74 | 75 | Future> getPostParams({ Encoding enc: UTF8 }) { 76 | Completer c = new Completer(); 77 | this.getBody().listen((content) { 78 | final postParams = new Map.fromIterable( 79 | content.split("&").map((kvs) => kvs.split("=")), 80 | key: (kv) => Uri.decodeQueryComponent(kv[0], encoding: enc), 81 | value: (kv) => Uri.decodeQueryComponent(kv[1], encoding: enc) 82 | ); 83 | c.complete(postParams); 84 | }); 85 | return c.future; 86 | } 87 | 88 | void async(value) { 89 | _asyncCallCompleter.complete(value); 90 | } 91 | 92 | Future get asyncFuture => _asyncCallCompleter.future; 93 | 94 | } 95 | -------------------------------------------------------------------------------- /lib/server/handler_interceptor.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class HandlerInterceptor { 4 | 5 | bool preHandle(ForceRequest req, Model model, Object handler) => true; 6 | 7 | void postHandle(ForceRequest req, Model model, Object handler) {} 8 | 9 | void afterCompletion(ForceRequest req, Model model, Object handler) {} 10 | 11 | } -------------------------------------------------------------------------------- /lib/server/http_request_streamer.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class HttpRequestStreamer { 4 | 5 | StreamController _controller; 6 | 7 | HttpRequestStreamer() { 8 | this._controller = new StreamController(); 9 | } 10 | 11 | void add(HttpRequest request) { 12 | this._controller.add(request); 13 | } 14 | 15 | Stream get stream => _controller.stream; 16 | } -------------------------------------------------------------------------------- /lib/server/interceptors_collection.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class InterceptorsCollection { 4 | 5 | List interceptors = new List(); 6 | 7 | void add(HandlerInterceptor interceptor) { 8 | this.interceptors.add(interceptor); 9 | } 10 | 11 | void addAll(List interceptors) { 12 | this.interceptors.addAll(interceptors); 13 | } 14 | 15 | void preHandle(ForceRequest req, Model model, Object handler) { 16 | for (HandlerInterceptor interceptor in interceptors) { 17 | if (!interceptor.preHandle(req, model, handler)) { 18 | break; 19 | } 20 | } 21 | } 22 | 23 | void postHandle(ForceRequest req, Model model, Object handler) { 24 | for (HandlerInterceptor interceptor in interceptors) { 25 | interceptor.postHandle(req, model, handler); 26 | } 27 | } 28 | 29 | void afterCompletion(ForceRequest req, Model model, Object handler) { 30 | for (HandlerInterceptor interceptor in interceptors) { 31 | interceptor.afterCompletion(req, model, handler); 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /lib/server/model.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class Model { 4 | 5 | final Logger log = new Logger('Model'); 6 | 7 | var dynamic; 8 | // Changed from Map into generic, 9 | // You can add other types of object without limitation 10 | Map values = new Map(); 11 | 12 | void addAttributeObject(dynamic) { 13 | this.dynamic = dynamic; 14 | } 15 | 16 | void addAttribute(String key, var value) { 17 | values[key] = value; 18 | } 19 | 20 | bool containsAttribute(String key) { 21 | return values.containsKey(key); 22 | } 23 | 24 | getData() { 25 | if (dynamic!=null) { 26 | if (values.isNotEmpty) { 27 | List dataList = new List(); 28 | dataList.add(dynamic); 29 | dataList.add(values); 30 | return dataList; 31 | } 32 | return dynamic; 33 | } else { 34 | return values; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /lib/server/mvc_typedefs.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | typedef WebSocketHandler(WebSocket ws, HttpRequest req); 4 | typedef dynamic ControllerHandler(ForceRequest req, Model model); 5 | typedef ResponseHook(HttpResponse res); -------------------------------------------------------------------------------- /lib/server/path_analyzer.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class PathAnalyzer { 4 | 5 | String _path; 6 | String route = ""; 7 | String expression = "([^/]+?)"; 8 | 9 | List variables; 10 | 11 | PathAnalyzer(this._path) { 12 | analyze(); 13 | } 14 | 15 | void analyze() { 16 | variables = new List(); 17 | 18 | bool capture = false; 19 | String variable = ""; 20 | var chars_raw = this._path.split(""); 21 | for (var ch in chars_raw) { 22 | if (ch == "{") { 23 | capture = true; 24 | variable = ""; 25 | } else if (ch == "}") { 26 | capture = false; 27 | variables.add(variable); 28 | route = "$route$expression"; 29 | } else if (capture) { 30 | variable = "$variable$ch"; 31 | } else { 32 | route = "$route$ch"; 33 | } 34 | } 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /lib/server/registry.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class ForceRegistry { 4 | 5 | WebApplication webApplication; 6 | File _basePath; 7 | HttpMessageRegulator messageRegulator = new HttpMessageRegulator(); 8 | 9 | ForceRegistry(this.webApplication) { 10 | _basePath = new File(Platform.script.toFilePath()); 11 | 12 | ApplicationContext.bootstrap(); 13 | 14 | ApplicationContext.setBean("HttpMessageRegulator", messageRegulator); 15 | } 16 | 17 | void loadValues(String path) { 18 | var valuesUri = new Uri.file(_basePath.path).resolve(path); 19 | var file = new File(valuesUri.toFilePath()); 20 | var yaml = file.readAsStringSync(); 21 | 22 | ApplicationContext.registerMessage(path, yaml); 23 | } 24 | 25 | void scanning() { 26 | // scan for controllers 27 | var classes = ApplicationContext.addComponents(new Scanner<_Controller>().scan()); 28 | 29 | // scan for restcontrollers 30 | var rest_classes = ApplicationContext.addComponents(new Scanner<_RestController>().scan()); 31 | 32 | // scan for controllerAdvicers classes 33 | var advisers = ApplicationContext.addComponents(new Scanner<_ControllerAdvice>().scan()); 34 | 35 | List> adviserModels = new List>(); 36 | List> adviserExc = new List>(); 37 | for (var obj in advisers) { 38 | adviserModels.addAll(new MetaDataHelper().from(obj)); 39 | adviserExc.addAll(new MetaDataHelper().from(obj)); 40 | } 41 | 42 | /* now register all the controller classes */ 43 | for (var obj in classes) { 44 | this._register(obj, adviserModels, adviserExc); 45 | } 46 | 47 | /* now register all the controller classes */ 48 | for (var obj in rest_classes) { 49 | this._register(obj, adviserModels, adviserExc, isRest: true); 50 | } 51 | 52 | // Search for interceptors 53 | ClassSearcher searcher = new ClassSearcher(); 54 | List interceptorList = searcher.scan(); 55 | 56 | webApplication.interceptors.addAll(interceptorList); 57 | } 58 | 59 | void register(Object obj) { 60 | _register(obj, new List>(), new List>()); 61 | } 62 | 63 | void _register(Object obj, List> adviserModels, List> adviserExc, {bool isRest: false}) { 64 | List> mirrorValues = new MetaDataHelper().from(obj); 65 | List> mirrorModels = new MetaDataHelper().from(obj); 66 | mirrorModels.addAll(adviserModels); 67 | 68 | var _ref; //Variable to check null values 69 | 70 | // first look if the controller has a @Authentication annotation 71 | var roles = (_ref = new AnnotationScanner<_Authentication>().instanceFrom(obj))== null ? null : _ref.roles; 72 | // then look at PreAuthorizeRoles, when they are defined // then look at PreAuthorizeRoles, when they are defined 73 | roles = (_ref = new AnnotationScanner().instanceFrom(obj))== null ? roles : _ref.roles; 74 | 75 | String startPath = (_ref = new AnnotationScanner().instanceFrom(obj)) != null ? _ref.value : ""; 76 | 77 | for (MetaDataValue mv in mirrorValues) { 78 | // execute all ! ! ! 79 | PathAnalyzer pathAnalyzer = new PathAnalyzer(mv.object.value); 80 | 81 | UrlPattern urlPattern = new UrlPattern("${startPath}${pathAnalyzer.route}"); 82 | this.webApplication.use(urlPattern, (ForceRequest req, Model model) { 83 | try { 84 | // prepare model 85 | model = _prepareModel(model, mirrorModels); 86 | 87 | // Has ResponseStatus in metaData? 88 | bool hasResponseBody = _hasResponseBody(mv.getOtherMetadata(), req) || isRest; 89 | 90 | // search for path variables 91 | for (var i = 0; pathAnalyzer.variables.length > i; i++) { 92 | var variableName = pathAnalyzer.variables[i], 93 | value = urlPattern.parse(req.request.uri.path)[i]; 94 | req.path_variables[variableName] = value; 95 | } 96 | 97 | List positionalArguments = _calculate_positionalArguments(mv, model, req); 98 | Object obj = _executeFunction(mv, positionalArguments); 99 | 100 | if (hasResponseBody) { 101 | // model.getData().clear(); 102 | // model.addAttributeObject(obj); 103 | messageRegulator.loopOverMessageConverters(req, obj); 104 | return new ResponseDone(); 105 | } else { 106 | return obj; 107 | } 108 | 109 | } catch (e, stackTrace) { 110 | // Look for exceptionHandlers in this case 111 | print(stackTrace); 112 | List> mirrorExceptions = new MetaDataHelper().from(obj); 113 | mirrorExceptions.addAll(adviserExc); 114 | 115 | return _errorHandling(mirrorExceptions, model, req, e); 116 | } 117 | }, method: mv.object.method, roles: roles); 118 | } 119 | } 120 | 121 | Model _prepareModel(Model model, List> mirrorModels) { 122 | for (MetaDataValue mvModel in mirrorModels) { 123 | 124 | InstanceMirror res = mvModel.invoke([]); 125 | 126 | if (res != null && res.hasReflectee) { 127 | model.addAttribute(mvModel.object.value, res.reflectee); 128 | } 129 | } 130 | return model; 131 | } 132 | 133 | bool _hasResponseBody(List otherMetaData, req) { 134 | bool hasResponseBody = false; 135 | 136 | for (var metaData in otherMetaData) { 137 | if (metaData is ResponseStatus) { 138 | ResponseStatus responseStatus = metaData; 139 | // set response status 140 | req.statusCode(responseStatus.value); 141 | } 142 | if (metaData is _ResponseBody) { 143 | hasResponseBody = true; 144 | } 145 | } 146 | return hasResponseBody; 147 | } 148 | 149 | _errorHandling(List> mirrorExceptions, Model model, ForceRequest req, e) { 150 | if (mirrorExceptions.length == 0) { 151 | throw e; 152 | } else { 153 | MetaDataValue mdvException = null; 154 | 155 | for (MetaDataValue mdv in mirrorExceptions) { 156 | if (mdv.object.type !=null && e.runtimeType == mdv.object.type) { 157 | mdvException = mdv; 158 | } 159 | if (mdvException == null && mdv.object.type==null) { 160 | mdvException = mdv; 161 | } 162 | } 163 | 164 | if (mdvException!=null) { 165 | List positionalArguments = _calculate_positionalArguments(mdvException, model, req, e); 166 | return _executeFunction(mdvException, positionalArguments); 167 | } else { 168 | throw e; 169 | } 170 | } 171 | } 172 | 173 | _executeFunction(MetaDataValue mdv, List positionalArguments) { 174 | InstanceMirror res = mdv.invoke(positionalArguments); 175 | return res.reflectee; 176 | } 177 | 178 | List _calculate_positionalArguments(MetaDataValue mv, Model model, ForceRequest req, [ex_er]) { 179 | List positionalArguments = []; 180 | for (ParameterMirror pm in mv.parameters) { 181 | String name = (MirrorSystem.getName(pm.simpleName)); 182 | 183 | if (pm.type is Model || name == 'model') { 184 | positionalArguments.add(model); 185 | } else if (pm.type is ForceRequest || name == 'req') { 186 | positionalArguments.add(req); 187 | } else if (pm.type is HttpSession || name == 'session') { 188 | positionalArguments.add(req.request.session); 189 | } else if (pm.type is HttpHeaders || name == 'headers') { 190 | positionalArguments.add(req.request.headers); 191 | } else if (pm.type is Exception || name == 'exception') { 192 | positionalArguments.add(ex_er); 193 | } else if (pm.type is Error || name == 'error') { 194 | positionalArguments.add(ex_er); 195 | } else if (pm.type is Locale || name == 'locale') { 196 | positionalArguments.add(req.locale); 197 | } else { 198 | if (req.path_variables[name] != null) { 199 | positionalArguments.add(req.path_variables[name]); 200 | } else { 201 | for (InstanceMirror im in pm.metadata) { 202 | if (im.reflectee is PathVariable) { 203 | PathVariable pathVariable = im.reflectee; 204 | if (req.path_variables[pathVariable.value] != null) { 205 | positionalArguments.add(req.path_variables[pathVariable.value]); 206 | } 207 | } 208 | if (im.reflectee is RequestParam) { 209 | RequestParam rp = im.reflectee; 210 | String qvalue = (rp.value == "" ? name : rp.value); 211 | if (req.request.uri.queryParameters[qvalue] != null) { 212 | positionalArguments.add(req.request.uri.queryParameters[qvalue]); 213 | } else { 214 | if (rp.required) { 215 | throw new RequiredError("${qvalue} not found on the queryParameters"); 216 | } else { 217 | positionalArguments.add(rp.defaultValue); 218 | } 219 | } 220 | } 221 | } 222 | } 223 | } 224 | } 225 | if (positionalArguments.isEmpty && mv.parameters.length == 2) { 226 | positionalArguments = [req, model]; 227 | } 228 | return positionalArguments; 229 | } 230 | } 231 | 232 | /** 233 | * When a request parameter is been required this will be thrown. 234 | */ 235 | class RequiredError extends Error { 236 | final String message; 237 | RequiredError(this.message); 238 | String toString() => "Required: $message"; 239 | } 240 | 241 | /** 242 | * When a response is already done by a responseBody or RestController. 243 | */ 244 | class ResponseDone {} 245 | -------------------------------------------------------------------------------- /lib/server/request_method.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class RequestMethod { 4 | static const GET = 'GET'; 5 | static const POST = 'POST'; 6 | static const DELETE = 'DELETE'; 7 | static const PUT = 'PUT'; 8 | static const HEAD = 'HEAD'; 9 | static const OPTIONS = 'OPTIONS'; 10 | static const TRACE = 'TRACE'; 11 | } -------------------------------------------------------------------------------- /lib/server/response_hooks.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | response_hook_cors(HttpResponse response) { 4 | response 5 | ..headers.add("Access-Control-Allow-Origin", "*, ") 6 | ..headers.add("Access-Control-Allow-Methods", "POST, GET, OPTIONS") 7 | ..headers.add("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); 8 | } -------------------------------------------------------------------------------- /lib/server/serving_assistent.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class ServingAssistent { 4 | final Uri pubServeUrl; // They read that from the Environment variable 5 | final client = new HttpClient(); 6 | final http_server.VirtualDirectory vd; 7 | 8 | ServingAssistent(this.pubServeUrl, this.vd); 9 | 10 | Future proxyToPub(HttpRequest request, String path) { 11 | const RESPONSE_HEADERS = const [ 12 | HttpHeaders.CONTENT_LENGTH, 13 | HttpHeaders.CONTENT_TYPE ]; 14 | 15 | var uri = pubServeUrl.resolve(path); 16 | return client.openUrl(request.method, uri) 17 | .then((proxyRequest) { 18 | proxyRequest.headers.removeAll(HttpHeaders.ACCEPT_ENCODING); 19 | return proxyRequest.close(); 20 | }) 21 | .then((proxyResponse) { 22 | proxyResponse.headers.forEach((name, values) { 23 | if (RESPONSE_HEADERS.contains(name)) { 24 | request.response.headers.set(name, values); 25 | } 26 | }); 27 | request.response.statusCode = proxyResponse.statusCode; 28 | request.response.reasonPhrase = proxyResponse.reasonPhrase; 29 | return proxyResponse.pipe(request.response); 30 | }) 31 | .catchError((e) { 32 | print("Unable to connect to 'pub serve' for '${request.uri}': $e"); 33 | var error = new AssistentError( 34 | "Unable to connect to 'pub serve' for '${request.uri}': $e"); 35 | return new Future.error(error); 36 | }); 37 | } 38 | 39 | Future serveFromFile(HttpRequest request, String path) { 40 | // Check if the request path is pointing to a static resource. 41 | Uri fileUri = Platform.script.resolve(path); 42 | File file = new File(fileUri.toFilePath()); 43 | return file.exists().then((exists) { 44 | if (exists) { 45 | return vd.serveFile(file, request); 46 | } else { 47 | // look outside the build folder! 48 | path = path.replaceFirst("/build", ""); 49 | fileUri = Platform.script.resolve(path); 50 | file = new File(fileUri.toFilePath()); 51 | if (file.existsSync()) { 52 | return vd.serveFile(file, request); 53 | } else { 54 | print("Unable to serve for file at this path '${request.uri}'"); 55 | var error = new AssistentError("Unable to serve form file to 'pub serve' for '${request.uri}'"); 56 | return new Future.error(error); 57 | } 58 | } 59 | }); 60 | } 61 | 62 | Future>> readFromPub(String path) { 63 | var uri = pubServeUrl.resolve(path); 64 | return client.openUrl('GET', uri) 65 | .then((request) => request.close()) 66 | .then((response) { 67 | if (response.statusCode == HttpStatus.OK) { 68 | return response; 69 | } else { 70 | var error = new AssistentError( 71 | "Failed to fetch asset '$path' from pub: " 72 | "${response.statusCode}."); 73 | return new Future.error(error); 74 | } 75 | }) 76 | .catchError((error) { 77 | if (error is! AssistentError) { 78 | error = new AssistentError( 79 | "Failed to fetch asset '$path' from pub: '${path}': $error"); 80 | } 81 | return new Future.error(error); 82 | }); 83 | } 84 | 85 | Future serve(HttpRequest request, String root, String path) { 86 | if (pubServeUrl != null) { 87 | // path = normalize(path); 88 | 89 | return proxyToPub(request, path); 90 | } else { 91 | return serveFromFile(request, "${root}${path}"); 92 | } 93 | } 94 | 95 | Future just_serve(HttpRequest request, String root, String path) { 96 | if (pubServeUrl != null) { 97 | // path = normalize(path); 98 | 99 | return proxyToPub(request, path); 100 | } else { 101 | return serveFromFile(request, "${root}${path}"); 102 | } 103 | } 104 | 105 | Future>> readFromFile(String root, String path) { 106 | path = normalize(path); 107 | return FileSystemEntity.isFile(root + path).then((exists) { 108 | if (exists) { 109 | return new File(root + path).openRead(); 110 | } else { 111 | var error = new AssistentError("Asset '$path' not found"); 112 | return new Future.error(error); 113 | } 114 | }); 115 | } 116 | 117 | Future>> read(String root, String path) { 118 | if (pubServeUrl != null) { 119 | return readFromPub(path); 120 | } else { 121 | return readFromFile(root, path); 122 | } 123 | } 124 | 125 | } 126 | 127 | class AssistentError extends Error { 128 | final message; 129 | 130 | /** The [message] describes the erroneous argument. */ 131 | AssistentError([this.message]); 132 | 133 | String toString() { 134 | if (message != null) { 135 | return "Illegal argument(s): $message"; 136 | } 137 | return "Illegal argument(s)"; 138 | } 139 | } -------------------------------------------------------------------------------- /lib/server/serving_files.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class ServingFiles { 4 | final Logger log = new Logger('ServingFiles'); 5 | 6 | Router router; 7 | 8 | var virDir; 9 | 10 | List staticFileTypes = ["dart", "js", "css", "png", "gif", "jpeg", "jpg", "webp", "html", "map"]; 11 | 12 | void _serveClient(staticFiles, clientFiles, clientServe) { 13 | if(clientServe == true) { 14 | // Set up default handler. This will serve files from our 'build' directory. 15 | Uri clientFilesAbsoluteUri = Platform.script.resolve(clientFiles); 16 | virDir = new http_server.VirtualDirectory(clientFilesAbsoluteUri.toFilePath()); 17 | 18 | servingAssistent = new ServingAssistent(_pubServeUrl(), virDir); 19 | 20 | // Disable jail-root, as packages are local sym-links. 21 | virDir..jailRoot = false 22 | ..allowDirectoryListing = true; 23 | 24 | // Add an error page handler. 25 | virDir.errorPageHandler = (HttpRequest request) { 26 | _notFoundHandling(request); 27 | }; 28 | 29 | // Serve everything not routed elsewhere through the virtual directory. 30 | virDir.serve(router.defaultStream); 31 | 32 | // Start serving static files 33 | _serveStaticFiles(staticFiles); 34 | 35 | // Start serving transformable files 36 | _serveTransformableFiles(clientFiles); 37 | } else { 38 | log.info("You are serving the clientside files your self! Force is not serving clientside files!"); 39 | } 40 | } 41 | 42 | void _notFoundHandling(HttpRequest request) { 43 | log.warning("Resource not found ${request.uri.path}"); 44 | request.response.statusCode = HttpStatus.NOT_FOUND; 45 | } 46 | 47 | void serveFile(HttpRequest request, String root, String fileName) { 48 | if (servingAssistent==null) { 49 | log.warning("servingAssistent is not defined!"); 50 | } else { 51 | servingAssistent.just_serve(request, root, fileName).catchError((e) { 52 | log.warning(e); 53 | _notFoundHandling(request); 54 | }); 55 | } 56 | } 57 | 58 | void _serveTransformableFiles(clientFiles) { 59 | for (var fileType in staticFileTypes) { 60 | String parts = '([/|.|\\-|\\w|\\W|\\s])*\\.(?:${fileType})'; 61 | var pattern = new UrlPattern(parts); 62 | 63 | this._serveWithPatterns(clientFiles, pattern); 64 | } 65 | } 66 | 67 | 68 | void _serveWithPatterns(clientFiles, UrlPattern pattern) { 69 | router.serve(pattern).listen((request) { 70 | var path = request.uri.path; 71 | serveFile(request, clientFiles, path); 72 | }); 73 | } 74 | 75 | void _serveStaticFiles(clientFiles) { 76 | var pattern = new UrlPattern('/static/([/|.|\\-|\\w|\\s])*'); 77 | 78 | router.serve(pattern).listen((request) { 79 | String path = request.uri.path; 80 | path = path.replaceAll('/static/', ''); 81 | 82 | if (servingAssistent!=null) { 83 | servingAssistent.serveFromFile(request, "${clientFiles}${path}").catchError((e) { 84 | print(e); 85 | _notFoundHandling(request); 86 | }); 87 | } 88 | }); 89 | } 90 | 91 | Uri _pubServeUrl() { 92 | var env = Platform.environment; 93 | String pubServeUrlString = env['DART_PUB_SERVE']; 94 | 95 | Uri pubServeUrl = pubServeUrlString != null 96 | ? Uri.parse(pubServeUrlString) 97 | : null; 98 | return pubServeUrl; 99 | } 100 | 101 | void set servingAssistent(ServingAssistent servingAssistent) 102 | => ApplicationContext.setBean("ServingAssistent", servingAssistent); 103 | 104 | ServingAssistent get servingAssistent 105 | => ApplicationContext.getBeanByType(ServingAssistent); 106 | } 107 | 108 | -------------------------------------------------------------------------------- /lib/server/simple_web_server.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class SimpleWebServer { 4 | final Logger log = new Logger('SimpleServer'); 5 | 6 | Router router; 7 | 8 | var host; 9 | var port; 10 | var wsPath; 11 | var staticFiles; 12 | var clientFiles; 13 | var clientServe; 14 | var virDir; 15 | var bind_address = InternetAddress.ANY_IP_V6; 16 | 17 | Completer _completer = new Completer.sync(); 18 | 19 | SimpleWebServer(this.host, 20 | this.port, 21 | this.wsPath, 22 | this.staticFiles, 23 | this.clientFiles, 24 | this.clientServe) { 25 | init(); 26 | } 27 | 28 | void init() { 29 | if (host != null) { 30 | this.bind_address = host; 31 | } 32 | 33 | if(clientServe == true) { 34 | String clientFilesPath = Platform.script.resolve(clientFiles).toFilePath(); 35 | 36 | _exists(clientFilesPath); 37 | } 38 | } 39 | 40 | void _exists(dir) { 41 | try { 42 | if (!new Directory(dir).existsSync()) { 43 | log.severe("The '$dir' directory was not found."); 44 | } 45 | } on FileSystemException { 46 | log.severe("The '$dir' directory was not found."); 47 | } 48 | } 49 | 50 | /** 51 | * This method helps to start your webserver. 52 | * 53 | * You can add a [WebSocketHandler]. 54 | */ 55 | 56 | Future start({WebSocketHandler handleWs: null, FallbackStart fallback}) { 57 | HttpServer.bind(bind_address, port).then((server) { 58 | _onStartComplete(server, handleWs); 59 | }).catchError((e) { 60 | if (fallback==null) { 61 | var error = _errorOnStart(e); 62 | return new Future.error(error); 63 | } else { 64 | fallback(this, handleWs); 65 | } 66 | }); 67 | 68 | return _completer.future; 69 | } 70 | 71 | /** 72 | * This method helps to start your webserver in a secure way. 73 | * 74 | * You can add a [WebSocketHandler], [SecurityContext], requestClientCertificate, backlog 75 | * 76 | * The optional argument [backlog] can be used to specify the listen 77 | * backlog for the underlying OS listen setup. If [backlog] has the 78 | * value of [:0:] (the default) a reasonable value will be chosen by 79 | * the system. 80 | * 81 | * The certificate chain and the private key are set in the [context] SecurityContext object 82 | * that is passed to bindSecure. 83 | * 84 | */ 85 | 86 | Future startSecure({WebSocketHandler handleWs: null, SecurityContext context, bool requestClientCertificate: false, 87 | int backlog: 0}) { 88 | HttpServer.bindSecure(bind_address, port, context, 89 | requestClientCertificate: requestClientCertificate, 90 | backlog: backlog).then((server) { 91 | _onStartComplete(server, handleWs); 92 | }).catchError((e) { 93 | var error = _errorOnStart(e); 94 | return new Future.error(error); 95 | }); 96 | 97 | return _completer.future; 98 | } 99 | 100 | void _onStartComplete(Stream incoming, [WebSocketHandler handleWs]) { 101 | _onStart(incoming, handleWs); 102 | _completer.complete(const []); 103 | } 104 | 105 | Error _errorOnStart(e) { 106 | log.warning("Could not startup the web server ... $e"); 107 | log.warning("Is your port already in use?"); 108 | return new WebApplicationStartError("Unable to start with '${host}' - '${port}': $e"); 109 | } 110 | 111 | Stream serve(String name) { 112 | return router.serve(name); 113 | } 114 | 115 | void setupConsoleLog([Level level = Level.INFO]) { 116 | Logger.root.level = level; 117 | Logger.root.onRecord.listen((LogRecord rec) { 118 | if (rec.level >= Level.SEVERE) { 119 | var stack = rec.stackTrace != null ? rec.stackTrace : ""; 120 | print('${rec.level.name}: ${rec.time}: ${rec.message} - ${rec.error} $stack'); 121 | } else { 122 | print('${rec.level.name}: ${rec.time}: ${rec.message}'); 123 | } 124 | }); 125 | } 126 | 127 | _onStart(Stream incoming, [WebSocketHandler handleWs]) {} 128 | } 129 | 130 | class WebApplicationStartError extends Error { 131 | final message; 132 | 133 | /** The [message] describes the erroneous argument. */ 134 | WebApplicationStartError([this.message]); 135 | 136 | String toString() { 137 | if (message != null) { 138 | return "WebApplication start error: $message"; 139 | } 140 | return "WebApplication start error"; 141 | } 142 | } 143 | 144 | /* 145 | * This is been used in the restartFallback 146 | */ 147 | typedef FallbackStart(SimpleWebServer sws, WebSocketHandler wsHandler); 148 | 149 | var randomPortFallback = (SimpleWebServer sws, WebSocketHandler wsHandler) { 150 | var rng = new Random(); 151 | var newPortNumber = rng.nextInt(8888) + 1000; 152 | 153 | sws.port = newPortNumber; 154 | sws.start(handleWs: wsHandler); 155 | }; 156 | -------------------------------------------------------------------------------- /lib/server/web_application.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class WebApplication extends SimpleWebServer with ServingFiles { 4 | final Logger log = new Logger('WebApplication'); 5 | 6 | bool cors=false; 7 | Router router; 8 | String views; 9 | ForceRegistry registry; 10 | 11 | InterceptorsCollection interceptors = new InterceptorsCollection(); 12 | 13 | List responseHooks = new List(); 14 | HttpRequestStreamer requestStreamer; 15 | Map _staticResources = new Map(); 16 | 17 | ControllerHandler _notFound; 18 | 19 | WebApplication({host: "127.0.0.1", 20 | port: 8080, 21 | wsPath: '/ws', 22 | staticFiles: '../static/', 23 | clientFiles: '../build/web/', 24 | clientServe: true, 25 | this.views: "../views/", 26 | startPage, 27 | cors:true}) : 28 | super(host, port, wsPath, staticFiles, 29 | clientFiles, clientServe) { 30 | if (startPage!=null) { static("/", startPage); }; 31 | if(cors==true){ this.responseHooks.add(response_hook_cors); } 32 | 33 | securityContext = new SecurityContextHolder(new NoSecurityStrategy()); 34 | localeResolver = new AcceptHeaderLocaleResolver(); 35 | exceptionResolver = new SimpleExceptionResolver(); 36 | 37 | registry = new ForceRegistry(this); 38 | } 39 | 40 | void _scanning() { 41 | this.registry.scanning(); 42 | } 43 | 44 | void use(Pattern url, ControllerHandler controllerHandler, 45 | {method: RequestMethod.GET, List roles}) { 46 | _completer.future.whenComplete(() { 47 | this.router.serve(url, method: method).listen((HttpRequest req) { 48 | if (checkSecurity(req, roles)) { 49 | _resolveRequest(req, controllerHandler); 50 | } else { 51 | 52 | Uri location = securityContext.redirectUri(req); 53 | req.response.redirect(location, status: HttpStatus.MOVED_PERMANENTLY); 54 | } 55 | }); 56 | }); 57 | } 58 | 59 | void static(Pattern url, String name, 60 | {method: RequestMethod.GET, List roles}) { 61 | bool containsAlreadyStatic = _staticResources.containsKey(url); 62 | _staticResources[url] = name; 63 | 64 | _completer.future.whenComplete(() { 65 | if (!containsAlreadyStatic) { 66 | this.router.serve(url, method: method).listen((HttpRequest req) { 67 | if (checkSecurity(req, roles)) { 68 | _resolveStatic(req, _staticResources[url]); 69 | } else { 70 | SecurityContextHolder sch = ApplicationContext.getBeanByType(SecurityContextHolder); 71 | Uri location = sch.redirectUri(req); 72 | req.response.redirect(location, status: HttpStatus.MOVED_PERMANENTLY); 73 | } 74 | }); 75 | } 76 | }); 77 | } 78 | 79 | void notFound(ControllerHandler controllerHandler) { 80 | this._notFound = controllerHandler; 81 | } 82 | 83 | void _notFoundHandling(HttpRequest request) { 84 | super._notFoundHandling(request); 85 | if (_notFound!=null) { 86 | _resolveRequest(request, _notFound); 87 | } else { 88 | request.response.close(); 89 | } 90 | } 91 | 92 | bool checkSecurity(HttpRequest req, List roles) { 93 | if (roles != null) { 94 | SecurityContextHolder securityContext = ApplicationContext.getBeanByType(SecurityContextHolder); 95 | return securityContext.checkAuthorization(req, roles); 96 | } else { 97 | return true; 98 | } 99 | } 100 | 101 | void _resolveStatic(HttpRequest req, String name) { 102 | if (servingAssistent==null) { 103 | log.warning("servingAssistent is not defined!"); 104 | } else { 105 | servingAssistent.just_serve(req, clientFiles, name).catchError((e) { 106 | print(e); 107 | _notFoundHandling(req); 108 | }); 109 | } 110 | } 111 | 112 | void _resolveRequest(HttpRequest req, ControllerHandler controllerHandler) { 113 | Model model = new Model(); 114 | ForceRequest forceRequest = new ForceRequest(req); 115 | 116 | // check locale 117 | if (localeResolver != null) forceRequest.locale = localeResolver.resolveLocale(forceRequest); 118 | 119 | var result; 120 | 121 | try { 122 | interceptors.preHandle(forceRequest, model, this); 123 | result = controllerHandler(forceRequest, model); 124 | interceptors.postHandle(forceRequest, model, this); 125 | } catch (e) { 126 | // do proper exception handling 127 | if (e is Exception) { 128 | result = exceptionResolver.resolveException(forceRequest, model, e); 129 | } else if (e is Error) { 130 | result = exceptionResolver.resolveError(forceRequest, model, e); 131 | } 132 | } 133 | if (result != null) { 134 | // template rendering 135 | if (result is String) { 136 | _resolveView(result, req, model); 137 | } else if (result is Future) { 138 | Future future = result; 139 | future.then((e) { 140 | if (e is String) { 141 | _resolveView(e, req, model); 142 | } else if (e is! HttpResponse) { 143 | model.addAttributeObject(e); 144 | _send_json(model.getData(), req); 145 | } 146 | }); 147 | } else if (result is ResponseDone) { 148 | req.response.close(); 149 | } else if (result is! HttpResponse) { 150 | model.addAttributeObject(result); 151 | _send_json(model.getData(), req); 152 | } 153 | } else { 154 | _send_json(model.getData(), req); 155 | } 156 | interceptors.afterCompletion(forceRequest, model, this); 157 | } 158 | 159 | void _send_json(rawData, HttpRequest req) { 160 | String data = JSON.encode(rawData); 161 | _send_response(req.response, new ContentType("application", "json", charset: "utf-8"), data); 162 | } 163 | 164 | void _resolveView(String view, HttpRequest req, Model model) { 165 | if (view.startsWith("redirect:")) { 166 | Uri location = Uri.parse(view.substring(9)); 167 | req.response.redirect(location, status: HttpStatus.MOVED_TEMPORARILY); 168 | } else { 169 | _send_template(req, model, view); 170 | } 171 | } 172 | 173 | void register(Object obj) { 174 | this.registry.register(obj); 175 | } 176 | 177 | void _send_template(HttpRequest req, Model model, String view) { 178 | this.viewRender.render(view, model.getData()).then((String result) { 179 | _send_response(req.response, new ContentType("text", "html", charset: "utf-8"), result); 180 | }); 181 | } 182 | 183 | void _send_response(HttpResponse response, ContentType contentType, String result) { 184 | responseHooks.forEach((ResponseHook responseHook) { 185 | responseHook(response); 186 | }); 187 | response 188 | ..headers.contentType = contentType 189 | ..write(result) 190 | ..close(); 191 | } 192 | 193 | /** 194 | * This requestHandler can be used to hook into the system without having to start a server. 195 | * You need to use this method for example with Google App Engine runtime. 196 | * 197 | * @param request is the current HttpRequest that needs to be handled by the system. 198 | * @param optional parameter to handle webSockets 199 | */ 200 | void requestHandler(HttpRequest request, [WebSocketHandler handleWs]) { 201 | if (requestStreamer==null) { 202 | // initialize all 203 | requestStreamer = new HttpRequestStreamer(); 204 | _onStartComplete(requestStreamer.stream, handleWs); 205 | } 206 | this.requestStreamer.add(request); 207 | } 208 | 209 | void _onStart(Stream incoming, [WebSocketHandler handleWs]) { 210 | log.info("Web server is running on " 211 | "'http://${Platform.localHostname}:$port/'"); 212 | _scanning(); 213 | router = new Router(incoming); 214 | 215 | // The client will connect using a WebSocket. Upgrade requests to '/ws' and 216 | // forward them to 'handleWebSocket'. 217 | if (handleWs != null) { 218 | Stream stream = router.serve(this.wsPath); 219 | stream.listen((HttpRequest req) { 220 | // stream.transform(new WebSocketTransformer()) 221 | // .listen(handleWs); 222 | if(WebSocketTransformer.isUpgradeRequest(req)) { 223 | WebSocketTransformer.upgrade(req).then((WebSocket ws) { 224 | handleWs(ws, req); 225 | }); 226 | } 227 | }); 228 | } 229 | 230 | // Serve dart and static files (if not explicitly disabled by clientServe) 231 | _serveClient(staticFiles, clientFiles, clientServe); 232 | if (viewRender == null) { 233 | viewRender = new MustacheRender(servingAssistent, views, clientFiles, clientServe); 234 | } 235 | } 236 | 237 | // For Backwards compatibility 238 | void set strategy(SecurityStrategy strategy) { 239 | securityContext.strategy = strategy; 240 | } 241 | 242 | void set securityContext(SecurityContextHolder sch) 243 | => ApplicationContext.setBean("securityContextHolder", sch); 244 | 245 | SecurityContextHolder get securityContext 246 | => ApplicationContext.getBeanByType(SecurityContextHolder); 247 | 248 | void set exceptionResolver(HandlerExceptionResolver handlerExceptionResolver) 249 | => ApplicationContext.setBean("exceptionResolver", handlerExceptionResolver); 250 | 251 | HandlerExceptionResolver get exceptionResolver 252 | => ApplicationContext.getBeanByType(HandlerExceptionResolver); 253 | 254 | void set localeResolver(LocaleResolver localeResolver) 255 | => ApplicationContext.setBean("localeResolver", localeResolver); 256 | 257 | LocaleResolver get localeResolver 258 | => ApplicationContext.getBeanByType(LocaleResolver); 259 | 260 | void set viewRender(ForceViewRender viewRender) 261 | => ApplicationContext.setBean("viewRender", viewRender); 262 | 263 | ForceViewRender get viewRender 264 | => ApplicationContext.getBeanByType(ForceViewRender); 265 | 266 | void loadValues(String path) => this.registry.loadValues(path); 267 | } 268 | -------------------------------------------------------------------------------- /lib/test.dart: -------------------------------------------------------------------------------- 1 | library dart_force_mvc_unittest_lib; 2 | 3 | import 'package:mock/mock.dart'; 4 | import 'package:forcemvc/force_mvc.dart'; 5 | import 'dart:io'; 6 | import 'dart:async'; 7 | import 'dart:convert'; 8 | 9 | class MockHttpRequest extends Mock implements HttpRequest {} 10 | 11 | class MockForceRequest implements ForceRequest { 12 | 13 | var postData; 14 | Map postParams = new Map(); 15 | List mockCookies = new List(); 16 | 17 | HttpRequest request; 18 | Map path_variables; 19 | Completer _asyncCallCompleter; 20 | Locale locale; 21 | 22 | MockForceRequest({this.postData: "test"}) { 23 | path_variables = new Map(); 24 | _asyncCallCompleter = new Completer(); 25 | request = new MockHttpRequest(); 26 | } 27 | 28 | List header(String name) => new List(); 29 | 30 | bool accepts(String type) => true; 31 | 32 | bool isMime(String type) => true; 33 | 34 | bool get isForwarded => false; 35 | 36 | List get cookies => mockCookies; 37 | 38 | void statusCode(int statusCode) {} 39 | 40 | // HTTPInputMessage 41 | Stream getBody() { 42 | return this.request.transform(const AsciiDecoder()); 43 | } 44 | 45 | IOSink getOutputBody() { 46 | return request.response; 47 | } 48 | 49 | HttpHeadersWrapper getResponseHeaders() { 50 | return new HttpHeadersWrapper(this.request.response.headers); 51 | } 52 | 53 | HttpHeadersWrapper getRequestHeaders() { 54 | return new HttpHeadersWrapper(this.request.headers); 55 | } 56 | 57 | Future getPostData({ bool usejson: true }) { 58 | Completer completer = new Completer(); 59 | completer.complete(postData); 60 | 61 | return completer.future; 62 | } 63 | 64 | Future> getPostRawData() { 65 | Completer c = new Completer(); 66 | c.complete(postParams); 67 | return c.future; 68 | } 69 | 70 | Future> getPostParams({ Encoding enc: UTF8 }) { 71 | Completer c = new Completer(); 72 | c.complete(postParams); 73 | return c.future; 74 | } 75 | 76 | void async(value) { 77 | this._asyncCallCompleter.complete(value); 78 | } 79 | 80 | Future get asyncFuture => _asyncCallCompleter.future; 81 | 82 | } 83 | -------------------------------------------------------------------------------- /lib/utils/mime_type_utils.dart: -------------------------------------------------------------------------------- 1 | part of dart_force_mvc_lib; 2 | 3 | class MimeTypeUtils { 4 | 5 | /** 6 | * Parse the given String into a single [MimeType]. 7 | * @param mimeType the string to parse 8 | * @return the mime type 9 | */ 10 | static MimeType parseMimeType(String mimeType) { 11 | if (mimeType.length == 0) { 12 | throw new InvalidMimeTypeError(mimeType, "'mimeType' must not be empty"); 13 | } 14 | List parts = mimeType.split(";"); 15 | String fullType = parts[0].trim(); 16 | 17 | if (MimeType.WILDCARD_TYPE == fullType) { 18 | fullType = "*/*"; 19 | } 20 | int subIndex = fullType.indexOf('/'); 21 | if (subIndex == -1) { 22 | throw new InvalidMimeTypeError(mimeType, "does not contain '/'"); 23 | } 24 | if (subIndex == fullType.length - 1) { 25 | throw new InvalidMimeTypeError(mimeType, "does not contain subtype after '/'"); 26 | } 27 | String type = fullType.substring(0, subIndex); 28 | String subtype = fullType.substring(subIndex + 1, fullType.length); 29 | if (MimeType.WILDCARD_TYPE == type && !(MimeType.WILDCARD_TYPE == subtype)) { 30 | throw new InvalidMimeTypeError(mimeType, "wildcard type is legal only in '*/*' (all mime types)"); 31 | } 32 | 33 | Map parameters = null; 34 | if (parts.length > 1) { 35 | parameters = new LinkedHashMap(); 36 | for (int i = 1; i < parts.length; i++) { 37 | String parameter = parts[i]; 38 | int eqIndex = parameter.indexOf('='); 39 | if (eqIndex != -1) { 40 | String attribute = parameter.substring(0, eqIndex); 41 | String value = parameter.substring(eqIndex + 1, parameter.length); 42 | parameters[attribute] = value; 43 | } 44 | } 45 | } 46 | return new MimeType(type, subtype: subtype, parameters: parameters); 47 | } 48 | 49 | /** 50 | * Return a string representation of the given list of MimeType objects. 51 | * @param mimeTypes the string to parse 52 | * @return the list of mime types 53 | * @throws IllegalArgumentException if the String cannot be parsed 54 | */ 55 | static String toStringify(Iterable mimeTypes) { 56 | String builder = ""; 57 | bool hasNext = true; 58 | for (Iterator iterator = mimeTypes.iterator; hasNext;) { 59 | MimeType mimeType = iterator.current; 60 | builder = "$builder${mimeType.toString()}"; 61 | hasNext = iterator.moveNext(); 62 | if (hasNext) { 63 | builder = "$builder, "; 64 | } 65 | } 66 | return builder; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: forcemvc 2 | version: 0.8.5 3 | author: Joris Hermans 4 | description: A serverside web mvc framework with templates 5 | homepage: https://github.com/ForceUniverse/dart-forcemvc 6 | dependencies: 7 | http_server: '>=0.9.0 <0.10.0' 8 | logging: '>=0.11.0 <0.12.0' 9 | route: '>=0.4.0 <0.5.0' 10 | mustache4dart: '>=1.0.0 <1.1.0' 11 | wired: '>=0.4.5 <0.4.6' 12 | locale: '>=0.1.1+2 <0.1.2' 13 | jsonify: '>=0.0.1 <0.0.2' 14 | dev_dependencies: 15 | test: '>=0.12.1 <0.13.0' 16 | mock: '>=0.11.0 <0.11.1' 17 | environment: 18 | sdk: '>=1.9.0 <2.0.0' 19 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ![VERSION!](https://img.shields.io/pub/v/forcemvc.svg) [![Build Status](https://drone.io/github.com/ForceUniverse/dart-forcemvc/status.png)](https://drone.io/github.com/ForceUniverse/dart-forcemvc/latest) 2 | 3 | ### Dart Force MVC ### 4 | 5 | ![LOGO!](https://raw.github.com/ForceUniverse/dart-force/master/resources/dart_force_logo.jpg) 6 | 7 | Serverside MVC based implementation for Dart. Easy to setup and part of the dart force framework! 8 | 9 | #### Introduction #### 10 | 11 | Dart ForceMVC is a dartlang webframework, with a lot of similarities to java spring mvc 12 | 13 | First of all you need to setup a webserver: 14 | 15 | ```dart 16 | library x; 17 | import 'package:wired/wired.dart'; 18 | import 'package:forcemvc/force_mvc.dart'; 19 | 20 | part 'controllers/x_controller.dart'; 21 | 22 | main() { 23 | WebApplication app = new WebApplication(startPage: "start.html"); 24 | 25 | app.start(); 26 | } 27 | ``` 28 | 29 | And then ofcource we need to create our controller. 30 | 31 | ```dart 32 | part of x; 33 | 34 | @Controller 35 | class XController { 36 | 37 | @RequestMapping(value: "/home", method: RequestMethod.GET) 38 | String home(Locale locale, Model model) { 39 | model.addAttribute("someprop", "value" ); 40 | 41 | return "home"; 42 | } 43 | 44 | } 45 | ``` 46 | 47 | In the web folder of your application you can create a home.html file. That will be your view. 48 | 49 | #### Walkthrough #### 50 | 51 | Use a Web Application Server with dart very easily, create controllers with annotations ... similar to java spring mvc. 52 | 53 | First you will setup a new web application. 54 | 55 | WebApplication app = new WebApplication(wsPath: wsPath, port: port, host: host, buildPath: buildPath); 56 | 57 | Then you use the 'use' method to handle http requests. 58 | 59 | app.use(url, (ForceRequest req, Model model) { /* logic */ }, method: "GET"); 60 | 61 | You can also use the annotation RequestMapping in a dart object 62 | 63 | @RequestMapping(value: "/someurl", method: "GET") 64 | void index(ForceRequest req, Model model) 65 | 66 | You can also use the annotation @ModelAttribute to add an object to all the scopes in the methods. 67 | An @ModelAttribute on a method argument indicates the argument should be retrieved from the model. If not present in the model, the argument should be instantiated first and then added to the model. Once present in the model, the argument's fields should be populated from all request parameters that have matching names. 68 | 69 | @ModelAttribute("someValue") 70 | String someName() { 71 | return mv.getValue(); 72 | } 73 | 74 | Then you register that object on the WebApplication object. 75 | 76 | app.register(someObjectWithRequestMappingAnnotations) 77 | 78 | Or you can annotate a class with @Controller and then it will be registered automatically in the force WebApplication. 79 | 80 | @Controller 81 | class SomeObject { 82 | 83 | } 84 | 85 | #### Starting your web application #### 86 | 87 | You can do this as follow! 88 | 89 | app.start(); 90 | 91 | It is also possible to start a web application with SSL possibilities. 92 | 93 | app.startSecure(); 94 | 95 | #### ForceRequest #### 96 | 97 | ForceRequest is an extension for HttpRequest 98 | 99 | forceRequest.postData().then((data) => print(data)); 100 | 101 | #### Interceptors #### 102 | 103 | You can define inteceptors as follow, the framework will pick up all the HandlerInterceptor classes or implementations. 104 | 105 | class RandomInterceptor implements HandlerInterceptor { 106 | 107 | bool preHandle(ForceRequest req, Model model, Object handler) { return true; } 108 | void postHandle(ForceRequest req, Model model, Object handler) {} 109 | void afterCompletion(ForceRequest req, Model model, Object handler) {} 110 | 111 | } 112 | 113 | #### Path variables #### 114 | 115 | You can now use path variables in force mvc. 116 | 117 | @RequestMapping(value: "/var/{var1}/other/{var2}/", method: "GET") 118 | void pathvariable(ForceRequest req, Model model, String var1, String var2) 119 | 120 | This is an alternative way how you can access path variables. 121 | 122 | req.path_variables['var1'] 123 | 124 | You can also use the annotation @PathVariable("name") to match the pathvariable, like below: 125 | 126 | @RequestMapping(value: "/var/{var1}/", method: "GET") 127 | String multivariable(req, Model model, @PathVariable("var1") variable) {} 128 | 129 | #### Redirect #### 130 | 131 | You can instead of returning a view name, performing a redirect as follow: 132 | 133 | @RequestMapping(value: "/redirect/") 134 | String redirect(req, Model model) { 135 | redirect++; 136 | return "redirect:/viewable/"; 137 | } 138 | 139 | #### Asynchronous Controller #### 140 | 141 | In the controller you can have asynchronous methods to handle for example POST methods much easier. 142 | 143 | On the ForceRequest object you have a method .async and his value is the return value that matters for the req. 144 | 145 | When a method is asynchrounous you must return req.asyncFuture. 146 | 147 | This is an example how you can use it. 148 | 149 | @RequestMapping(value: "/post/", method: "POST") 150 | Future countMethod(req, Model model) { 151 | req.getPostParams().then((map) { 152 | model.addAttribute("email", map["email"]); 153 | 154 | req.async(null); 155 | }); 156 | model.addAttribute("status", "ok"); 157 | 158 | return req.asyncFuture; 159 | } 160 | 161 | #### Authentication #### 162 | 163 | You can now add the annotation @Authentication to a controller class. 164 | This will make it necessary to for a user to authenticate before accessing these resources. 165 | 166 | An authentication in force is following a strategy. 167 | You can set a strategy by extending the class SecurityStrategy. 168 | 169 | class SessionStrategy extends SecurityStrategy { 170 | 171 | bool checkAuthorization(HttpRequest req, {data: null}) { 172 | HttpSession session = req.session; 173 | return (session["user"]!=null); 174 | } 175 | 176 | Uri getRedirectUri(HttpRequest req) { 177 | var referer = req.uri.toString(); 178 | return Uri.parse("/login/?referer=$referer"); 179 | } 180 | } 181 | 182 | And then add this strategy to the web application. 183 | 184 | app.strategy = new SessionStrategy(); 185 | 186 | ##### Roles ##### 187 | 188 | You can also define authorize roles. This can be done as follow. 189 | 190 | @Controller 191 | @PreAuthorizeRoles(const ["ADMIN"]) 192 | class AdminController { 193 | 194 | } 195 | 196 | ##### ExceptionHandler ##### 197 | 198 | This helps in defining methods that will be executed when an error or exception occured. 199 | 200 | @ExceptionHandler() 201 | String error_catch(req, Model model) { 202 | ... 203 | } 204 | 205 | You can also specify a type, only when an error or exception happend of that Type, that method will be executed. 206 | 207 | @ExceptionHandler(type: DoorLockedError) 208 | String doorLockedError(req, Model model) { 209 | model.addAttribute("explanation", "This is a specific error!"); 210 | return "error"; 211 | } 212 | 213 | #### Logging #### 214 | 215 | You can easily boostrap logging. 216 | 217 | app.setupConsoleLog(); 218 | 219 | #### Wired #### 220 | 221 | Wired is a dependency injection package. 222 | You can use @Autowired and @bean and more in forcemvc find info [here](https://github.com/ForceUniverse/dart-force_it) 223 | 224 | #### LocaleResolver #### 225 | 226 | In ForceMVC you have a locale resolver to handle locale. 227 | The implementation that is been used by default is the AcceptHeaderLocale Resolver, this resolver looks at the request header accept-language. 228 | 229 | You can choose for a fixed locale resolver implementation or a cookie locale resolver or just implement your own handling if need. 230 | 231 | #### View / Templating #### 232 | 233 | In forcemvc you can define view templates. ForceMvc will look into the viewfolder and in the client 'build' folder for a .html file with the viewname that you provide the system in the controller. 234 | 235 | @RequestMapping(value: "/hello/") 236 | String redirect(req, Model model) { 237 | // do something 238 | model.addAttribute("text", "greetings"); 239 | return "hello"; 240 | } 241 | 242 | So in the example about we are returning a string with the value 'hello'. So the system will search in the view & build folder for a hello.html file. 243 | 244 | The default implementation in ForceMVC for templating is mustache. 245 | 246 | In the html file {{text}} will be replaced by greetings. 247 | 248 | More info about creating your own viewrender implementation [here](https://github.com/ForceUniverse/dart-forcemvc/wiki/Create-your-own-viewrender) 249 | 250 | #### Development trick #### 251 | 252 | Following the next steps will make it easier for you to develop, this allows you to adapt clientside files and immidiatly see results with doing a pub build. 253 | 254 | pub serve web --hostname 0.0.0.0 --port 7777 && 255 | export DART_PUB_SERVE="http://localhost:7777" && 256 | pub run bin/server.dart 257 | 258 | #### GAE #### 259 | 260 | You can now easily run your Force apps on a Google App Engine infrastructure by the following code! The rest is the same as a normal dart force app. 261 | 262 | WebApplication app = new WebApplication(); 263 | 264 | runAppEngine(app.requestHandler).then((_) { 265 | // Server running. and you can do all the stuff you want! 266 | }); 267 | 268 | You don't need to start WebApplication anymore, the start of the server will be done by AppEngine! 269 | 270 | More info about [GAE overall](https://www.dartlang.org/cloud/) 271 | 272 | #### Rest API's #### 273 | 274 | If you want to learn more about how to build Rest api's with ForceMVC go [here](https://github.com/ForceUniverse/dart-forcemvc/tree/master/example/server/controllers/rest) 275 | 276 | #### Example #### 277 | 278 | You can find a simple example with a page counter implementation [here](https://github.com/jorishermans/dart-forcemvc-example) - [live demo](http://forcemvc.herokuapp.com/) 279 | 280 | Or visit Github issue mover [here](https://github.com/google/github-issue-mover) 281 | 282 | #### Youtube #### 283 | 284 | You can watch a simple youtube video to get you started, have an idea between the transformation of java spring mvc and force mvc [here](https://www.youtube.com/watch?v=AcwnoiYEv8I) 285 | 286 | #### TODO #### 287 | 288 | - get more annotations and options for sending the response back 289 | - writing tests 290 | 291 | ### Notes to Contributors ### 292 | 293 | #### Fork Dart Force MVC #### 294 | 295 | If you'd like to contribute back to the core, you can [fork this repository](https://help.github.com/articles/fork-a-repo) and send us a pull request, when it is ready. 296 | 297 | If you are new to Git or GitHub, please read [this guide](https://help.github.com/) first. 298 | 299 | #### Dart Force #### 300 | 301 | Realtime web framework for dart that uses force MVC & wired [source code](https://github.com/ForceUniverse/dart-force) 302 | 303 | #### Twitter #### 304 | 305 | Follow us on twitter https://twitter.com/usethedartforce 306 | 307 | #### Google+ #### 308 | 309 | Follow us on [google+](https://plus.google.com/111406188246677273707) 310 | 311 | #### Join our discussion group #### 312 | 313 | [Google group](https://groups.google.com/forum/#!forum/dart-force) 314 | -------------------------------------------------------------------------------- /resources/dart_force_logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ForceUniverse/dart-forcemvc/f1199f185783c0caca699ed0b6f18113feb7d256/resources/dart_force_logo.jpg -------------------------------------------------------------------------------- /test/locale_resolver_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:forcemvc/force_mvc.dart'; 3 | 4 | main() { 5 | // First tests! 6 | var accept_header_string = "en-ca,en;q=0.8,en-us;q=0.6,de-de;q=0.4,de;q=0.2"; 7 | 8 | test("test locale resolving", () { 9 | AcceptHeaderLocaleResolver lr = new AcceptHeaderLocaleResolver(); 10 | 11 | Locale locale = lr.resolveLocaleWithHeader(accept_header_string); 12 | 13 | expect("en_CA", locale.toString()); 14 | }); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /test/locale_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:forcemvc/force_mvc.dart'; 3 | 4 | main() { 5 | // First tests! 6 | var locale = Locale.ENGLISH; 7 | 8 | test('test basic locale', () { 9 | expect(locale.toString(), "en"); 10 | }); 11 | 12 | // "en", "de_DE", "_GB", "en_US_WIN", "fr__MAC" 13 | test('test locale parsing en', () { 14 | Locale locale = Locale.parseString("en"); 15 | expect(locale.getLanguage(), "en"); 16 | }); 17 | 18 | test('test locale parsing de_DE', () { 19 | Locale locale = Locale.parseString("de_DE"); 20 | expect(locale.getLanguage(), "de"); 21 | expect(locale.getCountry(), "DE"); 22 | }); 23 | 24 | test('test locale parsing _GB', () { 25 | Locale locale = Locale.parseString("_GB"); 26 | expect(locale.getLanguage(), ""); 27 | expect(locale.getCountry(), "GB"); 28 | }); 29 | 30 | test('test locale parsing en_US_WIN', () { 31 | Locale locale = Locale.parseString("en_US_WIN"); 32 | expect(locale.getLanguage(), "en"); 33 | expect(locale.getCountry(), "US"); 34 | expect(locale.getVariant(), "WIN"); 35 | }); 36 | 37 | test('test locale parsing fr__MAC', () { 38 | Locale locale = Locale.parseString("fr__MAC"); 39 | expect(locale.getLanguage(), "fr"); 40 | expect(locale.getCountry(), ""); 41 | expect(locale.getVariant(), "MAC"); 42 | }); 43 | 44 | } -------------------------------------------------------------------------------- /test/path_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:test/test.dart'; 2 | import 'package:forcemvc/force_mvc.dart'; 3 | import 'package:route/server.dart' show UrlPattern, Router; 4 | 5 | main() { 6 | // First tests! 7 | var path = "/some/{var1}/path/{var2}"; 8 | PathAnalyzer pathAnalyzer = new PathAnalyzer(path); 9 | 10 | var expression = pathAnalyzer.expression; 11 | 12 | test('test the path analyzer', () { 13 | expect(pathAnalyzer.variables.length, 2); 14 | expect(pathAnalyzer.variables.last, "var2"); 15 | expect(pathAnalyzer.route, "/some/$expression/path/$expression"); 16 | }); 17 | 18 | UrlPattern urlPattern = getMeUrlPattern("/some/{var1}"); 19 | test('test the urlpattern of path analyzer route', () { 20 | expect(urlPattern.matches("/some/blablabla"), true); 21 | }); 22 | 23 | UrlPattern urlPattern2 = getMeUrlPattern("/entities/entity/{id}"); 24 | test('test special characters in the testing group', () { 25 | expect(urlPattern2.matches("/entities/entity/188575e0-d700-11e5-d18b-f130bc592bc4"), true); 26 | }); 27 | } 28 | 29 | UrlPattern getMeUrlPattern(path) { 30 | PathAnalyzer path_analyzer = new PathAnalyzer(path); 31 | var route = path_analyzer.route; 32 | //var regex = r"/some/(\w+)"; 33 | UrlPattern urlPattern = new UrlPattern(route); 34 | return urlPattern; 35 | } 36 | --------------------------------------------------------------------------------