mirror of
https://github.com/0xMassi/webclaw.git
synced 2026-07-21 07:01:01 +02:00
feat: add webclaw-api rest server with axum
This commit is contained in:
parent
cb4a7f3f06
commit
f76df1fdba
3 changed files with 153 additions and 2 deletions
|
|
@ -23,6 +23,7 @@ COPY crates/webclaw-llm/Cargo.toml crates/webclaw-llm/Cargo.toml
|
||||||
COPY crates/webclaw-pdf/Cargo.toml crates/webclaw-pdf/Cargo.toml
|
COPY crates/webclaw-pdf/Cargo.toml crates/webclaw-pdf/Cargo.toml
|
||||||
COPY crates/webclaw-mcp/Cargo.toml crates/webclaw-mcp/Cargo.toml
|
COPY crates/webclaw-mcp/Cargo.toml crates/webclaw-mcp/Cargo.toml
|
||||||
COPY crates/webclaw-cli/Cargo.toml crates/webclaw-cli/Cargo.toml
|
COPY crates/webclaw-cli/Cargo.toml crates/webclaw-cli/Cargo.toml
|
||||||
|
COPY crates/webclaw-api/Cargo.toml crates/webclaw-api/Cargo.toml
|
||||||
|
|
||||||
# RUSTFLAGS (reqwest_unstable) — required by Impit's patched rustls
|
# RUSTFLAGS (reqwest_unstable) — required by Impit's patched rustls
|
||||||
COPY .cargo .cargo
|
COPY .cargo .cargo
|
||||||
|
|
@ -33,7 +34,8 @@ RUN mkdir -p crates/webclaw-core/src && echo "" > crates/webclaw-core/src/lib.rs
|
||||||
&& mkdir -p crates/webclaw-llm/src && echo "" > crates/webclaw-llm/src/lib.rs \
|
&& mkdir -p crates/webclaw-llm/src && echo "" > crates/webclaw-llm/src/lib.rs \
|
||||||
&& mkdir -p crates/webclaw-pdf/src && echo "" > crates/webclaw-pdf/src/lib.rs \
|
&& mkdir -p crates/webclaw-pdf/src && echo "" > crates/webclaw-pdf/src/lib.rs \
|
||||||
&& mkdir -p crates/webclaw-mcp/src && echo "fn main() {}" > crates/webclaw-mcp/src/main.rs \
|
&& mkdir -p crates/webclaw-mcp/src && echo "fn main() {}" > crates/webclaw-mcp/src/main.rs \
|
||||||
&& mkdir -p crates/webclaw-cli/src && echo "fn main() {}" > crates/webclaw-cli/src/main.rs
|
&& mkdir -p crates/webclaw-cli/src && echo "fn main() {}" > crates/webclaw-cli/src/main.rs \
|
||||||
|
&& mkdir -p crates/webclaw-api/src && echo "fn main() {}" > crates/webclaw-api/src/main.rs
|
||||||
|
|
||||||
# Pre-build dependencies (this layer is cached until Cargo.toml/lock changes)
|
# Pre-build dependencies (this layer is cached until Cargo.toml/lock changes)
|
||||||
RUN cargo build --release 2>/dev/null || true
|
RUN cargo build --release 2>/dev/null || true
|
||||||
|
|
@ -52,9 +54,10 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
# Copy both binaries
|
# Copy all binaries
|
||||||
COPY --from=builder /build/target/release/webclaw /usr/local/bin/webclaw
|
COPY --from=builder /build/target/release/webclaw /usr/local/bin/webclaw
|
||||||
COPY --from=builder /build/target/release/webclaw-mcp /usr/local/bin/webclaw-mcp
|
COPY --from=builder /build/target/release/webclaw-mcp /usr/local/bin/webclaw-mcp
|
||||||
|
COPY --from=builder /build/target/release/webclaw-api /usr/local/bin/webclaw-api
|
||||||
|
|
||||||
# Default: run the CLI
|
# Default: run the CLI
|
||||||
CMD ["webclaw"]
|
CMD ["webclaw"]
|
||||||
|
|
|
||||||
21
crates/webclaw-api/Cargo.toml
Normal file
21
crates/webclaw-api/Cargo.toml
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
[package]
|
||||||
|
name = "webclaw-api"
|
||||||
|
description = "REST API server for webclaw web extraction toolkit"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "webclaw-api"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
webclaw-core = { workspace = true }
|
||||||
|
webclaw-fetch = { workspace = true }
|
||||||
|
dotenvy = { workspace = true }
|
||||||
|
serde = { workspace = true }
|
||||||
|
serde_json = { workspace = true }
|
||||||
|
tokio = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
tracing-subscriber = { workspace = true }
|
||||||
|
axum = "0.7"
|
||||||
127
crates/webclaw-api/src/main.rs
Normal file
127
crates/webclaw-api/src/main.rs
Normal file
|
|
@ -0,0 +1,127 @@
|
||||||
|
use axum::{
|
||||||
|
extract::{Json, State},
|
||||||
|
http::{HeaderMap, StatusCode},
|
||||||
|
response::IntoResponse,
|
||||||
|
routing::post,
|
||||||
|
Router,
|
||||||
|
};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::env;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use webclaw_core::ExtractionOptions;
|
||||||
|
use webclaw_fetch::{FetchClient, FetchConfig};
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ScrapeRequest {
|
||||||
|
url: String,
|
||||||
|
// future proofing if user wants to expand features
|
||||||
|
#[serde(default = "default_format")]
|
||||||
|
format: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn default_format() -> String {
|
||||||
|
"json".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
struct ApiError {
|
||||||
|
error: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() {
|
||||||
|
dotenvy::dotenv().ok();
|
||||||
|
|
||||||
|
tracing_subscriber::fmt()
|
||||||
|
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
|
||||||
|
.init();
|
||||||
|
|
||||||
|
// The API key is defined in the Dokploy Environment Settings as API_KEY
|
||||||
|
let api_key = env::var("API_KEY").unwrap_or_else(|_| {
|
||||||
|
tracing::warn!("API_KEY environment variable not set! API is running without authentication.");
|
||||||
|
"".to_string()
|
||||||
|
});
|
||||||
|
|
||||||
|
let app = Router::new()
|
||||||
|
.route("/api/scrape", post(scrape_handler))
|
||||||
|
.with_state(api_key);
|
||||||
|
|
||||||
|
let port: u16 = env::var("PORT")
|
||||||
|
.unwrap_or_else(|_| "3000".to_string())
|
||||||
|
.parse()
|
||||||
|
.expect("PORT must be a number");
|
||||||
|
|
||||||
|
let addr = SocketAddr::from(([0, 0, 0, 0], port));
|
||||||
|
tracing::info!("webclaw-api listening on {}", addr);
|
||||||
|
|
||||||
|
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||||
|
axum::serve(listener, app).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn scrape_handler(
|
||||||
|
headers: HeaderMap,
|
||||||
|
State(api_key): State<String>,
|
||||||
|
Json(payload): Json<ScrapeRequest>,
|
||||||
|
) -> impl IntoResponse {
|
||||||
|
// Basic Authentication Check
|
||||||
|
if !api_key.is_empty() {
|
||||||
|
let auth_header = headers.get("authorization").and_then(|h| h.to_str().ok());
|
||||||
|
let expected = format!("Bearer {}", api_key);
|
||||||
|
if auth_header != Some(&expected) {
|
||||||
|
let err = ApiError {
|
||||||
|
error: "Unauthorized: Invalid API_KEY".to_string(),
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
StatusCode::UNAUTHORIZED,
|
||||||
|
Json(serde_json::to_value(err).unwrap()),
|
||||||
|
)
|
||||||
|
.into_response();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the fetch client with default webclaw configuration
|
||||||
|
let client = match FetchClient::new(FetchConfig::default()) {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
let err = ApiError {
|
||||||
|
error: format!("Failed to initialize fetch client: {}", e),
|
||||||
|
};
|
||||||
|
return (
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(serde_json::to_value(err).unwrap()),
|
||||||
|
)
|
||||||
|
.into_response();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Perform extraction
|
||||||
|
let options = ExtractionOptions::default();
|
||||||
|
match client.fetch_and_extract_with_options(&payload.url, &options).await {
|
||||||
|
Ok(result) => {
|
||||||
|
// Return JSON containing metadata, content, and the structured_data we fixed
|
||||||
|
match serde_json::to_value(result) {
|
||||||
|
Ok(val) => (StatusCode::OK, Json(val)).into_response(),
|
||||||
|
Err(e) => {
|
||||||
|
let err = ApiError {
|
||||||
|
error: format!("Failed to serialize result: {}", e),
|
||||||
|
};
|
||||||
|
(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
Json(serde_json::to_value(err).unwrap()),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let err = ApiError {
|
||||||
|
error: format!("Extraction failed for {}: {}", payload.url, e),
|
||||||
|
};
|
||||||
|
(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
Json(serde_json::to_value(err).unwrap()),
|
||||||
|
)
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue