FeignHttp
FeignHttp is a declarative HTTP client. Based on rust macros.
Features
- Easy to use
- Asynchronous request
- Configurable timeout settings
- Supports plain text and json
- Selectable HTTP backends (reqwest or isahc)
Usage
FeignHttp mark macros on asynchronous functions, you need a runtime for support async/await. You can use async-std or tokio.
async-std:
[dependencies]
async-std = { version = "1", features = ["attributes", "tokio1"] }
The feature tokio1
is need when use reqwest as the HTTP backend.
tokio:
[dependencies]
tokio = { version = "1", features = ["full"] }
Add feignhttp in your Cargo.toml
and use default feature:
feignhttp = { version = "0.2" }
Then add the following code:
use feignhttp::get;
#[get("https://api.github.com")]
async fn github() -> feignhttp::Result<String> {}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = github().await?;
println!("result: {}", r);
Ok(())
}
The get
attribute macro specifies get request, feignhttp::Result
specifies the return result. It will send get request to https://api.github.com
and receive a plain text body.
Using non-default HTTP backend:
feignhttp = { version = "0.2", default-features = false, features = ["isahc-client"] }
The default-features = false
option disable default reqwest.
Making a POST request
For a post request, you should use the post
attribute macro to specify request method and use a body
attribute to specify a request body.
use feignhttp::post;
#[post(url = "https://httpbin.org/anything")]
async fn post_data(#[body] text: String) -> feignhttp::Result<String> {}
The #[body]
mark a request body. Function parameter text
is a String type, it will put in the request body as plain text. String and &str will be put as plain text into the request body.
Paths
Using path
to specify path value:
use feignhttp::get;
#[get("https://api.github.com/repos/{owner}/{repo}")]
async fn repository(
#[path] owner: &str,
#[path] repo: String,
) -> feignhttp::Result<String> {}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = repository("dxx", "feignhttp".to_string()).await?;
println!("repository result: {}", r);
Ok(())
}
dxx
will replace {owner}
and feignhttp
will replace {repo}
, the url to be send will be https://api.github.com/repos/dxx/feignhttp
. You can specify a path name like #[path("owner")]
.
Query Parameters
Using param
to specify query parameter:
use feignhttp::get;
#[get("https://api.github.com/repos/{owner}/{repo}/contributors")]
async fn contributors(
#[path("owner")] user: &str,
#[path] repo: &str,
#[param] page: u32,
) -> feignhttp::Result<String> {}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = contributors (
"dxx",
"feignhttp",
1,
).await?;
println!("contributors result: {}", r);
Ok(())
}
The page
parameter will as query parameter in the url. An url which will be send is https://api.github.com/repos/dxx/feignhttp?page=1
.
Note: A function parameter without
param
attribute will as a query parameter by default.
Headers
Using header
to specify request header:
use feignhttp::get;
#[get("https://api.github.com/repos/{owner}/{repo}/commits")]
async fn commits(
#[header] accept: &str,
#[path] owner: &str,
#[path] repo: &str,
#[param] page: u32,
#[param] per_page: u32,
) -> feignhttp::Result<String> {}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = commits(
"application/vnd.github.v3+json",
"dxx",
"feignhttp",
1,
5,
)
.await?;
println!("commits result: {}", r);
Ok(())
}
A header accept:application/vnd.github.v3+json
will be send.
URL constant
We can use constant to maintain all urls of request:
use feignhttp::get;
const GITHUB_URL: &str = "https://api.github.com";
#[get(GITHUB_URL, path = "/repos/{owner}/{repo}/languages")]
async fn languages(
#[path] owner: &str,
#[path] repo: &str,
) -> feignhttp::Result<String> {}
Url constant must be the first metadata in get attribute macro. You also can specify metadata key:
#[get(url = GITHUB_URL, path = "/repos/{owner}/{repo}/languages")]
async fn languages(
#[path] owner: &str,
#[path] repo: &str,
) -> feignhttp::Result<String> {}
JSON
Serde is a framework for serializing and deserializing Rust data structures. When use json, you should add serde in Cargo.toml
:
[dependencies]
serde = { version = "1", features = ["derive"] }
Here is an example of getting json:
use feignhttp::get;
use serde::Deserialize;
// Deserialize: Specifies deserialization
#[derive(Debug, Deserialize)]
struct IssueItem {
pub id: u32,
pub number: u32,
pub title: String,
pub url: String,
pub repository_url: String,
pub state: String,
pub body: Option<String>,
}
const GITHUB_URL: &str = "https://api.github.com";
#[get(url = GITHUB_URL, path = "/repos/{owner}/{repo}/issues")]
async fn issues(
#[path] owner: &str,
#[path] repo: &str,
page: u32,
per_page: u32,
) -> feignhttp::Result<Vec> {}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = issues("octocat", "hello-world", 1, 2).await?;
println!("issues: {:#?}", r);
Ok(())
}
This issues function return Vec
, it is deserialized according to the content of the response.
Send a json request:
use feignhttp::post;
use serde::Serialize;
#[derive(Debug, Serialize)]
struct User {
id: i32,
name: String,
}
#[post(url = "https://httpbin.org/anything")]
async fn post_user(#[body] user: User) -> feignhttp::Result<String> {}
#[async_std::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let user = User {
id: 1,
name: "jack".to_string(),
};
let r = post_user(user).await?;
println!("{}", r);
Ok(())
}
See here for a complete example.
Using structure
Structure is a good way to manage requests. Define a structure and then define a large number of request methodsοΌ
use feignhttp::feign;
const GITHUB_URL: &str = "https://api.github.com";
struct Github;
#[feign(url = GITHUB_URL)]
impl Github {
#[get]
async fn home() -> feignhttp::Result<String> {}
#[get("/repos/{owner}/{repo}")]
async fn repository(
#[path] owner: &str,
#[path] repo: &str,
) -> feignhttp::Result<String> {}
// ...
// Structure method still send request
#[get(path = "/repos/{owner}/{repo}/languages")]
async fn languages(
&self,
#[path] owner: &str,
#[path] repo: &str,
) -> feignhttp::Result<String> {}
}
See here for a complete example.
Timeout configuration
If you need to configure the timeout, use connect_timeout
and timeout
to specify connect timeout and read timeout.
Connect timeout:
#[get(url = "http://xxx.com", connect_timeout = 3000)]
async fn connect_timeout() -> feignhttp::Result<String> {}
Read timeout:
#[get(url = "https://httpbin.org/delay/5", timeout = 3000)]
async fn timeout() -> feignhttp::Result<String> {}
License
FeignHttp is provided under the MIT license. See LICENSE.