Flutter应用程序正在调试模式下工作,但由于CORS策略,构建/Web版本无法与服务器通信

jchrr9hc  于 2023-08-07  发布在  Flutter
关注(0)|答案(1)|浏览(201)

bounty还有6天到期。回答此问题可获得+100声望奖励。brownfox希望引起更多注意这个问题:我期望Web应用程序版本能够像调试Linux版本一样与服务器通信以发送和接收消息,以便我可以将其部署在某个地方。

我正在努力学习Webdev,作为我第一个项目的一部分,我构建了一个简单的聊天机器人应用程序。该应用程序由一个Python-Flask后端(运行在localhost/5000上)和一个Flutter应用程序前端组成。Flutter应用程序接受我们的输入,将其发送到Flask服务器,并将响应打印给我们。
当我在本地以调试模式运行应用程序时,它按预期正常工作。但是,当我使用

flutter build web

字符串
然后尝试通过创建Python服务器从/build/web/运行应用程序

python -m http.server 8081


出现问题,Web应用程序正在浏览器中启动:但是,现在它不能从运行在localhost/5000的服务器发送和接收消息。我得到一个错误:x1c 0d1x的数据

flutter.js:368 Exception while loading service worker: Error: Service Worker API unavailable.
The current context is NOT secure.
Read more: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts
    at FlutterServiceWorkerLoader.loadServiceWorker (flutter.js:130:11)
    at FlutterLoader.loadEntrypoint (flutter.js:366:33)
    at (index):46:23
(anonymous) @ flutter.js:368
0.0.0.0/:1 Access to XMLHttpRequest at 'http://127.0.0.1:5000/api/chat' from 
origin 'http://0.0.0.0:8081' has been blocked by CORS policy:
The request client is not a secure context and the resource is in more-private address space `local`.
main.dart.js:6506 Uncaught Error
    at Object.alx (main.dart.js:6573:24)
    at TE.$1 (main.dart.js:73160:53)
    at abl.adR (main.dart.js:36153:34)
    at abl.DP (main.dart.js:36155:21)
    at a95.$0 (main.dart.js:35896:11)
    at Object.r4 (main.dart.js:5825:40)
    at az.mx (main.dart.js:35827:3)
    at Object.ayw (main.dart.js:5908:8)
    at a4N.$1 (main.dart.js:35938:9)
    at a8N.$1 (main.dart.js:39265:21)
127.0.0.1:5000/api/chat:1     Failed to load resource: net::ERR_FAILED


它基本上说,由于CORS安全策略,我无法从源'http://0.0.0.0:8081'与'http://127.0.0.1:5000/api/chat'通信。当我尝试运行相同的Web应用程序时,通过禁用浏览器安全性,我不再收到此错误,Web应用程序能够与服务器通信。

chromium-browser --disable-web-security


如何在不牺牲安全性的情况下解决这个问题?我希望在修复此问题后在线部署应用程序。
为什么会这样?下面是Github repo的链接,其中包含完整的代码:https://github.com/sri-gpt/simple_chat_app
我正在使用:

  • Ubuntu版本22.04 LTS
  • Flutter3.13.0-0.2.pre
  • dart 3.1.0
  • DevTools 2.25.0
  • flask 2.3.2
  • Flask-Cors 4.0.0
  • Python 3.9

示例代码:Python Flask服务器

from flask import Flask, request,  jsonify
from flask_cors import CORS

app = Flask(__name__)

CORS(app)

@app.route('/')
def home():
    return 'ServerDEployed'

@app.route('/about')
def about():
    return 'About'

@app.route('/api/chat', methods=['POST'])
def chat():
    data = request.get_json()
    input_text = data.get('input_text')
    return jsonify({"response_mal": str("response_recived:")+str(input_text)})

if __name__ == '__main__':
    app.run(debug=True)


以下是Flutter应用程序的主.dart文件:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

void main() {
  runApp(const ChatApp());
}

class ChatApp extends StatelessWidget {
  const ChatApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Chat_app',
      theme: ThemeData(
        primarySwatch: Colors.indigo, // Change primary color
        fontFamily: 'Montserrat', // Add custom font (Montserrat can be replaced with your preferred font)
      ),
      home: const MyHomePage(title: 'Chat_app'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final _messages = <Map<String, dynamic>>[];
  final _controller = TextEditingController();
  final double fontText = 24.0; // <-- This is your pseudo variable for font size

  Future<void> _sendText() async {
    if (_controller.text.isEmpty) {
      return;
    }

    setState(() {
      _messages.add({"text": _controller.text, "type": "outgoing"});
    });

    final url = Uri.parse('http://127.0.0.1:5000/api/chat');
    final response = await http.post(
      url,
      headers: <String, String>{
        'Content-Type': 'application/json; charset=UTF-8',
      },
      body: jsonEncode(<String, String>{
        'input_text': _controller.text,
      }),
    );

    if (response.statusCode == 200) {
      final Map<String, dynamic> data = jsonDecode(response.body);

      setState(() {
        _messages.add({"text": data["response_mal"], "type": "incoming"});
        _controller.text = "";
      });
    } else {
      throw Exception('Failed to send message');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title, style: TextStyle(fontSize: fontText)), // <-- Apply font size here.
      ),
      body: Column(
        children: <Widget>[
          Expanded(
            child: ListView.builder(
              padding: const EdgeInsets.all(8),
              itemCount: _messages.length,
              itemBuilder: (_, int index) {
                final message = _messages[index];
                final isOutgoing = message["type"] == "outgoing";

                return Container(
                  margin: const EdgeInsets.symmetric(vertical: 5),
                  padding: const EdgeInsets.all(10),
                  alignment:
                      isOutgoing ? Alignment.centerRight : Alignment.centerLeft,
                  child: Text(
                    message["text"],
                    style: TextStyle(fontSize: fontText), // <-- Apply font size here.
                  ),
                  decoration: BoxDecoration(
                    color: isOutgoing ? Colors.orange[100] : Colors.blue[100],
                    borderRadius: BorderRadius.circular(5),
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black12,
                        blurRadius: 5,
                        offset: Offset(0, 2),
                      ),
                    ],
                  ),
                );
              },
            ),
          ),
          Row(
            children: <Widget>[
              Expanded(
                child: Padding(
                  padding: EdgeInsets.all(8.0),
                  child: TextField(
                    controller: _controller,
                    onSubmitted: (_) => _sendText(),
                    decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: 'Enter your message',
                    ),
                    style: TextStyle(fontSize: fontText), // <-- Apply font size here.
                  ),
                ),
              ),
              Padding(
                padding: EdgeInsets.all(8.0),
                child: ElevatedButton( // Change the send button to ElevatedButton
                  onPressed: _sendText,
                  child: Icon(Icons.send),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

ryevplcw

ryevplcw1#

然而,当我通过禁用Web安全性$chromium-browser --disable-web-security来运行Chromium浏览器时,应用程序工作正常。所以这似乎是由某些CORS安全策略引起的。
如何在不牺牲安全性的情况下解决这个问题?
在调试模式下运行应用时,应用可能没有强制执行CORS规则。然而,当你构建和服务Web应用时,CORS会成为一个因素,特别是当你的Flutter应用和Flask后端来自不同的来源时(在这种情况下,是本地主机上的不同端口)。
尝试并包含corydolphin/flask-cors,这是一个用于处理跨域资源共享(CORS)的Flask扩展,使跨域 AJAX 成为可能。
不使用默认的CORS(app),您可以指定允许哪些源向Flask后端发出请求。例如:

from flask_cors import CORS

cors = CORS(app, resources={r"/api/*": {"origins": "http://0.0.0.0:8081"}})

字符串
这将允许来自http://0.0.0.0:8081上托管的Flutter Web应用程序的请求,但拒绝其他请求。
确保在运行时将Flask应用绑定到0.0.0.0,以便外部请求可以访问,而不仅仅是来自localhost的请求。

if __name__ == '__main__':
    app.run(host='0.0.0.0', debug=True)


但是错误Exception while loading service worker: Error: Service Worker API unavailable.表明服务工作者在不安全的上下文(HTTP)上服务时存在问题。
考虑使用HTTPS为您的Web应用程序提供服务。
您可以按照Harrison Kinsley中的“Securing your Flask website with SSL for HTTPS using Let's Encrypt”作为示例。

相关问题