Flutter app is working in debug mode, however build/web version is unable to communicate with the server because of CORS policy

huangapple go评论104阅读模式
英文:

Flutter app is working in debug mode, however build/web version is unable to communicate with the server because of CORS policy

问题

I'm trying to learn Webdev and as part of my first project, I have built a simple chat-bot-app. The app consists of a Python-Flask backend (running on localhost/5000) and a Flutter app front end. The Flutter app takes our input, sends it to the Flask server, and will print us the response.

When I run the app in debug mode locally, it is working fine as expected. However, when I build a web version of the app using

flutter build web

and then try to run the app from /build/web/ by creating a Python server

python -m http.server 8081

There is a problem, the web app is launching in the browser: however, now it cannot send and receive messages from the server running at localhost/5000. I'm getting an error:

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

It basically says I cannot communicate with 'http://127.0.0.1:5000/api/chat' from origin 'http://0.0.0.0:8081' due to CORS security policy. When I try to run the same web app by disabling the browser security, I'm no longer getting this error, and the web app is able to communicate with the server.

chromium-browser --disable-web-security

How can I fix this without sacrificing security? I wish to deploy the app online after fixing this.

Why is this happening?
Here is the link to the Github repo which has the full code:
https://github.com/sri-gpt/simple_chat_app

I'm using:

  • Ubuntu version 22.04 LTS,
  • Flutter 3.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

Sample code:
Python Flask server

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)

Here is the main.dart file of the Flutter app:

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],


<details>
<summary>英文:</summary>

I&#39;m trying to learn Webdev and as part of my first project, I have built a simple chat-bot-app. The app consists of a Python-Flask backend (running on localhost/5000) and a Flutter app front end. The Flutter app takes our input, sends it to the Flask server, and will print us the response. 

When I run the app in debug mode locally, it is working fine as expected. However, when I build a web version of the app using 

```bash
flutter build web

and then try to run the app from /build/web/ by creating a Python server

python -m http.server 8081

There is a problem, the web app is launching in the browser: however, now it can not send and receive messages from the server running at localhost/5000. I'm getting an error:
Flutter app is working in debug mode, however build/web version is unable to communicate with the server because of CORS policy

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 &#39;http://127.0.0.1:5000/api/chat&#39; from 
origin &#39;http://0.0.0.0:8081&#39; 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

It basically says I can not communicate with 'http://127.0.0.1:5000/api/chat' from origin 'http://0.0.0.0:8081' due to CORS security policy. When I try to run the same web app, by disabling the browser security, I'm no longer getting this error and the web app is able to communicate with the server.

chromium-browser --disable-web-security

How can I fix this without sacrificing security? I wish to deploy the app online after fixing this.

Why is this happening?
Here is the link to the Github repo which has the full code:
https://github.com/sri-gpt/simple_chat_app

I'm using:

  • Ubuntu version 22.04 LTS,
  • Flutter 3.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

Sample code:
Python Flask server

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


app = Flask(__name__)

CORS(app)

@app.route(&#39;/&#39;)
def home():
    return &#39;ServerDEployed&#39;

@app.route(&#39;/about&#39;)
def about():
    return &#39;About&#39;

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

if __name__ == &#39;__main__&#39;:
    app.run(debug=True)

Here is the main.dart file of the Flutter app:

import &#39;dart:convert&#39;;
import &#39;package:flutter/material.dart&#39;;
import &#39;package:http/http.dart&#39; 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: &#39;Chat_app&#39;,
      theme: ThemeData(
        primarySwatch: Colors.indigo, // Change primary color
        fontFamily: &#39;Montserrat&#39;, // Add custom font (Montserrat can be replaced with your preferred font)
      ),
      home: const MyHomePage(title: &#39;Chat_app&#39;),
    );
  }
}

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

  final String title;

  @override
  _MyHomePageState createState() =&gt; _MyHomePageState();
}

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

  Future&lt;void&gt; _sendText() async {
    if (_controller.text.isEmpty) {
      return;
    }

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

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

    if (response.statusCode == 200) {
      final Map&lt;String, dynamic&gt; data = jsonDecode(response.body);

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

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

                return Container(
                  margin: const EdgeInsets.symmetric(vertical: 5),
                  padding: const EdgeInsets.all(10),
                  alignment:
                      isOutgoing ? Alignment.centerRight : Alignment.centerLeft,
                  child: Text(
                    message[&quot;text&quot;],
                    style: TextStyle(fontSize: fontText), // &lt;-- 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: &lt;Widget&gt;[
              Expanded(
                child: Padding(
                  padding: EdgeInsets.all(8.0),
                  child: TextField(
                    controller: _controller,
                    onSubmitted: (_) =&gt; _sendText(),
                    decoration: InputDecoration(
                      border: OutlineInputBorder(),
                      labelText: &#39;Enter your message&#39;,
                    ),
                    style: TextStyle(fontSize: fontText), // &lt;-- Apply font size here.
                  ),
                ),
              ),
              Padding(
                padding: EdgeInsets.all(8.0),
                child: ElevatedButton( // Change the send button to ElevatedButton
                  onPressed: _sendText,
                  child: Icon(Icons.send),
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

答案1

得分: 2

然而,当我通过禁用Web安全性来运行Chromium浏览器,$chromium-browser --disable-web-security,应用程序可以正常工作。因此,似乎这是由某些CORS安全策略引起的。如何在不牺牲安全性的情况下修复这个问题?

当您以调试模式运行应用程序时,应用程序可能不会执行CORS规则。然而,当您构建和提供Web应用程序时,CORS成为一个因素,特别是当您的Flutter应用程序和Flask后端来自不同的来源(在这种情况下,是localhost上的不同端口)。

尝试包括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的示例,使用**Let's Encrypt为Flask网站提供SSL以进行HTTPS安全**。

英文:

> However, when I run the Chromium browser by disabling web security, $chromium-browser --disable-web-security, the app is working fine. So it seems that this is caused by some CORS security policy.
How can I fix this without sacrificing security?

When you run your app in debug mode, it is likely the app is not enforcing CORS rules. However, when you build and serve the web app, CORS becomes a factor, especially when your Flutter app and your Flask backend are served from different origins (in this case, different ports on localhost).

Try and include corydolphin/flask-cors, a Flask extension for handling Cross Origin Resource Sharing (CORS), making cross-origin AJAX possible.

Instead of using the default CORS(app), you can specify which origins are allowed to make requests to your Flask backend. For instance:

from flask_cors import CORS

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

That will allow requests from your Flutter web app hosted on http://0.0.0.0:8081 but deny others.

And make sure you are binding your Flask app to 0.0.0.0 when running so that it is accessible to external requests, not just those coming from localhost.

if __name__ == &#39;__main__&#39;:
    app.run(host=&#39;0.0.0.0&#39;, debug=True)

But the error Exception while loading service worker: Error: Service Worker API unavailable. suggests that there is a problem with service workers when served on an insecure context (HTTP).
Consider using HTTPS for serving your web applications.

You can follow "Securing your Flask website with SSL for HTTPS using Let's Encrypt" from Harrison Kinsley as an example.

答案2

得分: 0

Header set Access-Control-Allow-Origin "*";

英文:

Header set Access-Control-Allow-Origin "*"

huangapple
  • 本文由 发表于 2023年8月4日 06:40:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/76831987.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定