Dart学习笔记(25):Http Client

发表于2018-07-04 13:28 阅读(37)

本文地址:http://www.cndartlang.com/854.html

1、HttpClient

在 正则表达式实例 那一节里面
使用dart:io中的HttpClient发送请求来采集数据,这一节再简单说一下
HTTP请求包括GET、PUT、POST、DELETE等
我们可以通过向服务器发送请求,来模拟登陆、上传、下载等操作

upload_server.dart

import 'dart:io';
import 'dart:convert';

main() async {
  var server = await HttpServer.bind(InternetAddress.LOOPBACK_IP_V4, 4049);

  await for (var req in server) {
    ContentType contentType = req.headers.contentType;

    if (req.method == 'POST' &&
        contentType != null &&
        contentType.mimeType == 'application/json') {
      try {
        var jsonString = await req.transform(UTF8.decoder).join();

        // 从URI中获取保存文件的文件名,file.txt
        var filename = req.uri.pathSegments.last;

        var file =  new File(filename);
        await file.writeAsString(jsonString, mode: FileMode.WRITE);

        //返回HTTP 200状态码
        req.response
          ..statusCode = HttpStatus.OK
          ..write("Server: 文件接收完毕!")
          ..close();

        print("文件保存成功:${file.absolute.path}");
      } catch (e) {
        req.response
          ..statusCode = HttpStatus.INTERNAL_SERVER_ERROR
          ..write("Server: 文件I/O操作出错: $e.")
          ..close();
      }
    } else {
      req.response
        ..statusCode = HttpStatus.METHOD_NOT_ALLOWED
        ..write("Server: 未支持的请求: ${req.method}.")
        ..close();
    }
  }
}

在HttpClient进行post、get等操作的时候
需要设置host、port和path
Http服务器一般http协议使用80端口,https协议使用443端口
Uri中具体的参数值可以参考下图

upload_client.dart

import 'dart:io';
import 'dart:convert' show UTF8, JSON;

//需要以Post方式发送的数据
Map jsonData = {
  'name': 'Dart语言中文社区',
  'url': 'http://www.cndartlang.com',
  'date': new DateTime.now().toString(),
};

main() async {
  //path设置为'file.txt',服务器中取值用作保存文件的文件名
  new HttpClient()
      .post(InternetAddress.LOOPBACK_IP_V4.host, 4049, '/file.txt')
      .then((HttpClientRequest request) {

    /**
     * 等同于以下写法:
     * request.headers.contentType = ContentType.parse("application/json; charset:utf-8");
     * request.headers.contentType =  new ContentType("application", "json", charset: "utf-8");
     * request.headers.contentType =  new ContentType("application", "json", charset: "utf-8");
     *
     * request.headers.add 作用为添加,如果变量名相同,会覆盖之前的值
     * request.headers.set 作用为清空headers,然后重新设置header
     */
    request.headers.contentType = ContentType.JSON;

    //写入数据
    request.write(JSON.encode(jsonData));

    return request.close();
  }).then((HttpClientResponse response) {

    if(response.statusCode == HttpStatus.OK) {
      print("Client: 文件上传成功!");
    }

    //接收服务器write的信息
    response.transform(UTF8.decoder).listen((String contents) {
      print(contents);
    });
  });
}

运行结果:

重要提示:
在使用HttpClient的时候
如果Response没有进行任何的读操作
如listen、transform、toList、toSet、any之类
那么程序会一直阻塞,Response不会被释放
如果不需要读取数据,可以调用drain函数将数据丢弃
(感谢 雲, 的测试 2016.5.26)

2、Http

除了SDK中的API,官方还出了http库
提供了常用方法来简化操作
特点是可以运行在客户端 package:http/browser_client.dart
以及服务器端 package:http/http.dart
感兴趣的可以查看pub或github

由于http库简化了操作
在细节处理方面就不及dart:io中的HttpClient了
下面通过http库和HttpClient来下载mp3文件
看两者有什么区别

