
이봐, 오늘은 ROST에 REST API를 쓰는 방법에 대한 지식을 공유하고 싶었습니다. 생각보다 쉬울 수 있습니다! 이 기사에서는 데이터베이스 연결을 보여주지 않을 것입니다. 대신, 우리는 OpenAPI 사양을 생성하고 Swagger UI 제공하는 방법을 보여주는 데 중점을 두었습니다.
GitHub에서 전체 코드 소스를 찾을 수 있습니다.
시작하기 전에 녹이 설치되어 있는지 확인하십시오.
cargo init 사용하여 새로운 프로젝트를 초기화하는 것으로 시작하겠습니다.
cargo init my-rest-api
cd my-rest-api다음과 같은 디렉토리 구조를 생성해야합니다.
├── Cargo.toml
└── src
└── main.rs 서식에 rustfmt 사용할 수 있습니다. 이렇게하려면 다음 내용으로 rustfmt.toml 파일을 만듭니다.
indent_style = " Block "
max_width = 80
tab_spaces = 2
reorder_imports = false
reorder_modules = false
force_multiline_blocks = true
brace_style = " PreferSameLine "
control_brace_style = " AlwaysSameLine " 나는 개인적으로 vscode를 사용합니다. 선택적으로 .vscode/settings.json 에이 구성을 추가 할 수 있습니다.
{
"editor.rulers" : [ 80 ],
"editor.tabSize" : 2 ,
"editor.detectIndentation" : false ,
"editor.trimAutoWhitespace" : true ,
"editor.formatOnSave" : true ,
"files.insertFinalNewline" : true ,
"files.trimTrailingWhitespace" : true ,
"rust-analyzer.showUnlinkedFileNotification" : false ,
"rust-analyzer.checkOnSave" : true ,
"rust-analyzer.check.command" : " clippy "
}새 디렉토리 구조는 다음과 같습니다.
├── .gitignore
├── .vscode
│ └── settings.json
├── Cargo.lock
├── Cargo.toml
├── rustfmt.toml
└── src
└── main.rs 우리는 NTEX를 HTTP 프레임 워크로 사용할 것입니다.
cargo add 실행하여 Rust Dependencies를 설치할 수 있습니다.
ntex 사용할 때는 runtime 선택할 수 있습니다.
신속하게 요약하기 위해 runtime async|await 패턴을 관리합니다.
nodejs runtime 에 익숙하다면 사용이 비슷합니다.
이 튜토리얼에서는 더 인기있는 선택 인 것처럼 Tokio를 사용할 것입니다. NTEX를 종속성으로 추가합시다.
cargo add ntex --features tokio 그런 다음 다음 내용으로 main.rs 파일을 업데이트 할 것입니다.
use ntex :: web ;
# [ web :: get ( "/" ) ]
async fn index ( ) -> & ' static str {
"Hello world!"
}
# [ ntex :: main ]
async fn main ( ) -> std :: io :: Result < ( ) > {
web :: server ( || web :: App :: new ( ) . service ( index ) )
. bind ( ( "0.0.0.0" , 8080 ) ) ?
. run ( )
. await ? ;
Ok ( ( ) )
}다음 명령을 사용하여 프로젝트를 실행할 수 있습니다.
cargo run 이 명령은 코드를 컴파일하고 실행합니다.
다음 출력이 표시됩니다.
Finished dev [unoptimized + debuginfo] target(s) in 17.38s
Running `target/debug/my-rest-api`CURL을 사용하여 서버를 테스트 할 수 있습니다.
curl -v localhost:8080
* Trying 127.0.0.1:8080...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.68.0
> Accept: * / *
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< content-length: 12
< content-type: text/plain; charset=utf-8
< date: Fri, 26 May 2023 11:43:01 GMT
<
* Connection #0 to host localhost left intact
Hello world!% 축하해요! 이제 Rust 에 첫 번째 HTTP 서버가 있습니다!
이제 첫 번째 REST endpoints 만들어 봅시다.
디렉토리 아키텍처와 관련하여 개인 선호도에 달려 있습니다. ntex 에서는 .service() 메소드를 사용하여 새로운 endpoints 추가합니다. 따라서 엔드 포인트를 수용하기 위해 services 라는 디렉토리를 만들기로 선택했습니다.
디렉토리를 만들어 봅시다 :
mkdir src/services
touch src/services/mod.rs 기본적으로 Rust 디렉토리에서 mod.rs 파일을 가져 오려고합니다.
services/mod.rs 내부에서 기본 endpoints 작성하겠습니다.
use ntex :: web ;
pub async fn default ( ) -> web :: HttpResponse {
web :: HttpResponse :: NotFound ( ) . finish ( )
}이제 우리는 Main.rs 에서이 모듈을 사용하고 싶다는 것을 나타냅니다.
use ntex :: web ;
mod services ;
# [ ntex :: main ]
async fn main ( ) -> std :: io :: Result < ( ) > {
web :: server ( || {
web :: App :: new ( )
// Default endpoint for unregisterd endpoints
. default_service ( web :: route ( ) . to ( services :: default )
)
} )
. bind ( ( "0.0.0.0" , 8080 ) ) ?
. run ( )
. await ? ;
Ok ( ( ) )
} 이제 등록되지 않은 endpoints 의 경우 404 오류가 발생합니다.
계속하기 전에 JSON 직렬화를위한 serde 및 serde_json , utoipa-swagger-ui 의 utoipa OpenAPI Swagger를 갖습니다.
cargo add serde --features derive
cargo add serde_json utoipa utoipa-swagger-ui 다음으로, 우리는 도우미로서 우리 자신의 HttpError 유형을 만들 것입니다. 다음 내용으로 src/error.rs 아래에서 파일을 만듭니다.
use ntex :: web ;
use ntex :: http ;
use utoipa :: ToSchema ;
use serde :: { Serialize , Deserialize } ;
/// An http error response
# [ derive ( Clone , Debug , Serialize , Deserialize , ToSchema ) ]
pub struct HttpError {
/// The error message
pub msg : String ,
/// The http status code, skipped in serialization
# [ serde ( skip ) ]
pub status : http :: StatusCode ,
}
/// Helper function to display an HttpError
impl std :: fmt :: Display for HttpError {
fn fmt ( & self , f : & mut std :: fmt :: Formatter < ' _ > ) -> std :: fmt :: Result {
write ! ( f , "[{}] {}" , self . status , self . msg )
}
}
/// Implement standard error for HttpError
impl std :: error :: Error for HttpError { }
/// Helper function to convert an HttpError into a ntex::web::HttpResponse
impl web :: WebResponseError for HttpError {
fn error_response ( & self , _ : & web :: HttpRequest ) -> web :: HttpResponse {
web :: HttpResponse :: build ( self . status ) . json ( & self )
}
} main.rs 에서 오류 모듈을 가져와야합니다.
use ntex :: web ;
mod error ;
mod services ;
# [ ntex :: main ]
async fn main ( ) -> std :: io :: Result < ( ) > {
web :: server ( || {
web :: App :: new ( )
// Default endpoint for unregisterd endpoints
. default_service ( web :: route ( ) . to ( services :: default )
)
} )
. bind ( ( "0.0.0.0" , 8080 ) ) ?
. run ( )
. await ? ;
Ok ( ( ) )
} 나는 우리가 예제 endpoints 쓸 준비가되었다고 생각합니다. TODO 목록을 시뮬레이션하고 src/services/todo.rs 에서 새 파일을 작성하겠습니다.
use ntex :: web ;
# [ web :: get ( "/todos" ) ]
pub async fn get_todos ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
# [ web :: post ( "/todos" ) ]
pub async fn create_todo ( ) -> web :: HttpResponse {
web :: HttpResponse :: Created ( ) . finish ( )
}
# [ web :: get ( "/todos/{id}" ) ]
pub async fn get_todo ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
# [ web :: put ( "/todos/{id}" ) ]
pub async fn update_todo ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
# [ web :: delete ( "/todos/{id}" ) ]
pub async fn delete_todo ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
pub fn ntex_config ( cfg : & mut web :: ServiceConfig ) {
cfg . service ( get_todos ) ;
cfg . service ( create_todo ) ;
cfg . service ( get_todo ) ;
cfg . service ( update_todo ) ;
cfg . service ( delete_todo ) ;
} todo.rs 가져 오려면 src/services/mod.rs 업데이트해야합니다.
pub mod todo ;
use ntex :: web ;
pub async fn default ( ) -> web :: HttpResponse {
web :: HttpResponse :: NotFound ( ) . finish ( )
} 우리의 main.rs :
use ntex :: web ;
mod error ;
mod services ;
# [ ntex :: main ]
async fn main ( ) -> std :: io :: Result < ( ) > {
web :: server ( || {
web :: App :: new ( )
// Register todo endpoints
. configure ( services :: todo :: ntex_config )
// Default endpoint for unregisterd endpoints
. default_service ( web :: route ( ) . to ( services :: default ) )
} )
. bind ( ( "0.0.0.0" , 8080 ) ) ?
. run ( )
. await ? ;
Ok ( ( ) )
} 우리의 Todo 에 대한 데이터 구조를 만들어 봅시다. 우리는 그의 mod.rs 와 todo.rs 사용하여 새로운 디렉토리 src/models 만들 것입니다.
mkdir src/models
touch src/models/mod.rs
touch src/models/todo.rs 우리의 src/models/mod.rs 에서 우리는 todo.rs 가져올 것입니다.
pub mod todo ; src/models/todo.rs 내부에서는 data structure 추가 할 것입니다.
use utoipa :: ToSchema ;
use serde :: { Serialize , Deserialize } ;
/// Todo model
# [ derive ( Clone , Debug , Serialize , Deserialize , ToSchema ) ]
pub struct Todo {
/// The todo id
pub id : i32 ,
/// The todo title
pub title : String ,
/// The todo completed status
pub completed : bool ,
}
/// Partial Todo model
# [ derive ( Clone , Debug , Serialize , Deserialize , ToSchema ) ]
pub struct TodoPartial {
/// The todo title
pub title : String ,
} serde 및 utoipa 파생 매크로를 사용하여 JSON 직렬화 및 OpenAPI Schema 로의 변환을 가능하게합니다.
main.rs 업데이트하여 models 가져 오는 것을 잊지 마십시오.
use ntex :: web ;
mod error ;
mod models ;
mod services ;
# [ ntex :: main ]
async fn main ( ) -> std :: io :: Result < ( ) > {
web :: server ( || {
web :: App :: new ( )
// Register todo endpoints
. configure ( services :: todo :: ntex_config )
// Default endpoint for unregisterd endpoints
. default_service ( web :: route ( ) . to ( services :: default ) )
} )
. bind ( ( "0.0.0.0" , 8080 ) ) ?
. run ( )
. await ? ;
Ok ( ( ) )
} 모델을 사용하면 이제 문서로 유형 안전 엔드 포인트를 생성 할 수 있습니다. src/services/todo.rs 내에서 endpoints 를 업데이트합시다.
use ntex :: web ;
use crate :: models :: todo :: TodoPartial ;
/// List all todos
# [ utoipa :: path (
get ,
path = "/todos" ,
responses (
( status = 200 , description = "List of Todo" , body = [ Todo ] ) ,
) ,
) ]
# [ web :: get ( "/todos" ) ]
pub async fn get_todos ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
/// Create a new todo
# [ utoipa :: path (
post ,
path = "/todos" ,
request_body = TodoPartial ,
responses (
( status = 201 , description = "Todo created" , body = Todo ) ,
) ,
) ]
# [ web :: post ( "/todos" ) ]
pub async fn create_todo (
_todo : web :: types :: Json < TodoPartial > ,
) -> web :: HttpResponse {
web :: HttpResponse :: Created ( ) . finish ( )
}
/// Get a todo by id
# [ utoipa :: path (
get ,
path = "/todos/{id}" ,
responses (
( status = 200 , description = "Todo found" , body = Todo ) ,
( status = 404 , description = "Todo not found" , body = HttpError ) ,
) ,
) ]
# [ web :: get ( "/todos/{id}" ) ]
pub async fn get_todo ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
/// Update a todo by id
# [ utoipa :: path (
put ,
path = "/todos/{id}" ,
request_body = TodoPartial ,
responses (
( status = 200 , description = "Todo updated" , body = Todo ) ,
( status = 404 , description = "Todo not found" , body = HttpError ) ,
) ,
) ]
# [ web :: put ( "/todos/{id}" ) ]
pub async fn update_todo ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
/// Delete a todo by id
# [ utoipa :: path (
delete ,
path = "/todos/{id}" ,
responses (
( status = 200 , description = "Todo deleted" , body = Todo ) ,
( status = 404 , description = "Todo not found" , body = HttpError ) ,
) ,
) ]
# [ web :: delete ( "/todos/{id}" ) ]
pub async fn delete_todo ( ) -> web :: HttpResponse {
web :: HttpResponse :: Ok ( ) . finish ( )
}
pub fn ntex_config ( cfg : & mut web :: ServiceConfig ) {
cfg . service ( get_todos ) ;
cfg . service ( create_todo ) ;
cfg . service ( get_todo ) ;
cfg . service ( update_todo ) ;
cfg . service ( delete_todo ) ;
}Utoipa를 사용하면 Swagger 문서를 제공 할 수 있습니다.
src/services/openapi.rs 아래에서 새 파일을 만들어 봅시다 :
use std :: sync :: Arc ;
use ntex :: web ;
use ntex :: http ;
use ntex :: util :: Bytes ;
use utoipa :: OpenApi ;
use crate :: error :: HttpError ;
use crate :: models :: todo :: { Todo , TodoPartial } ;
use super :: todo ;
/// Main structure to generate OpenAPI documentation
# [ derive ( OpenApi ) ]
# [ openapi (
paths (
todo :: get_todos ,
todo :: create_todo ,
todo :: get_todo ,
todo :: update_todo ,
todo :: delete_todo ,
) ,
components ( schemas ( Todo , TodoPartial , HttpError ) )
) ]
pub ( crate ) struct ApiDoc ;
# [ web :: get ( "/{tail}*" ) ]
async fn get_swagger (
tail : web :: types :: Path < String > ,
openapi_conf : web :: types :: State < Arc < utoipa_swagger_ui :: Config < ' static > > > ,
) -> Result < web :: HttpResponse , HttpError > {
if tail . as_ref ( ) == "swagger.json" {
let spec = ApiDoc :: openapi ( ) . to_json ( ) . map_err ( |err| HttpError {
status : http :: StatusCode :: INTERNAL_SERVER_ERROR ,
msg : format ! ( "Error generating OpenAPI spec: {}" , err ) ,
} ) ? ;
return Ok (
web :: HttpResponse :: Ok ( )
. content_type ( "application/json" )
. body ( spec ) ,
) ;
}
let conf = openapi_conf . as_ref ( ) . clone ( ) ;
match utoipa_swagger_ui :: serve ( & tail , conf . into ( ) ) . map_err ( |err| {
HttpError {
msg : format ! ( "Error serving Swagger UI: {}" , err ) ,
status : http :: StatusCode :: INTERNAL_SERVER_ERROR ,
}
} ) ? {
None => Err ( HttpError {
status : http :: StatusCode :: NOT_FOUND ,
msg : format ! ( "path not found: {}" , tail ) ,
} ) ,
Some ( file ) => Ok ( {
let bytes = Bytes :: from ( file . bytes . to_vec ( ) ) ;
web :: HttpResponse :: Ok ( )
. content_type ( file . content_type )
. body ( bytes )
} ) ,
}
}
pub fn ntex_config ( config : & mut web :: ServiceConfig ) {
let swagger_config = Arc :: new (
utoipa_swagger_ui :: Config :: new ( [ "/explorer/swagger.json" ] )
. use_base_layout ( ) ,
) ;
config . service (
web :: scope ( "/explorer/" )
. state ( swagger_config )
. service ( get_swagger ) ,
) ;
} src/services/mod.rs 업데이트하여 src/services/openapi.rs 가져 오는 것을 잊지 마십시오.
pub mod todo ;
pub mod openapi ;
use ntex :: web ;
pub async fn default ( ) -> web :: HttpResponse {
web :: HttpResponse :: NotFound ( ) . finish ( )
} 그런 다음 main.rs 업데이트하여 Explorer 엔드 포인트를 등록 할 수 있습니다.
use ntex :: web ;
mod error ;
mod models ;
mod services ;
# [ ntex :: main ]
async fn main ( ) -> std :: io :: Result < ( ) > {
web :: server ( || {
web :: App :: new ( )
// Register swagger endpoints
. configure ( services :: openapi :: ntex_config )
// Register todo endpoints
. configure ( services :: todo :: ntex_config )
// Default endpoint for unregisterd endpoints
. default_service ( web :: route ( ) . to ( services :: default ) )
} )
. bind ( ( "0.0.0.0" , 8080 ) ) ?
. run ( )
. await ? ;
Ok ( ( ) )
}우리는 가기 좋다. 서버를 실행합시다.
cargo run그런 다음 http : // localhost : 8080/explorer/에서 탐색기에 액세스 할 수 있어야합니다.

