用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/puk0806/gugbab-claude --skill reqwest命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
DDD(Domain-Driven Design) 아키텍처 핵심 패턴 - 유비쿼터스 언어, 서브도메인, 바운디드 컨텍스트, Aggregate, Entity/VO, 도메인 서비스/이벤트, 레이어드 아키텍처
대규모 React/Next.js 프로젝트를 layer-first(types/·utils/·hooks/·api/·components/ 밑에 도메인이 반복되는 구조)에서 domain-first(feature/도메인 우선) 구조로 전환하는 설계 기준과 절차. Feature-Sliced Design 2.1 정본(layers 6종·slices·segments·import 규칙·@x 크로스임포트·public API), FSD를 쓰지 않는 경량 대안(features + shared 2~3계층 + ESLint import/no-restricted-paths), Next.js App Router 공존 전략(route group `()`·private folder `_`·colocation), Turborepo/Nx 모노레포에서 폴더↔패키지 승격 기준, colocation과 배럴 파일 성능 트레이드오프, 도메인 경계 역추출(import 그래프·change coupling·용어 클러스터), 전환 실패 패턴(shared 비대화·entities 남용·순환 의존·도메인=라우트 착각·조기 추상화). 도메인 개념 자체(바운디드 컨텍스트·유비쿼터스 언어)는 `architecture/ddd` 스킬을 참조한다.
소스 파일 수천 개 규모 프론트엔드 코드베이스를 멈추지 않고 점진 재구조화하는 실행 전략 - Strangler Fig / Branch by Abstraction / Parallel Change, ts-morph·jscodeshift codemod, PR 분할·검증 게이트·되돌리기, 테스트 없는 코드의 안전망, 작업 순서 설계와 위반 수 기반 진행 추적
| name | reqwest |
| description | Rust reqwest HTTP 클라이언트 핵심 패턴 — GET/POST, JSON, 헤더, 스트리밍, 에러 처리, Client 재사용 |
소스: https://docs.rs/reqwest/latest/reqwest/ | https://github.com/seanmonstar/reqwest 검증일: 2026-08-12
주의: 이 문서는 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-5".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-5",
"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 == {
;
}
: serde_json::Value = serde_json::(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 = client.(url)
.()
.?
.()?;
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::()
.(headers)
.(Duration::())
.()
}
(
client: &Client,
model: &,
user_message: &,
) <, < std::error::Error>> {
= MessagesRequest {
model: model.(),
max_tokens: ,
messages: [Message {
role: .(),
content: user_message.(),
}],
};
: MessagesResponse = client
.()
.(&body)
.()
.?
.()?
.()
.?;
(resp.content.()
.(|b| b.text)
.collect::<<_>>()
.())
}
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 * .(attempt));
(delay).;
}
(last_err.())
}
주의:
reqwest-middleware0.4.x /reqwest-retry0.7.x 기준. 버전 호환성 확인 필요.