HTTP requests with Hyper with Parameters

Hi,
I love to use the youtube api and I am new to apis but I have done https requests with python but how can I send https requests in rust with parameters like the api key and other parameters

I want to specifically use Hyper and kindly explain how the parameters work

Because in python we can insert a dictionary as a parameter but I think in rust it would be Hash map

Kindly help
Thanks In advance

There's no such one thing as "parameters" in HTTP. Options to the server can be sent via:

  • The request's path, e.g. /api/123
  • Query parameters, e.g. /api?value=123
  • Headers, e.g. authorization: Basic dXNlcm5hbWU6cGFzc3dvcmQ
  • The payload of the request, e.g. { "value": 123 }

The first two can be configured via the Uri part of a request, the headers and body can be configured by their respective fields in the request.

To send HTTPS requests with Hyper you can use the hyper-tls crate.

Afaik the thing that Youtube's API call parameters are the query parameters.

ok @alice I was taking about them but how do I do that in hyper

To specify the parameters for the youtube API endpoint, you can use the url crate like this:

let mut url = Url::parse("https://example.net")?;
url.query_pairs_mut().append_pair("foo", "bar");
url.query_pairs_mut().append_pair("key", "dkhdsihdsaiufds");
url.query_pairs_mut().append_pair("hello", "world");
println!("{}", url.as_str());
https://example.net/?foo=bar&key=dkhdsihdsaiufds&hello=world

playground

You can now send a request with this url like this:

use hyper_tls::HttpsConnector;
use hyper::{Body, Client};

let request = hyper::Request::get(url.as_str())
    .body(Body::empty())?;

let https = HttpsConnector::new();
let client = Client::builder().build::<_, hyper::Body>(https);
let response = client.request(request).await?;

If the API endpoint also requires a request body, you can create it using serde. For example, to define a comment resource, you do this:

use serde::Serialize;

#[derive(Serialize)]
#[serde(rename = "camelCase")]
struct CommentResource<'a> {
    kind: &'a str,
    etag: &'a str,
    id: &'a str,
    snippet: CommentResourceSnippet<'a>,
}
#[derive(Serialize)]
#[serde(rename = "camelCase")]
struct CommentResourceSnippet<'a> {
    author_display_name: &'a str,
    author_profile_image_url: &'a str,
    ...
}

Then to use a struct like this as body, you do this:

let comment = CommentResource {
    kind: "youtube#comment",
    ...
};

let request = hyper::Request::post(url.as_str())
    .body(Body::from(serde_json::to_vec(&comment)?))?;

let response = client.request(request).await?;

Note that you should try to reuse the Client instead of creating a new one per request.

2 Likes

what is the 'a in your program and can you explain the struct part

and what does this do let client = Client::builder().build::<_, hyper::Body>(https);

It creates a hyper::Client that is able to use https instead of only http.

The 'a is a lifetime. When you put <'a> on a struct, this is a way to say "this struct contains references to values it does not own". This has the consequence that the struct cannot exist after the thing it has a reference to is destroyed because this would break the references it contains. This makes them impractical to use in many situations. For example, this would not work:

#[derive(Debug)]
struct HasRef<'a> {
    inner: &'a str,
}

fn main() {
    // This String will own the string data.
    let s = String::from("Hello world");
    
    // Create a struct with a reference to s.
    let has_ref = HasRef { inner: &s };
    
    // Destroy s.
    drop(s);
    
    // Try to use has_ref after s was destroyed.
    println!("{:?}", has_ref);
}
error[E0505]: cannot move out of `s` because it is borrowed
  --> src/main.rs:15:10
   |
12 |     let has_ref = HasRef { inner: &s };
   |                                   -- borrow of `s` occurs here
...
15 |     drop(s);
   |          ^ move out of `s` occurs here
...
18 |     println!("{:?}", has_ref);
   |                      ------- borrow later used here

However for our use case the struct will only exist for a very short time, as we just need it for the call to serde_json::to_vec, so this restriction is not a problem for us.

no method named query_pairs_mut found for enum std::result::Result<Url, url::ParseError> in the current scope

You need a question mark or unwrap. Read this chapter.

// Imports
extern crate hyper;

#[allow(unused_imports)]
use hyper_tls::HttpsConnector;
use hyper::{Body};
use hyper::Client;
use serde;
use url::{Url};

// defining a function to make and produce a url with the query parameters needed by the endpoint
fn params(mut url:Url, key_value: Vec<(&str, &str)>) -> String{

    // for every element in vector
    for i in key_value {
        // it is appended as a query parameter to the url
        url.query_pairs_mut().append_pair(i.0, i.1);

    }

    // makng a reference to the url to ignore the error
    let str = &url;

    // returning the final url in the form of a string
    return str.as_str().to_string()
}


