如何在 Flutter 里建立一个内部的 HTTP 服务器

1. 介绍

如果你想要让你的用户通过浏览器将文件上传到你的应用中,那就可以使用 Flutter 创建一个内部的 HTTP 服务器。这个功能非常有用,例如,你的应用要提供离线的视频或者音频播放,可以让用户上传自己的视频或音频文件。

2. 让我们开始吧

其实要有很多问题需要解决的,不过由于篇幅所限,因此我在本文中只会介绍几个重要的问题,但不用担心,我最终会提供一套完整的代码 🙂

好的,现在就让我们来解决以下的问题吧:

1) 创建 HTTP 服务器

我们可以使用 HttpServer 创建 HTTP 服务器,然后使用 bind 方法去设置其 IP 和端口:

final s = await HttpServer.bind(address, port);

然后监听所有的访问

s.listen(_handleReq);

 _handleReq(HttpRequest request) async {
    ...
 }

但下一个问题就是,我们要如何获取 IP 地址然后传递给 HttpServer 呢?

我们可以使用 NetworkInterface 列出当前所有网络,然后通过 IP 地址类型将 IPV4 的地址找出来

for (var interface in await NetworkInterface.list()) {
  for (var addr in interface.addresses) {
    if (addr.type == InternetAddressType.IPv4) {
      currentIP = addr.address;
    }
  }
}

但你很可能会找到多个 IP 地址,而其中只有一个是正确的,所以我们还需要对 IP 地址进行过滤,只要以 192 开头的内网地址就好了。这个方法在 Android 里完全没问题,但在 iOS 中就会发现原来并不只有一个 IP 是以 192 开头的,这时我们就要再检测其网络接口名称是否以 en 开头以确定是我们真正需要的 IP,以下就是相关的代码:

 for (var interface in await NetworkInterface.list()) {
  for (var addr in interface.addresses) {
    if (addr.type == InternetAddressType.IPv4 &&
        (Platform.isIOS ? interface.name.startsWith('en') : 1 == 1) &&
        addr.address.startsWith('192')) {
      currentIP = addr.address;
    }
  }
}

2) 在 HTTP 服务器里运行 HTML 文件

我们要在此 HTTP 服务器里运行由 HTML 文件编写的前台网站,所以要解决如何处理 HTML 文件的问题。你可以将这个 HTML 网站放到 asset 目录里,然后更新 pubspec.yaml 文件

flutter:
  assets:
    - assets/
    - assets/webserver/
    - assets/webserver/css/
    - assets/webserver/fonts/
    - assets/webserver/js/

HTML 的网站目录结构如下:

assets
    \webserver
        \css
        \fonts
        \js
        index.html        

然后创建一个 Map 对象以存放请求的 HTML 文件

import 'dart:typed_data';

class AssetsCache {
  /// Assets cache
  static Map<String, ByteData> assets = {};

  /// Clears assets cache
  static void clear() {
    assets = {};
  }
}

基于上面的监听访问代码,我们要修改 _handleReq 方法:

首先,从请求的 URL 中获取当前要访问的文件

_handleReq(HttpRequest request) async {
    String path = request.requestedUri.path.replaceFirst('/', '');
    ...
}

上面的代码将会从当前 URL 中取得访问文件的连接,如以下例子:

first request url:  http://192.168.1.215:8080/index.html
second request url: http://192.168.1.215:8080/js/index.js
...

第一个请求的 path 的值就是 index.html,第二个请求的值是 js/index.js,所以我们可以创建一个 _loadAsset 方法来处理,这个方法会返回请求文件所在的 webserver 文件夹里的实际文件路径

Future<ByteData> _loadAsset(String path) async {
    if (AssetsCache.assets.containsKey(path)) {
      return AssetsCache.assets[path]!;
    }

    if (_rootDir == null) {
      // print('path=============');
      // print(join(assetsBasePath, path));
      ByteData data = await rootBundle.load(join(assetsBasePath, path));
      return data;
    }

    if (await Directory(_rootDir.path).exists()) {}

    debugPrint(join(_rootDir.path, path));
    final f = File(join(_rootDir.path, path));
    return (await f.readAsBytes()).buffer.asByteData();
}

然后将文件呈现在客户端

final data = await _loadAsset(path);

request.response.headers.add('Content-Type', '$mime; charset=utf-8');
request.response.add(data.buffer.asUint8List());
request.response.close();

最后,你就可以通过这个 HTTP 服务器访问你的 HTML 网站了

3) 从 HTML 上传文件到 Flutter

你可以使用任何的 ajax 方法以从客户端上传文件,但在这个例子中,我使用了 jquery file upload 插件,我就不介绍在客户端的详细做法了,大家可自行查看相关文档,在这里只介绍如何处理服务器端,即 Flutter 如何获取客户端的文件

当使用了 ajax 从客户端发送文件到服务器端后,我们可以在 Flutter 里使用 MimeMultipartTransformer 来获取文件

var transformer = MimeMultipartTransformer(
          request.headers.contentType!.parameters['boundary']!);

