feat(mime): upgrade to mime v0.3
The new mime crate has several benefits: - Faster formatting - Easier to use. Most common mime types are now just constants, like `mime::TEXT_PLAIN`. - Proper suffix support. - Extensible without breaking backwards compatiblity. This means we can always add new constants, but before we couldn't add new variants to the enums. - It's now impossible for a `Mime` to contain invalid tokens. Before, with the `Ext(String)` variants, it was possible to create an illegal mime. Closes #738 BREAKING CHANGE: Most uses of `mime` will likely break. There is no more `mime!` macro, nor a `Mime` constructor, nor `TopLevel` and `SubLevel` enums. Instead, in most cases, a constant exists that can now be used. For less common mime types, they can be created by parsing a string.
This commit is contained in:
@@ -26,7 +26,7 @@ futures-cpupool = "0.1"
|
|||||||
httparse = "1.0"
|
httparse = "1.0"
|
||||||
language-tags = "0.2"
|
language-tags = "0.2"
|
||||||
log = "0.3"
|
log = "0.3"
|
||||||
mime = "0.2"
|
mime = "0.3"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
tokio-core = "0.1.6"
|
tokio-core = "0.1.6"
|
||||||
tokio-proto = "0.1"
|
tokio-proto = "0.1"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use mime::Mime;
|
use mime::{self, Mime};
|
||||||
|
|
||||||
use header::{QualityItem, qitem};
|
use header::{QualityItem, qitem};
|
||||||
|
|
||||||
@@ -24,94 +24,89 @@ header! {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Example values
|
/// # Example values
|
||||||
/// * `audio/*; q=0.2, audio/basic` (`*` value won't parse correctly)
|
/// * `audio/*; q=0.2, audio/basic`
|
||||||
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
/// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c`
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, Accept, qitem};
|
/// use hyper::header::{Headers, Accept, qitem};
|
||||||
/// use hyper::mime::{Mime, TopLevel, SubLevel};
|
/// use hyper::mime;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
///
|
///
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(Mime(TopLevel::Text, SubLevel::Html, vec![])),
|
/// qitem(mime::TEXT_HTML),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, Accept, qitem};
|
/// use hyper::header::{Headers, Accept, qitem};
|
||||||
/// use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
|
/// use hyper::mime;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(Mime(TopLevel::Application, SubLevel::Json,
|
/// qitem(mime::APPLICATION_JSON),
|
||||||
/// vec![(Attr::Charset, Value::Utf8)])),
|
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, Accept, QualityItem, q, qitem};
|
/// use hyper::header::{Headers, Accept, QualityItem, q, qitem};
|
||||||
/// use hyper::mime::{Mime, TopLevel, SubLevel};
|
/// use hyper::mime;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
///
|
///
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
/// Accept(vec![
|
/// Accept(vec![
|
||||||
/// qitem(Mime(TopLevel::Text, SubLevel::Html, vec![])),
|
/// qitem(mime::TEXT_HTML),
|
||||||
/// qitem(Mime(TopLevel::Application,
|
/// qitem("application/xhtml+xml".parse().unwrap()),
|
||||||
/// SubLevel::Ext("xhtml+xml".to_owned()), vec![])),
|
/// QualityItem::new(
|
||||||
/// QualityItem::new(Mime(TopLevel::Application, SubLevel::Xml, vec![]),
|
/// mime::TEXT_XML,
|
||||||
/// q(900)),
|
/// q(900)
|
||||||
/// qitem(Mime(TopLevel::Image,
|
/// ),
|
||||||
/// SubLevel::Ext("webp".to_owned()), vec![])),
|
/// qitem("image/webp".parse().unwrap()),
|
||||||
/// QualityItem::new(Mime(TopLevel::Star, SubLevel::Star, vec![]),
|
/// QualityItem::new(
|
||||||
/// q(800))
|
/// mime::STAR_STAR,
|
||||||
|
/// q(800)
|
||||||
|
/// ),
|
||||||
/// ])
|
/// ])
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
///
|
|
||||||
/// # Notes
|
|
||||||
/// * Using always Mime types to represent `media-range` differs from the ABNF.
|
|
||||||
/// * **FIXME**: `accept-ext` is not supported.
|
|
||||||
(Accept, "Accept") => (QualityItem<Mime>)+
|
(Accept, "Accept") => (QualityItem<Mime>)+
|
||||||
|
|
||||||
test_accept {
|
test_accept {
|
||||||
// Tests from the RFC
|
// Tests from the RFC
|
||||||
// FIXME: Test fails, first value containing a "*" fails to parse
|
test_header!(
|
||||||
// test_header!(
|
test1,
|
||||||
// test1,
|
vec![b"audio/*; q=0.2, audio/basic"],
|
||||||
// vec![b"audio/*; q=0.2, audio/basic"],
|
Some(HeaderField(vec![
|
||||||
// Some(HeaderField(vec![
|
QualityItem::new("audio/*".parse().unwrap(), q(200)),
|
||||||
// QualityItem::new(Mime(TopLevel::Audio, SubLevel::Star, vec![]), q(200)),
|
qitem("audio/basic".parse().unwrap()),
|
||||||
// qitem(Mime(TopLevel::Audio, SubLevel::Ext("basic".to_owned()), vec![])),
|
])));
|
||||||
// ])));
|
|
||||||
test_header!(
|
test_header!(
|
||||||
test2,
|
test2,
|
||||||
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
|
||||||
Some(HeaderField(vec![
|
Some(HeaderField(vec![
|
||||||
QualityItem::new(Mime(TopLevel::Text, SubLevel::Plain, vec![]), q(500)),
|
QualityItem::new(TEXT_PLAIN, q(500)),
|
||||||
qitem(Mime(TopLevel::Text, SubLevel::Html, vec![])),
|
qitem(TEXT_HTML),
|
||||||
QualityItem::new(
|
QualityItem::new(
|
||||||
Mime(TopLevel::Text, SubLevel::Ext("x-dvi".to_owned()), vec![]),
|
"text/x-dvi".parse().unwrap(),
|
||||||
q(800)),
|
q(800)),
|
||||||
qitem(Mime(TopLevel::Text, SubLevel::Ext("x-c".to_owned()), vec![])),
|
qitem("text/x-c".parse().unwrap()),
|
||||||
])));
|
])));
|
||||||
// Custom tests
|
// Custom tests
|
||||||
test_header!(
|
test_header!(
|
||||||
test3,
|
test3,
|
||||||
vec![b"text/plain; charset=utf-8"],
|
vec![b"text/plain; charset=utf-8"],
|
||||||
Some(Accept(vec![
|
Some(Accept(vec![
|
||||||
qitem(Mime(TopLevel::Text, SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)])),
|
qitem(TEXT_PLAIN_UTF_8),
|
||||||
])));
|
])));
|
||||||
test_header!(
|
test_header!(
|
||||||
test4,
|
test4,
|
||||||
vec![b"text/plain; charset=utf-8; q=0.5"],
|
vec![b"text/plain; charset=utf-8; q=0.5"],
|
||||||
Some(Accept(vec![
|
Some(Accept(vec![
|
||||||
QualityItem::new(Mime(TopLevel::Text,
|
QualityItem::new(TEXT_PLAIN_UTF_8,
|
||||||
SubLevel::Plain, vec![(Attr::Charset, Value::Utf8)]),
|
|
||||||
q(500)),
|
q(500)),
|
||||||
])));
|
])));
|
||||||
|
|
||||||
@@ -127,22 +122,22 @@ header! {
|
|||||||
impl Accept {
|
impl Accept {
|
||||||
/// A constructor to easily create `Accept: */*`.
|
/// A constructor to easily create `Accept: */*`.
|
||||||
pub fn star() -> Accept {
|
pub fn star() -> Accept {
|
||||||
Accept(vec![qitem(mime!(Star/Star))])
|
Accept(vec![qitem(mime::STAR_STAR)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: application/json`.
|
/// A constructor to easily create `Accept: application/json`.
|
||||||
pub fn json() -> Accept {
|
pub fn json() -> Accept {
|
||||||
Accept(vec![qitem(mime!(Application/Json))])
|
Accept(vec![qitem(mime::APPLICATION_JSON)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: text/*`.
|
/// A constructor to easily create `Accept: text/*`.
|
||||||
pub fn text() -> Accept {
|
pub fn text() -> Accept {
|
||||||
Accept(vec![qitem(mime!(Text/Star))])
|
Accept(vec![qitem(mime::TEXT_STAR)])
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create `Accept: image/*`.
|
/// A constructor to easily create `Accept: image/*`.
|
||||||
pub fn image() -> Accept {
|
pub fn image() -> Accept {
|
||||||
Accept(vec![qitem(mime!(Image/Star))])
|
Accept(vec![qitem(mime::IMAGE_STAR)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use mime::Mime;
|
use mime::{self, Mime};
|
||||||
|
|
||||||
header! {
|
header! {
|
||||||
/// `Content-Type` header, defined in
|
/// `Content-Type` header, defined in
|
||||||
@@ -22,7 +22,8 @@ header! {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// # Example values
|
/// # Example values
|
||||||
/// * `text/html; charset=ISO-8859-4`
|
/// * `text/html; charset=utf-8`
|
||||||
|
/// * `application/json
|
||||||
///
|
///
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
/// ```
|
||||||
@@ -36,13 +37,12 @@ header! {
|
|||||||
/// ```
|
/// ```
|
||||||
/// ```
|
/// ```
|
||||||
/// use hyper::header::{Headers, ContentType};
|
/// use hyper::header::{Headers, ContentType};
|
||||||
/// use hyper::mime::{Mime, TopLevel, SubLevel, Attr, Value};
|
/// use hyper::mime;
|
||||||
///
|
///
|
||||||
/// let mut headers = Headers::new();
|
/// let mut headers = Headers::new();
|
||||||
///
|
///
|
||||||
/// headers.set(
|
/// headers.set(
|
||||||
/// ContentType(Mime(TopLevel::Text, SubLevel::Html,
|
/// ContentType(mime::TEXT_HTML)
|
||||||
/// vec![(Attr::Charset, Value::Utf8)]))
|
|
||||||
/// );
|
/// );
|
||||||
/// ```
|
/// ```
|
||||||
(ContentType, "Content-Type") => [Mime]
|
(ContentType, "Content-Type") => [Mime]
|
||||||
@@ -50,13 +50,8 @@ header! {
|
|||||||
test_content_type {
|
test_content_type {
|
||||||
test_header!(
|
test_header!(
|
||||||
test1,
|
test1,
|
||||||
// FIXME: Should be b"text/html; charset=ISO-8859-4" but mime crate lowercases
|
vec![b"text/html"],
|
||||||
// the whole value so parsing and formatting the value gives a different result
|
Some(HeaderField(TEXT_HTML)));
|
||||||
vec![b"text/html; charset=iso-8859-4"],
|
|
||||||
Some(HeaderField(Mime(
|
|
||||||
TopLevel::Text,
|
|
||||||
SubLevel::Html,
|
|
||||||
vec![(Attr::Charset, Value::Ext("iso-8859-4".to_owned()))]))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,45 +59,45 @@ impl ContentType {
|
|||||||
/// A constructor to easily create a `Content-Type: application/json` header.
|
/// A constructor to easily create a `Content-Type: application/json` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn json() -> ContentType {
|
pub fn json() -> ContentType {
|
||||||
ContentType(mime!(Application/Json))
|
ContentType(mime::APPLICATION_JSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header.
|
/// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn plaintext() -> ContentType {
|
pub fn plaintext() -> ContentType {
|
||||||
ContentType(mime!(Text/Plain; Charset=Utf8))
|
ContentType(mime::TEXT_PLAIN_UTF_8)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create a `Content-Type: text/html; charset=utf-8` header.
|
/// A constructor to easily create a `Content-Type: text/html; charset=utf-8` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn html() -> ContentType {
|
pub fn html() -> ContentType {
|
||||||
ContentType(mime!(Text/Html; Charset=Utf8))
|
ContentType(mime::TEXT_HTML)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header.
|
/// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn form_url_encoded() -> ContentType {
|
pub fn form_url_encoded() -> ContentType {
|
||||||
ContentType(mime!(Application/WwwFormUrlEncoded))
|
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
|
||||||
}
|
}
|
||||||
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
/// A constructor to easily create a `Content-Type: image/jpeg` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn jpeg() -> ContentType {
|
pub fn jpeg() -> ContentType {
|
||||||
ContentType(mime!(Image/Jpeg))
|
ContentType(mime::IMAGE_JPEG)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create a `Content-Type: image/png` header.
|
/// A constructor to easily create a `Content-Type: image/png` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn png() -> ContentType {
|
pub fn png() -> ContentType {
|
||||||
ContentType(mime!(Image/Png))
|
ContentType(mime::IMAGE_PNG)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A constructor to easily create a `Content-Type: application/octet-stream` header.
|
/// A constructor to easily create a `Content-Type: application/octet-stream` header.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn octet_stream() -> ContentType {
|
pub fn octet_stream() -> ContentType {
|
||||||
ContentType(mime!(Application/OctetStream))
|
ContentType(mime::APPLICATION_OCTET_STREAM)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Eq for ContentType {}
|
impl Eq for ContentType {}
|
||||||
|
|
||||||
bench_header!(bench, ContentType, { vec![b"application/json; charset=utf-8".to_vec()] });
|
bench_header!(bench, ContentType, { vec![b"application/json".to_vec()] });
|
||||||
|
|||||||
@@ -905,9 +905,7 @@ mod tests {
|
|||||||
use http::{ServerTransaction, Http1Transaction};
|
use http::{ServerTransaction, Http1Transaction};
|
||||||
use bytes::BytesMut;
|
use bytes::BytesMut;
|
||||||
|
|
||||||
use mime::Mime;
|
use mime;
|
||||||
use mime::TopLevel::Text;
|
|
||||||
use mime::SubLevel::Plain;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_link() {
|
fn test_link() {
|
||||||
@@ -956,7 +954,7 @@ mod tests {
|
|||||||
.push_media_desc(MediaDesc::Screen)
|
.push_media_desc(MediaDesc::Screen)
|
||||||
.set_title("previous chapter")
|
.set_title("previous chapter")
|
||||||
.set_title_star("title* unparsed")
|
.set_title_star("title* unparsed")
|
||||||
.set_media_type(Mime(Text, Plain, vec![]));
|
.set_media_type(mime::TEXT_PLAIN);
|
||||||
|
|
||||||
let link_header = b"<http://example.com/TheBook/chapter2>; \
|
let link_header = b"<http://example.com/TheBook/chapter2>; \
|
||||||
rel=\"previous\"; anchor=\"../anchor/example/\"; \
|
rel=\"previous\"; anchor=\"../anchor/example/\"; \
|
||||||
@@ -1015,7 +1013,7 @@ mod tests {
|
|||||||
.push_media_desc(MediaDesc::Screen)
|
.push_media_desc(MediaDesc::Screen)
|
||||||
.set_title("previous chapter")
|
.set_title("previous chapter")
|
||||||
.set_title_star("title* unparsed")
|
.set_title_star("title* unparsed")
|
||||||
.set_media_type(Mime(Text, Plain, vec![]));
|
.set_media_type(mime::TEXT_PLAIN);
|
||||||
|
|
||||||
let link = Link::new(vec![link_value]);
|
let link = Link::new(vec![link_value]);
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
//! ## Mime
|
//! ## Mime
|
||||||
//!
|
//!
|
||||||
//! Several header fields use MIME values for their contents. Keeping with the
|
//! Several header fields use MIME values for their contents. Keeping with the
|
||||||
//! strongly-typed theme, the [mime](http://seanmonstar.github.io/mime.rs) crate
|
//! strongly-typed theme, the [mime](https://docs.rs/mime) crate
|
||||||
//! is used, such as `ContentType(pub Mime)`.
|
//! is used, such as `ContentType(pub Mime)`.
|
||||||
|
|
||||||
pub use self::accept::Accept;
|
pub use self::accept::Accept;
|
||||||
|
|||||||
@@ -696,11 +696,7 @@ impl PartialEq<HeaderName> for str {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use mime::Mime;
|
use super::{Headers, Header, Raw, ContentLength, ContentType, Host, SetCookie};
|
||||||
use mime::TopLevel::Text;
|
|
||||||
use mime::SubLevel::Plain;
|
|
||||||
use super::{Headers, Header, Raw, ContentLength, ContentType,
|
|
||||||
Accept, Host, qitem, SetCookie};
|
|
||||||
|
|
||||||
#[cfg(feature = "nightly")]
|
#[cfg(feature = "nightly")]
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
@@ -723,25 +719,6 @@ mod tests {
|
|||||||
assert_eq!(headers.get(), Some(&ContentLength(10)));
|
assert_eq!(headers.get(), Some(&ContentLength(10)));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_content_type() {
|
|
||||||
let content_type = Header::parse_header(&b"text/plain".as_ref().into());
|
|
||||||
assert_eq!(content_type.ok(), Some(ContentType(Mime(Text, Plain, vec![]))));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_accept() {
|
|
||||||
let text_plain = qitem(Mime(Text, Plain, vec![]));
|
|
||||||
let application_vendor = "application/vnd.github.v3.full+json; q=0.5".parse().unwrap();
|
|
||||||
|
|
||||||
let accept = Header::parse_header(&b"text/plain".as_ref().into());
|
|
||||||
assert_eq!(accept.ok(), Some(Accept(vec![text_plain.clone()])));
|
|
||||||
|
|
||||||
let bytevec = b"application/vnd.github.v3.full+json; q=0.5, text/plain".as_ref().into();
|
|
||||||
let accept = Header::parse_header(&bytevec);
|
|
||||||
assert_eq!(accept.ok(), Some(Accept(vec![application_vendor, text_plain])));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq, Debug)]
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
struct CrazyLength(Option<bool>, usize);
|
struct CrazyLength(Option<bool>, usize);
|
||||||
|
|
||||||
@@ -882,7 +859,7 @@ mod tests {
|
|||||||
let mut headers = Headers::new();
|
let mut headers = Headers::new();
|
||||||
headers.set(ContentLength(10));
|
headers.set(ContentLength(10));
|
||||||
assert_eq!(headers.len(), 1);
|
assert_eq!(headers.len(), 1);
|
||||||
headers.set(ContentType(Mime(Text, Plain, vec![])));
|
headers.set(ContentType::json());
|
||||||
assert_eq!(headers.len(), 2);
|
assert_eq!(headers.len(), 2);
|
||||||
// Redundant, should not increase count.
|
// Redundant, should not increase count.
|
||||||
headers.set(ContentLength(20));
|
headers.set(ContentLength(20));
|
||||||
@@ -893,7 +870,7 @@ mod tests {
|
|||||||
fn test_clear() {
|
fn test_clear() {
|
||||||
let mut headers = Headers::new();
|
let mut headers = Headers::new();
|
||||||
headers.set(ContentLength(10));
|
headers.set(ContentLength(10));
|
||||||
headers.set(ContentType(Mime(Text, Plain, vec![])));
|
headers.set(ContentType::json());
|
||||||
assert_eq!(headers.len(), 2);
|
assert_eq!(headers.len(), 2);
|
||||||
headers.clear();
|
headers.clear();
|
||||||
assert_eq!(headers.len(), 0);
|
assert_eq!(headers.len(), 0);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ extern crate futures_cpupool;
|
|||||||
extern crate httparse;
|
extern crate httparse;
|
||||||
extern crate language_tags;
|
extern crate language_tags;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
#[macro_use] pub extern crate mime;
|
pub extern crate mime;
|
||||||
extern crate base64;
|
extern crate base64;
|
||||||
extern crate time;
|
extern crate time;
|
||||||
extern crate tokio_core as tokio;
|
extern crate tokio_core as tokio;
|
||||||
|
|||||||
Reference in New Issue
Block a user