con un clic
reqwest
Rust reqwest HTTP 클라이언트 핵심 패턴 — GET/POST, JSON, 헤더, 스트리밍, 에러 처리, Client 재사용
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Menú
Rust reqwest HTTP 클라이언트 핵심 패턴 — GET/POST, JSON, 헤더, 스트리밍, 에러 처리, Client 재사용
Instalar con Codex o Claude Copia este prompt, pégalo en Codex, Claude u otro asistente, y deja que revise la página de la skill y la instale por ti.
Basado en la clasificación ocupacional SOC
Spring Security 5.5.x + jjwt 0.10.7 레거시 JWT 인증 - WebSecurityConfigurerAdapter, OncePerRequestFilter, javax.servlet 환경
Spring Boot 3.x + Spring Security 6.x + jjwt 0.12.x 기반 모던 JWT 인증 패턴. SecurityFilterChain Bean, 람다 DSL, jakarta.servlet, Virtual Threads 적용
Unity 6 LTS 2D 모바일 게임용 uGUI 시스템 전문 스킬. Canvas/RectTransform/TextMeshPro, 모바일 UI 패턴(팝업·무한 스크롤·광고·IAP), 성능 최적화, UI Toolkit과의 선택 기준 포함.
아크라시아(akrasia, 자제력 없음) 학술 논쟁의 핵심 구도와 주요 연구자·문헌을 빠르게 파악할 수 있는 도메인 지식 스킬. 도덕윤리교육 전공 대학원생(석/박사)이 학위논문·KCI 투고·세미나 준비 시 고대–현대–한국 학계–도덕심리학 흐름을 한 번에 짚도록 구성. <example>사용자: "아리스토텔레스의 propeteia와 astheneia 구분을 인용하려는데 출처를 알려줘"</example> <example>사용자: "데이비슨이 의지박약을 어떻게 가능하다고 봤는지 핵심 논증을 정리해줘"</example> <example>사용자: "한국 도덕교육 학계에서 아크라시아 다룬 논문 있어?"</example>
아리스토텔레스 『니코마코스 윤리학』에서 akrasia(자제력없음)와 akolasia(무절제)의 5축 차이를 정밀하게 정리한 학위논문 자료 스킬. NE VII.4 1147b20-1148b14, VII.8 1150b29-1151a28, III.10-12 1117b23-1119b18 절별 분해와 표준 학자 해석(Bostock, Broadie-Rowe, Pakaluk, Hursthouse, Charles 등)을 포함. 도덕교육 적용을 위한 두 상태 차이의 함의 및 한국어 번역어 처리 권장안 제공. <example>사용자: "akrates와 akolastos를 prohairesis 측면에서 어떻게 구분해야 하나요?"</example> <example>사용자: "NE VII.4의 ἁπλῶς akrasia가 akolasia와 어떻게 갈라지는지 절별 분해해주세요"</example> <example>사용자: "Hursthouse의 연속체 모델을 도덕교육 적용 절에서 어떻게 활용할 수 있나요?"</example>
한국 위기 대응 자원(자살·자해·정신건강·여성·청소년·노인·다문화) 핫라인과 앱·챗봇 안전 가드 응답 패턴 종합. 꿈 해몽·정신건강 앱 등 자가 진단/감정 콘텐츠 도메인에서 위험 신호 포착 시 안전한 자원 안내 문구를 작성할 때 참조. <example>사용자: "꿈 해몽 앱에 위기 안내 문구를 어떻게 넣을까?"</example> <example>사용자: "한국에서 자살예방 핫라인 번호가 어떻게 바뀌었지?"</example> <example>사용자: "정신건강 챗봇 안전 가드 응답 템플릿을 짜줘"</example>
| name | reqwest |
| description | Rust reqwest HTTP 클라이언트 핵심 패턴 — GET/POST, JSON, 헤더, 스트리밍, 에러 처리, Client 재사용 |
소스: https://docs.rs/reqwest/latest/reqwest/ | https://github.com/seanmonstar/reqwest 검증일: 2026-06-20
주의: 이 문서는 reqwest 0.12.x 기준으로 작성되었습니다. 0.13.x (최신 0.13.11, 2026-05-28 릴리즈)가 출시되어 Breaking Change가 있으므로 신규 프로젝트는 마이그레이션 노트를 참조하세요.
reqwest 0.13으로의 마이그레이션 시 주요 Breaking Change (0.12 → 0.13):
- rustls가 기본 TLS로 변경(aws-lc 기반),
rustls-tlsfeature가rustls로 rename- MSRV이 1.85로 상향
ClientBuilder::dns_resolver가dns_resolver2로 교체reqwest::Url의 serde Deserialize 지원이 별도 feature 필요- 0.11.x 이하(hyper 0.14 기반) 대비 API 변경 있음
# reqwest 0.12.x (현재 문서 기준) — 0.13이 2026-05-28 릴리즈됨, 마이그레이션 주의사항 참조
[dependencies]
reqwest = { version = "0.12", features = ["json", "stream"] }
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
json feature: .json() 메서드 활성화 (serde 연동)stream feature: bytes_stream() 메서드 활성화 (스트리밍 응답)Client는 내부에 커넥션 풀을 유지한다. 요청마다 새로 만들지 않고 재사용해야 한다.
use reqwest::Client;
// 기본 Client — 대부분의 경우 충분
let client = Client::new();
// ClientBuilder — 타임아웃, 기본 헤더, 프록시 등 설정 시
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION};
use std::time::Duration;
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, HeaderValue::from_str("Bearer sk-xxx")?);
let client = Client::builder()
.default_headers(headers) // 모든 요청에 적용
.timeout(Duration::from_secs(30)) // 요청별 타임아웃
.connect_timeout(Duration::from_secs(10))
.pool_max_idle_per_host(10) // 호스트별 유휴 커넥션 수
.build()?;
Client::new vs ClientBuilder:
Client::new(): 기본 설정, 빠른 프로토타이핑Client::builder(): 타임아웃, 기본 헤더, TLS 설정 등 커스터마이징 필요 시// 단순 텍스트 응답
let body = client.get("https://httpbin.org/get")
.send()
.await?
.text()
.await?;
// JSON 역직렬화
#[derive(serde::Deserialize)]
struct ApiResponse {
origin: String,
url: String,
}
let resp: ApiResponse = client.get("https://httpbin.org/get")
.send()
.await?
.json()
.await?;
#[derive(serde::Serialize)]
struct CreateRequest {
model: String,
max_tokens: u32,
messages: Vec<Message>,
}
#[derive(serde::Serialize)]
struct Message {
role: String,
content: String,
}
let request_body = CreateRequest {
model: "claude-sonnet-4-6".into(),
max_tokens: 1024,
messages: vec![Message {
role: "user".into(),
content: "Hello".into(),
}],
};
let response = client.post("https://api.anthropic.com/v1/messages")
.header("x-api-key", api_key)
.header("anthropic-version", "2023-06-01")
.json(&request_body) // Content-Type: application/json 자동 설정
.send()
.await?;
.json(&body) 호출 시 Content-Type: application/json 헤더가 자동으로 설정된다.
use reqwest::header::{HeaderMap, HeaderValue, AUTHORIZATION, CONTENT_TYPE};
// 방법 1: 요청별 개별 헤더
let resp = client.post(url)
.header(AUTHORIZATION, format!("Bearer {}", token))
.header(CONTENT_TYPE, "application/json")
.header("x-api-key", api_key) // 커스텀 헤더는 문자열 사용
.send()
.await?;
// 방법 2: HeaderMap 일괄 설정
let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, HeaderValue::from_str(&format!("Bearer {}", token))?);
headers.insert("x-api-key", HeaderValue::from_str(api_key)?);
let resp = client.post(url)
.headers(headers)
.send()
.await?;
Claude API의 Server-Sent Events 스트리밍 응답 처리 패턴.
use futures_util::StreamExt;
let response = client.post("https://api.anthropic.com/v1/messages")
.header("x-api-key", api_key)
.header("anthropic-version", "2023-06-01")
.json(&serde_json::json!({
"model": "claude-sonnet-4-6",
"max_tokens": 1024,
"stream": true,
"messages": [{"role": "user", "content": "Hello"}]
}))
.send()
.await?;
// stream feature 필요
let mut stream = response.bytes_stream();
while let Some(chunk) = stream.next().await {
let chunk = chunk?;
let text = String::from_utf8_lossy(&chunk);
// SSE 파싱: "data: " 접두사 처리
for line in text.lines() {
if let Some(data) = line.strip_prefix("data: ") {
if data == "[DONE]" {
break;
}
// JSON 파싱
let event: serde_json::Value = serde_json::from_str(data)?;
// 이벤트 처리...
}
}
}
주의: SSE 청크가 이벤트 경계와 정확히 일치하지 않을 수 있다. 프로덕션에서는 버퍼링 로직 또는
eventsource-stream같은 SSE 파서 크레이트 사용을 권장한다.
futures-util 의존성 필요:
futures-util = "0.3"
use reqwest::StatusCode;
#[derive(Debug)]
enum ApiError {
Network(reqwest::Error),
Status { code: StatusCode, body: String },
Parse(serde_json::Error),
}
impl From<reqwest::Error> for ApiError {
fn from(e: reqwest::Error) -> Self {
ApiError::Network(e)
}
}
async fn call_api(client: &Client, url: &str) -> Result<String, ApiError> {
let response = client.get(url).send().await?;
// 상태 코드 확인
let status = response.status();
if !status.is_success() {
let body = response.text().await.unwrap_or_default();
return Err(ApiError::Status { code: status, body });
}
Ok(response.text().await?)
}
// error_for_status() — 4xx/5xx를 reqwest::Error로 변환
let resp = client.get(url)
.send()
.await?
.error_for_status()?; // 4xx/5xx면 Err 반환
reqwest::Error 주요 판별 메서드:
is_timeout() — 타임아웃 발생 여부is_connect() — 연결 실패 여부is_status() — HTTP 상태 코드 에러 여부status() — Option<StatusCode> 반환use reqwest::{Client, header::{HeaderMap, HeaderValue}};
use serde::{Deserialize, Serialize};
use std::time::Duration;
#[derive(Serialize)]
struct MessagesRequest {
model: String,
max_tokens: u32,
messages: Vec<Message>,
}
#[derive(Serialize, Deserialize)]
struct Message {
role: String,
content: String,
}
#[derive(Deserialize)]
struct MessagesResponse {
id: String,
content: Vec<ContentBlock>,
model: String,
stop_reason: Option<String>,
}
#[derive(Deserialize)]
struct ContentBlock {
#[serde(rename = "type")]
block_type: String,
text: Option<String>,
}
fn build_client(api_key: &str) -> reqwest::Result<Client> {
let mut headers = HeaderMap::new();
headers.insert("x-api-key", HeaderValue::from_str(api_key).unwrap());
headers.insert("anthropic-version", HeaderValue::from_static("2023-06-01"));
Client::builder()
.default_headers(headers)
.timeout(Duration::from_secs(60))
.build()
}
async fn send_message(
client: &Client,
model: &str,
user_message: &str,
) -> Result<String, Box<dyn std::error::Error>> {
let body = MessagesRequest {
model: model.into(),
max_tokens: 1024,
messages: vec![Message {
role: "user".into(),
content: user_message.into(),
}],
};
let resp: MessagesResponse = client
.post("https://api.anthropic.com/v1/messages")
.json(&body)
.send()
.await?
.error_for_status()?
.json()
.await?;
Ok(resp.content.into_iter()
.filter_map(|b| b.text)
.collect::<Vec<_>>()
.join(""))
}
reqwest 자체에는 재시도 기능이 없다. 직접 구현하거나 reqwest-middleware + reqwest-retry 크레이트 사용.
주의: reqwest 0.12.23(2025-08-08 릴리즈)부터
reqwest::retry모듈과ClientBuilder::retries(policy)메서드가 내장됨. 단, 기본 내장 retry는 HTTP/2 REFUSED_STREAM 등 프로토콜 레벨 NACK 재시도 용도이며, 커스텀 정책이 필요한 경우 reqwest-middleware + reqwest-retry 조합이 더 유연함.
// 수동 재시도 (지수 백오프)
use std::time::Duration;
use tokio::time::sleep;
async fn retry_request(
client: &Client,
url: &str,
max_retries: u32,
) -> reqwest::Result<reqwest::Response> {
let mut last_err = None;
for attempt in 0..max_retries {
match client.get(url).send().await {
Ok(resp) if resp.status().is_server_error() => {
last_err = Some(resp.error_for_status().unwrap_err());
}
Ok(resp) => return Ok(resp),
Err(e) if e.is_timeout() || e.is_connect() => {
last_err = Some(e);
}
Err(e) => return Err(e), // 재시도 불가한 에러
}
let delay = Duration::from_millis(100 * 2u64.pow(attempt));
sleep(delay).await;
}
Err(last_err.unwrap())
}
주의:
reqwest-middleware0.4.x /reqwest-retry0.7.x 기준. 버전 호환성 확인 필요.