다음 REST API를 녹슬로 쓰려고 노력하시기 바랍니다!
종속성 문서를 살펴 보는 것을 잊지 마십시오.
프로덕션 도커 이미지를 만듭니다! 다음 내용으로 프로젝트 디렉토리에 Dockerfile 추가하십시오.
# Builder
FROM rust:1.69.0-alpine3.17 as builder
WORKDIR /app
# # Install build dependencies
RUN apk add alpine-sdk musl-dev build-base upx
# # Copy source code
COPY Cargo.toml Cargo.lock ./
COPY src ./src
# # Build release binary
RUN cargo build --release --target x86_64-unknown-linux-musl
# # Pack release binary with UPX (optional)
RUN upx --best --lzma /app/target/x86_64-unknown-linux-musl/release/my-rest-api
# Runtime
FROM scratch
# # Copy release binary from builder
COPY --from=builder /app/target/x86_64-unknown-linux-musl/release/my-rest-api /app
ENTRYPOINT [ "/app" ] 선택적 으로이 release 프로필을 Cargo.toml 에 추가 할 수 있습니다.
[ profile . release ]
opt-level = " z "
codegen-units = 1
strip = true
lto = true이것은 릴리스 바이너리를 가능한 작게 최적화합니다. 또한 UPX를 사용하면 정말 작은 Docker 이미지를 만들 수 있습니다!
이미지 구축 :
docker build -t my-rest-api:0.0.1 -f Dockerfile . 
더 실제 세계 usecase를보고 싶다면 내 OpenSource 프로젝트 NANOCL을 살펴 보도록 초대합니다. 이는 컨테이너 또는 가상 머신으로 마이크로 서비스의 개발 및 배포를 단순화하려고 노력합니다!
행복한 코딩!