mirror of
https://gitlab.freedesktop.org/gstreamer/gst-plugins-rs.git
synced 2025-01-11 03:35:26 +00:00
reqwest: Update to reqwest 0.10 / tokio 0.2
This commit is contained in:
parent
9d659fbd00
commit
672cb730a8
4 changed files with 246 additions and 213 deletions
|
@ -8,19 +8,19 @@ description = "Rust HTTP Plugin"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
url = "1.7"
|
url = "2.1"
|
||||||
glib = { git = "https://github.com/gtk-rs/glib" }
|
glib = { git = "https://github.com/gtk-rs/glib" }
|
||||||
reqwest = "0.9"
|
reqwest = { version = "0.10", features = ["cookies", "gzip"] }
|
||||||
futures = "0.1.23"
|
futures = "0.3"
|
||||||
hyperx = "0.15"
|
headers = "0.3"
|
||||||
|
mime = "0.3"
|
||||||
gstreamer = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_10"] }
|
gstreamer = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs", features = ["v1_10"] }
|
||||||
gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
gstreamer-base = { git = "https://gitlab.freedesktop.org/gstreamer/gstreamer-rs" }
|
||||||
tokio = "0.1"
|
tokio = { version = "0.2", features = ["time", "rt-threaded"] }
|
||||||
bytes = "0.4"
|
|
||||||
lazy_static = "1.0"
|
lazy_static = "1.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
hyper = "0.12"
|
hyper = "0.13.0-alpha"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
name = "gstreqwest"
|
name = "gstreqwest"
|
||||||
|
|
|
@ -12,13 +12,7 @@
|
||||||
extern crate glib;
|
extern crate glib;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate gstreamer as gst;
|
extern crate gstreamer as gst;
|
||||||
extern crate bytes;
|
|
||||||
extern crate futures;
|
|
||||||
extern crate gstreamer_base as gst_base;
|
extern crate gstreamer_base as gst_base;
|
||||||
extern crate hyperx;
|
|
||||||
extern crate reqwest;
|
|
||||||
extern crate tokio;
|
|
||||||
extern crate url;
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate lazy_static;
|
extern crate lazy_static;
|
||||||
|
|
||||||
|
|
|
@ -5,17 +5,13 @@
|
||||||
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
// option. This file may not be copied, modified, or distributed
|
// option. This file may not be copied, modified, or distributed
|
||||||
// except according to those terms.
|
// except according to those terms.
|
||||||
use bytes::Bytes;
|
|
||||||
use futures::future::Either;
|
|
||||||
use futures::sync::oneshot;
|
|
||||||
use futures::{Future, Stream};
|
|
||||||
use reqwest::r#async::{Client, Decoder};
|
|
||||||
use reqwest::StatusCode;
|
|
||||||
use std::mem;
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use std::u64;
|
use std::u64;
|
||||||
use tokio::prelude::*;
|
|
||||||
|
use futures::future;
|
||||||
|
use futures::prelude::*;
|
||||||
|
use reqwest::{Client, Response, StatusCode};
|
||||||
use tokio::runtime;
|
use tokio::runtime;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -201,7 +197,7 @@ enum State {
|
||||||
Stopped,
|
Stopped,
|
||||||
Started {
|
Started {
|
||||||
uri: Url,
|
uri: Url,
|
||||||
body: Option<Decoder>,
|
response: Option<Response>,
|
||||||
seekable: bool,
|
seekable: bool,
|
||||||
position: u64,
|
position: u64,
|
||||||
size: Option<u64>,
|
size: Option<u64>,
|
||||||
|
@ -224,8 +220,7 @@ pub struct ReqwestHttpSrc {
|
||||||
external_client: Mutex<Option<ClientContext>>,
|
external_client: Mutex<Option<ClientContext>>,
|
||||||
settings: Mutex<Settings>,
|
settings: Mutex<Settings>,
|
||||||
state: Mutex<State>,
|
state: Mutex<State>,
|
||||||
runtime: runtime::Runtime,
|
canceller: Mutex<Option<future::AbortHandle>>,
|
||||||
canceller: Mutex<Option<oneshot::Sender<Bytes>>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
|
@ -234,6 +229,12 @@ lazy_static! {
|
||||||
gst::DebugColorFlags::empty(),
|
gst::DebugColorFlags::empty(),
|
||||||
Some("Rust HTTP source"),
|
Some("Rust HTTP source"),
|
||||||
);
|
);
|
||||||
|
static ref RUNTIME: runtime::Runtime = runtime::Builder::new()
|
||||||
|
.threaded_scheduler()
|
||||||
|
.enable_all()
|
||||||
|
.core_threads(1)
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ReqwestHttpSrc {
|
impl ReqwestHttpSrc {
|
||||||
|
@ -352,11 +353,10 @@ impl ReqwestHttpSrc {
|
||||||
start: u64,
|
start: u64,
|
||||||
stop: Option<u64>,
|
stop: Option<u64>,
|
||||||
) -> Result<State, Option<gst::ErrorMessage>> {
|
) -> Result<State, Option<gst::ErrorMessage>> {
|
||||||
use hyperx::header::{
|
use headers::{
|
||||||
qitem, AcceptEncoding, AcceptRanges, ByteRangeSpec, Connection, ContentLength,
|
AcceptRanges, Connection, ContentLength, ContentRange, HeaderMapExt, Range, UserAgent,
|
||||||
ContentRange, ContentRangeSpec, ContentType, Cookie, Encoding, Headers, Range,
|
|
||||||
RangeUnit, RawLike, UserAgent,
|
|
||||||
};
|
};
|
||||||
|
use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
|
||||||
|
|
||||||
gst_debug!(CAT, obj: src, "Creating new request for {}", uri);
|
gst_debug!(CAT, obj: src, "Creating new request for {}", uri);
|
||||||
|
|
||||||
|
@ -366,115 +366,133 @@ impl ReqwestHttpSrc {
|
||||||
};
|
};
|
||||||
let settings = self.settings.lock().unwrap().clone();
|
let settings = self.settings.lock().unwrap().clone();
|
||||||
|
|
||||||
let mut headers = Headers::new();
|
let mut headers = HeaderMap::new();
|
||||||
|
|
||||||
if settings.keep_alive {
|
if settings.keep_alive {
|
||||||
headers.set(Connection::keep_alive());
|
headers.typed_insert(Connection::keep_alive());
|
||||||
} else {
|
} else {
|
||||||
headers.set(Connection::close());
|
headers.typed_insert(Connection::close());
|
||||||
}
|
}
|
||||||
|
|
||||||
match (start != 0, stop) {
|
match (start != 0, stop) {
|
||||||
(false, None) => (),
|
(false, None) => (),
|
||||||
(true, None) => {
|
(true, None) => {
|
||||||
headers.set(Range::Bytes(vec![ByteRangeSpec::AllFrom(start)]));
|
headers.typed_insert(Range::bytes(start..).unwrap());
|
||||||
}
|
}
|
||||||
(_, Some(stop)) => {
|
(_, Some(stop)) => {
|
||||||
headers.set(Range::Bytes(vec![ByteRangeSpec::FromTo(start, stop - 1)]));
|
headers.typed_insert(Range::bytes(start..stop).unwrap());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
headers.set(UserAgent::new(settings.user_agent.to_owned()));
|
if let Ok(user_agent) = settings.user_agent.parse::<UserAgent>() {
|
||||||
|
headers.typed_insert(user_agent);
|
||||||
|
} else {
|
||||||
|
gst_warning!(
|
||||||
|
CAT,
|
||||||
|
obj: src,
|
||||||
|
"Failed to transform user-agent '{}' to header value",
|
||||||
|
settings.user_agent
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
if !settings.compress {
|
if !settings.compress {
|
||||||
// Compression is the default
|
// Compression is the default
|
||||||
headers.set(AcceptEncoding(vec![qitem(Encoding::Identity)]));
|
headers.append("accept-encoding", "identity".parse().unwrap());
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ref extra_headers) = settings.extra_headers {
|
if let Some(ref extra_headers) = settings.extra_headers {
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
|
||||||
for (field, value) in extra_headers.iter() {
|
for (field, value) in extra_headers.iter() {
|
||||||
if let Ok(Some(values)) = value.get::<gst::Array>() {
|
let field = match HeaderName::try_from(field) {
|
||||||
for value in values.as_slice() {
|
Ok(field) => field,
|
||||||
if let Some(value) = value.transform::<String>() {
|
Err(err) => {
|
||||||
let value = value.get::<&str>().unwrap().unwrap_or("");
|
|
||||||
gst_debug!(
|
|
||||||
CAT,
|
|
||||||
obj: src,
|
|
||||||
"Appending extra-header: {}: {}",
|
|
||||||
field,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
headers.append_raw(String::from(field), value);
|
|
||||||
} else {
|
|
||||||
gst_warning!(
|
gst_warning!(
|
||||||
CAT,
|
CAT,
|
||||||
obj: src,
|
obj: src,
|
||||||
"Failed to transform extra-header '{}' to string",
|
"Failed to transform extra-header field name '{}' to header name: {}",
|
||||||
|
field,
|
||||||
|
err,
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut append_header = |field: &HeaderName, value: &glib::Value| {
|
||||||
|
let value = match value.transform::<String>() {
|
||||||
|
Some(value) => value,
|
||||||
|
None => {
|
||||||
|
gst_warning!(
|
||||||
|
CAT,
|
||||||
|
obj: src,
|
||||||
|
"Failed to transform extra-header '{}' value to string",
|
||||||
field
|
field
|
||||||
);
|
);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let value = value.get::<&str>().unwrap().unwrap_or("");
|
||||||
|
|
||||||
|
let value = match HeaderValue::from_str(value) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_) => {
|
||||||
|
gst_warning!(
|
||||||
|
CAT,
|
||||||
|
obj: src,
|
||||||
|
"Failed to transform extra-header '{}' value to header value",
|
||||||
|
field
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
headers.append(field.clone(), value);
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Ok(Some(values)) = value.get::<gst::Array>() {
|
||||||
|
for value in values.as_slice() {
|
||||||
|
append_header(&field, value);
|
||||||
}
|
}
|
||||||
} else if let Ok(Some(values)) = value.get::<gst::List>() {
|
} else if let Ok(Some(values)) = value.get::<gst::List>() {
|
||||||
for value in values.as_slice() {
|
for value in values.as_slice() {
|
||||||
if let Some(value) = value.transform::<String>() {
|
append_header(&field, value);
|
||||||
let value = value.get::<&str>().unwrap().unwrap_or("");
|
|
||||||
gst_debug!(
|
|
||||||
CAT,
|
|
||||||
obj: src,
|
|
||||||
"Appending extra-header: {}: {}",
|
|
||||||
field,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
headers.append_raw(String::from(field), value);
|
|
||||||
} else {
|
|
||||||
gst_warning!(
|
|
||||||
CAT,
|
|
||||||
obj: src,
|
|
||||||
"Failed to transform extra-header '{}' to string",
|
|
||||||
field
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if let Some(value) = value.transform::<String>() {
|
|
||||||
let value = value.get::<&str>().unwrap().unwrap_or("");
|
|
||||||
gst_debug!(
|
|
||||||
CAT,
|
|
||||||
obj: src,
|
|
||||||
"Appending extra-header: {}: {}",
|
|
||||||
field,
|
|
||||||
value
|
|
||||||
);
|
|
||||||
headers.append_raw(String::from(field), value);
|
|
||||||
} else {
|
} else {
|
||||||
gst_warning!(
|
append_header(&field, value);
|
||||||
CAT,
|
|
||||||
obj: src,
|
|
||||||
"Failed to transform extra-header '{}' to string",
|
|
||||||
field
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !settings.cookies.is_empty() {
|
if !settings.cookies.is_empty() {
|
||||||
let mut cookies = Cookie::new();
|
let mut cookies = String::new();
|
||||||
for cookie in settings.cookies {
|
for cookie in settings.cookies {
|
||||||
let mut split = cookie.splitn(2, '=');
|
let mut split = cookie.splitn(2, '=');
|
||||||
let key = split.next();
|
let key = split.next();
|
||||||
let value = split.next();
|
let value = split.next();
|
||||||
if let (Some(key), Some(value)) = (key, value) {
|
if let (Some(key), Some(value)) = (key, value) {
|
||||||
cookies.append(String::from(key), String::from(value));
|
if !cookies.is_empty() {
|
||||||
|
cookies.push_str("; ");
|
||||||
|
}
|
||||||
|
cookies.push_str(key);
|
||||||
|
cookies.push('=');
|
||||||
|
cookies.push_str(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
headers.set(cookies);
|
if let Ok(cookies) = HeaderValue::from_str(&cookies) {
|
||||||
|
headers.append("cookie", cookies);
|
||||||
|
} else {
|
||||||
|
gst_warning!(CAT, obj: src, "Failed to convert cookies into header value",);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.iradio_mode {
|
if settings.iradio_mode {
|
||||||
headers.append_raw("icy-metadata", "1");
|
headers.append("icy-metadata", "1".parse().unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add all headers for the request here
|
// Add all headers for the request here
|
||||||
let req = req.headers(headers.into());
|
let req = req.headers(headers);
|
||||||
|
|
||||||
let req = if let Some(ref user_id) = settings.user_id {
|
let req = if let Some(ref user_id) = settings.user_id {
|
||||||
// HTTP auth available
|
// HTTP auth available
|
||||||
|
@ -485,15 +503,17 @@ impl ReqwestHttpSrc {
|
||||||
|
|
||||||
gst_debug!(CAT, obj: src, "Sending new request: {:?}", req);
|
gst_debug!(CAT, obj: src, "Sending new request: {:?}", req);
|
||||||
|
|
||||||
let uri_clone = uri.clone();
|
let future = async {
|
||||||
let res = self.wait(req.send().map_err(move |err| {
|
req.send().await.map_err(|err| {
|
||||||
gst_error_msg!(
|
gst_error_msg!(
|
||||||
gst::ResourceError::OpenRead,
|
gst::ResourceError::OpenRead,
|
||||||
["Failed to fetch {}: {:?}", uri_clone, err]
|
["Failed to fetch {}: {:?}", uri, err]
|
||||||
)
|
)
|
||||||
}));
|
})
|
||||||
|
};
|
||||||
|
let res = self.wait(future);
|
||||||
|
|
||||||
let mut res = match res {
|
let res = match res {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
Err(Some(err)) => {
|
Err(Some(err)) => {
|
||||||
gst_debug!(CAT, obj: src, "Error {:?}", err);
|
gst_debug!(CAT, obj: src, "Error {:?}", err);
|
||||||
|
@ -536,21 +556,14 @@ impl ReqwestHttpSrc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let headers = Headers::from(res.headers());
|
let headers = res.headers();
|
||||||
let size = headers.get().map(|&ContentLength(cl)| cl + start);
|
let size = headers.typed_get().map(|ContentLength(cl)| cl + start);
|
||||||
|
let accept_byte_ranges = headers.typed_get() == Some(AcceptRanges::bytes());
|
||||||
let accept_byte_ranges = if let Some(&AcceptRanges(ref ranges)) = headers.get() {
|
|
||||||
ranges.iter().any(|u| *u == RangeUnit::Bytes)
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
};
|
|
||||||
|
|
||||||
let seekable = size.is_some() && accept_byte_ranges;
|
let seekable = size.is_some() && accept_byte_ranges;
|
||||||
|
|
||||||
let position = if let Some(&ContentRange(ContentRangeSpec::Bytes {
|
let position = if let Some((range_start, _range_end)) = headers
|
||||||
range: Some((range_start, _)),
|
.typed_get()
|
||||||
..
|
.and_then(|h| ContentRange::bytes_range(&h))
|
||||||
})) = headers.get()
|
|
||||||
{
|
{
|
||||||
range_start
|
range_start
|
||||||
} else {
|
} else {
|
||||||
|
@ -565,9 +578,8 @@ impl ReqwestHttpSrc {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut caps = headers
|
let mut caps = headers
|
||||||
.get_raw("icy-metaint")
|
.get("icy-metaint")
|
||||||
.and_then(|h| h.one())
|
.and_then(|s| s.to_str().ok())
|
||||||
.and_then(|s| std::str::from_utf8(s).ok())
|
|
||||||
.and_then(|s| s.parse::<i32>().ok())
|
.and_then(|s| s.parse::<i32>().ok())
|
||||||
.map(|icy_metaint| {
|
.map(|icy_metaint| {
|
||||||
gst::Caps::builder("application/x-icy")
|
gst::Caps::builder("application/x-icy")
|
||||||
|
@ -575,7 +587,11 @@ impl ReqwestHttpSrc {
|
||||||
.build()
|
.build()
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(ContentType(ref content_type)) = headers.get() {
|
if let Some(content_type) = headers
|
||||||
|
.get("content-type")
|
||||||
|
.and_then(|v| v.to_str().ok())
|
||||||
|
.and_then(|s| s.parse::<mime::Mime>().ok())
|
||||||
|
{
|
||||||
gst_debug!(CAT, obj: src, "Got content type {}", content_type);
|
gst_debug!(CAT, obj: src, "Got content type {}", content_type);
|
||||||
if let Some(ref mut caps) = caps {
|
if let Some(ref mut caps) = caps {
|
||||||
let caps = caps.get_mut().unwrap();
|
let caps = caps.get_mut().unwrap();
|
||||||
|
@ -606,38 +622,24 @@ impl ReqwestHttpSrc {
|
||||||
{
|
{
|
||||||
let tags = tags.get_mut().unwrap();
|
let tags = tags.get_mut().unwrap();
|
||||||
|
|
||||||
if let Some(ref icy_name) = headers
|
if let Some(ref icy_name) = headers.get("icy-name").and_then(|s| s.to_str().ok()) {
|
||||||
.get_raw("icy-name")
|
|
||||||
.and_then(|h| h.one())
|
|
||||||
.and_then(|s| std::str::from_utf8(s).ok())
|
|
||||||
{
|
|
||||||
tags.add::<gst::tags::Organization>(icy_name, gst::TagMergeMode::Replace);
|
tags.add::<gst::tags::Organization>(icy_name, gst::TagMergeMode::Replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref icy_genre) = headers
|
if let Some(ref icy_genre) = headers.get("icy-genre").and_then(|s| s.to_str().ok()) {
|
||||||
.get_raw("icy-genre")
|
|
||||||
.and_then(|h| h.one())
|
|
||||||
.and_then(|s| std::str::from_utf8(s).ok())
|
|
||||||
{
|
|
||||||
tags.add::<gst::tags::Genre>(icy_genre, gst::TagMergeMode::Replace);
|
tags.add::<gst::tags::Genre>(icy_genre, gst::TagMergeMode::Replace);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(ref icy_url) = headers
|
if let Some(ref icy_url) = headers.get("icy-url").and_then(|s| s.to_str().ok()) {
|
||||||
.get_raw("icy-url")
|
|
||||||
.and_then(|h| h.one())
|
|
||||||
.and_then(|s| std::str::from_utf8(s).ok())
|
|
||||||
{
|
|
||||||
tags.add::<gst::tags::Location>(icy_url, gst::TagMergeMode::Replace);
|
tags.add::<gst::tags::Location>(icy_url, gst::TagMergeMode::Replace);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
gst_debug!(CAT, obj: src, "Request successful");
|
gst_debug!(CAT, obj: src, "Request successful");
|
||||||
|
|
||||||
let body = mem::replace(res.body_mut(), Decoder::empty());
|
|
||||||
|
|
||||||
Ok(State::Started {
|
Ok(State::Started {
|
||||||
uri,
|
uri,
|
||||||
body: Some(body),
|
response: Some(res),
|
||||||
seekable,
|
seekable,
|
||||||
position,
|
position,
|
||||||
size,
|
size,
|
||||||
|
@ -648,59 +650,81 @@ impl ReqwestHttpSrc {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wait<F>(&self, future: F) -> Result<F::Item, Option<gst::ErrorMessage>>
|
fn wait<F, T>(&self, future: F) -> Result<T, Option<gst::ErrorMessage>>
|
||||||
where
|
where
|
||||||
F: Send + Future<Error = gst::ErrorMessage> + 'static,
|
F: Send + Future<Output = Result<T, gst::ErrorMessage>>,
|
||||||
F::Item: Send,
|
T: Send + 'static,
|
||||||
{
|
{
|
||||||
let timeout = self.settings.lock().unwrap().timeout;
|
let timeout = self.settings.lock().unwrap().timeout;
|
||||||
let mut canceller = self.canceller.lock().unwrap();
|
|
||||||
let (sender, receiver) = oneshot::channel::<Bytes>();
|
|
||||||
|
|
||||||
canceller.replace(sender);
|
let mut canceller = self.canceller.lock().unwrap();
|
||||||
|
let (abort_handle, abort_registration) = future::AbortHandle::new_pair();
|
||||||
|
canceller.replace(abort_handle);
|
||||||
drop(canceller);
|
drop(canceller);
|
||||||
|
|
||||||
// wrapping timeout around future
|
// Wrap in a timeout
|
||||||
let future_timeout = if timeout == 0 {
|
let future = async {
|
||||||
Either::A(future)
|
if timeout == 0 {
|
||||||
|
future.await
|
||||||
} else {
|
} else {
|
||||||
Either::B(
|
let res = tokio::time::timeout(Duration::from_secs(timeout.into()), future).await;
|
||||||
future
|
|
||||||
.timeout(Duration::from_secs(timeout.into()))
|
match res {
|
||||||
.map_err(|err| {
|
Ok(res) => res,
|
||||||
if err.is_elapsed() {
|
Err(_) => Err(gst_error_msg!(
|
||||||
gst_error_msg!(gst::ResourceError::Read, ["Request timeout"])
|
gst::ResourceError::Read,
|
||||||
} else if err.is_inner() {
|
["Request timeout"]
|
||||||
err.into_inner().unwrap()
|
)),
|
||||||
} else {
|
}
|
||||||
gst_error_msg!(gst::ResourceError::Read, ["Timer error: {}", err])
|
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let unlock_error = gst_error_msg!(gst::ResourceError::Busy, ["unlock"]);
|
// And make abortable
|
||||||
|
let future = async {
|
||||||
let res = oneshot::spawn(future_timeout, &self.runtime.executor())
|
match future::Abortable::new(future, abort_registration).await {
|
||||||
.select(receiver.then(|_| Err(unlock_error.clone())))
|
Ok(res) => res.map_err(Some),
|
||||||
.wait()
|
Err(_) => Err(None),
|
||||||
.map(|v| v.0)
|
|
||||||
.map_err(|err| {
|
|
||||||
if err.0 == unlock_error {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
Some(err.0)
|
|
||||||
}
|
}
|
||||||
});
|
};
|
||||||
|
|
||||||
|
let res = match block_on(&*RUNTIME, future) {
|
||||||
|
Ok(res) => res,
|
||||||
|
Err(_) => Err(Some(gst_error_msg!(
|
||||||
|
gst::ResourceError::Read,
|
||||||
|
["Join error"]
|
||||||
|
))),
|
||||||
|
};
|
||||||
|
|
||||||
/* Clear out the canceller */
|
/* Clear out the canceller */
|
||||||
canceller = self.canceller.lock().unwrap();
|
let _ = self.canceller.lock().unwrap().take();
|
||||||
*canceller = None;
|
|
||||||
|
|
||||||
res
|
res
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Until tokio 0.2.0-alpha.6 `Runtime::block_on()` didn't require a mutable reference
|
||||||
|
// to the runtime. Now it does and we need to work around that.
|
||||||
|
// See https://github.com/tokio-rs/tokio/issues/2042
|
||||||
|
fn block_on<F>(
|
||||||
|
runtime: &tokio::runtime::Runtime,
|
||||||
|
future: F,
|
||||||
|
) -> Result<F::Output, tokio::task::JoinError>
|
||||||
|
where
|
||||||
|
F: Send + Future,
|
||||||
|
F::Output: Send + 'static,
|
||||||
|
{
|
||||||
|
use futures::task::FutureObj;
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
let future = FutureObj::new(Box::pin(future));
|
||||||
|
|
||||||
|
// We make sure here to block until the future is completely handled before returning
|
||||||
|
let future = unsafe { mem::transmute::<_, FutureObj<'static, _>>(future) };
|
||||||
|
|
||||||
|
let join_handle = runtime.spawn(future);
|
||||||
|
futures::executor::block_on(join_handle)
|
||||||
|
}
|
||||||
|
|
||||||
impl ObjectImpl for ReqwestHttpSrc {
|
impl ObjectImpl for ReqwestHttpSrc {
|
||||||
glib_object_impl!();
|
glib_object_impl!();
|
||||||
|
|
||||||
|
@ -880,6 +904,14 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn unlock(&self, _src: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
|
||||||
|
let canceller = self.canceller.lock().unwrap();
|
||||||
|
if let Some(ref canceller) = *canceller {
|
||||||
|
canceller.abort();
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn start(&self, src: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
|
fn start(&self, src: &gst_base::BaseSrc) -> Result<(), gst::ErrorMessage> {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
|
@ -983,14 +1015,14 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
) -> Result<gst::Buffer, gst::FlowError> {
|
) -> Result<gst::Buffer, gst::FlowError> {
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
|
|
||||||
let (body, position, caps, tags) = match *state {
|
let (response, position, caps, tags) = match *state {
|
||||||
State::Started {
|
State::Started {
|
||||||
ref mut body,
|
ref mut response,
|
||||||
ref mut position,
|
ref mut position,
|
||||||
ref mut tags,
|
ref mut tags,
|
||||||
ref mut caps,
|
ref mut caps,
|
||||||
..
|
..
|
||||||
} => (body, position, caps, tags),
|
} => (response, position, caps, tags),
|
||||||
State::Stopped => {
|
State::Stopped => {
|
||||||
gst_element_error!(src, gst::LibraryError::Failed, ["Not started yet"]);
|
gst_element_error!(src, gst::LibraryError::Failed, ["Not started yet"]);
|
||||||
|
|
||||||
|
@ -1008,15 +1040,11 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
return Err(gst::FlowError::Error);
|
return Err(gst::FlowError::Error);
|
||||||
}
|
}
|
||||||
|
|
||||||
let current_body = match body.take() {
|
let mut current_response = match response.take() {
|
||||||
Some(body) => body,
|
Some(response) => response,
|
||||||
None => {
|
None => {
|
||||||
gst_error!(CAT, obj: src, "Don't have a response body");
|
gst_error!(CAT, obj: src, "Don't have a response");
|
||||||
gst_element_error!(
|
gst_element_error!(src, gst::ResourceError::Read, ["Don't have a response"]);
|
||||||
src,
|
|
||||||
gst::ResourceError::Read,
|
|
||||||
["Don't have a response body"]
|
|
||||||
);
|
|
||||||
|
|
||||||
return Err(gst::FlowError::Error);
|
return Err(gst::FlowError::Error);
|
||||||
}
|
}
|
||||||
|
@ -1038,12 +1066,15 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
pad.push_event(gst::Event::new_tag(tags).build());
|
pad.push_event(gst::Event::new_tag(tags).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = self.wait(current_body.into_future().map_err(move |(err, _body)| {
|
let future = async {
|
||||||
|
current_response.chunk().await.map_err(move |err| {
|
||||||
gst_error_msg!(
|
gst_error_msg!(
|
||||||
gst::ResourceError::Read,
|
gst::ResourceError::Read,
|
||||||
["Failed to read chunk at offset {}: {:?}", offset, err]
|
["Failed to read chunk at offset {}: {:?}", offset, err]
|
||||||
)
|
)
|
||||||
}));
|
})
|
||||||
|
};
|
||||||
|
let res = self.wait(future);
|
||||||
|
|
||||||
let res = match res {
|
let res = match res {
|
||||||
Ok(res) => res,
|
Ok(res) => res,
|
||||||
|
@ -1059,12 +1090,12 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut state = self.state.lock().unwrap();
|
let mut state = self.state.lock().unwrap();
|
||||||
let (body, position) = match *state {
|
let (response, position) = match *state {
|
||||||
State::Started {
|
State::Started {
|
||||||
ref mut body,
|
ref mut response,
|
||||||
ref mut position,
|
ref mut position,
|
||||||
..
|
..
|
||||||
} => (body, position),
|
} => (response, position),
|
||||||
State::Stopped => {
|
State::Stopped => {
|
||||||
gst_element_error!(src, gst::LibraryError::Failed, ["Not started yet"]);
|
gst_element_error!(src, gst::LibraryError::Failed, ["Not started yet"]);
|
||||||
|
|
||||||
|
@ -1073,7 +1104,7 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
};
|
};
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
(Some(chunk), current_body) => {
|
Some(chunk) => {
|
||||||
/* do something with the chunk and store the body again in the state */
|
/* do something with the chunk and store the body again in the state */
|
||||||
|
|
||||||
gst_trace!(
|
gst_trace!(
|
||||||
|
@ -1090,7 +1121,7 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
|
|
||||||
let mut buffer = gst::Buffer::from_slice(chunk);
|
let mut buffer = gst::Buffer::from_slice(chunk);
|
||||||
|
|
||||||
*body = Some(current_body);
|
*response = Some(current_response);
|
||||||
|
|
||||||
{
|
{
|
||||||
let buffer = buffer.get_mut().unwrap();
|
let buffer = buffer.get_mut().unwrap();
|
||||||
|
@ -1100,10 +1131,10 @@ impl BaseSrcImpl for ReqwestHttpSrc {
|
||||||
|
|
||||||
Ok(buffer)
|
Ok(buffer)
|
||||||
}
|
}
|
||||||
(None, current_body) => {
|
None => {
|
||||||
/* No further data, end of stream */
|
/* No further data, end of stream */
|
||||||
gst_debug!(CAT, obj: src, "End of stream");
|
gst_debug!(CAT, obj: src, "End of stream");
|
||||||
*body = Some(current_body);
|
*response = Some(current_response);
|
||||||
Err(gst::FlowError::Eos)
|
Err(gst::FlowError::Eos)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1146,11 +1177,6 @@ impl ObjectSubclass for ReqwestHttpSrc {
|
||||||
external_client: Mutex::new(None),
|
external_client: Mutex::new(None),
|
||||||
settings: Mutex::new(Default::default()),
|
settings: Mutex::new(Default::default()),
|
||||||
state: Mutex::new(Default::default()),
|
state: Mutex::new(Default::default()),
|
||||||
runtime: runtime::Builder::new()
|
|
||||||
.core_threads(1)
|
|
||||||
.name_prefix("gst-http-tokio")
|
|
||||||
.build()
|
|
||||||
.unwrap(),
|
|
||||||
canceller: Mutex::new(None),
|
canceller: Mutex::new(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,10 +51,9 @@ impl Harness {
|
||||||
http_func: F,
|
http_func: F,
|
||||||
setup_func: G,
|
setup_func: G,
|
||||||
) -> Harness {
|
) -> Harness {
|
||||||
use hyper::service::{make_service_fn, service_fn_ok};
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
use hyper::Server;
|
use hyper::Server;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use tokio::prelude::*;
|
|
||||||
|
|
||||||
// Create the HTTP source
|
// Create the HTTP source
|
||||||
let src = gst::ElementFactory::make("reqwesthttpsrc", None).unwrap();
|
let src = gst::ElementFactory::make("reqwesthttpsrc", None).unwrap();
|
||||||
|
@ -94,8 +93,10 @@ impl Harness {
|
||||||
pad.set_active(true).unwrap();
|
pad.set_active(true).unwrap();
|
||||||
|
|
||||||
// Create the tokio runtime used for the HTTP server in this test
|
// Create the tokio runtime used for the HTTP server in this test
|
||||||
let mut rt = tokio::runtime::Builder::new()
|
let rt = tokio::runtime::Builder::new()
|
||||||
.core_threads(1)
|
.core_threads(1)
|
||||||
|
.enable_all()
|
||||||
|
.threaded_scheduler()
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
@ -108,21 +109,35 @@ impl Harness {
|
||||||
let http_func = Arc::new(Mutex::new(http_func));
|
let http_func = Arc::new(Mutex::new(http_func));
|
||||||
let make_service = make_service_fn(move |_ctx| {
|
let make_service = make_service_fn(move |_ctx| {
|
||||||
let http_func = http_func.clone();
|
let http_func = http_func.clone();
|
||||||
service_fn_ok(move |req| (&mut *http_func.lock().unwrap())(req))
|
async move {
|
||||||
|
let http_func = http_func.clone();
|
||||||
|
Ok::<_, hyper::Error>(service_fn(move |req| {
|
||||||
|
let http_func = http_func.clone();
|
||||||
|
async move { Ok::<_, hyper::Error>((&mut *http_func.lock().unwrap())(req)) }
|
||||||
|
}))
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let (local_addr_sender, local_addr_receiver) = tokio::sync::oneshot::channel();
|
||||||
|
|
||||||
|
// Spawn the server in the background so that it can handle requests
|
||||||
|
rt.spawn(async move {
|
||||||
// Bind the server, retrieve the local port that was selected in the end and set this as
|
// Bind the server, retrieve the local port that was selected in the end and set this as
|
||||||
// the location property on the source
|
// the location property on the source
|
||||||
let server = Server::bind(&addr).serve(make_service);
|
let server = Server::bind(&addr).serve(make_service);
|
||||||
let local_addr = server.local_addr();
|
let local_addr = server.local_addr();
|
||||||
|
|
||||||
|
local_addr_sender.send(local_addr).unwrap();
|
||||||
|
|
||||||
|
if let Err(e) = server.await {
|
||||||
|
let _ = sender.send(Message::ServerError(format!("{:?}", e)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let local_addr = futures::executor::block_on(local_addr_receiver).unwrap();
|
||||||
src.set_property("location", &format!("http://{}/", local_addr))
|
src.set_property("location", &format!("http://{}/", local_addr))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// Spawn the server in the background so that it can handle requests
|
|
||||||
rt.spawn(server.map_err(move |e| {
|
|
||||||
let _ = sender.send(Message::ServerError(format!("{:?}", e)));
|
|
||||||
}));
|
|
||||||
|
|
||||||
// Let the test setup anything needed on the HTTP source now
|
// Let the test setup anything needed on the HTTP source now
|
||||||
setup_func(&src);
|
setup_func(&src);
|
||||||
|
|
||||||
|
@ -301,8 +316,6 @@ impl Harness {
|
||||||
|
|
||||||
impl Drop for Harness {
|
impl Drop for Harness {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
use tokio::prelude::*;
|
|
||||||
|
|
||||||
// Shut down everything that was set up for this test harness
|
// Shut down everything that was set up for this test harness
|
||||||
// and wait until the tokio runtime exited
|
// and wait until the tokio runtime exited
|
||||||
let bus = self.src.get_bus().unwrap();
|
let bus = self.src.get_bus().unwrap();
|
||||||
|
@ -316,7 +329,7 @@ impl Drop for Harness {
|
||||||
self.pad.set_active(false).unwrap();
|
self.pad.set_active(false).unwrap();
|
||||||
self.src.set_state(gst::State::Null).unwrap();
|
self.src.set_state(gst::State::Null).unwrap();
|
||||||
|
|
||||||
self.rt.take().unwrap().shutdown_now().wait().unwrap();
|
self.rt.take().unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue