龙空技术网

Flutter Web-404如何处理找不到页面404错误,android和IOS程序员

勤于奋老勤 66

前言:

如今你们对“404错误页面html”大致比较注重,各位老铁们都需要学习一些“404错误页面html”的相关资讯。那么小编同时在网摘上搜集了一些有关“404错误页面html””的相关文章,希望各位老铁们能喜欢,咱们快快来了解一下吧!

如何处理“找不到页面404”错误,手动直接输入URL并避免URL中的哈希字符?

介绍

当我必须在生产中部署我的第一个Flutter Web应用程序时,我必须处理所有与Web Server逻辑相关的常规功能,尤其是:

著名的“ 找不到页面404 ”从浏览器直接输入URL

我在互联网上进行了大量搜索,但从未找到任何好的解决方案。

本文介绍了我实施的解决方案…

背景资料

本文撰写于2020年2月,基于Flutter 1.14.6版(运行Channel Beta)。

看一下Flutter路线图2020,Flutter Web应该在今年正式发布,其结果是这篇文章可能不会很快相关,因为它所解决的问题可能在未来几个月内得到解决。

我也尝试与Service Workers玩耍,但找不到任何解决方案。

在向您提供我已实施的解决方案之前,我想与您分享一些重要的信息…

提醒-Flutter Web应用程序不能在完全可配置的Web服务器后面运行

“ Flutter Web应用程序不能在完全可配置的Web服务器后面运行 ”

这句话非常重要,常常被人遗忘……

确实,当您运行Flutter Web应用程序时,您“ 简单地 ”启动了一个基本的Web服务器,该服务器侦听某个“ IP_address:port ”并提供位于“ web ”文件夹中的文件。几乎没有配置/自定义可以添加到该Web服务器的实例。

不同的网页文件夹

如果以调试模式运行Flutter Web App,则Web文件夹为“ / web”

如果以发布模式运行,则Web文件夹为“ / build / web”

当您运行Flutter Web应用程序时,一旦激活了基本的Web服务器,就会从相应的“ web ”文件夹中自动调用“ index.html ”页面。

“ index.html ”页面会自动加载一些资产以及与整个应用程序相对应的“ main.dart.js ”文件。实际上,这对应于Dart代码和其他一些库的Javascript转换。

换句话说...

当您访问“ index.html ”时,您正在加载整个应用程序。

这意味着Flutter Web应用程序是一个单页应用程序,并且在大多数情况下,除了在加载并启动该单页应用程序后检索任何其他资产(字体,图像等)之外,您之间不再会有任何交互Flutter Web应用程序(在浏览器上运行)和Web服务器。

URL中的怪异“#”字符

当您运行Flutter Web应用程序并从一个页面(=路由)导航到另一页面时,我想您已经注意到浏览器URL导航栏级别的更改了……

例如,假设您的应用程序由2个页面组成:“主页”和“登录页面”。主页将在应用程序启动时自动显示,并具有一个导航到LoginPage的按钮。

浏览器的URL栏将包含:

当您启动应用程序时=>这对应于主页显示LoginPage时为。

主题标签指定URL片段,该片段通常在单页应用程序中用于导航,以替代URL路径。

URL片段最有趣的是

片段不会在HTTP请求消息中发送,因为片段仅由浏览器使用。

在我们的例子中,在Flutter Web中,浏览器使用它们来处理历史记录

(有关片段的更多信息,请点击此链接)

如何在网址中隐藏“#”字符?

我很多次在互联网上看到这个问题,答案很简单。

由于'#'字符通常对应于应用程序中的页面(= Route),因此您需要告诉浏览器更新URL,同时继续在浏览器历史记录中考虑该页面(以便浏览器的后退和前进按钮可以使用正确)。

为此,您需要使页面成为“ StatefulWidget ”,以便利用页面的初始化时间(= initState方法)。

实现此目的的代码如下:

import 'dart:html' as html;import 'package:flutter/material.dart';class MyPage extends StatefulWidget {    @override    _MyPageState createState() => _MyPageState();}class _MyPageState extends State<MyPage> {    @override    void initState(){        super.initState();        // this is the trick        html.window.history.pushState(null, "MyPage", "/mypage");    }}

从那时起,当用户将被重定向到“ MyPage”时,而不是在URL中显示“ ”,浏览器将显示“ :8080 / mypage“,它更加人性化。

但是,如果您将该页面加为书签并尝试重新调用它,或者直接在浏览器中键入该URL,则将遇到以下错误页面“ 无法找到此页面 ”,该页面对应到著名的HTTP错误404。

那么如何解决呢?

每次您通过手动输入的URL访问Flutter Web应用程序时,都会运行main()

在解释该解决方案之前,还必须注意,当您通过Web浏览器输入“ 有效 ” URL时,将对Flutter Web服务器进行访问以重新加载应用程序,并在加载后运行main()方法。 。

换句话说,如果您在Web浏览器URL栏级别手动输入“ ”或“ ”,则请求将发送到重新加载应用程序并最终运行“ main() ”方法的Web服务器。

当通过应用程序本身从一个页面(=路由)切换到应用程序的另一页面时,情况并非如此,因为代码仅在Web浏览器级别运行!

我的解决方案第一次尝试...不是解决方案...

下一篇文章过去已经讨论过该问题,并给出了解决方案的一些提示,但是该文章中公开的“ 迄今为止最好的解决方案 ”今天不再起作用(或者我无法使其起作用)。

因此,直接想到的第一个解决方案是基于同一篇文章中所述的“ 第二个解决方案 ” ,其中:

我们在initState()方法中调用pushState时会提到“ .html”扩展名,如下所示:html.window.history.pushState(null,“ MyPage”,“ / mypage .html ”);我们在每个屏幕上创建一个* .html页面…

但是,这当然很乏味且容易出错,因此我继续进行调查。

解决方案

然后我想:“ 如果我可以拦截URL请求并以正确的格式重定向它,该怎么办?”。

换句话说,类似……(但这不起作用)不幸的是,正如我之前所说,不可能在HTTP请求中中继片段(带有#字符)的概念。

因此,我需要找到其他东西。

如果我可以使应用程序“ 认为 ” URL不一样怎么办?

然后,我找到了Shelf Dart软件包,这是Dart的Web服务器中间件,它允许定义请求处理程序。

解决方案非常简单:

我们运行机架式 Web服务器的实例,侦听所有传入的请求我们在本地主机上运行Flutter Web我们检查请求是否指向页面对于所有这些请求,我们将它们重定向到标称index.html,保持Request URL不变,以便可以由main()方法拦截,然后显示请求的页面…

当然,与资产相关的请求(图片,JavaScript等)不应属于重定向的一部分……

架子再次提供了一个称为shelf_proxy的代理处理程序,该代理处理程序对外部服务器的请求。正是我需要的!

但是,此代理处理程序不提供任何功能来插入重新路由逻辑……太糟糕了。

因此,由于其源代码已获得BSD许可,因此我克隆了该代理处理程序的源代码,以插入自己的重新路由逻辑,该逻辑简单地包含在内(但当然可以扩展到需求):

如果URL不包含对扩展名的任何引用(例如“ .js”,“。json”,“。png”…),并且在路径中仅包含1个块(例如“ / mypage”,而不是 “ http://192.168.1.40:8080 / assets / package / ...”),然后我将请求重定向到Flutter Web服务器实例的页面“ index.html ”,否则,我只需将请求重定向到Flutter Web服务器实例,而无需提及“ index.html”页面。

“ 这意味着要运行2台Web服务器!”,你能告诉我吗

是的,它确实。

该代理 Web服务器(在这里,利用现有的),听着真正的 IP地址和端口该颤振Web应用程序,听本地主机

实作1.创建Flutter Web应用程序

照常创建Flutter Web应用程序。

2.修改您的“ main.dart”文件(在/ lib中)

这个想法是直接捕获浏览器URL中提供的路径。

import 'dart:html' as html;import 'package:flutter/material.dart';void main(){    //    // Retrieve the path that was sent    //    final String pathName = html.window.location.pathname;    //    // Tell the Application to take it into consideration    //    runApp(        Application(pathName: html),    );}class Application extends StatelessWidget {    const Application({        this.pathName,    });    final String pathName;    @override    Widget build(BuildContext context){        return MaterialApp(            onUnknownRoute: (_) => UnknownPage.route(),            onGenerateRoute: Routes.onGenerateRoute,            initialRoute: pathName,        );    }}class Routes {    static Route<dynamic> onGenerateRoute(RouteSettings settings){        switch (settings.name.toLowerCase()){            case "/": return HomePage.route();            case "/page1": return Page1.route();            case "/page2": return Page2.route();            default:                return UnknownPage.route();        }    }}class HomePage extends StatefulWidget {    @override    _HomePageState createState() => _HomePageState();    //    // Static Routing    //    static Route<dynamic> route()         => MaterialPageRoute(                builder: (BuildContext context) => HomePage(),            );}class _HomePageState extends State<HomePage>{    @override    void initState(){        super.initState();        //        // Push this page in the Browser history        //        html.window.history.pushState(null, "Home", "/");    }    @override    Widget build(BuildContext context){        return Scaffold(            appBar: AppBar(title: Text('Home Page')),            body: Column(                children: <Widget>[                    RaisedButton(                        child: Text('page1'),                        onPressed: () => Navigator.of(context).pushNamed('/page1'),                    ),                    RaisedButton(                        child: Text('page2'),                        onPressed: () => Navigator.of(context).pushNamed('/page2'),                    ),                    //                    // Intentionally redirect to an Unknown page                    //                    RaisedButton(                        child: Text('page3'),                        onPressed: () => Navigator.of(context).pushNamed('/page3'),                    ),                ],            ),        );    }}// Similar code as HomePage, for Page1, Page2 and UnknownPage

说明:

在main()方法级别,我们捕获提交的路径(第8行)并将其提供给Application的应用认为路径作为“ 初始一个 ” =>“ initialRoute: 路径”(行#30)所述Routes.onGenerateRoute(...)则方法被调用并返回的路线,其对应于所提供的路径如果路由不存在,它将重定向到UnknownPage()3.创建代理服务器

1 – 在项目的根目录中创建一个bin文件夹2 – 在/ bin文件夹中创建一个名为“ proxy_server.dart ”的文件 3 –将以下代码放入该“ proxy_server.dart ”文件中:

import 'dart:async';import 'package:self/self_io.dart' as shelf_io;import './proxy_handler.dart';void main() async {    var server;    try {        server = await shelf_io.serve(            proxyHandler(";), // redirection to            "localhost",    // listening to hostname            8080,           // listening to port        );    } catch(e){        print('Proxy error: $e');    }}

说明:

在主()方法简单地初始化的一个实例货架 web服务器,其

在端口8080上侦听“ localhost”将所有传入的HTTP请求发送到proxyHandler()方法,该方法被指示重定向到“ localhost:8081”

4 –将以下文件“ proxy_handler.dart ”从该要点复制到您的/ bin文件夹中。

import 'dart:async';import 'package:http/http.dart' as http;import 'package:shelf/shelf.dart';import 'package:path/path.dart' as p;import 'package:pedantic/pedantic.dart';// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file// for details. All rights reserved. Use of this source code is governed by a// BSD-style license that can be found in the LICENSE file []./// A handler that proxies requests to [url].////// To generate the proxy request, this concatenates [url] and [Request.url]./// This means that if the handler mounted under `/documentation` and [url] is/// ``, a request to `/documentation/tutorials`/// will be proxied to ``.////// [url] must be a [String] or [Uri].////// [client] is used internally to make HTTP requests. It defaults to a/// `dart:io`-based client.////// [proxyName] is used in headers to identify this proxy. It should be a valid/// HTTP token or a hostname. It defaults to `shelf_proxy`.Handler proxyHandler(url, {http.Client client, String proxyName}) {  Uri uri;  if (url is String) {    uri = Uri.parse(url);  } else if (url is Uri) {    uri = url;  } else {    throw ArgumentError.value(url, 'url', 'url must be a String or Uri.');  }  client ??= http.Client();  proxyName ??= 'shelf_proxy';  return (serverRequest) async {    var requestUrl = uri.resolve(serverRequest.url.toString());    //    // Insertion of the business logic    //    if (_needsRedirection(requestUrl.path)){      requestUrl = Uri.parse(url + "/index.html");    }    var clientRequest = http.StreamedRequest(serverRequest.method, requestUrl);    clientRequest.followRedirects = false;    clientRequest.headers.addAll(serverRequest.headers);    clientRequest.headers['Host'] = uri.authority;    // Add a Via header. See    //     _addHeader(clientRequest.headers, 'via',        '${serverRequest.protocolVersion} $proxyName');    unawaited(store(serverRequest.read(), clientRequest.sink));    var clientResponse = await client.send(clientRequest);    // Add a Via header. See    //     _addHeader(clientResponse.headers, 'via', '1.1 $proxyName');    // Remove the transfer-encoding since the body has already been decoded by    // [client].    clientResponse.headers.remove('transfer-encoding');    // If the original response was gzipped, it will be decoded by [client]    // and we'll have no way of knowing its actual content-length.    if (clientResponse.headers['content-encoding'] == 'gzip') {      clientResponse.headers.remove('content-encoding');      clientResponse.headers.remove('content-length');      // Add a Warning header. See      //       _addHeader(          clientResponse.headers, 'warning', '214 $proxyName "GZIP decoded"');    }    // Make sure the Location header is pointing to the proxy server rather    // than the destination server, if possible.    if (clientResponse.isRedirect &&        clientResponse.headers.containsKey('location')) {      var location =          requestUrl.resolve(clientResponse.headers['location']).toString();      if (p.url.isWithin(uri.toString(), location)) {        clientResponse.headers['location'] =            '/' + p.url.relative(location, from: uri.toString());      } else {        clientResponse.headers['location'] = location;      }    }    return Response(clientResponse.statusCode,        body: clientResponse.stream, headers: clientResponse.headers);  };}/// Use [proxyHandler] instead.@deprecatedHandler createProxyHandler(Uri rootUri) => proxyHandler(rootUri);/// Add a header with [name] and [value] to [headers], handling existing headers/// gracefully.void _addHeader(Map<String, String> headers, String name, String value) {  if (headers.containsKey(name)) {    headers[name] += ', $value';  } else {    headers[name] = value;  }}/// Pipes all data and errors from [stream] into [sink].////// When [stream] is done, the returned [Future] is completed and [sink] is/// closed if [closeSink] is true.////// When an error occurs on [stream], that error is passed to [sink]. If/// [cancelOnError] is true, [Future] will be completed successfully and no/// more data or errors will be piped from [stream] to [sink]. If/// [cancelOnError] and [closeSink] are both true, [sink] will then be/// closed.Future store(Stream stream, EventSink sink,    {bool cancelOnError = true, bool closeSink = true}) {  var completer = Completer();  stream.listen(sink.add, onError: (e, StackTrace stackTrace) {    sink.addError(e, stackTrace);    if (cancelOnError) {      completer.complete();      if (closeSink) sink.close();    }  }, onDone: () {    if (closeSink) sink.close();    completer.complete();  }, cancelOnError: cancelOnError);  return completer.future;}////// Checks if the path requires to a redirection///bool _needsRedirection(String path){  if (!path.startsWith("/")){    return false;  }  final List<String> pathParts = path.substring(1).split('/');  ///  /// We only consider a path which is only made up of 1 part  ///  if (pathParts.isNotEmpty && pathParts.length == 1){    final bool hasExtension = pathParts[0].split('.').length > 1;    if (!hasExtension){      return true;    }  }  return false;}
总结

当我需要在生产中发布Flutter Web应用程序时,我必须找到一个能够处理以下问题的解决方案:

URL异常(例如“ 未找到页面-错误404 ”);友好的网址(不包含#个字符)

在地方,我把该解决方案(本文的主题),工程但这只能被看作是一个解决办法。

我想应该还有其他一些解决方案,更多的是“ 官方的 ”,但迄今为止我还没有发现其他解决方案。

我真的希望Flutter团队能够尽快解决此问题,以便在已有解决方案的情况下提供“ 干净的 ”解决方案或记录该解决方案。

标签: #404错误页面html