前言:
如今你们对“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