var bodyStream = transformer.bind(request);

//获取文件对象
await for (var part in bodyStream) {

  if (isFirstPart) {
    isFirstPart = false;
  } else {
    //这里要特别说明一下,因为我们要支持上传大文件(超过500MB),所以要将文件分割成块进行上传,
    //这时每一个块都会被添加上独立的 content-disposition 的文件头,因此在这里要将其他块里的
    // content-disposition 移除,否则最终拼接出来的文件将不能读取
    part.headers.remove('content-disposition');
  }
  var fileByte =
      await part.fold<List<int>>(<int>[], (b, d) => b..addAll(d));
  if (fileByte.length > 1) {
    //我们只需要处理大于 0 字节的文件块
    uploadFile.add(fileByte);
    await Future.delayed(Duration.zero);
  }
}    

然后返回上传的状态

final response = {
  'message': 'File uploadeding',
};
request.response
  ..statusCode = HttpStatus.ok
  ..headers.contentType = ContentType.json
  ..write(json.encode(response))
  ..close();

你可在这里查看完整的代码。

3. 使用 internal_http_server 包

好吧,如果你不想自己写代码实现的话,我已为你准备好了,我创建了 internal_http_server 这个包并且也发布到 pub.dev 上了,所以如果你想直接使用,可到这里查看详情 🙂

3.1 安装包

你可以直接在 pubspec.yaml 文件里添加以下代码:

internal_http_server: ^0.0.4

3.2 创建 HTTP 服务器

你可以用以下方法创建一个服务器

InternalHttpServer server = InternalHttpServer(
    title: 'Testing Web Server',
    address: InternetAddress.anyIPv4,
    port: 8080,
    logger: const DebugLogger(),
  );

3.3 设置服务器的使用说明

你可以方便地设置你的 WEB 服务器的一些使用说明,好让用户知道要如何使用它。例如,你想让用户上传自己的文件,就可以做一些说明,这些说明使用的是 HTML 代码:

(由于我发布的包是英文版的,所以以下说明文本我就不另作改动和翻译了,大家根据自己情况修改即可)

final String server_description = ''' Description:
        <ul>
          <li>
            You can put your <strong>webserver</strong> description here 
          </li>
          <li>
          It's support any <strong style='color:red'>HTML</strong>, you can describe what you want to say
          </li>
        </ul>

        How to use:
        <ul>
          <li>1. You can drag and drop the file here or click the 'Upload File' button  to upload</li>
          <li>2. It's support larger file</li>
          <li>3. You can upload multiple files once time</li>
        </ul>
       ''';

server.setDescription(server_description);

3.4 创建启动与停止服务器的方法

你还需要为你的服务器创建一个启动和停止的方法:

startServer() async {
  server.serve().then((value) {
    setState(() {
      isListening = true;
      buttonLabel = 'Stop';
    });
  });
}

stopServer() async {
  server.stop().then((value) {
    setState(() {
      isListening = false;
      buttonLabel = 'Start';
    });
  });
}

然后再创建一个按钮进行启动和停止之用

ElevatedButton(
  child: Text(buttonLabel),
  onPressed: () {
    if (isListening) {
      stopServer();
    } else {
      startServer();
    }
  },
),

同时,你也需要让用户知道服务器的 IP,因此你还要需要获取当前设备 IP 然后告知用户。我们可以使用 NetworkInterface 来获取当前设备的网络信息:

Future<String> getCurrentIP() async {
  // Getting WIFI IP Details
  String currentIP = '';
  try {
    for (var interface in await NetworkInterface.list()) {
      for (var addr in interface.addresses) {
        print(
            'Name: {interface.name}  IP Address:{addr.address}  IPV4: {InternetAddress.anyIPv4}');
        if (addr.type == InternetAddressType.IPv4 &&
            addr.address.startsWith('192')) {
          currentIP = addr.address;
        }
      }
    }
  } catch (e) {
    debugPrint(e.toString());
  }
  // print('currentIP========:currentIP');
  return currentIP;
}

然后,你就可以将 IP 呈现给用户,用户将可看到类似下面的效果

然后当用户使用浏览器访问 IP 时,就会看到一个漂亮的 web 服务器页面了,此时就可以直接上传文件到手机

最后,你可在这里找到项目的完整代码。

4. 总结

实际上如果你想用 Flutter 自己建立一个 HTTP 内部服务器,还是有很多问题要解决的,因此我创建了这个独立的包可以让大家免费使用,但这里面也有很多功能需要完善,如现在还不支持无限子文件夹读取,但我的时间也有限,因此如果大家有好的想法也欢迎交流和一起改善 🙂

代码部落

免费订阅以得到最新文章发布的通知

请放心,这个绝对不会是垃圾邮件
而且您随时也可以取消的

版权声明:
作者:winson
链接:https://www.coderblog.cc/2024/06/how-to-create-http-server-in-flutter/
来源:代码部落中文站
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
< <上一篇
下一篇>>
文章目录
关闭
目 录