async fn a() {
    // Defining the endpoint and unwrapping it because Result<Url, ParseError> is annoying
    let  endpoint = Url::parse("https://www.googleapis.com/youtube/v3/search").unwrap();

    // Making a vector for the parameters
    let param_vector= vec![("part", "snippet")];

    // getting the final url with the query parameters
    let final_url = params(endpoint, param_vector);


    // Now the request part

    // defining request
    let request = hyper::Request::get(final_url)
    .body(Body::empty()).unwrap();

    let https = HttpsConnector::new();
    let client = Client::builder().build::<_, hyper::Body>(https);
    let response = client.request(request).await;
    let ures = response.unwrap();

    println!("{}",ures.status().is_success());

I dont get anything but it should print the false

It is not printing I don't know why

Can you post some more full code?

Thats literally all of the code and I tried printing a normal string like hello but still it was not printing\

That is a function and the main function literally calls this function

It worked after a bit of research on the warnings I finally got it
from Demystifying Closures, Futures and async-await in Rust–Part 3: Async & Await | by Alistair A. Israel | Medium

async->future part

// Imports
extern crate hyper;

#[allow(unused_imports)]
use hyper_tls::HttpsConnector;
use hyper::{Body, Response};
use hyper::Client;
use serde;
use url::{Url};
use tokio;

// defining a function to make and produce a url with the query parameters needed by the endpoint
fn params(mut url:Url, key_value: Vec<(&str, &str)>) -> String{

    // for every element in vector
    for i in key_value {
        // it is appended as a query parameter to the url
        url.query_pairs_mut().append_pair(i.0, i.1);

    }

    // makng a reference to the url to ignore the error
    let str = &url;

    // returning the final url in the form of a string
    return str.as_str().to_string()
}


async fn a(){
    // Defining the endpoint and unwrapping it because Result<Url, ParseError> is annoying
    let  endpoint = Url::parse("https://www.googleapis.com/youtube/v3/search").unwrap();

    // Making a vector for the parameters
    let param_vector= vec![("part", "snippet")];

    // getting the final url with the query parameters
    let final_url = params(endpoint, param_vector);


    // Now the request part

    // defining request
    let request = hyper::Request::get(&final_url)
    .body(Body::empty()).unwrap();

    let https = HttpsConnector::new();
    let client = Client::builder().build::<_, hyper::Body>(https);
    let response = client.request(request).await;
    let ures = response.unwrap();

    // let mut rt = tokio::runtime::Runtime::new().unwrap();

    println!("{}",ures.status())



}

fn main() {
    let mut rt = tokio::runtime::Runtime::new().unwrap();
    rt.block_on(a());

}

And How can I get the response json

When I run the code you gave me with

[dependencies]
hyper = {version="0.14", features=["client", "http1", "http2"]}
serde = {version="1", features=["derive"]}
serde_json = "1"
hyper-tls = "0.5"
url = "2"
tokio = {version="1", features = ["full"]}

it will run and print "403 Forbidden". What did you get?

403 was expected
it is working fine

but how do I get the json

To view the response json, you can do this:

let body = ures.into_body();
let body_vec = hyper::body::to_bytes(body).await.unwrap();
println!("{}", std::str::from_utf8(&body_vec).unwrap());

This prints this:

{
  "error": {
    "code": 403,
    "message": "The request is missing a valid API key.",
    "errors": [
      {
        "message": "The request is missing a valid API key.",
        "domain": "global",
        "reason": "forbidden"
      }
    ],
    "status": "PERMISSION_DENIED"
  }
}

However to actually get values, you need to use serde:

use serde::Deserialize;

#[derive(Deserialize)]
struct YoutubeError {
    error: YoutubeErrorInner,
}
#[derive(Deserialize)]
struct YoutubeErrorInner {
    code: u32,
    message: String,
    status: String,
    errors: Vec<YoutubeErrorListItem>,
}
#[derive(Deserialize)]
struct YoutubeErrorListItem {
    message: String,
    domain: String,
    reason: String,
}

You can now turn body_vec into a value of type YoutubeError like this:

let body = ures.into_body();
let body_vec = hyper::body::to_bytes(body).await.unwrap();
let e: YoutubeError = serde_json::from_slice(&body_vec).unwrap();

// You can now access individual values.
println!("{}", e.error.message);

The names of the structs is not important. You can name the structs whatever you want.

No @alice I am so done with errors will just get the json and will think what to do later

If the response is successful, you do it in the same way as how I got the data out of the error. Define a struct that has the right fields, then call serde_json::from_slice.