Expand description
§FeignHTTP
FeignHTTP is a declarative HTTP client. Based on rust macros.
Here are some features:
- Easy to use
- Asynchronous request
- Supports plain text, form, multipart and JSON
- Configurable timeout settings
- Friendly error handling
- Selectable HTTP backends (reqwest, reqwest-middleware or isahc)
§Table of contents
- Usage
- Making a POST request
- Paths
- URL
- Query Parameters
- Headers
- Form
- Multipart
- JSON
- Using Trait
- Timeout
- Params
- Configuration
- Customize Client
- Error Handling
- Debug Logs
- Optional Features
§Usage
FeignHTTP mark macros on asynchronous functions, you need a runtime for support async/await. You can use tokio.
tokio:
[dependencies]
tokio = { version = "1", features = ["full"] }Add feignhttp in your Cargo.toml and use default feature:
feignhttp = { version = "0.6" }Then add the following code:
use feignhttp::get;
#[get("https://api.github.com")]
async fn github() -> feignhttp::Result<String> {}
#[tokio::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<String> 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.6", 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("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. Before send request, a header content-type: text/plain will be added automatically.
§Paths
Using path to specify path value:
use feignhttp::get;
#[get("https://api.github.com/repos/{owner}/{repo}")]
async fn repository(
#[path("owner")] user: &str,
#[path] repo: String,
) -> feignhttp::Result<String> {}
#[tokio::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")].
§URL
You 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:
use feignhttp::get;
const GITHUB_URL: &str = "https://api.github.com";
#[get(url = GITHUB_URL, path = "/repos/{owner}/{repo}/languages")]
async fn languages(
#[path] owner: &str,
#[path] repo: &str,
) -> feignhttp::Result<String> {}See here for more examples.
§Query Parameters
Using query to specify query parameter:
use feignhttp::get;
#[get("https://api.github.com/repos/{owner}/{repo}/contributors")]
async fn contributors(
#[path] owner: &str,
#[path] repo: &str,
#[query] page: u32,
) -> feignhttp::Result<String> {}
#[tokio::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 query attribute will as a query parameter by default.
§Headers
Using header to specify request header:
use feignhttp::get;
#[get("https://api.github.com/repos/dxx/feignhttp/commits")]
async fn commits(
#[header] accept: &str,
#[query] page: u32,
#[query] per_page: u32,
) -> feignhttp::Result<String> {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = commits("application/vnd.github.v3+json", 1, 5).await?;
println!("commits result: {}", r);
Ok(())
}A header accept: application/vnd.github.v3+json will be added.
You also can use headers key to specify one or more headers in get attribute:
use feignhttp::get;
#[get("https://httpbin.org/headers", headers = "key1: value1; key2: value2")]
async fn headers() -> feignhttp::Result<String> {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = headers().await?;
println!("headers result: {}", r);
Ok(())
}The format of headers must be header-key1: header-value1; header-key2: header-value2;....
§Form
Using form to specify form parameter:
use feignhttp::post;
#[post(url = "https://httpbin.org/anything")]
async fn post_user(
#[form] id: i32,
#[form] name: &str,
) -> feignhttp::Result<String> {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let r = post_user(1, "jack").await?;
println!("{}", r);
Ok(())
}Before sending a request, a header content-type: application/x-www-form-urlencoded will be added automatically.
See here for more examples.
§Multipart
Using part for form fields and file for file uploads:
feignhttp = { version = "<version>", features = ["reqwest-multipart"] }use feignhttp::post;
use std::path::PathBuf;
#[post("https://httpbin.org/post")]
async fn upload_file(
#[file("file")] file: PathBuf,
#[part("name")] name: &str,
) -> feignhttp::Result<String> {}You can also specify content_type and filename:
use feignhttp::post;
use std::path::PathBuf;
#[post("https://httpbin.org/post")]
async fn upload_file(
#[file("file", content_type = "image/png", filename = "custom.png")] file: PathBuf,
#[part("name")] name: &str,
) -> feignhttp::Result<String> {}Supported file types: PathBuf, std::fs::File, Vec<u8>.
See here for a complete example.
§JSON
Serde is a framework for serializing and deserializing Rust data structures. When use json, you should add serde in Cargo.toml:
serde = { version = "1", features = ["derive"] }You also need enable json feature:
feignhttp = { version = "<version>", features = ["reqwest-json"] }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<IssueItem>> {}
#[tokio::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<IssueItem>, 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> {}
#[tokio::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(())
}Before send request, a header content-type: application/json will be added automatically.
See here for a complete example.
§Using Trait
Trait is a good way to manage requests. Define a Trait and then define a large number of request methods:
use feignhttp::{Context, FeignClientBuilder, feign};
#[derive(Context)]
struct GithubContext {
// `url_path` and `param` are used to set the shared data.
// The other two for share data are `header` and `query`.
#[url_path("owner")]
user: &'static str,
#[url_path]
repo: &'static str,
#[param]
accept: &'static str,
}
#[feign(
url = "https://api.github.com/repos/{owner}/{repo}",
headers = "Accept: {accept}"
)]
pub trait Github {
// The method must have a self argument.
#[get]
async fn home(&self) -> feignhttp::Result<String>;
#[get(path = "", headers = "Accept: application/json")]
async fn repository(&self) -> feignhttp::Result<String>;
#[get("/commits")]
async fn commits(
&self,
#[header] accept: &str,
#[query] page: u32,
#[query] per_page: u32,
) -> feignhttp::Result<String>;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let context = GithubContext {
user: "dxx",
repo: "feignhttp",
accept: "*/*",
};
let github = Github::builder().context(context).build()?;
let r = github.home().await?;
println!("github result: {}\n", r);
Ok(())
}See here for a complete example.
§Timeout
If you need to configure the timeout, use timeout to specify total timeout.
Total timeout:
use feignhttp::get;
#[get(url = "https://httpbin.org/delay/5", timeout = 3000)]
async fn timeout() -> feignhttp::Result<String> {}See here for more example.
§Params
Sometimes you need dynamic values, like config or others. param is designed to support such ability. You can use
param to specify a value that used as a dynamic replacement.
use feignhttp::get;
#[get(url = "https://httpbin.org/delay/5", timeout = "{time}")]
async fn timeout(#[param] time: u16) -> feignhttp::Result<String> {}When call timeout function, the time’s value will replace the {time}.
Another example is replace headers:
use feignhttp::get;
#[get("https://httpbin.org/headers", headers = "token: {token}")]
async fn headers(#[param] token: &str) -> feignhttp::Result<String> {}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// A request with a header `token: ZmVpZ25odHRw`.
let res = headers("ZmVpZ25odHRw").await?;
println!("headers: {}", res);
Ok(())
}Note: param can’t replace placeholder in url or path.
§Configuration
You can use ClientConfig to configure timeout settings for trait-based clients:
use feignhttp::{ClientConfig, FeignClientBuilder, feign};
#[feign(url = "https://api.github.com")]
pub trait GitHub {
#[get("/repos/{owner}/{repo}")]
async fn repository(
&self,
#[path] owner: &str,
#[path] repo: &str,
) -> feignhttp::Result<String>;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig {
connect_timeout: Some(5000), // 5 seconds
timeout: Some(10000), // 10 seconds
..Default::default()
};
let github = GitHub::builder().config(config).build()?;
let r = github.repository("dxx", "feignhttp").await?;
println!("repository: {}\n", r);
Ok(())
}See here for a complete example.
§Customize Client
You can customize the HTTP client by using ClientWrapper::with_client. This allows you to configure
the client with custom settings before building the feign client:
use feignhttp::{ClientWrapper, Client, FeignClientBuilder, feign};
#[feign(url = "https://api.github.com")]
pub trait GitHub {
#[get("/users/{user}")]
async fn user(&self, #[path] user: &str) -> feignhttp::Result<String>;
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Build a custom reqwest client with default headers
let client = reqwest::Client::builder()
.user_agent("Feign HTTP")
.build()?;
let client_wrapper = ClientWrapper::with_client(client)?;
let github = GitHubBuilder::build_with_client(client_wrapper)?;
let r = github.user("dxx").await?;
println!("{}", r);
Ok(())
}See here for more examples.
§Error Handling
FeignHTTP use feignhttp::Result to receive return result. The error is
Error struct which has some error kinds and some useful methods.
ErrorKind is used to indicate an error type.
Url is incorrect:
use feignhttp::get;
#[get("httpbin.org/anything")]
async fn url_error() -> feignhttp::Result<()> {}
#[tokio::main]
async fn main() {
match url_error().await {
Err(err) => {
// Build client error.
if err.is_build_error() {
println!("url_error: {}", err);
}
}
_ => {}
}
}Parse config error:
use feignhttp::get;
#[get(url = "https://httpbin.org/delay/3", timeout = "abc")]
async fn config_error() -> feignhttp::Result<()> {}
#[tokio::main]
async fn main() {
match config_error().await {
Err(err) => {
// Parse config error.
if err.is_config_error() {
println!("config_error: {}", err);
}
}
_ => {}
}
}When parsing the configuration, an error is thrown if the value is incorrect. timeout is an integer type, when parse abc to integer will throw an error.
HTTP status is an important indicator of response health. The status code can tell whether the client or server encountered an error. The following is an example of handling through HTTP status:
use feignhttp::{get, ErrorKind};
#[get(url = "https://httpbin.org/123")]
async fn status_error() -> feignhttp::Result<()> {}
#[tokio::main]
async fn main() {
match status_error().await {
Err(err) => {
// Status error.
if err.is_status_error() {
println!("status_error: {}", err);
}
if let ErrorKind::Status(status) = err.error_kind() {
println!("status error code: {}", status.as_u16());
if status.is_client_error() {
// Handle error.
}
if status.is_server_error() {
// Handle error.
}
}
}
_ => {}
}
}The status is StatusCode struct that supply by http crate. For more examples, see here.
§Debug Logs
FeignHTTP logs some useful information about requests and responses with the log crate.
To enable the log information, specify log feature in Cargo.toml, then set the log level to debug.
features = ["log"]§Optional Features
The following features are available. The default features are reqwest-client
- reqwest-client: Use
reqwestas the HTTP backend - reqwest-middleware-client: Use
reqwest-middlewareas the HTTP backend - isahc-client: Use
isahcas the HTTP backend - reqwest-json: Enable json for
reqwestbackend - reqwest-middleware-json: Enable json for
reqwest-middlewarebackend - isahc-json: Enable json for
isahcbackend - reqwest-multipart: Enable multipart for
reqwestbackend - reqwest-middleware-multipart: Enable multipart for
reqwest-middlewarebackend - isahc-multipart: Enable multipart for
isahcbackend - json: Enable json serialization and deserialization
- multipart: Enable multipart support (required for file uploads)
- log: Enable request and response logs
Structs§
- Client
Config - Configuration of an HTTP client.
- Client
Wrapper - Error
- The errors that may occur when processing a request.
- Http
Client - An HTTP client to create ClientWrapper.
- Request
Builder - An HTTP requet builder to make requests.
- Request
Config - Configuration of an HTTP request.
- Request
Wrapper - A wrapper of HTTP request.
- Response
Wrapper - A wrapper of HTTP response.
Enums§
Traits§
- Client
- A trait for HTTP client.
- Feign
Client Builder - Define a trait for code generation.
- Feign
Context - Http
Request - A trait of HTTP request.
- Http
Response - A trait of HTTP response.
Type Aliases§
- Result
- A
Resultalias.