mp3的地址随便找了一个
(资源不稳定,有可能请求失败,抛出异常,多请求几次试试)
http://www.baidu190.com/md5/A0D8D48BD70AC99C7C6E34B5A4B7C9FA.mp3
然后试着访问,看了一下发送的包
虽然源地址是以”.mp3″作为后缀,但是实际上并不是mp3文件
而是text/html数据,返回302状态码,经过了3次跳转
最后mp3的实际地址是
http://58.16.42.71:9999/other.web.re01.sycdn.kuwo.cn/bc9d5ab895cc4435fc977c7965f160e9/57457828/resource/n3/10/63/3640333498.mp3
通过Get请求,返回200状态码
接着分段获取数据,过程中返回206状态码

这个跳转的过程让人有点烦躁
重要的是,如果要模拟发送请求
是按照跳转的顺序,不停的来回请求、响应
还是一步到位?看下面的代码

http_download.dart

import 'package:http/http.dart' as Http;
import 'dart:io';

void main() {
  //通过Http库提供的api来下载文件
  downloadByHttp();

  //通过HttpClient提供的api来下载文件
  downloadByHttpClient();
}

void downloadByHttp() {
  //创建File对象,如果文件已经存在,则删除
  var file = new File("Http_Download.mp3");
  if(file.existsSync()) {
    file.deleteSync();
  }
  //以FileMode.APPEND模式写入数据
  IOSink ios = file.openWrite(mode: FileMode.APPEND);

  /**
   * 请求的Header在get(.., headers:...)中进行设置
   * headers的类型为Map<String, String>
   */
  Http.get("http://www.baidu190.com/md5/A0D8D48BD70AC99C7C6E34B5A4B7C9FA.mp3")
      .then((Http.Response response) {

    //输出响应的状态码、Header以及数据大小
    print("State Code: ${response.statusCode}");
    print(response.headers);
    print("Date Size: ${response.bodyBytes.length}");

    //将接收到的数据写入缓冲池
    ios.add(response.bodyBytes);

    //响应结束后,关闭数据缓冲池
  }).whenComplete(() => ios.close());
}

void downloadByHttpClient() {
  var file = new File("IO_Download.mp3");
  if(file.existsSync()) {
    file.deleteSync();
  }
  IOSink ios = file.openWrite(mode: FileMode.APPEND);

  new HttpClient()
      .getUrl(Uri.parse("http://www.baidu190.com/md5/A0D8D48BD70AC99C7C6E34B5A4B7C9FA.mp3"))
      //不用进行设置请求的Header、写入数据等操作,因此直接close
      .then((HttpClientRequest request) => request.close())
      .then((HttpClientResponse response) {
    print(response.headers);
    response.listen((List event) {

      //接收到的数据为List<int>,写入缓冲池
      ios.add(event);
      //输出状态码和接收到的数据大小
      print("State Code: ${response.statusCode} Data Size: ${event.length}");

      //响应结束后,关闭数据缓冲池
    }, onDone: () => ios.close());
  });
}

运行结果:

第一个是用http库来下载数据
第二个是用dart:io中的HttpClient下载数据

幸运的是,不管是http还是HttpClient
对于302、206等响应状态
都能自动进行跳转、接收数据等处理

不过还是有两点明显的区别:

一是如果要关闭自动跳转
http库中get、post等函数就实现不了
需要创建Http.Request,然后设置followRedirects
而HttpClient会返回HttpClientRequest,可以直接设置followRedirects

二是在接收数据的时候
http库中的get、post等函数是同步模式
会等待数据加载结束,再一次返回
而HttpClient是异步模式,会在每次加载数据之后调用回调函数
看运行结果就可以看出来

http库中如果要对每次请求的数据进行操作
需要创建Http.StreamedRequest,然后对变量stream数据流进行处理
而HttpClient如果要一次读取所有的数据
创建一个缓存存放临时数据,然后在onDone事件中读取
或者await IOSink.addStream(response)
如果不需要对每个响应的数据进行处理
IOSink.add(response)这句代码真的很简洁

总的来说,就普通Http的Server-side请求而言
两者区别不大
调用过程都很简单直接

但是各项功能HttpClient实现起来更加的精练,代码更统一
http针对不同的环境会用到不同的类和方法,略显复杂

不过在http/browser_client.dart中
http库提供了BrowserClient类,应用于Client-side浏览器客户端
实现了部分异步请求XMLHttpRequest的功能

至于取舍,哪个库方便好用
看你个人喜好,以及实际应用吧!