utf8-如何正确读取hyper::client::Request中的字节序列,并将其作为utf-8字符串打印到控制台



我正在探索Rust,并尝试制作一个简单的HTTP请求(使用hyper-crace),并将响应体打印到控制台。响应实现std::io::Read。阅读了各种文档来源和基本教程,我得到了以下代码,我编译了&使用RUST_BACKTRACE=1 cargo run:执行

use hyper::client::Client;
use std::io::Read;
pub fn print_html(url: &str) {
    let client = Client::new();
    let req = client.get(url).send();
    match req {
        Ok(mut res) => {
            println!("{}", res.status);
            let mut body = String::new();
            match res.read_to_string(&mut body) {
                Ok(body) => println!("{:?}", body),
                Err(why) => panic!("String conversion failure: {:?}", why)
            }
        },
        Err(why) => panic!("{:?}", why)
    }
}

预期:

由HTTP服务器传递的正文中一个漂亮的、可读的HTML内容被打印到控制台。

实际:

200 OK
thread '<main>' panicked at 'String conversion failure: Error { repr: Custom(Custom { kind: InvalidData, error: StringError("stream did not contain valid UTF-8") }) }', src/printer.rs:16
stack backtrace:
   1:        0x109e1faeb - std::sys::backtrace::tracing::imp::write::h3800f45f421043b8
   2:        0x109e21565 - std::panicking::default_hook::_$u7b$$u7b$closure$u7d$$u7d$::h0ef6c8db532f55dc
   3:        0x109e2119e - std::panicking::default_hook::hf3839060ccbb8764
   4:        0x109e177f7 - std::panicking::rust_panic_with_hook::h5dd7da6bb3d06020
   5:        0x109e21b26 - std::panicking::begin_panic::h9bf160aee246b9f6
   6:        0x109e18248 - std::panicking::begin_panic_fmt::haf08a9a70a097ee1
   7:        0x109d54378 - libplayground::printer::print_html::hff00c339aa28fde4
   8:        0x109d53d76 - playground::main::h0b7387c23270ba52
   9:        0x109e20d8d - std::panicking::try::call::hbbf4746cba890ca7
  10:        0x109e23fcb - __rust_try
  11:        0x109e23f65 - __rust_maybe_catch_panic
  12:        0x109e20bb1 - std::rt::lang_start::hbcefdc316c2fbd45
  13:        0x109d53da9 - main
error: Process didn't exit successfully: `target/debug/playground` (exit code: 101)

思想

由于我从服务器收到了200 OK,我相信我已经收到了来自服务器的有效响应(我也可以通过用更熟悉的编程语言执行相同的请求来凭经验证明这一点)。因此,错误一定是由于我错误地将字节序列转换为UTF-8字符串造成的。

备选方案

我还尝试了以下解决方案,它使我可以将字节作为一系列十六进制字符串打印到控制台,但我知道这是根本错误的,因为UTF-8字符可以有1-4个字节。因此,在本例中,尝试将单个字节转换为UTF-8字符只适用于非常有限(确切地说是255)的UTF-8字符子集。

use hyper::client::Client;
use std::io::Read;
pub fn print_html(url: &str) {
    let client = Client::new();
    let req = client.get(url).send();
    match req {
        Ok(res) => {
            println!("{}", res.status);
            for byte in res.bytes() {
                print!("{:x}", byte.unwrap());
            }
        },
        Err(why) => panic!("{:?}", why)
    }
}

我们可以通过iconv命令确认从http://www.google.com返回的数据不是有效的UTF-8:

$ wget http://google.com -O page.html
$ iconv -f utf-8 page.html > /dev/null
iconv: illegal input sequence at position 5591

对于其他一些url(如http://www.reddit.com),代码运行良好。

如果我们假设大部分数据是有效的UTF-8,我们可以使用String::from_utf8_lossy来解决这个问题:

pub fn print_html(url: &str) {
    let client = Client::new();
    let req = client.get(url).send();
    match req {
        Ok(mut res) => {
            println!("{}", res.status);
            let mut body = Vec::new();
            match res.read_to_end(&mut body) {
                Ok(_) => println!("{:?}", String::from_utf8_lossy(&*body)),
                Err(why) => panic!("String conversion failure: {:?}", why),
            }
        }
        Err(why) => panic!("{:?}", why),
    }
}

请注意,Read::read_to_stringRead::read_to_end返回的Ok具有成功读取的字节数,而不是读取的数据。

如果你真的看到谷歌返回的标题:

HTTP/1.1 200 OK
Date: Fri, 22 Jul 2016 20:45:54 GMT
Expires: -1
Cache-Control: private, max-age=0
Content-Type: text/html; charset=ISO-8859-1
P3P: CP="This is not a P3P policy! See https://www.google.com/support/accounts/answer/151657?hl=en for more info."
Server: gws
X-XSS-Protection: 1; mode=block
X-Frame-Options: SAMEORIGIN
Set-Cookie: NID=82=YwAD4Rj09u6gUA8OtQH73BUz6UlNdeRc9Z_iGjyaDqFdRGMdslypu1zsSDWQ4xRJFyEn9-UtR7U6G7HKehoyxvy9HItnDlg8iLsxzlhNcg01luW3_-HWs3l9S3dmHIVh; expires=Sat, 21-Jan-2017 20:45:54 GMT; path=/; domain=.google.ca; HttpOnly
Alternate-Protocol: 443:quic
Alt-Svc: quic=":443"; ma=2592000; v="36,35,34,33,32,31,30,29,28,27,26,25"
Accept-Ranges: none
Vary: Accept-Encoding
Transfer-Encoding: chunked

你可以看到

内容类型:text/html字符集=ISO-8859-1

另外

因此,错误一定是由于我错误地将字节序列转换为UTF-8字符串造成的。

没有转换为UTF-8read_to_string只是确保数据是UTF-8。

简单地说,假设任意HTML页面是用UTF-8编码的是完全不正确的。充其量,您必须解析标头以找到编码,然后转换数据。这很复杂,因为没有真正定义头文件的编码方式

一旦你找到了正确的编码,你就可以使用一个板条箱,比如编码,将结果正确地转换为UTF-8,如果结果甚至是文本!请记住,HTTP可以返回图像等二进制文件。

最新更新