Skip to content

Commit 4e1c640

Browse files
authored
github: Use async reqwest client (#7478)
1 parent 2e58566 commit 4e1c640

File tree

14 files changed

+115
-75
lines changed

14 files changed

+115
-75
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ p256 = "=0.13.2"
7676
parking_lot = "=0.12.1"
7777
prometheus = { version = "=0.13.3", default-features = false }
7878
rand = "=0.8.5"
79-
reqwest = { version = "=0.11.22", features = ["blocking", "gzip", "json"] }
79+
reqwest = { version = "=0.11.22", features = ["gzip", "json"] }
8080
retry = "=2.0.0"
8181
scheduled-thread-pool = "=0.2.7"
8282
secrecy = "=0.8.0"

src/app.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use axum::extract::{FromRef, FromRequestParts, State};
1616
use diesel::r2d2;
1717
use moka::future::{Cache, CacheBuilder};
1818
use oauth2::basic::BasicClient;
19-
use reqwest::blocking::Client;
19+
use reqwest::Client;
2020
use scheduled_thread_pool::ScheduledThreadPool;
2121

2222
/// The `App` struct holds the main components of the application like

src/bin/server.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use std::{sync::Arc, time::Duration};
1010
use axum::ServiceExt;
1111
use futures_util::future::FutureExt;
1212
use prometheus::Encoder;
13-
use reqwest::blocking::Client;
13+
use reqwest::Client;
1414
use std::io::{self, Write};
1515
use std::net::SocketAddr;
1616
use tokio::signal::unix::{signal, SignalKind};

src/controllers/crate_owner_invitation.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use chrono::{Duration, Utc};
1414
use diesel::{pg::Pg, sql_types::Bool};
1515
use indexmap::IndexMap;
1616
use std::collections::{HashMap, HashSet};
17+
use tokio::runtime::Handle;
1718

1819
/// Handles the `GET /api/v1/me/crate_owner_invitations` route.
1920
pub async fn list(app: AppState, req: Parts) -> AppResult<Json<Value>> {
@@ -106,7 +107,7 @@ fn prepare_list(
106107
// Only allow crate owners to query pending invitations for their crate.
107108
let krate: Crate = Crate::by_name(&crate_name).first(conn)?;
108109
let owners = krate.owners(conn)?;
109-
if user.rights(state, &owners)? != Rights::Full {
110+
if Handle::current().block_on(user.rights(state, &owners))? != Rights::Full {
110111
return Err(forbidden());
111112
}
112113

src/controllers/github/secret_scanning.rs

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ use once_cell::sync::Lazy;
1111
use p256::ecdsa::signature::Verifier;
1212
use p256::ecdsa::VerifyingKey;
1313
use p256::PublicKey;
14-
use parking_lot::Mutex;
1514
use serde_json as json;
1615
use std::str::FromStr;
16+
use tokio::sync::Mutex;
1717

1818
// Minimum number of seconds to wait before refreshing cache of GitHub's public keys
1919
static PUBLIC_KEY_CACHE_LIFETIME_SECONDS: i64 = 60 * 60 * 24; // 24 hours
@@ -55,18 +55,17 @@ fn is_cache_valid(timestamp: Option<chrono::DateTime<chrono::Utc>>) -> bool {
5555
}
5656

5757
// Fetches list of public keys from GitHub API
58-
fn get_public_keys(state: &AppState) -> Result<Vec<GitHubPublicKey>, BoxedAppError> {
58+
async fn get_public_keys(state: &AppState) -> Result<Vec<GitHubPublicKey>, BoxedAppError> {
5959
// Return list from cache if populated and still valid
60-
let mut cache = PUBLIC_KEY_CACHE.lock();
60+
let mut cache = PUBLIC_KEY_CACHE.lock().await;
6161
if is_cache_valid(cache.timestamp) {
6262
return Ok(cache.keys.clone());
6363
}
6464

6565
// Fetch from GitHub API
66-
let keys = state.github.public_keys(
67-
&state.config.gh_client_id,
68-
state.config.gh_client_secret.secret(),
69-
)?;
66+
let client_id = &state.config.gh_client_id;
67+
let client_secret = state.config.gh_client_secret.secret();
68+
let keys = state.github.public_keys(client_id, client_secret).await?;
7069

7170
// Populate cache
7271
cache.keys = keys.clone();
@@ -76,7 +75,7 @@ fn get_public_keys(state: &AppState) -> Result<Vec<GitHubPublicKey>, BoxedAppErr
7675
}
7776

7877
/// Verifies that the GitHub signature in request headers is valid
79-
fn verify_github_signature(
78+
async fn verify_github_signature(
8079
headers: &HeaderMap,
8180
state: &AppState,
8281
json: &[u8],
@@ -98,6 +97,7 @@ fn verify_github_signature(
9897
.map_err(|e| bad_request(&format!("failed to parse signature from ASN.1 DER: {e:?}")))?;
9998

10099
let public_keys = get_public_keys(state)
100+
.await
101101
.map_err(|e| bad_request(&format!("failed to fetch GitHub public keys: {e:?}")))?;
102102

103103
let key = public_keys
@@ -222,13 +222,14 @@ pub async fn verify(
222222
headers: HeaderMap,
223223
body: Bytes,
224224
) -> AppResult<Json<Vec<GitHubSecretAlertFeedback>>> {
225-
conduit_compat(move || {
226-
verify_github_signature(&headers, &state, &body)
227-
.map_err(|e| bad_request(&format!("failed to verify request signature: {e:?}")))?;
225+
verify_github_signature(&headers, &state, &body)
226+
.await
227+
.map_err(|e| bad_request(&format!("failed to verify request signature: {e:?}")))?;
228228

229-
let alerts: Vec<GitHubSecretAlert> = json::from_slice(&body)
230-
.map_err(|e| bad_request(&format!("invalid secret alert request: {e:?}")))?;
229+
let alerts: Vec<GitHubSecretAlert> = json::from_slice(&body)
230+
.map_err(|e| bad_request(&format!("invalid secret alert request: {e:?}")))?;
231231

232+
conduit_compat(move || {
232233
let feedback = alerts
233234
.into_iter()
234235
.map(|alert| {

src/controllers/krate/owners.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::models::token::EndpointScope;
66
use crate::models::{Crate, Owner, Rights, Team, User};
77
use crate::views::EncodableOwner;
88
use axum::body::Bytes;
9+
use tokio::runtime::Handle;
910

1011
/// Handles the `GET /crates/:crate_id/owners` route.
1112
pub async fn owners(state: AppState, Path(crate_name): Path<String>) -> AppResult<Json<Value>> {
@@ -113,7 +114,7 @@ fn modify_owners(
113114
let krate: Crate = Crate::by_name(crate_name).first(conn)?;
114115
let owners = krate.owners(conn)?;
115116

116-
match user.rights(app, &owners)? {
117+
match Handle::current().block_on(user.rights(app, &owners))? {
117118
Rights::Full => {}
118119
// Yes!
119120
Rights::Publish => {

src/controllers/krate/publish.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ pub async fn publish(app: AppState, req: BytesRequest) -> AppResult<Json<GoodCra
295295
};
296296

297297
let owners = krate.owners(conn)?;
298-
if user.rights(&app, &owners)? < Rights::Publish {
298+
if Handle::current().block_on(user.rights(&app, &owners))? < Rights::Publish {
299299
return Err(cargo_err(MISSING_RIGHTS_ERROR_MESSAGE));
300300
}
301301

src/controllers/user/session.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::controllers::frontend_prelude::*;
22

33
use oauth2::reqwest::http_client;
44
use oauth2::{AuthorizationCode, Scope, TokenResponse};
5+
use tokio::runtime::Handle;
56

67
use crate::email::Emails;
78
use crate::github::GithubUser;
@@ -100,7 +101,7 @@ pub async fn authorize(
100101
let token = token.access_token();
101102

102103
// Fetch the user info from GitHub using the access token we just got and create a user record
103-
let ghuser = app.github.current_user(token)?;
104+
let ghuser = Handle::current().block_on(app.github.current_user(token))?;
104105
let user =
105106
save_user_to_database(&ghuser, token.secret(), &app.emails, &mut *app.db_write()?)?;
106107

src/controllers/version/yank.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use crate::models::{insert_version_owner_action, VersionAction};
99
use crate::rate_limiter::LimitedAction;
1010
use crate::schema::versions;
1111
use crate::worker::jobs;
12+
use tokio::runtime::Handle;
1213

1314
/// Handles the `DELETE /crates/:crate_id/:version/yank` route.
1415
/// This does not delete a crate version, it makes the crate
@@ -67,7 +68,7 @@ fn modify_yank(
6768
let user = auth.user();
6869
let owners = krate.owners(conn)?;
6970

70-
if user.rights(state, &owners)? < Rights::Publish {
71+
if Handle::current().block_on(user.rights(state, &owners))? < Rights::Publish {
7172
return Err(cargo_err("must already be an owner to yank or unyank"));
7273
}
7374

src/github.rs

Lines changed: 41 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,37 @@ use std::str;
99

1010
use crate::controllers::github::secret_scanning::{GitHubPublicKey, GitHubPublicKeyList};
1111
use crate::util::errors::{cargo_err, internal, not_found, AppResult, BoxedAppError};
12-
use reqwest::blocking::Client;
12+
use async_trait::async_trait;
13+
use reqwest::Client;
1314

15+
#[async_trait]
1416
pub trait GitHubClient: Send + Sync {
15-
fn current_user(&self, auth: &AccessToken) -> AppResult<GithubUser>;
16-
fn org_by_name(&self, org_name: &str, auth: &AccessToken) -> AppResult<GitHubOrganization>;
17-
fn team_by_name(
17+
async fn current_user(&self, auth: &AccessToken) -> AppResult<GithubUser>;
18+
async fn org_by_name(
19+
&self,
20+
org_name: &str,
21+
auth: &AccessToken,
22+
) -> AppResult<GitHubOrganization>;
23+
async fn team_by_name(
1824
&self,
1925
org_name: &str,
2026
team_name: &str,
2127
auth: &AccessToken,
2228
) -> AppResult<GitHubTeam>;
23-
fn team_membership(
29+
async fn team_membership(
2430
&self,
2531
org_id: i32,
2632
team_id: i32,
2733
username: &str,
2834
auth: &AccessToken,
2935
) -> AppResult<GitHubTeamMembership>;
30-
fn org_membership(
36+
async fn org_membership(
3137
&self,
3238
org_id: i32,
3339
username: &str,
3440
auth: &AccessToken,
3541
) -> AppResult<GitHubOrgMembership>;
36-
fn public_keys(&self, username: &str, password: &str) -> AppResult<Vec<GitHubPublicKey>>;
42+
async fn public_keys(&self, username: &str, password: &str) -> AppResult<Vec<GitHubPublicKey>>;
3743
}
3844

3945
#[derive(Debug)]
@@ -47,7 +53,7 @@ impl RealGitHubClient {
4753
}
4854

4955
/// Does all the nonsense for sending a GET to Github.
50-
fn _request<T>(&self, url: &str, auth: &str) -> AppResult<T>
56+
async fn _request<T>(&self, url: &str, auth: &str) -> AppResult<T>
5157
where
5258
T: DeserializeOwned,
5359
{
@@ -59,27 +65,31 @@ impl RealGitHubClient {
5965
.header(header::ACCEPT, "application/vnd.github.v3+json")
6066
.header(header::AUTHORIZATION, auth)
6167
.header(header::USER_AGENT, "crates.io (https://crates.io)")
62-
.send()?
68+
.send()
69+
.await?
6370
.error_for_status()
6471
.map_err(|e| handle_error_response(&e))?
6572
.json()
73+
.await
6674
.map_err(Into::into)
6775
}
6876

6977
/// Sends a GET to GitHub using OAuth access token authentication
70-
pub fn request<T>(&self, url: &str, auth: &AccessToken) -> AppResult<T>
78+
pub async fn request<T>(&self, url: &str, auth: &AccessToken) -> AppResult<T>
7179
where
7280
T: DeserializeOwned,
7381
{
7482
self._request(url, &format!("token {}", auth.secret()))
83+
.await
7584
}
7685

7786
/// Sends a GET to GitHub using basic authentication
78-
pub fn request_basic<T>(&self, url: &str, username: &str, password: &str) -> AppResult<T>
87+
pub async fn request_basic<T>(&self, url: &str, username: &str, password: &str) -> AppResult<T>
7988
where
8089
T: DeserializeOwned,
8190
{
8291
self._request(url, &format!("basic {username}:{password}"))
92+
.await
8393
}
8494

8595
/// Returns a client for making HTTP requests to upload crate files.
@@ -98,38 +108,43 @@ impl RealGitHubClient {
98108
}
99109
}
100110

111+
#[async_trait]
101112
impl GitHubClient for RealGitHubClient {
102-
fn current_user(&self, auth: &AccessToken) -> AppResult<GithubUser> {
103-
self.request("/user", auth)
113+
async fn current_user(&self, auth: &AccessToken) -> AppResult<GithubUser> {
114+
self.request("/user", auth).await
104115
}
105116

106-
fn org_by_name(&self, org_name: &str, auth: &AccessToken) -> AppResult<GitHubOrganization> {
117+
async fn org_by_name(
118+
&self,
119+
org_name: &str,
120+
auth: &AccessToken,
121+
) -> AppResult<GitHubOrganization> {
107122
let url = format!("/orgs/{org_name}");
108-
self.request(&url, auth)
123+
self.request(&url, auth).await
109124
}
110125

111-
fn team_by_name(
126+
async fn team_by_name(
112127
&self,
113128
org_name: &str,
114129
team_name: &str,
115130
auth: &AccessToken,
116131
) -> AppResult<GitHubTeam> {
117132
let url = format!("/orgs/{org_name}/teams/{team_name}");
118-
self.request(&url, auth)
133+
self.request(&url, auth).await
119134
}
120135

121-
fn team_membership(
136+
async fn team_membership(
122137
&self,
123138
org_id: i32,
124139
team_id: i32,
125140
username: &str,
126141
auth: &AccessToken,
127142
) -> AppResult<GitHubTeamMembership> {
128143
let url = format!("/organizations/{org_id}/team/{team_id}/memberships/{username}");
129-
self.request(&url, auth)
144+
self.request(&url, auth).await
130145
}
131146

132-
fn org_membership(
147+
async fn org_membership(
133148
&self,
134149
org_id: i32,
135150
username: &str,
@@ -139,12 +154,16 @@ impl GitHubClient for RealGitHubClient {
139154
&format!("/organizations/{org_id}/memberships/{username}"),
140155
auth,
141156
)
157+
.await
142158
}
143159

144160
/// Returns the list of public keys that can be used to verify GitHub secret alert signatures
145-
fn public_keys(&self, username: &str, password: &str) -> AppResult<Vec<GitHubPublicKey>> {
161+
async fn public_keys(&self, username: &str, password: &str) -> AppResult<Vec<GitHubPublicKey>> {
146162
let url = "/meta/public_keys/secret_scanning";
147-
match self.request_basic::<GitHubPublicKeyList>(url, username, password) {
163+
match self
164+
.request_basic::<GitHubPublicKeyList>(url, username, password)
165+
.await
166+
{
148167
Ok(v) => Ok(v.public_keys),
149168
Err(e) => Err(e),
150169
}

0 commit comments

Comments
 (0)