From 133d75fba746677ca3baf33909b61adb3a039acf Mon Sep 17 00:00:00 2001 From: Feodor Fitsner Date: Wed, 27 Jul 2022 15:40:52 -0700 Subject: [PATCH] Switching between "hash" and "path" routing URL strategy (#110) * Page route URL strategy can be changed * The same behaviour for hash and path strategies * defaultRouteUrlStrategy is hash * Index page notion --- client/lib/main.dart | 13 ++++++-- client/lib/reducers.dart | 10 +++---- client/lib/session_store/session_store.dart | 9 ------ client/lib/utils/platform_utils.dart | 3 -- ...ls_io.dart => platform_utils_non_web.dart} | 4 +++ ..._utils_js.dart => platform_utils_web.dart} | 6 ++++ .../session_store_non_web.dart} | 0 .../session_store_web.dart} | 0 client/lib/utils/uri.dart | 9 +++++- client/pubspec.lock | 19 ++++++++++++ client/pubspec.yaml | 1 + client/test/utils/uri_test.dart | 7 +++++ client/web/index.html | 3 ++ sdk/python/flet/flet.py | 15 ++++++++-- server/config/config.go | 13 ++++++-- server/model/page_name.go | 6 ++++ server/page/client.go | 11 +++++-- server/server/server.go | 30 +++++++++++++++++-- 18 files changed, 127 insertions(+), 32 deletions(-) delete mode 100644 client/lib/session_store/session_store.dart delete mode 100644 client/lib/utils/platform_utils.dart rename client/lib/utils/{platform_utils_io.dart => platform_utils_non_web.dart} (50%) rename client/lib/utils/{platform_utils_js.dart => platform_utils_web.dart} (57%) rename client/lib/{session_store/session_store_io.dart => utils/session_store_non_web.dart} (100%) rename client/lib/{session_store/session_store_js.dart => utils/session_store_web.dart} (100%) diff --git a/client/lib/main.dart b/client/lib/main.dart index 1e98233c3..9df79fae0 100644 --- a/client/lib/main.dart +++ b/client/lib/main.dart @@ -6,14 +6,16 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_redux/flutter_redux.dart'; import 'package:redux/redux.dart'; +import 'package:url_strategy/url_strategy.dart'; +import '../utils/platform_utils_non_web.dart' + if (dart.library.js) "../utils/platform_utils_web.dart"; +import '../utils/session_store_non_web.dart' + if (dart.library.js) "../utils/session_store_web.dart"; import 'controls/create_control.dart'; import 'models/app_state.dart'; import 'models/page_view_model.dart'; import 'reducers.dart'; -import 'session_store/session_store.dart' - if (dart.library.io) "session_store/session_store_io.dart" - if (dart.library.js) "session_store/session_store_js.dart"; import 'web_socket_client.dart'; const bool isProduction = bool.fromEnvironment('dart.vm.product'); @@ -35,6 +37,11 @@ void main([List? args]) async { if (kIsWeb) { debugPrint("Flet View is running in Web mode"); + var routeUrlStrategy = getRouteUrlStrategy(); + debugPrint("URL Strategy: $routeUrlStrategy"); + if (routeUrlStrategy == "path") { + setPathUrlStrategy(); + } } else if ((Platform.isWindows || Platform.isMacOS || Platform.isLinux) && !kDebugMode) { debugPrint("Flet View is running in Desktop mode"); diff --git a/client/lib/reducers.dart b/client/lib/reducers.dart index 87864a404..acc31725c 100644 --- a/client/lib/reducers.dart +++ b/client/lib/reducers.dart @@ -7,15 +7,13 @@ import 'package:flet_view/protocol/update_control_props_payload.dart'; import 'package:flet_view/web_socket_client.dart'; import 'package:flutter/cupertino.dart'; -import '../utils/platform_utils.dart' - if (dart.library.io) "../utils/platform_utils_io.dart" - if (dart.library.js) "../utils/platform_utils_js.dart"; +import '../utils/platform_utils_non_web.dart' + if (dart.library.js) "../utils/platform_utils_web.dart"; +import '../utils/session_store_non_web.dart' + if (dart.library.js) "../utils/session_store_web.dart"; import 'actions.dart'; import 'models/app_state.dart'; import 'models/control.dart'; -import 'session_store/session_store.dart' - if (dart.library.io) "session_store/session_store_io.dart" - if (dart.library.js) "session_store/session_store_js.dart"; import 'utils/desktop.dart'; import 'utils/uri.dart'; diff --git a/client/lib/session_store/session_store.dart b/client/lib/session_store/session_store.dart deleted file mode 100644 index b27370857..000000000 --- a/client/lib/session_store/session_store.dart +++ /dev/null @@ -1,9 +0,0 @@ -class SessionStore { - static String? get(String name) { - throw UnsupportedError("Not supported!"); - } - - static void set(String name, String value) { - throw UnsupportedError("Not supported!"); - } -} diff --git a/client/lib/utils/platform_utils.dart b/client/lib/utils/platform_utils.dart deleted file mode 100644 index 9baf547fe..000000000 --- a/client/lib/utils/platform_utils.dart +++ /dev/null @@ -1,3 +0,0 @@ -bool isProgressiveWebApp() { - throw UnsupportedError("Not supported!"); -} diff --git a/client/lib/utils/platform_utils_io.dart b/client/lib/utils/platform_utils_non_web.dart similarity index 50% rename from client/lib/utils/platform_utils_io.dart rename to client/lib/utils/platform_utils_non_web.dart index 34291380d..6bdf6b1d1 100644 --- a/client/lib/utils/platform_utils_io.dart +++ b/client/lib/utils/platform_utils_non_web.dart @@ -1,3 +1,7 @@ bool isProgressiveWebApp() { return false; } + +String getRouteUrlStrategy() { + return ""; +} diff --git a/client/lib/utils/platform_utils_js.dart b/client/lib/utils/platform_utils_web.dart similarity index 57% rename from client/lib/utils/platform_utils_js.dart rename to client/lib/utils/platform_utils_web.dart index 5bb65f673..3e4b45312 100644 --- a/client/lib/utils/platform_utils_js.dart +++ b/client/lib/utils/platform_utils_web.dart @@ -5,3 +5,9 @@ bool isProgressiveWebApp() { window.matchMedia('(display-mode: fullscreen)').matches || window.matchMedia('(display-mode: minimal-ui)').matches; } + +String getRouteUrlStrategy() { + var meta = + document.head?.querySelector("meta[name='flet-route-url-strategy']"); + return meta != null ? meta.attributes["content"]! : ""; +} diff --git a/client/lib/session_store/session_store_io.dart b/client/lib/utils/session_store_non_web.dart similarity index 100% rename from client/lib/session_store/session_store_io.dart rename to client/lib/utils/session_store_non_web.dart diff --git a/client/lib/session_store/session_store_js.dart b/client/lib/utils/session_store_web.dart similarity index 100% rename from client/lib/session_store/session_store_js.dart rename to client/lib/utils/session_store_web.dart diff --git a/client/lib/utils/uri.dart b/client/lib/utils/uri.dart index 9eacd3064..8ea51cd53 100644 --- a/client/lib/utils/uri.dart +++ b/client/lib/utils/uri.dart @@ -1,7 +1,14 @@ import 'strings.dart'; String getWebPageName(Uri uri) { - return trim(uri.path, "/"); + var urlPath = trim(uri.path, "/"); + if (urlPath != "") { + var pathParts = urlPath.split("/"); + if (pathParts.length > 1) { + urlPath = pathParts.sublist(0, 2).join("/"); + } + } + return urlPath; } String getWebSocketEndpoint(Uri uri) { diff --git a/client/pubspec.lock b/client/pubspec.lock index 8dec870b5..830537a0b 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -116,6 +116,11 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_plugins: + dependency: transitive + description: flutter + source: sdk + version: "0.0.0" http: dependency: "direct main" description: @@ -137,6 +142,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "3.2.0" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.4" lints: dependency: transitive description: @@ -247,6 +259,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.3.0" + url_strategy: + dependency: "direct main" + description: + name: url_strategy + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0" vector_math: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 6637ce109..ef90885c1 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -40,6 +40,7 @@ dependencies: window_manager: ^0.2.5 http: ^0.13.3 collection: ^1.16.0 + url_strategy: ^0.2.0 dev_dependencies: flutter_test: diff --git a/client/test/utils/uri_test.dart b/client/test/utils/uri_test.dart index ee911433f..8d767bcee 100644 --- a/client/test/utils/uri_test.dart +++ b/client/test/utils/uri_test.dart @@ -15,6 +15,13 @@ void main() { expect( getWebPageName(Uri.parse('http://localhost:8550/p/test/')), "p/test"); expect(getWebPageName(Uri.parse('http://localhost:8550/p/test')), "p/test"); + expect(getWebPageName(Uri.parse('http://localhost:8550/aaa')), "aaa"); + expect(getWebPageName(Uri.parse('http://localhost:8550/p/test/store')), + "p/test"); + expect( + getWebPageName( + Uri.parse('http://localhost:8550/p/test/store/products/1')), + "p/test"); expect(getWebPageName(Uri.parse('http://localhost:8550/')), ""); expect(getWebPageName(Uri.parse('http://localhost:8550/#/')), ""); }); diff --git a/client/web/index.html b/client/web/index.html index ada2658c8..37a945c8e 100644 --- a/client/web/index.html +++ b/client/web/index.html @@ -16,6 +16,9 @@ + + + Flet diff --git a/sdk/python/flet/flet.py b/sdk/python/flet/flet.py index 0f561db05..f2daa6d85 100644 --- a/sdk/python/flet/flet.py +++ b/sdk/python/flet/flet.py @@ -44,6 +44,7 @@ def page( view: AppViewer = WEB_BROWSER, assets_dir=None, web_renderer="canvaskit", + route_url_strategy="hash", ): conn = _connect_internal( page_name=name, @@ -52,6 +53,7 @@ def page( permissions=permissions, assets_dir=assets_dir, web_renderer=web_renderer, + route_url_strategy=route_url_strategy, ) print("Page URL:", conn.page_url) page = Page(conn, constants.ZERO_SESSION) @@ -71,6 +73,7 @@ def app( view: AppViewer = FLET_APP, assets_dir=None, web_renderer="canvaskit", + route_url_strategy="hash", ): if target == None: @@ -84,6 +87,7 @@ def app( session_handler=target, assets_dir=assets_dir, web_renderer=web_renderer, + route_url_strategy=route_url_strategy, ) print("App URL:", conn.page_url) @@ -139,6 +143,7 @@ def _connect_internal( session_handler=None, assets_dir=None, web_renderer=None, + route_url_strategy=None, ): if share and server == None: server = constants.HOSTED_SERVICE_URL @@ -151,7 +156,9 @@ def _connect_internal( # page with a custom port starts detached process attached = False if not is_app and port != 0 else True - port = _start_flet_server(port, attached, assets_dir, web_renderer) + port = _start_flet_server( + port, attached, assets_dir, web_renderer, route_url_strategy + ) server = f"http://127.0.0.1:{port}" connected = threading.Event() @@ -219,7 +226,7 @@ def _on_ws_failed_connect(): return conn -def _start_flet_server(port, attached, assets_dir, web_renderer): +def _start_flet_server(port, attached, assets_dir, web_renderer, route_url_strategy): if port == 0: port = _get_free_tcp_port() @@ -263,6 +270,10 @@ def _start_flet_server(port, attached, assets_dir, web_renderer): logging.info(f"Web renderer configured: {web_renderer}") fletd_env["FLET_WEB_RENDERER"] = web_renderer + if route_url_strategy != None: + logging.info(f"Route URL strategy configured: {route_url_strategy}") + fletd_env["FLET_ROUTE_URL_STRATEGY"] = route_url_strategy + args = [fletd_path, "--port", str(port)] creationflags = 0 diff --git a/server/config/config.go b/server/config/config.go index fa0d93b89..d95c041cd 100644 --- a/server/config/config.go +++ b/server/config/config.go @@ -71,8 +71,10 @@ const ( defaultMasterSecretKey = "master_secret_key" // development - staticRootDir = "STATIC_ROOT_DIR" - webRenderer = "WEB_RENDERER" + staticRootDir = "STATIC_ROOT_DIR" + webRenderer = "WEB_RENDERER" + routeUrlStrategy = "ROUTE_URL_STRATEGY" + defaultRouteUrlStrategy = "hash" ) func init() { @@ -124,6 +126,9 @@ func init() { // security viper.SetDefault(cookieSecret, getSecretManagerValue(cookieSecret, defaultCookieSecret)) viper.SetDefault(masterSecretKey, getSecretManagerValue(masterSecretKey, defaultMasterSecretKey)) + + // development + viper.SetDefault(routeUrlStrategy, defaultRouteUrlStrategy) } func getSecretManagerValue(name string, defaultValue string) string { @@ -288,3 +293,7 @@ func StaticRootDir() string { func WebRenderer() string { return viper.GetString(webRenderer) } + +func RouteUrlStrategy() string { + return viper.GetString(routeUrlStrategy) +} diff --git a/server/model/page_name.go b/server/model/page_name.go index d3576d81f..e3ec5035a 100644 --- a/server/model/page_name.go +++ b/server/model/page_name.go @@ -19,6 +19,7 @@ const ( type PageName struct { Account string Name string + IsIndex bool } func ParsePageName(pageName string) (*PageName, error) { @@ -27,6 +28,7 @@ func ParsePageName(pageName string) (*PageName, error) { return &PageName{ Account: publicAccount, Name: indexPage, + IsIndex: true, }, nil } @@ -62,6 +64,10 @@ func ParsePageName(pageName string) (*PageName, error) { return nil, fmt.Errorf("page name exceeds the maximum allowed size of %d symbols", maxSlugSize) } + if p.Account == publicAccount && p.Name == indexPage { + p.IsIndex = true + } + return p, nil } diff --git a/server/page/client.go b/server/page/client.go index 9dfa394f8..37f337a86 100644 --- a/server/page/client.go +++ b/server/page/client.go @@ -209,9 +209,14 @@ func (c *Client) registerWebClientCore(request *RegisterWebClientRequestPayload) // get page page := store.GetPageByName(pageName.String()) - if page == nil { - response.Error = pageNotFoundMessage - return + if page == nil && !pageName.IsIndex { + // fallback to index + pageName, _ = model.ParsePageName("") + page = store.GetPageByName(pageName.String()) + if page == nil { + response.Error = pageNotFoundMessage + return + } } // func: check if "Sign in required" response should be sent diff --git a/server/server/server.go b/server/server/server.go index 16d60c7b5..1b3da0cf4 100644 --- a/server/server/server.go +++ b/server/server/server.go @@ -99,8 +99,27 @@ func Start(ctx context.Context, wg *sync.WaitGroup, serverPort int) { router.NoRoute(func(c *gin.Context) { if !strings.HasPrefix(c.Request.RequestURI, apiRoutePrefix+"/") { - urlPath := strings.TrimRight(c.Request.URL.Path, "/") + "/" - log.Debugln("Request path:", urlPath) + baseHref := strings.Trim(c.Request.URL.Path, "/") + log.Debugln("Request path:", baseHref) + + if baseHref != "" { + hrefParts := strings.Split(baseHref, "/") + if len(hrefParts) > 1 { + baseHref = strings.Join(hrefParts[:2], "/") + if store.GetPageByName(baseHref) == nil { + // fallback to index page + baseHref = "" + } + } else { + baseHref = "" + } + } + + if baseHref != "" { + baseHref = "/" + baseHref + "/" + } else { + baseHref = "/" + } index, _ := assetsFS.Open(siteDefaultDocument) indexData, _ := ioutil.ReadAll(index) @@ -108,7 +127,12 @@ func Start(ctx context.Context, wg *sync.WaitGroup, serverPort int) { // base path indexData = bytes.Replace(indexData, []byte(""), - []byte(""), 1) + []byte(""), 1) + + // route URL strategy + indexData = bytes.Replace(indexData, + []byte("%FLET_ROUTE_URL_STRATEGY%"), + []byte(config.RouteUrlStrategy()), 1) // web renderer if config.WebRenderer() != "" {