зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1862002 - Prototype OHTTP in FOG r=wstuckey,TravisLong
Differential Revision: https://phabricator.services.mozilla.com/D203529
This commit is contained in:
Родитель
79a8ded3fc
Коммит
2c2bf61977
|
@ -1852,15 +1852,19 @@ dependencies = [
|
|||
name = "fog_control"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"bhttp",
|
||||
"cstr",
|
||||
"firefox-on-glean",
|
||||
"glean",
|
||||
"log",
|
||||
"mozbuild",
|
||||
"nserror",
|
||||
"nsstring",
|
||||
"ohttp",
|
||||
"once_cell",
|
||||
"static_prefs",
|
||||
"thin-vec",
|
||||
"thiserror",
|
||||
"url",
|
||||
"viaduct",
|
||||
"xpcom",
|
||||
|
|
|
@ -18,6 +18,10 @@ cstr = "0.2"
|
|||
viaduct = "0.1"
|
||||
url = "2.1"
|
||||
thin-vec = { version = "0.2.1", features = ["gecko-ffi"] }
|
||||
ohttp = { version = "0.3", default-features = false, features = ["gecko", "nss", "client"] }
|
||||
bhttp = "0.3"
|
||||
thiserror = "1.0"
|
||||
mozbuild = "0.1"
|
||||
|
||||
[features]
|
||||
# Leave data collection enabled, but disable upload.
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
//!
|
||||
//! The contents of this module are generated by
|
||||
//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from
|
||||
//! 'toolkit/components/glean/pings.yaml`.
|
||||
//! ping definitions files identified by `toolkit/components/glean/metrics_index.py`.
|
||||
|
||||
include!(mozbuild::objdir_path!(
|
||||
"toolkit/components/glean/api/src/pings.rs"
|
||||
|
|
|
@ -230,5 +230,32 @@ def jog_file(output_fd, *args):
|
|||
return get_deps()
|
||||
|
||||
|
||||
def ohttp_pings(output_fd, *args):
|
||||
all_objs, options = parse(args)
|
||||
ohttp_pings = []
|
||||
for ping in all_objs["pings"].values():
|
||||
if ping.metadata.get("use_ohttp", False):
|
||||
if ping.include_info_sections:
|
||||
raise ParserError(
|
||||
"Cannot send pings with OHTTP that contain {client|ping}_info sections. Specify `metadata: include_info_sections: false`"
|
||||
)
|
||||
ohttp_pings.append(ping.name)
|
||||
|
||||
env = jinja2.Environment(
|
||||
loader=jinja2.PackageLoader("run_glean_parser", "templates"),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
)
|
||||
env.filters["quote_and_join"] = lambda l: "\n| ".join(f'"{x}"' for x in l)
|
||||
template = env.get_template("ohttp.jinja2")
|
||||
output_fd.write(
|
||||
template.render(
|
||||
ohttp_pings=ohttp_pings,
|
||||
)
|
||||
)
|
||||
output_fd.write("\n")
|
||||
return get_deps()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main(sys.stdout, *sys.argv[1:])
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// -*- mode: Rust -*-
|
||||
|
||||
// AUTOGENERATED BY glean_parser. DO NOT EDIT.
|
||||
{# The rendered source is autogenerated, but this
|
||||
Jinja2 template is not. Please file bugs! #}
|
||||
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
pub fn uses_ohttp(ping_name: &str) -> bool {
|
||||
matches!(ping_name, {{ ohttp_pings|quote_and_join }})
|
||||
}
|
|
@ -199,6 +199,17 @@ if CONFIG["MOZ_ARTIFACT_BUILDS"]:
|
|||
# Once generated, it needs to be placed in GreD so it can be found.
|
||||
FINAL_TARGET_FILES += ["!jogfile.json"]
|
||||
|
||||
# OHTTP support requires the fog_control crate know which pings wish to be sent
|
||||
# using OHTTP. fog_control has no access to the firefox_on_glean crate, so it
|
||||
# needs its own codegen.
|
||||
GeneratedFile(
|
||||
"src/ohttp_pings.rs",
|
||||
script="build_scripts/glean_parser_ext/run_glean_parser.py",
|
||||
entry_point="ohttp_pings",
|
||||
flags=[CONFIG["MOZ_APP_VERSION"]],
|
||||
inputs=pings_yamls + tags_yamls,
|
||||
)
|
||||
|
||||
DIRS += [
|
||||
"tests", # Must be in DIRS, not TEST_DIRS or python-test won't find it.
|
||||
"xpcom",
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
use glean::net::{PingUploadRequest, PingUploader, UploadResult};
|
||||
use once_cell::sync::OnceCell;
|
||||
use std::sync::Once;
|
||||
use url::Url;
|
||||
use viaduct::{Error::*, Request};
|
||||
|
||||
|
@ -21,53 +23,174 @@ impl PingUploader for ViaductUploader {
|
|||
///
|
||||
/// * `upload_request` - the ping and its metadata to upload.
|
||||
fn upload(&self, upload_request: PingUploadRequest) -> UploadResult {
|
||||
let PingUploadRequest {
|
||||
url, body, headers, ..
|
||||
} = upload_request;
|
||||
log::trace!("FOG Ping Uploader uploading to {}", url);
|
||||
let url_clone = url.clone();
|
||||
let result: std::result::Result<UploadResult, viaduct::Error> = (move || {
|
||||
log::trace!("FOG Ping Uploader uploading to {}", upload_request.url);
|
||||
|
||||
// SAFETY NOTE: Safe because it returns a primitive by value.
|
||||
if unsafe { FOG_TooLateToSend() } {
|
||||
log::trace!("Attempted to send ping too late into shutdown.");
|
||||
return Ok(UploadResult::done());
|
||||
return UploadResult::done();
|
||||
}
|
||||
let debug_tagged = headers.iter().any(|(name, _)| name == "X-Debug-ID");
|
||||
|
||||
let debug_tagged = upload_request
|
||||
.headers
|
||||
.iter()
|
||||
.any(|(name, _)| name == "X-Debug-ID");
|
||||
let localhost_port = static_prefs::pref!("telemetry.fog.test.localhost_port");
|
||||
if localhost_port < 0
|
||||
|| (localhost_port == 0 && !debug_tagged && cfg!(feature = "disable_upload"))
|
||||
{
|
||||
log::info!("FOG Ping uploader faking success");
|
||||
return Ok(UploadResult::http_status(200));
|
||||
}
|
||||
let parsed_url = Url::parse(&url_clone)?;
|
||||
|
||||
log::info!("FOG Ping uploader uploading to {:?}", parsed_url);
|
||||
|
||||
let mut req = Request::post(parsed_url.clone()).body(body.clone());
|
||||
for (header_key, header_value) in &headers {
|
||||
req = req.header(header_key.to_owned(), header_value)?;
|
||||
return UploadResult::http_status(200);
|
||||
}
|
||||
|
||||
log::trace!("FOG Ping Uploader sending ping to {}", parsed_url);
|
||||
let res = req.send()?;
|
||||
Ok(UploadResult::http_status(res.status as i32))
|
||||
})();
|
||||
// Localhost-destined pings are sent without OHTTP,
|
||||
// even if configured to use OHTTP.
|
||||
let result = if localhost_port == 0 && should_ohttp_upload(&upload_request) {
|
||||
ohttp_upload(upload_request)
|
||||
} else {
|
||||
viaduct_upload(upload_request)
|
||||
};
|
||||
|
||||
log::trace!(
|
||||
"FOG Ping Uploader completed uploading to {} (Result {:?})",
|
||||
url,
|
||||
"FOG Ping Uploader completed uploading (Result {:?})",
|
||||
result
|
||||
);
|
||||
|
||||
match result {
|
||||
Ok(result) => result,
|
||||
Err(NonTlsUrl | UrlError(_)) => UploadResult::unrecoverable_failure(),
|
||||
Err(
|
||||
Err(ViaductUploaderError::Viaduct(ve)) => match ve {
|
||||
NonTlsUrl | UrlError(_) => UploadResult::unrecoverable_failure(),
|
||||
RequestHeaderError(_)
|
||||
| BackendError(_)
|
||||
| NetworkError(_)
|
||||
| BackendNotInitialized
|
||||
| SetBackendError,
|
||||
) => UploadResult::recoverable_failure(),
|
||||
| SetBackendError => UploadResult::recoverable_failure(),
|
||||
},
|
||||
Err(
|
||||
ViaductUploaderError::Bhttp(_)
|
||||
| ViaductUploaderError::Ohttp(_)
|
||||
| ViaductUploaderError::Fatal,
|
||||
) => UploadResult::unrecoverable_failure(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn viaduct_upload(upload_request: PingUploadRequest) -> Result<UploadResult, ViaductUploaderError> {
|
||||
let parsed_url = Url::parse(&upload_request.url)?;
|
||||
|
||||
log::info!("FOG viaduct uploader uploading to {:?}", parsed_url);
|
||||
|
||||
let mut req = Request::post(parsed_url.clone()).body(upload_request.body);
|
||||
for (header_key, header_value) in &upload_request.headers {
|
||||
req = req.header(header_key.to_owned(), header_value)?;
|
||||
}
|
||||
|
||||
log::trace!("FOG viaduct uploader sending ping to {:?}", parsed_url);
|
||||
let res = req.send()?;
|
||||
Ok(UploadResult::http_status(res.status as i32))
|
||||
}
|
||||
|
||||
fn should_ohttp_upload(upload_request: &PingUploadRequest) -> bool {
|
||||
crate::ohttp_pings::uses_ohttp(&upload_request.ping_name)
|
||||
&& !upload_request.body_has_info_sections
|
||||
}
|
||||
|
||||
fn ohttp_upload(upload_request: PingUploadRequest) -> Result<UploadResult, ViaductUploaderError> {
|
||||
static CELL: OnceCell<Vec<u8>> = once_cell::sync::OnceCell::new();
|
||||
let config = CELL.get_or_try_init(|| get_config())?;
|
||||
|
||||
let binary_request = bhttp_encode(upload_request)?;
|
||||
|
||||
static OHTTP_INIT: Once = Once::new();
|
||||
OHTTP_INIT.call_once(|| {
|
||||
ohttp::init();
|
||||
});
|
||||
|
||||
let ohttp_request = ohttp::ClientRequest::new(config)?;
|
||||
let (capsule, ohttp_response) = ohttp_request.encapsulate(&binary_request)?;
|
||||
|
||||
const OHTTP_RELAY_URL: &str = "https://mozilla-ohttp-dev.fastly-edge.com/";
|
||||
let parsed_relay_url = Url::parse(OHTTP_RELAY_URL)?;
|
||||
|
||||
log::trace!("FOG ohttp uploader uploading to {}", parsed_relay_url);
|
||||
|
||||
const OHTTP_MESSAGE_CONTENT_TYPE: &str = "message/ohttp-req";
|
||||
let req = Request::post(parsed_relay_url)
|
||||
.header(
|
||||
viaduct::header_names::CONTENT_TYPE,
|
||||
OHTTP_MESSAGE_CONTENT_TYPE,
|
||||
)?
|
||||
.body(capsule);
|
||||
let res = req.send()?;
|
||||
|
||||
if res.status == 200 {
|
||||
// This just tells us the HTTP went well. Check OHTTP's status.
|
||||
let binary_response = ohttp_response.decapsulate(&res.body)?;
|
||||
let mut cursor = std::io::Cursor::new(binary_response);
|
||||
let bhttp_message = bhttp::Message::read_bhttp(&mut cursor)?;
|
||||
let res = bhttp_message
|
||||
.control()
|
||||
.status()
|
||||
.ok_or(ViaductUploaderError::Fatal)?;
|
||||
Ok(UploadResult::http_status(res as i32))
|
||||
} else {
|
||||
Ok(UploadResult::http_status(res.status as i32))
|
||||
}
|
||||
}
|
||||
|
||||
fn get_config() -> Result<Vec<u8>, ViaductUploaderError> {
|
||||
const OHTTP_CONFIG_URL: &str =
|
||||
"https://stage.ohttp-gateway.nonprod.webservices.mozgcp.net/ohttp-configs";
|
||||
log::trace!("Getting OHTTP config from {}", OHTTP_CONFIG_URL);
|
||||
let parsed_config_url = Url::parse(OHTTP_CONFIG_URL)?;
|
||||
Ok(Request::get(parsed_config_url).send()?.body)
|
||||
}
|
||||
|
||||
/// Encode the ping upload request in binary HTTP.
|
||||
/// (draft-ietf-httpbis-binary-message)
|
||||
fn bhttp_encode(upload_request: PingUploadRequest) -> Result<Vec<u8>, ViaductUploaderError> {
|
||||
let parsed_url = Url::parse(&upload_request.url)?;
|
||||
let mut message = bhttp::Message::request(
|
||||
"POST".into(),
|
||||
parsed_url.scheme().into(),
|
||||
parsed_url
|
||||
.host_str()
|
||||
.ok_or(ViaductUploaderError::Fatal)?
|
||||
.into(),
|
||||
parsed_url.path().into(),
|
||||
);
|
||||
|
||||
upload_request
|
||||
.headers
|
||||
.into_iter()
|
||||
.for_each(|(k, v)| message.put_header(k, v));
|
||||
|
||||
message.write_content(upload_request.body);
|
||||
|
||||
let mut encoded = vec![];
|
||||
message.write_bhttp(bhttp::Mode::KnownLength, &mut encoded)?;
|
||||
|
||||
Ok(encoded)
|
||||
}
|
||||
|
||||
/// Unioned error across upload backends.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
enum ViaductUploaderError {
|
||||
#[error("bhttp::Error {0}")]
|
||||
Bhttp(#[from] bhttp::Error),
|
||||
|
||||
#[error("ohttp::Error {0}")]
|
||||
Ohttp(#[from] ohttp::Error),
|
||||
|
||||
#[error("viaduct::Error {0}")]
|
||||
Viaduct(#[from] viaduct::Error),
|
||||
|
||||
#[error("Fatal upload error")]
|
||||
Fatal,
|
||||
}
|
||||
|
||||
impl From<url::ParseError> for ViaductUploaderError {
|
||||
fn from(e: url::ParseError) -> Self {
|
||||
ViaductUploaderError::Viaduct(viaduct::Error::from(e))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ extern crate cstr;
|
|||
extern crate xpcom;
|
||||
|
||||
mod init;
|
||||
mod ohttp_pings;
|
||||
|
||||
pub use init::fog_init;
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
// This Source Code Form is subject to the terms of the Mozilla Public
|
||||
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
//! This file contains the generated logic for ohttp pings.
|
||||
//!
|
||||
//! The contents of this module are generated by
|
||||
//! `toolkit/components/glean/build_scripts/glean_parser_ext/run_glean_parser.py`, from
|
||||
//! ping definitions files identified by `toolkit/components/glean/metrics_index.py`.
|
||||
|
||||
include!(mozbuild::objdir_path!(
|
||||
"toolkit/components/glean/src/ohttp_pings.rs"
|
||||
));
|
|
@ -352,6 +352,14 @@
|
|||
"tomorrow",
|
||||
"upgrade"
|
||||
]
|
||||
],
|
||||
[
|
||||
"not-ohttp",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
[]
|
||||
]
|
||||
]
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
# `glean_parser` PyPI package.
|
||||
|
||||
---
|
||||
$schema: moz://mozilla.org/schemas/glean/pings/1-0-0
|
||||
$schema: moz://mozilla.org/schemas/glean/pings/2-0-0
|
||||
|
||||
not-baseline:
|
||||
description: >
|
||||
|
@ -114,3 +114,19 @@ not-deletion-request:
|
|||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1587095#c6
|
||||
notification_emails:
|
||||
- glean-team@mozilla.com
|
||||
|
||||
not-ohttp:
|
||||
description: >
|
||||
A fake OHTTP-using ping
|
||||
include_client_id: false
|
||||
metadata:
|
||||
include_info_sections: false
|
||||
use_ohttp: true
|
||||
send_if_empty: true
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
|
||||
notification_emails:
|
||||
- chutten@mozilla.com
|
||||
- glean-team@mozilla.com
|
||||
|
|
|
@ -76,6 +76,19 @@ pub static not_metrics: Lazy<Ping> = Lazy::new(|| {
|
|||
)
|
||||
});
|
||||
|
||||
#[allow(non_upper_case_globals)]
|
||||
/// A fake OHTTP-using ping
|
||||
pub static not_ohttp: Lazy<Ping> = Lazy::new(|| {
|
||||
Ping::new(
|
||||
"not-ohttp",
|
||||
false,
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
vec![],
|
||||
)
|
||||
});
|
||||
|
||||
|
||||
/// Instantiate custom pings once to trigger registration.
|
||||
///
|
||||
|
@ -91,6 +104,7 @@ pub fn register_pings(application_id: Option<&str>) {
|
|||
let _ = &*not_deletion_request;
|
||||
let _ = &*not_events;
|
||||
let _ = &*not_metrics;
|
||||
let _ = &*not_ohttp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -114,6 +128,7 @@ pub(crate) fn submit_ping_by_id(id: u32, reason: Option<&str>) {
|
|||
2 => not_deletion_request.submit(reason),
|
||||
3 => not_events.submit(reason),
|
||||
4 => not_metrics.submit(reason),
|
||||
5 => not_ohttp.submit(reason),
|
||||
_ => {
|
||||
// TODO: instrument this error.
|
||||
log::error!("Cannot submit unknown ping {} by id.", id);
|
||||
|
|
|
@ -56,6 +56,13 @@ constexpr glean::impl::Ping NotEvents(3);
|
|||
*/
|
||||
constexpr glean::impl::Ping NotMetrics(4);
|
||||
|
||||
/*
|
||||
* Generated from not-ohttp.
|
||||
*
|
||||
* A fake OHTTP-using ping
|
||||
*/
|
||||
constexpr glean::impl::Ping NotOhttp(5);
|
||||
|
||||
|
||||
} // namespace mozilla::glean_pings
|
||||
|
||||
|
|
|
@ -33,15 +33,17 @@ constexpr char gPingStringTable[] = {
|
|||
/* 12 - "notDeletionRequest" */ 'n', 'o', 't', 'D', 'e', 'l', 'e', 't', 'i', 'o', 'n', 'R', 'e', 'q', 'u', 'e', 's', 't', '\0',
|
||||
/* 31 - "notEvents" */ 'n', 'o', 't', 'E', 'v', 'e', 'n', 't', 's', '\0',
|
||||
/* 41 - "notMetrics" */ 'n', 'o', 't', 'M', 'e', 't', 'r', 'i', 'c', 's', '\0',
|
||||
/* 52 - "notOhttp" */ 'n', 'o', 't', 'O', 'h', 't', 't', 'p', '\0',
|
||||
};
|
||||
|
||||
|
||||
|
||||
const ping_entry_t sPingByNameLookupEntries[] = {
|
||||
65536,
|
||||
327732,
|
||||
262185,
|
||||
131084,
|
||||
196639,
|
||||
262185
|
||||
196639
|
||||
};
|
||||
|
||||
|
||||
|
@ -88,7 +90,7 @@ PingByNameLookup(const nsACString& aKey)
|
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
@ -108,7 +110,7 @@ PingByNameLookup(const nsACString& aKey)
|
|||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
|
|
@ -28,6 +28,6 @@ const char* GetPingName(ping_entry_t aEntry);
|
|||
*/
|
||||
Maybe<uint32_t> PingByNameLookup(const nsACString&);
|
||||
|
||||
extern const ping_entry_t sPingByNameLookupEntries[4];
|
||||
extern const ping_entry_t sPingByNameLookupEntries[5];
|
||||
} // namespace mozilla::glean
|
||||
#endif // mozilla_GleanJSPingsLookup_h
|
||||
|
|
|
@ -40,3 +40,22 @@ test-ping:
|
|||
- glean-team@mozilla.com
|
||||
no_lint:
|
||||
- REDUNDANT_PING
|
||||
|
||||
test-ohttp-ping:
|
||||
description: |
|
||||
This ping is for tests only.
|
||||
Resembles how OHTTP pings are defined.
|
||||
include_client_id: false
|
||||
metadata:
|
||||
include_info_sections: false
|
||||
use_ohttp: true
|
||||
send_if_empty: true
|
||||
bugs:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
|
||||
data_reviews:
|
||||
- https://bugzilla.mozilla.org/show_bug.cgi?id=1862002
|
||||
notification_emails:
|
||||
- chutten@mozilla.com
|
||||
- glean-team@mozilla.com
|
||||
no_lint:
|
||||
- REDUNDANT_PING
|
||||
|
|
|
@ -4,3 +4,155 @@
|
|||
const { XPCOMUtils } = ChromeUtils.importESModule(
|
||||
"resource://gre/modules/XPCOMUtils.sys.mjs"
|
||||
);
|
||||
|
||||
ChromeUtils.defineESModuleGetters(this, {
|
||||
HttpServer: "resource://testing-common/httpd.sys.mjs",
|
||||
NetUtil: "resource://gre/modules/NetUtil.sys.mjs",
|
||||
});
|
||||
|
||||
const PingServer = {
|
||||
_httpServer: null,
|
||||
_started: false,
|
||||
_defers: [Promise.withResolvers()],
|
||||
_currentDeferred: 0,
|
||||
|
||||
get port() {
|
||||
return this._httpServer.identity.primaryPort;
|
||||
},
|
||||
|
||||
get host() {
|
||||
return this._httpServer.identity.primaryHost;
|
||||
},
|
||||
|
||||
get started() {
|
||||
return this._started;
|
||||
},
|
||||
|
||||
registerPingHandler(handler) {
|
||||
this._httpServer.registerPrefixHandler("/submit/", handler);
|
||||
},
|
||||
|
||||
resetPingHandler() {
|
||||
this.registerPingHandler(request => {
|
||||
let r = request;
|
||||
console.trace(
|
||||
`defaultPingHandler() - ${r.method} ${r.scheme}://${r.host}:${r.port}${r.path}`
|
||||
);
|
||||
let deferred = this._defers[this._defers.length - 1];
|
||||
this._defers.push(Promise.withResolvers());
|
||||
deferred.resolve(request);
|
||||
});
|
||||
},
|
||||
|
||||
start() {
|
||||
this._httpServer = new HttpServer();
|
||||
this._httpServer.start(-1);
|
||||
this._started = true;
|
||||
this.clearRequests();
|
||||
this.resetPingHandler();
|
||||
},
|
||||
|
||||
stop() {
|
||||
return new Promise(resolve => {
|
||||
this._httpServer.stop(resolve);
|
||||
this._started = false;
|
||||
});
|
||||
},
|
||||
|
||||
clearRequests() {
|
||||
this._defers = [Promise.withResolvers()];
|
||||
this._currentDeferred = 0;
|
||||
},
|
||||
|
||||
promiseNextRequest() {
|
||||
const deferred = this._defers[this._currentDeferred++];
|
||||
// Send the ping to the consumer on the next tick, so that the completion gets
|
||||
// signaled to Telemetry.
|
||||
return new Promise(r =>
|
||||
Services.tm.dispatchToMainThread(() => r(deferred.promise))
|
||||
);
|
||||
},
|
||||
|
||||
promiseNextPing() {
|
||||
return this.promiseNextRequest().then(request =>
|
||||
decodeRequestPayload(request)
|
||||
);
|
||||
},
|
||||
|
||||
async promiseNextRequests(count) {
|
||||
let results = [];
|
||||
for (let i = 0; i < count; ++i) {
|
||||
results.push(await this.promiseNextRequest());
|
||||
}
|
||||
|
||||
return results;
|
||||
},
|
||||
|
||||
promiseNextPings(count) {
|
||||
return this.promiseNextRequests(count).then(requests => {
|
||||
return Array.from(requests, decodeRequestPayload);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Decode the payload of an HTTP request into a ping.
|
||||
*
|
||||
* @param {object} request The data representing an HTTP request (nsIHttpRequest).
|
||||
* @returns {object} The decoded ping payload.
|
||||
*/
|
||||
function decodeRequestPayload(request) {
|
||||
let s = request.bodyInputStream;
|
||||
let payload = null;
|
||||
|
||||
if (
|
||||
request.hasHeader("content-encoding") &&
|
||||
request.getHeader("content-encoding") == "gzip"
|
||||
) {
|
||||
let observer = {
|
||||
buffer: "",
|
||||
onStreamComplete(loader, context, status, length, result) {
|
||||
// String.fromCharCode can only deal with 500,000 characters
|
||||
// at a time, so chunk the result into parts of that size.
|
||||
const chunkSize = 500000;
|
||||
for (let offset = 0; offset < result.length; offset += chunkSize) {
|
||||
this.buffer += String.fromCharCode.apply(
|
||||
String,
|
||||
result.slice(offset, offset + chunkSize)
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let scs = Cc["@mozilla.org/streamConverters;1"].getService(
|
||||
Ci.nsIStreamConverterService
|
||||
);
|
||||
let listener = Cc["@mozilla.org/network/stream-loader;1"].createInstance(
|
||||
Ci.nsIStreamLoader
|
||||
);
|
||||
listener.init(observer);
|
||||
let converter = scs.asyncConvertData(
|
||||
"gzip",
|
||||
"uncompressed",
|
||||
listener,
|
||||
null
|
||||
);
|
||||
converter.onStartRequest(null, null);
|
||||
converter.onDataAvailable(null, s, 0, s.available());
|
||||
converter.onStopRequest(null, null, null);
|
||||
// TODO: nsIScriptableUnicodeConverter is deprecated
|
||||
// But I can't figure out how else to ungzip bodyInputStream.
|
||||
let unicodeConverter = Cc[
|
||||
"@mozilla.org/intl/scriptableunicodeconverter"
|
||||
].createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
unicodeConverter.charset = "UTF-8";
|
||||
let utf8string = unicodeConverter.ConvertToUnicode(observer.buffer);
|
||||
utf8string += unicodeConverter.Finish();
|
||||
payload = JSON.parse(utf8string);
|
||||
} else {
|
||||
let bytes = NetUtil.readInputStream(s, s.available());
|
||||
payload = JSON.parse(new TextDecoder().decode(bytes));
|
||||
}
|
||||
|
||||
return payload;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
"use strict";
|
||||
|
||||
add_setup(async () => {
|
||||
// FOG needs a profile dir to put its data in.
|
||||
do_get_profile();
|
||||
|
||||
PingServer.start();
|
||||
|
||||
registerCleanupFunction(async () => {
|
||||
await PingServer.stop();
|
||||
});
|
||||
|
||||
Services.prefs.setIntPref(
|
||||
"telemetry.fog.test.localhost_port",
|
||||
PingServer.port
|
||||
);
|
||||
// Port pref needs to be set before init, so let's reset to reinit.
|
||||
Services.fog.testResetFOG();
|
||||
});
|
||||
|
||||
add_task(async () => {
|
||||
PingServer.clearRequests();
|
||||
GleanPings.testOhttpPing.submit();
|
||||
|
||||
let ping = await PingServer.promiseNextPing();
|
||||
|
||||
ok(!("client_info" in ping), "No client_info allowed.");
|
||||
ok(!("ping_info" in ping), "No ping_info allowed.");
|
||||
});
|
|
@ -30,3 +30,6 @@ skip-if = ["os == 'android'"] # Server Knobs on mobile will be handled by the sp
|
|||
|
||||
["test_MillionQ.js"]
|
||||
skip-if = ["os == 'android'"] # Android inits its own FOG, so the test won't work.
|
||||
|
||||
["test_OHTTP.js"]
|
||||
skip-if = ["os == 'android'"] # FOG isn't responsible for monitoring prefs and controlling upload on Android
|
||||
|
|
Загрузка…
Ссылка в новой задаче