From ead67de58d81d656da60d4f36d94a18d8a423968 Mon Sep 17 00:00:00 2001 From: Jeremy Andrews Date: Sat, 30 Oct 2021 09:15:00 +0200 Subject: [PATCH] start updating documentation --- examples/simple_with_session.rs | 2 +- src/goose.rs | 168 ++++++++++++++++++++++---------- 2 files changed, 116 insertions(+), 54 deletions(-) diff --git a/examples/simple_with_session.rs b/examples/simple_with_session.rs index 6d0d7df8..a4a1d19d 100644 --- a/examples/simple_with_session.rs +++ b/examples/simple_with_session.rs @@ -77,7 +77,7 @@ async fn authenticated_index(user: &mut GooseUser) -> GooseTaskResult { let session = user.get_session_data_unchecked::(); let goose_request = GooseRequest::builder() .request_builder( - user.request_builder(&GooseMethod::Get, "/")? + user.get_request_builder(&GooseMethod::Get, "/")? .bearer_auth(&session.jwt_token), ) .build(); diff --git a/src/goose.rs b/src/goose.rs index 9efee45b..1d981dbe 100644 --- a/src/goose.rs +++ b/src/goose.rs @@ -2,10 +2,10 @@ //! //! Goose manages load tests with a series of objects: //! -//! - [`GooseTaskSet`](./struct.GooseTaskSet.html) each user is assigned a task set, which is a collection of tasks. -//! - [`GooseTask`](./struct.GooseTask.html) tasks define one or more web requests and are assigned to task sets. -//! - [`GooseUser`](./struct.GooseUser.html) a user state responsible for repeatedly running all tasks in the assigned task set. -//! - [`GooseRequest`](./struct.GooseRequest.html) optional metrics collected for each URL/method pair. +//! - [`GooseTaskSet`] each user is assigned a task set, which is a collection of tasks. +//! - [`GooseTask`] tasks define one or more web requests and are assigned to task sets. +//! - [`GooseUser`] a user state responsible for repeatedly running all tasks in the assigned task set. +//! - [`GooseRequestMetric`] optional metrics collected for each URL/method pair. //! //! ## Creating Task Sets //! @@ -686,7 +686,7 @@ pub fn goose_method_from_method(method: Method) -> Result GooseTaskResult { + /// let params = [("foo", "bar"), ("foo2", "bar2")]; + /// let _goose = user.post_form("path/to/foo/", ¶ms).await?; + /// + /// Ok(()) + /// } + /// ``` pub async fn post_form( &mut self, path: &str, @@ -1181,16 +1200,14 @@ impl GooseUser { /// A helper to make a `HEAD` request of a path and collect relevant metrics. /// Automatically prepends the correct host. /// - /// (If you need to set headers, change timeouts, or otherwise make use of the - /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html) - /// object, you can instead call `goose_head` which returns a - /// [`RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html), - /// then call `goose_send` to invoke the request.) - /// /// Calls to `head()` return a [`GooseResponse`](./struct.GooseResponse.html) object which /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)), /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)). /// + /// If you need to set headers, change timeouts, or otherwise make use of the + /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html) + /// object, refer to [`GooseUser::get_request_builder`]. + /// /// # Example /// ```rust /// use goose::prelude::*; @@ -1211,16 +1228,14 @@ impl GooseUser { /// A helper to make a `DELETE` request of a path and collect relevant metrics. /// Automatically prepends the correct host. /// - /// (If you need to set headers, change timeouts, or otherwise make use of the - /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html) - /// object, you can instead call `goose_delete` which returns a - /// [`RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html), - /// then call `goose_send` to invoke the request.) - /// /// Calls to `delete()` return a [`GooseResponse`](./struct.GooseResponse.html) object which /// contains a copy of the request you made ([`GooseRequestMetric`](./struct.GooseRequestMetric.html)), /// and the response ([`reqwest::Response`](https://docs.rs/reqwest/*/reqwest/struct.Response.html)). /// + /// If you need to set headers, change timeouts, or otherwise make use of the + /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html) + /// object, refer to [`GooseUser::get_request_builder`]. + /// /// # Example /// ```rust /// use goose::prelude::*; @@ -1238,12 +1253,54 @@ impl GooseUser { Ok(self.request(GooseRequest::delete(path)).await?) } - pub fn request_builder( + /// Used to get a [`reqwest::RequestBuilder`] object. If no [`reqwest::RequestBuilder`] is + /// already defined in the [`GooseRequest`] passed to [`GooseUser::request`] it will automatically + /// invoke this function. + /// + /// The HTTP request method must be defined as a [`GooseMethod`], and the path that will be requested + /// must be defined as a [`&str`]. + /// + /// It is possible to use this function to directly interact with the [`reqwest::RequestBuilder`] + /// object and the [`GooseRequest`] object during load tests. In the following example, we set a + /// timeout on the Request, and tell Goose to expect a 404 HTTP response status code. + /// + /// # Example + /// ```rust + /// use goose::prelude::*; + /// + /// let mut task = task!(test_404); + /// + /// async fn test_404(user: &mut GooseUser) -> GooseTaskResult { + /// use std::time::Duration; + /// + /// // Manually interact with the Reqwest RequestBuilder object. + /// let request_builder = user.get_request_builder(&GooseMethod::Get, "no/such/path")? + /// // Configure the request to timeout if it takes longer than 500 milliseconds. + /// .timeout(Duration::from_millis(500)); + /// + /// // Manually build a GooseRequest. + /// let goose_request = GooseRequest::builder() + /// // Manually add our custom RequestBuilder object. + /// .request_builder(request_builder) + /// // Tell Goose to expect a 404 status code. + /// .expect_status_code(404) + /// // Turn the GooseRequestBuilder object into a GooseRequest. + /// .build(); + /// + /// // Finaly make the actual request with our custom GooseRequest object. + /// let _goose = user.request(goose_request).await?; + /// + /// Ok(()) + /// } + /// ``` + pub fn get_request_builder( &self, method: &GooseMethod, path: &str, ) -> Result { + // Prepend the `base_url` to all relative paths. let url = self.build_url(path)?; + // Invoke Reqwest function appropriate to the request method. Ok(match method { GooseMethod::Delete => self.client.delete(&url), GooseMethod::Get => self.client.get(&url), @@ -1254,18 +1311,10 @@ impl GooseUser { }) } - /// Builds the provided - /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html) - /// object and then executes the response. If metrics are being displayed, it - /// also captures request metrics. - /// - /// It is possible to build and execute a - /// [`reqwest::RequestBuilder`](https://docs.rs/reqwest/*/reqwest/struct.RequestBuilder.html) - /// object directly with [`reqwest`](https://docs.rs/reqwest/) without using this helper - /// function, but then Goose is unable to capture metrics. + /// Makes a request for the provided [`GooseRequest`] object, and if metrics are enabled + /// captures relevant metrics. /// - /// Calls to `goose_send()` returns a `Result` containing a - /// [`GooseResponse`](./struct.GooseResponse.html) on success, and a + /// Calls to `request()` return a [`Result`] containing a [`GooseResponse`] on success, and a /// [`flume::SendError`](https://docs.rs/flume/*/flume/struct.SendError.html)``, /// on failure. Failure only happens when `--throttle-requests` is enabled and the load test /// completes. The [`GooseResponse`](./struct.GooseResponse.html) object contains a copy of @@ -1278,11 +1327,16 @@ impl GooseUser { /// /// let mut task = task!(get_function); /// - /// /// A simple task that makes a GET request, exposing the Reqwest - /// /// request builder. - /// /// @TODO + /// /// A simple task that makes a GET request. /// async fn get_function(user: &mut GooseUser) -> GooseTaskResult { - /// let goose = user.get("/path/to/foo").await?; + /// let goose_request = GooseRequest::builder() + /// // Goose will prepend a host name to this path. + /// .path("path/to/loadtest") + /// // GET is the default method, this is not necessary. + /// .method(GooseMethod::Get) + /// // Assemble the `GooseRequestBuilder` into a `GooseRequest. + /// .build(); + /// let goose = user.request(goose_request).await?; /// /// // Do stuff with goose.request and/or goose.response here. /// @@ -1293,10 +1347,12 @@ impl GooseUser { &mut self, mut request: GooseRequest<'_>, ) -> Result { + // If the RequestBuilder is already defined in the GooseRequest use it. let request_builder = if request.request_builder.is_some() { request.request_builder.take().unwrap() + // Otherwise get a new RequestBuilder. } else { - self.request_builder(&request.method, request.path)? + self.get_request_builder(&request.method, request.path)? }; // Determine the name for this request. @@ -1310,12 +1366,13 @@ impl GooseUser { self.throttle.clone().unwrap().send_async(true).await?; }; - // The request is officially started + // Once past the throttle, the request is officially started. let started = Instant::now(); + // Create a Reqwest Request object from the RequestBuilder. let built_request = request_builder.build()?; - // String version of request path. + // Get a string version of request path for logging. let path = match Url::parse(&built_request.url().to_string()) { Ok(u) => u.path().to_string(), Err(e) => { @@ -1365,24 +1422,29 @@ impl GooseUser { let response = self.client.execute(built_request).await; request_metric.set_response_time(started.elapsed().as_millis()); + // Determine if the request suceeded or failed. match &response { Ok(r) => { let status_code = r.status(); debug!("{:?}: status_code {}", &path, status_code); + // Update the request_metric object. + request_metric.set_status_code(Some(status_code)); + request_metric.set_final_url(r.url().as_str()); + + // Check if we were expecting a specific status code. if let Some(expect_status_code) = request.expect_status_code { + // Record a failure if the expected status code was not returned. if status_code != expect_status_code { request_metric.success = false; request_metric.error = format!("{}: {}", status_code, request_name); } + // Otherwise record a failure if the returned status code was not a success. } else if !status_code.is_success() { request_metric.success = false; request_metric.error = format!("{}: {}", status_code, request_name); } - request_metric.set_status_code(Some(status_code)); - request_metric.set_final_url(r.url().as_str()); - // Load test user was redirected. if self.config.sticky_follow && request_metric.raw.url != request_metric.final_url { let base_url = self.base_url.to_string();