跳到主要内容Rust 异步 Web 框架 Axum:核心原理与实战进阶 | 极客日志RustSaaS
Rust 异步 Web 框架 Axum:核心原理与实战进阶
本文深入探讨了 Rust 异步 Web 框架 Axum 的核心原理与高级用法。内容涵盖 Axum 的架构设计理念、请求提取器与响应映射器的定制、路由系统的嵌套与组合、中间件的应用、WebSocket 支持、流式处理、错误处理机制以及性能优化策略。通过实战案例展示了如何在微服务架构中集成 Axum,并总结了常见问题的解决方案。旨在帮助开发者全面掌握 Axum,构建高效可靠的异步 Web 应用。
指针猎手1 浏览 Rust 异步 Web 框架 Axum:核心原理与实战进阶
一、Axum 框架的架构与核心组件
1.1 Axum 的设计理念
Axum 是建立在 Tokio 异步运行时之上的 Rust Web 框架,由 Tokio 团队官方维护。它的设计初衷很明确:既要利用 Rust 的类型系统保证安全,又要提供足够的灵活性让开发者按需组合功能。
核心特点包括:
- 模块化:通过中间件、请求提取器和响应映射器,你可以像搭积木一样构建应用。
- 类型安全:编译期就能发现很多潜在错误,减少运行时崩溃。
- 异步优先:完全基于 Tokio,充分利用现代硬件的并发能力。
- 易用性:API 设计直观,新手能快速上手,老手也能发挥极限性能。
1.2 核心组件详解
1.2.1 请求提取器
请求提取器(Extractor)是 Axum 最迷人的部分之一。它负责从 HTTP 请求中解析数据,比如路径参数、查询字符串或请求体。内置了多种提取器,也支持自定义。
内置提取器示例:
use axum::{extract::Path, response::IntoResponse, routing::get, Router};
async fn get_user(Path(user_id): Path<i32>) -> impl IntoResponse {
format!("Get user with ID: {}", user_id)
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/users/:id", get(get_user));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
自定义提取器示例:
有时候我们需要更复杂的逻辑,比如从 Header 中提取用户代理。
use axum::{async_trait, extract::FromRequestParts, http::request::Parts, response::IntoResponse, routing::get, Router};
();
<()> {
= ();
(parts: & Parts, _state: &()) <, ::Rejection> {
parts.headers.()
.(|value| value.().())
.(|s| (s.()))
.(())
}
}
(agent: UserAgent) {
(, agent.)
}
() {
= Router::().(, (get_user_agent));
axum::Server::(&.().())
.(app.())
.
.();
}
struct
UserAgent
String
#[async_trait]
impl
FromRequestParts
for
UserAgent
type
Rejection
async
fn
from_request_parts
mut
->
Result
Self
Self
get
"user-agent"
and_then
to_str
ok
map
UserAgent
to_string
ok_or
async
fn
get_user_agent
->
impl
IntoResponse
format!
"User-Agent: {}"
0
#[tokio::main]
async
fn
main
let
app
new
route
"/user-agent"
get
bind
"0.0.0.0:3000"
parse
unwrap
serve
into_make_service
await
unwrap
1.2.2 响应映射器
响应映射器(IntoResponse)负责将函数返回值转换为 HTTP 响应。你不仅可以返回状态码和 JSON,还可以定义自己的结构体来统一 API 格式。
use axum::{http::StatusCode, response::IntoResponse, routing::get, Router};
use serde_json::json;
async fn get_user() -> impl IntoResponse {
(StatusCode::OK, json!({"id": 1, "name": "张三", "email": "[email protected]"}))
}
async fn create_user() -> impl IntoResponse {
(StatusCode::CREATED, "User created successfully")
}
async fn delete_user() -> impl IntoResponse {
StatusCode::NO_CONTENT
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/users/1", get(get_user))
.route("/users", get(create_user))
.route("/users/1", get(delete_user));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
自定义映射器示例:
如果你希望所有接口都返回统一的 {code, message, data} 结构,可以这样实现:
use axum::{http::StatusCode, response::IntoResponse, routing::get, Router};
use serde_json::json;
struct ApiResponse {
code: i32,
message: String,
data: serde_json::Value,
}
impl IntoResponse for ApiResponse {
fn into_response(self) -> axum::response::Response {
let status = if self.code == 200 { StatusCode::OK } else { StatusCode::BAD_REQUEST };
(status, json!({"code": self.code, "message": self.message, "data": self.data})).into_response()
}
}
async fn get_user() -> ApiResponse {
ApiResponse {
code: 200,
message: "Success".to_string(),
data: json!({"id": 1, "name": "张三", "email": "[email protected]"}),
}
}
async fn create_user() -> ApiResponse {
ApiResponse {
code: 400,
message: "Invalid request".to_string(),
data: serde_json::Value::Null,
}
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/users/1", get(get_user))
.route("/users", get(create_user));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
1.2.3 中间件
中间件是处理请求和响应的通用组件,适合做日志、认证、CORS 等横切关注点。Axum 允许在路由层添加中间件。
内置中间件示例:
使用 tower_http 的 TraceLayer 可以轻松记录 HTTP 请求详情。
use axum::{middleware, routing::get, Router};
use tower_http::trace::TraceLayer;
async fn handler() -> &'static str {
"Hello, World!"
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http());
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
自定义中间件示例:
如果你想计算响应时间并添加到 Header 中,可以这样写:
use axum::{async_trait, extract::FromRequestParts, http::request::Parts, middleware::Next, response::IntoResponse, routing::get, Router};
use std::time::Duration;
use tokio::time::Instant;
struct RequestTime(Duration);
#[async_trait]
impl FromRequestParts<()> for RequestTime {
type Rejection = ();
async fn from_request_parts(parts: &mut Parts, _state: &()) -> Result<Self, Self::Rejection> {
parts.extensions.get::<RequestTime>().copied().ok_or(())
}
}
async fn timing_middleware<B>(request: axum::http::Request<B>, next: Next<B>) -> impl IntoResponse {
let start = Instant::now();
let response = next.run(request).await;
let duration = start.elapsed();
let mut response = response.into_response();
response.headers().insert(
"X-Response-Time",
format!("{}ms", duration.as_millis()).parse().unwrap(),
);
response
}
async fn handler(time: RequestTime) -> impl IntoResponse {
format!("Response time: {}ms", time.0.as_millis())
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(handler))
.layer(middleware::from_fn(timing_middleware));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
二、Axum 框架的路由系统
2.1 路由的定义与匹配
Axum 的路由基于路径匹配,支持静态路径、动态参数和通配符。
use axum::{extract::Path, response::IntoResponse, routing::get, Router};
async fn get_user(Path(user_id): Path<i32>) -> impl IntoResponse {
format!("Get user with ID: {}", user_id)
}
async fn get_product(Path((category_id, product_id)): Path<(i32, i32)>) -> impl IntoResponse {
format!("Get product {} in category {}", product_id, category_id)
}
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/users/:id", get(get_user))
.route("/categories/:category_id/products/:product_id", get(get_product));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
通配符路径:
用于捕获未匹配的路径,通常作为 404 处理。
use axum::{extract::Path, response::IntoResponse, routing::get, Router};
async fn catch_all(Path(path): Path<String>) -> impl IntoResponse {
format!("Not found: {}", path)
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/:rest..", get(catch_all));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
2.2 路由的嵌套与组合
当项目变大时,路由管理变得重要。Axum 支持嵌套和合并路由,让代码结构更清晰。
use axum::{extract::Path, response::IntoResponse, routing::{get, post}, Router};
async fn get_user(Path(user_id): Path<i32>) -> impl IntoResponse {
format!("Get user with ID: {}", user_id)
}
async fn create_user() -> impl IntoResponse {
"User created successfully"
}
async fn delete_user(Path(user_id): Path<i32>) -> impl IntoResponse {
format!("Delete user with ID: {}", user_id)
}
#[tokio::main]
async fn main() {
let user_routes = Router::new()
.route("/:id", get(get_user).delete(delete_user))
.route("/", post(create_user));
let app = Router::new().nest("/users", user_routes);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
use axum::{response::IntoResponse, routing::get, Router};
async fn home() -> impl IntoResponse { "Home page" }
async fn about() -> impl IntoResponse { "About page" }
async fn contact() -> impl IntoResponse { "Contact page" }
#[tokio::main]
async fn main() {
let public_routes = Router::new()
.route("/", get(home))
.route("/about", get(about));
let contact_routes = Router::new().route("/contact", get(contact));
let app = public_routes.merge(contact_routes);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
2.3 路由的状态共享
Axum 允许通过 Router.with_state 共享全局状态,比如数据库连接池或配置信息。
use axum::{extract::State, response::IntoResponse, routing::get, Router};
use sqlx::PgPool;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db_pool: Arc<PgPool>,
config: crate::config::Config,
}
async fn get_user_count(State(state): State<AppState>) -> impl IntoResponse {
let count = sqlx::query_scalar!("SELECT COUNT(*) FROM users")
.fetch_one(&*state.db_pool)
.await
.unwrap();
format!("Total users: {}", count)
}
#[tokio::main]
async fn main() {
let config = crate::config::Config::from_env().unwrap();
let db_pool = Arc::new(sqlx::PgPool::connect(&config.db.url).await.unwrap());
let state = AppState { db_pool, config };
let app = Router::new()
.route("/users/count", get(get_user_count))
.with_state(state);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
三、Axum 框架的高级功能
3.1 WebSocket 支持
Axum 原生支持 WebSocket,非常适合实时通信场景。
use axum::{extract::WebSocketUpgrade, response::IntoResponse, routing::get, Router};
use tokio_tungstenite::tungstenite::Message;
async fn websocket_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
ws.on_upgrade(|mut socket| async move {
println!("WebSocket connection established");
while let Some(msg) = socket.next().await {
match msg {
Ok(Message::Text(text)) => {
println!("Received text message: {}", text);
socket.send(Message::Text(format!("Echo: {}", text))).await.unwrap();
}
Ok(Message::Binary(data)) => {
println!("Received binary message with length: {}", data.len());
socket.send(Message::Binary(data)).await.unwrap();
}
Ok(Message::Ping(ping)) => {
socket.send(Message::Pong(ping)).await.unwrap();
}
Ok(Message::Pong(_)) => {}
Ok(Message::Close(frame)) => {
println!("WebSocket connection closing: {:?}", frame);
}
Err(e) => {
println!("WebSocket error: {:?}", e);
}
}
}
println!("WebSocket connection closed");
})
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/ws", get(websocket_handler));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
3.2 流式请求与响应
use axum::{body::Body, response::IntoResponse, routing::get, Router};
use futures::stream::{self, StreamExt};
use http_body_util::Full;
async fn stream_response() -> impl IntoResponse {
let items = vec!["First item", "Second item", "Third item"];
let stream = stream::iter(items).map(|item| Ok::<_, std::io::Error>(Full::new(item.as_bytes())));
Body::wrap_stream(stream)
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/stream", get(stream_response));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
use axum::{body::Body, extract::Request, response::IntoResponse, routing::post, Router};
use futures::StreamExt;
async fn stream_request(request: Request<Body>) -> impl IntoResponse {
let mut body = request.into_body();
let mut buffer = Vec::new();
while let Some(chunk) = body.next().await {
buffer.extend_from_slice(&chunk.unwrap());
}
format!("Received {} bytes", buffer.len())
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/upload", post(stream_request));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
3.3 错误处理与响应
Axum 允许定义自定义错误类型,并自动转换为 HTTP 响应。
use axum::{extract::Path, http::StatusCode, response::{IntoResponse, Response}, routing::get, Router};
use serde_json::json;
use thiserror::Error;
#[derive(Error, Debug)]
enum AppError {
#[error("User not found")]
UserNotFound,
#[error("Invalid request")]
InvalidRequest,
#[error(transparent)]
Unexpected(#[from] anyhow::Error),
}
impl IntoResponse for AppError {
fn into_response(self) -> Response {
let (status, message) = match self {
AppError::UserNotFound => (StatusCode::NOT_FOUND, "User not found"),
AppError::InvalidRequest => (StatusCode::BAD_REQUEST, "Invalid request"),
AppError::Unexpected(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"),
};
(status, json!({"code": status.as_u16(), "message": message})).into_response()
}
}
async fn get_user(Path(user_id): Path<i32>) -> Result<impl IntoResponse, AppError> {
if user_id == 0 {
return Err(AppError::InvalidRequest);
}
if user_id == 999 {
return Err(AppError::UserNotFound);
}
Ok(json!({"id": user_id, "name": "张三", "email": "[email protected]"}))
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/users/:id", get(get_user));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
3.4 CORS 支持
跨域请求是前端开发常遇到的问题,Axum 通过 tower_http 轻松解决。
use axum::{response::IntoResponse, routing::get, Router};
use tower_http::cors::{Any, CorsLayer};
async fn handler() -> impl IntoResponse { "CORS enabled" }
#[tokio::main]
async fn main() {
let cors = CorsLayer::new()
.allow_origin(Any)
.allow_methods(Any)
.allow_headers(Any);
let app = Router::new()
.route("/", get(handler))
.layer(cors);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
3.5 身份验证与授权
JWT 是常见的鉴权方式,Axum 结合 Extractor 可以实现优雅的 JWT 校验。
use axum::{async_trait, extract::FromRequestParts, http::request::Parts, response::IntoResponse, routing::{get, post}, Router};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
#[derive(Debug, Serialize, Deserialize)]
struct Claims {
sub: String,
exp: usize,
}
impl Claims {
fn new(sub: &str) -> Self {
let expiration = SystemTime::now()
.checked_add(Duration::from_secs(3600))
.unwrap()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as usize;
Claims { sub: sub.to_string(), exp: expiration }
}
}
struct JwtSecret(String);
#[async_trait]
impl FromRequestParts<JwtSecret> for Claims {
type Rejection = ();
async fn from_request_parts(parts: &mut Parts, state: &JwtSecret) -> Result<Self, Self::Rejection> {
parts.headers.get("authorization")
.and_then(|value| value.to_str().ok())
.and_then(|s| s.strip_prefix("Bearer ").map(|s| s.to_string()))
.and_then(|token| {
decode::<Claims>(&token, &DecodingKey::from_secret(state.0.as_bytes()), &Validation::new(Algorithm::HS256)).ok()
})
.map(|data| data.claims)
.ok_or(())
}
}
async fn login() -> impl IntoResponse {
let claims = Claims::new("user123");
let token = encode(&Header::new(Algorithm::HS256), &claims, &EncodingKey::from_secret(b"secret")).unwrap();
token
}
async fn protected(claims: Claims) -> impl IntoResponse {
format!("Welcome, {}", claims.sub)
}
#[tokio::main]
async fn main() {
let secret = JwtSecret("secret".to_string());
let public_routes = Router::new().route("/login", post(login));
let protected_routes = Router::new()
.route("/protected", get(protected))
.with_state(secret.clone());
let app = public_routes.merge(protected_routes);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
四、Axum 框架的性能优化
4.1 工作线程数配置
Tokio 的运行线程数直接影响并发能力,建议根据 CPU 核心数调整。
use axum::{response::IntoResponse, routing::get, Router};
use num_cpus;
use tokio::runtime::Builder;
async fn handler() -> impl IntoResponse { "Hello, World!" }
fn main() {
let runtime = Builder::new_multi_thread()
.worker_threads(num_cpus::get())
.max_blocking_threads(10)
.build()
.unwrap();
runtime.block_on(async {
let app = Router::new().route("/", get(handler));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
});
}
4.2 请求提取器与响应映射器的优化
use axum::{async_trait, extract::{FromRef, FromRequestParts}, http::request::Parts, response::IntoResponse, routing::get, Router};
use serde_json::json;
use std::sync::Arc;
#[derive(Clone)]
struct AppState {
db_pool: Arc<sqlx::PgPool>,
config: crate::config::Config,
}
struct UserExtractor(i32);
#[async_trait]
impl FromRef<AppState> for crate::db::DbPool {
fn from_ref(state: &AppState) -> Self {
state.db_pool.clone()
}
}
#[async_trait]
impl FromRequestParts<AppState> for UserExtractor {
type Rejection = ();
async fn from_request_parts(parts: &mut Parts, state: &AppState) -> Result<Self, Self::Rejection> {
parts.headers.get("user-id")
.and_then(|value| value.to_str().ok())
.and_then(|s| s.parse().ok())
.map(|id| UserExtractor(id))
.ok_or(())
}
}
async fn get_user(extractor: UserExtractor) -> impl IntoResponse {
json!({"id": extractor.0, "name": "张三", "email": "[email protected]"})
}
#[tokio::main]
async fn main() {
let config = crate::config::Config::from_env().unwrap();
let db_pool = Arc::new(sqlx::PgPool::connect(&config.db.url).await.unwrap());
let state = AppState { db_pool, config };
let app = Router::new()
.route("/users", get(get_user))
.with_state(state);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
4.3 中间件的优化
合理使用 tower_http 的内置中间件可以减少开销。
use axum::{response::IntoResponse, routing::get, Router};
use tower_http::trace::{DefaultMakeSpan, DefaultOnResponse, TraceLayer};
async fn handler() -> impl IntoResponse { "Hello, World!" }
#[tokio::main]
async fn main() {
let app = Router::new()
.route("/", get(handler))
.layer(TraceLayer::new_for_http()
.make_span_with(DefaultMakeSpan::new().include_headers(true))
.on_response(DefaultOnResponse::new().include_headers(true)),
);
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
五、实战项目的 Axum 应用
在实际微服务架构中,Axum 常被用于构建各类业务服务。
5.1 用户同步服务
use axum::{http::StatusCode, response::IntoResponse, routing::{get, post}, Router};
use user_sync_service::sync;
use user_sync_service::config::Config;
async fn health() -> impl IntoResponse {
StatusCode::OK
}
async fn sync_users() -> impl IntoResponse {
match sync::sync_users().await {
Ok(_) => StatusCode::ACCEPTED,
Err(e) => {
tracing::error!("User sync failed: {:?}", e);
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
#[tokio::main]
async fn main() {
let config = Config::from_env().unwrap();
let app = Router::new()
.route("/health", get(health))
.route("/api/users/sync", post(sync_users));
axum::Server::bind(&"0.0.0.0:3000".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
5.2 订单处理服务
use axum::{http::StatusCode, response::IntoResponse, routing::{get, post}, Router};
use order_processing_service::process;
use order_processing_service::config::Config;
async fn health() -> impl IntoResponse {
StatusCode::OK
}
async fn process_order() -> impl IntoResponse {
match process::process_orders().await {
Ok(_) => StatusCode::ACCEPTED,
Err(e) => {
tracing::error!("Order processing failed: {:?}", e);
StatusCode::INTERNAL_SERVER_ERROR
}
}
}
#[tokio::main]
async fn main() {
let config = Config::from_env().unwrap();
let app = Router::new()
.route("/health", get(health))
.route("/api/orders/process", post(process_order));
axum::Server::bind(&"0.0.0.0:3001".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
5.3 监控服务
use axum::{extract::WebSocketUpgrade, http::StatusCode, response::IntoResponse, routing::{get, post}, Router};
use monitoring_service::monitor;
use monitoring_service::config::Config;
async fn health() -> impl IntoResponse {
StatusCode::OK
}
async fn websocket_handler(ws: WebSocketUpgrade) -> impl IntoResponse {
monitor::handle_websocket_connection(ws).await
}
#[tokio::main]
async fn main() {
let config = Config::from_env().unwrap();
let app = Router::new()
.route("/health", get(health))
.route("/ws", get(websocket_handler));
axum::Server::bind(&"0.0.0.0:3002".parse().unwrap())
.serve(app.into_make_service())
.await
.unwrap();
}
六、Axum 框架的常见问题与解决方案
6.1 请求提取器的类型不匹配
问题:提取器类型与请求数据不一致会导致编译或运行时错误。
解决:确保路径参数 :id 的类型(如 i32)与提取器定义一致。
6.2 响应映射器的返回值类型不匹配
问题:返回值未实现 IntoResponse trait。
解决:使用 (StatusCode, json) 或自定义实现了该 Trait 的结构体。
6.3 中间件的顺序问题
问题:错误的中间件顺序导致逻辑失效。
解决:遵循从外到内的原则,例如认证放在路由前,CORS 放在最外层。
6.4 状态共享的生命周期问题
问题:状态生命周期管理不当引发编译错误。
解决:确保状态类型实现了 Clone,并使用 Arc 管理共享数据。
七、总结
Axum 凭借 Tokio 生态的强大支持,已成为 Rust Web 开发的首选框架之一。它的模块化设计、类型安全保障以及灵活的扩展能力,使其在处理高并发、低延迟场景时表现优异。掌握 Axum 的核心组件、路由机制及性能调优技巧,能帮助我们在实际项目中构建出高质量、可维护的异步 Web 服务。
相关免费在线工具
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
- JSON 压缩
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
- JSON美化和格式化
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online