1 changed files with 255 additions and 0 deletions
@ -0,0 +1,255 @@ |
|||
# ์๋ฌ ์ฒ๋ฆฌ { #handling-errors } |
|||
|
|||
API๋ฅผ ์ฌ์ฉํ๋ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ฌ๋ฅผ ์๋ ค์ผ ํ๋ ์ํฉ์ด ๋ง์ต๋๋ค. |
|||
|
|||
ํด๋ผ์ด์ธํธ๋ ํ๋ก ํธ์๋๊ฐ ์๋ ๋ธ๋ผ์ฐ์ ์ผ ์๋ ์๊ณ , ๋ค๋ฅธ ์ฌ๋์ ์ฝ๋์ผ ์๋ ์๊ณ , IoT ์ฅ์น์ผ ์๋ ์์ต๋๋ค. |
|||
|
|||
๋ค์๊ณผ ๊ฐ์ ์ํฉ์ ํด๋ผ์ด์ธํธ์๊ฒ ์๋ ค์ผ ํ ์ ์์ต๋๋ค: |
|||
|
|||
* ํด๋น ์์
์ ๋ํ ๊ถํ์ด ์์ |
|||
* ํด๋น ๋ฆฌ์์ค์ ์ ๊ทผํ ์ ์์ |
|||
* ์ ๊ทผํ๋ ค๋ ํญ๋ชฉ์ด ์กด์ฌํ์ง ์์ |
|||
* ๊ธฐํ ๋ฑ๋ฑ |
|||
|
|||
์ด๋ฐ ๊ฒฝ์ฐ, ๋ณดํต **400**๋ฒ๋(400~499)์ **HTTP ์ํ ์ฝ๋**๋ฅผ ๋ฐํํฉ๋๋ค. |
|||
|
|||
์ด๋ 200๋ฒ๋ HTTP ์ํ ์ฝ๋(200~299)์ ๋น์ทํฉ๋๋ค. "200"๋ฒ๋ ์ํ ์ฝ๋๋ ์์ฒญ์ด ์ด๋ป๊ฒ๋ "์ฑ๊ณต"ํ๋ค๋ ์๋ฏธ์
๋๋ค. |
|||
|
|||
400๋ฒ๋ ์ํ ์ฝ๋๋ ํด๋ผ์ด์ธํธ์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค๋ ์๋ฏธ์
๋๋ค. |
|||
|
|||
**"404 Not Found"** ์๋ฌ(๊ทธ๋ฆฌ๊ณ ๋๋ด๋ค)๋ฅผ ๊ธฐ์ตํ์๋์? |
|||
|
|||
## `HTTPException` ์ฌ์ฉํ๊ธฐ { #use-httpexception } |
|||
|
|||
์๋ฌ๊ฐ ์๋ HTTP ์๋ต์ ํด๋ผ์ด์ธํธ์ ๋ฐํํ๋ ค๋ฉด `HTTPException`์ ์ฌ์ฉํฉ๋๋ค. |
|||
|
|||
### `HTTPException` ์ํฌํธํ๊ธฐ { #import-httpexception } |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial001.py hl[1] *} |
|||
|
|||
### ์ฝ๋์์ `HTTPException` ๋ฐ์์ํค๊ธฐ { #raise-an-httpexception-in-your-code } |
|||
|
|||
`HTTPException`์ API ๊ด๋ จ ์ถ๊ฐ ๋ฐ์ดํฐ๋ฅผ ๋ด์ ์ผ๋ฐ ํ์ด์ฌ ์์ธ์
๋๋ค. |
|||
|
|||
ํ์ด์ฌ ์์ธ์ด๋ฏ๋ก `return`์ด ์๋ `raise`๋ฅผ ์ฌ์ฉํฉ๋๋ค. |
|||
|
|||
*๊ฒฝ๋ก ์๋ ํจ์* ๋ด์์ ํธ์ถํ๋ ์ ํธ๋ฆฌํฐ ํจ์๊ฐ ์๋ค๋ฉด, ๊ทธ ํจ์ ์์์ `HTTPException`์ ๋ฐ์์์ผฐ์ ๋ *๊ฒฝ๋ก ์๋ ํจ์*์ ๋๋จธ์ง ์ฝ๋๋ ์คํ๋์ง ์๊ณ , ์์ฒญ์ด ์ฆ์ ์ข
๋ฃ๋๋ฉฐ `HTTPException`์ HTTP ์๋ฌ๊ฐ ํด๋ผ์ด์ธํธ๋ก ์ ์ก๋ฉ๋๋ค. |
|||
|
|||
๊ฐ์ ๋ฐํํ๋ ๊ฒ๋ณด๋ค ์์ธ๋ฅผ ๋ฐ์์ํค๋ ๊ฒ์ ์ด์ ์ ์์กด์ฑ๊ณผ ๋ณด์ ์น์
์์ ๋ ๋ช
ํํด์ง ๊ฒ์
๋๋ค. |
|||
|
|||
์ด ์์ ์์๋ ํด๋ผ์ด์ธํธ๊ฐ ์กด์ฌํ์ง ์๋ ID๋ก ํญ๋ชฉ์ ์์ฒญํ๋ฉด ์ํ ์ฝ๋ `404`์ ์์ธ๋ฅผ ๋ฐ์์ํต๋๋ค: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial001.py hl[11] *} |
|||
|
|||
### ๊ฒฐ๊ณผ ์๋ต { #the-resulting-response } |
|||
|
|||
ํด๋ผ์ด์ธํธ๊ฐ `http://example.com/items/foo`(`item_id`๊ฐ `"foo"`)๋ฅผ ์์ฒญํ๋ฉด, HTTP ์ํ ์ฝ๋ 200๊ณผ ๋ค์ JSON ์๋ต์ ๋ฐ์ต๋๋ค: |
|||
|
|||
```JSON |
|||
{ |
|||
"item": "The Foo Wrestlers" |
|||
} |
|||
``` |
|||
|
|||
ํ์ง๋ง ํด๋ผ์ด์ธํธ๊ฐ `http://example.com/items/bar`(์กด์ฌํ์ง ์๋ `item_id` `"bar"`)๋ฅผ ์์ฒญํ๋ฉด, HTTP ์ํ ์ฝ๋ 404("not found" ์๋ฌ)์ ๋ค์ JSON ์๋ต์ ๋ฐ์ต๋๋ค: |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": "Item not found" |
|||
} |
|||
``` |
|||
|
|||
/// tip | ํ |
|||
|
|||
`HTTPException`์ ๋ฐ์์ํฌ ๋ `detail` ๋งค๊ฐ๋ณ์์ `str`๋ฟ๋ง ์๋๋ผ JSON์ผ๋ก ๋ณํ ๊ฐ๋ฅํ ๋ชจ๋ ๊ฐ์ ์ ๋ฌํ ์ ์์ต๋๋ค. |
|||
|
|||
`dict`, `list` ๋ฑ์ ์ ๋ฌํ ์ ์์ต๋๋ค. |
|||
|
|||
์ด๋ค์ **FastAPI**๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌํ์ฌ JSON์ผ๋ก ๋ณํํฉ๋๋ค. |
|||
|
|||
/// |
|||
|
|||
## ์ปค์คํ
ํค๋ ์ถ๊ฐํ๊ธฐ { #add-custom-headers } |
|||
|
|||
HTTP ์๋ฌ์ ์ปค์คํ
ํค๋๋ฅผ ์ถ๊ฐํ๋ ๊ฒ์ด ์ ์ฉํ ๋๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ผ๋ถ ๋ณด์ ์๋๋ฆฌ์ค์์ ๋ง์ด์ฃ . |
|||
|
|||
์ง์ ์ฌ์ฉํ ํ์๋ ๊ฑฐ์ ์์ต๋๋ค. |
|||
|
|||
ํ์ง๋ง ํ์ํ ๊ณ ๊ธ ์๋๋ฆฌ์ค๋ฅผ ์ํด ์ปค์คํ
ํค๋๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial002.py hl[14] *} |
|||
|
|||
## ์ปค์คํ
์์ธ ํธ๋ค๋ฌ ์ค์นํ๊ธฐ { #install-custom-exception-handlers } |
|||
|
|||
<a href="https://www.starlette.dev/exceptions/" class="external-link" target="_blank">Starlette์ ์์ธ ์ ํธ๋ฆฌํฐ</a>๋ฅผ ์ฌ์ฉํ์ฌ ์ปค์คํ
์์ธ ํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค. |
|||
|
|||
์ฌ๋ฌ๋ถ(๋๋ ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ)์ด `raise`ํ ์ ์๋ ์ปค์คํ
์์ธ `UnicornException`์ด ์๋ค๊ณ ๊ฐ์ ํด๋ด
์๋ค. |
|||
|
|||
์ด ์์ธ๋ฅผ FastAPI์์ ์ ์ญ์ผ๋ก ์ฒ๋ฆฌํ๊ณ ์ถ์ต๋๋ค. |
|||
|
|||
`@app.exception_handler()`๋ก ์ปค์คํ
์์ธ ํธ๋ค๋ฌ๋ฅผ ์ถ๊ฐํ ์ ์์ต๋๋ค: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial003.py hl[5:7,13:18,24] *} |
|||
|
|||
์ฌ๊ธฐ์ `/unicorns/yolo`๋ฅผ ์์ฒญํ๋ฉด *๊ฒฝ๋ก ์๋*์ด `UnicornException`์ `raise`ํฉ๋๋ค. |
|||
|
|||
ํ์ง๋ง `unicorn_exception_handler`๊ฐ ์ด๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. |
|||
|
|||
๋ฐ๋ผ์ HTTP ์ํ ์ฝ๋ `418`๊ณผ ๋ค์ JSON ๋ด์ฉ์ ๊น๋ํ ์๋ฌ๋ฅผ ๋ฐ์ต๋๋ค: |
|||
|
|||
```JSON |
|||
{"message": "Oops! yolo did something. There goes a rainbow..."} |
|||
``` |
|||
|
|||
/// note | ๊ธฐ์ ์ ์ธ๋ถ์ฌํญ |
|||
|
|||
`from starlette.requests import Request`์ `from starlette.responses import JSONResponse`๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. |
|||
|
|||
**FastAPI**๋ ๊ฐ๋ฐ์ ํธ์๋ฅผ ์ํด `starlette.responses`๋ฅผ `fastapi.responses`๋ก๋ ์ ๊ณตํฉ๋๋ค. ํ์ง๋ง ์ฌ์ฉ ๊ฐ๋ฅํ ๋๋ถ๋ถ์ ์๋ต์ Starlette์์ ์ง์ ๊ฐ์ ธ์ต๋๋ค. `Request`๋ ๋ง์ฐฌ๊ฐ์ง์
๋๋ค. |
|||
|
|||
/// |
|||
|
|||
## ๊ธฐ๋ณธ ์์ธ ํธ๋ค๋ฌ ์ค๋ฒ๋ผ์ด๋ํ๊ธฐ { #override-the-default-exception-handlers } |
|||
|
|||
**FastAPI**์๋ ๋ช ๊ฐ์ง ๊ธฐ๋ณธ ์์ธ ํธ๋ค๋ฌ๊ฐ ์์ต๋๋ค. |
|||
|
|||
์ด ํธ๋ค๋ฌ๋ค์ `HTTPException`์ `raise`ํ๊ฑฐ๋ ์์ฒญ์ ์ ํจํ์ง ์์ ๋ฐ์ดํฐ๊ฐ ์์ ๋ ๊ธฐ๋ณธ JSON ์๋ต์ ๋ฐํํฉ๋๋ค. |
|||
|
|||
์ด๋ฌํ ์์ธ ํธ๋ค๋ฌ๋ฅผ ์ฌ๋ฌ๋ถ๋ง์ ๊ฒ์ผ๋ก ์ค๋ฒ๋ผ์ด๋ํ ์ ์์ต๋๋ค. |
|||
|
|||
### ์์ฒญ ๊ฒ์ฆ ์์ธ ์ค๋ฒ๋ผ์ด๋ํ๊ธฐ { #override-request-validation-exceptions } |
|||
|
|||
์์ฒญ์ ์ ํจํ์ง ์์ ๋ฐ์ดํฐ๊ฐ ํฌํจ๋๋ฉด **FastAPI**๋ ๋ด๋ถ์ ์ผ๋ก `RequestValidationError`๋ฅผ ๋ฐ์์ํต๋๋ค. |
|||
|
|||
๊ทธ๋ฆฌ๊ณ ๊ธฐ๋ณธ ์์ธ ํธ๋ค๋ฌ๋ ํฌํจํ๊ณ ์์ต๋๋ค. |
|||
|
|||
์ด๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ๋ ค๋ฉด `RequestValidationError`๋ฅผ ์ํฌํธํ๊ณ `@app.exception_handler(RequestValidationError)`๋ก ์์ธ ํธ๋ค๋ฌ๋ฅผ ๋ฐ์ฝ๋ ์ดํธํฉ๋๋ค. |
|||
|
|||
์์ธ ํธ๋ค๋ฌ๋ `Request`์ ์์ธ๋ฅผ ๋ฐ์ต๋๋ค. |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial004.py hl[2,14:16] *} |
|||
|
|||
์ด์ `/items/foo`๋ก ๊ฐ๋ฉด ๋ค์๊ณผ ๊ฐ์ ๊ธฐ๋ณธ JSON ์๋ฌ ๋์ : |
|||
|
|||
```JSON |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"path", |
|||
"item_id" |
|||
], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer" |
|||
} |
|||
] |
|||
} |
|||
``` |
|||
|
|||
๋ค์๊ณผ ๊ฐ์ ํ
์คํธ ๋ฒ์ ์ ๋ฐ์ต๋๋ค: |
|||
|
|||
``` |
|||
1 validation error |
|||
path -> item_id |
|||
value is not a valid integer (type=type_error.integer) |
|||
``` |
|||
|
|||
#### `RequestValidationError` vs `ValidationError` { #requestvalidationerror-vs-validationerror } |
|||
|
|||
/// warning | ์ฃผ์ |
|||
|
|||
์ง๊ธ ๋น์ฅ ์ค์ํ์ง ์๋ค๋ฉด ๊ฑด๋๋ธ ์ ์๋ ๊ธฐ์ ์ธ๋ถ์ฌํญ์
๋๋ค. |
|||
|
|||
/// |
|||
|
|||
`RequestValidationError`๋ Pydantic์ <a href="https://docs.pydantic.dev/latest/concepts/models/#error-handling" class="external-link" target="_blank">`ValidationError`</a>์ ์๋ธํด๋์ค์
๋๋ค. |
|||
|
|||
**FastAPI**๋ ์ด๋ฅผ ์ฌ์ฉํ์ฌ `response_model`์์ Pydantic ๋ชจ๋ธ์ ์ฌ์ฉํ๊ณ ๋ฐ์ดํฐ์ ์๋ฌ๊ฐ ์๋ ๊ฒฝ์ฐ ๋ก๊ทธ์์ ์๋ฌ๋ฅผ ๋ณผ ์ ์๊ฒ ํฉ๋๋ค. |
|||
|
|||
ํ์ง๋ง ํด๋ผ์ด์ธํธ/์ฌ์ฉ์๋ ์ด๋ฅผ ๋ณด์ง ๋ชปํฉ๋๋ค. ๋์ HTTP ์ํ ์ฝ๋ `500`๊ณผ ํจ๊ป "Internal Server Error"๋ฅผ ๋ฐ์ต๋๋ค. |
|||
|
|||
*์๋ต*์ด๋ ์ฝ๋ ์ด๋๊ฐ์(ํด๋ผ์ด์ธํธ *์์ฒญ*์ด ์๋) Pydantic `ValidationError`๊ฐ ์๋ค๋ฉด ์ค์ ๋ก๋ ์ฝ๋์ ๋ฒ๊ทธ์ด๋ฏ๋ก ์ด๋ ๊ฒ ๋์ด์ผ ํฉ๋๋ค. |
|||
|
|||
๊ทธ๋ฆฌ๊ณ ์ด๋ฅผ ์์ ํ๋ ๋์ ํด๋ผ์ด์ธํธ/์ฌ์ฉ์๋ ์๋ฌ์ ๋ํ ๋ด๋ถ ์ ๋ณด์ ์ ๊ทผํ ์ ์์ด์ผ ํฉ๋๋ค. ๋ณด์ ์ทจ์ฝ์ ์ ๋
ธ์ถํ ์ ์๊ธฐ ๋๋ฌธ์
๋๋ค. |
|||
|
|||
### `HTTPException` ์๋ฌ ํธ๋ค๋ฌ ์ค๋ฒ๋ผ์ด๋ํ๊ธฐ { #override-the-httpexception-error-handler } |
|||
|
|||
๊ฐ์ ๋ฐฉ์์ผ๋ก `HTTPException` ํธ๋ค๋ฌ๋ฅผ ์ค๋ฒ๋ผ์ด๋ํ ์ ์์ต๋๋ค. |
|||
|
|||
์๋ฅผ ๋ค์ด ์ด๋ฐ ์๋ฌ๋ค์ ๋ํด JSON ๋์ ์ผ๋ฐ ํ
์คํธ ์๋ต์ ๋ฐํํ๊ณ ์ถ์ ์ ์์ต๋๋ค: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial004.py hl[3:4,9:11,22] *} |
|||
|
|||
/// note | ๊ธฐ์ ์ ์ธ๋ถ์ฌํญ |
|||
|
|||
`from starlette.responses import PlainTextResponse`๋ฅผ ์ฌ์ฉํ ์๋ ์์ต๋๋ค. |
|||
|
|||
**FastAPI**๋ ๊ฐ๋ฐ์ ํธ์๋ฅผ ์ํด `starlette.responses`๋ฅผ `fastapi.responses`๋ก๋ ์ ๊ณตํฉ๋๋ค. ํ์ง๋ง ์ฌ์ฉ ๊ฐ๋ฅํ ๋๋ถ๋ถ์ ์๋ต์ Starlette์์ ์ง์ ๊ฐ์ ธ์ต๋๋ค. |
|||
|
|||
/// |
|||
|
|||
### `RequestValidationError` ๋ณธ๋ฌธ ์ฌ์ฉํ๊ธฐ { #use-the-requestvalidationerror-body } |
|||
|
|||
`RequestValidationError`๋ ์ ํจํ์ง ์์ ๋ฐ์ดํฐ์ ํจ๊ป ๋ฐ์ `body`๋ฅผ ํฌํจํฉ๋๋ค. |
|||
|
|||
์ฑ ๊ฐ๋ฐ ์ค ์ด๋ฅผ ์ฌ์ฉํ์ฌ ๋ณธ๋ฌธ์ ๋ก๊น
ํ๊ณ ๋๋ฒ๊ทธํ๊ฑฐ๋ ์ฌ์ฉ์์๊ฒ ๋ฐํํ ์ ์์ต๋๋ค. |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial005.py hl[14] *} |
|||
|
|||
์ด์ ๋ค์๊ณผ ๊ฐ์ด ์ ํจํ์ง ์์ ํญ๋ชฉ์ ๋ณด๋ด๋ณด์ธ์: |
|||
|
|||
```JSON |
|||
{ |
|||
"title": "towel", |
|||
"size": "XL" |
|||
} |
|||
``` |
|||
|
|||
๋ฐ์ดํฐ๊ฐ ์ ํจํ์ง ์๋ค๋ ๋ด์ฉ๊ณผ ๋ฐ์ ๋ณธ๋ฌธ์ ํฌํจํ ์๋ต์ ๋ฐ์ต๋๋ค: |
|||
|
|||
```JSON hl_lines="12-15" |
|||
{ |
|||
"detail": [ |
|||
{ |
|||
"loc": [ |
|||
"body", |
|||
"size" |
|||
], |
|||
"msg": "value is not a valid integer", |
|||
"type": "type_error.integer" |
|||
} |
|||
], |
|||
"body": { |
|||
"title": "towel", |
|||
"size": "XL" |
|||
} |
|||
} |
|||
``` |
|||
|
|||
#### FastAPI์ `HTTPException` vs Starlette์ `HTTPException` { #fastapis-httpexception-vs-starlettes-httpexception } |
|||
|
|||
**FastAPI**๋ ์์ฒด `HTTPException`์ ๊ฐ์ง๊ณ ์์ต๋๋ค. |
|||
|
|||
**FastAPI**์ `HTTPException` ์๋ฌ ํด๋์ค๋ Starlette์ `HTTPException` ์๋ฌ ํด๋์ค๋ฅผ ์์๋ฐ์ต๋๋ค. |
|||
|
|||
์ ์ผํ ์ฐจ์ด์ ์ **FastAPI**์ `HTTPException`์ `detail` ํ๋์ JSON์ผ๋ก ๋ณํ ๊ฐ๋ฅํ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ์ฉํ๋ ๋ฐ๋ฉด, Starlette์ `HTTPException`์ ๋ฌธ์์ด๋ง ํ์ฉํ๋ค๋ ๊ฒ์
๋๋ค. |
|||
|
|||
๋ฐ๋ผ์ ์ฌ๋ฌ๋ถ์ ์ฝ๋์์ ํ์์ฒ๋ผ **FastAPI**์ `HTTPException`์ ๊ณ์ ๋ฐ์์ํฌ ์ ์์ต๋๋ค. |
|||
|
|||
ํ์ง๋ง ์์ธ ํธ๋ค๋ฌ๋ฅผ ๋ฑ๋กํ ๋๋ Starlette์ `HTTPException`์ ๋ํด ๋ฑ๋กํด์ผ ํฉ๋๋ค. |
|||
|
|||
์ด๋ ๊ฒ ํ๋ฉด Starlette ๋ด๋ถ ์ฝ๋๋ Starlette ํ์ฅ/ํ๋ฌ๊ทธ์ธ์์ Starlette `HTTPException`์ ๋ฐ์์์ผฐ์ ๋ ์ฌ๋ฌ๋ถ์ ํธ๋ค๋ฌ๊ฐ ์ด๋ฅผ ์ก์์ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค. |
|||
|
|||
์ด ์์ ์์๋ ๊ฐ์ ์ฝ๋์์ ๋ `HTTPException`์ ๋ชจ๋ ์ฌ์ฉํ๊ธฐ ์ํด Starlette์ ์์ธ๋ฅผ `StarletteHTTPException`์ผ๋ก ์ด๋ฆ์ ๋ฐ๊ฟจ์ต๋๋ค: |
|||
|
|||
```Python |
|||
from starlette.exceptions import HTTPException as StarletteHTTPException |
|||
``` |
|||
|
|||
### **FastAPI**์ ์์ธ ํธ๋ค๋ฌ ์ฌ์ฌ์ฉํ๊ธฐ { #reuse-fastapis-exception-handlers } |
|||
|
|||
์์ธ์ ํจ๊ป **FastAPI**์ ๊ธฐ๋ณธ ์์ธ ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด `fastapi.exception_handlers`์์ ๊ธฐ๋ณธ ์์ธ ํธ๋ค๋ฌ๋ฅผ ์ํฌํธํ์ฌ ์ฌ์ฌ์ฉํ ์ ์์ต๋๋ค: |
|||
|
|||
{* ../../docs_src/handling_errors/tutorial006.py hl[2:5,15,21] *} |
|||
|
|||
์ด ์์ ์์๋ ์์ฃผ ์ค๋ช
์ ์ธ ๋ฉ์์ง๋ก ์๋ฌ๋ฅผ ์ถ๋ ฅํ์ง๋ง, ํต์ฌ์ ์ดํดํ ์ ์์ ๊ฒ์
๋๋ค. ์์ธ๋ฅผ ์ฌ์ฉํ ๋ค์ ๊ธฐ๋ณธ ์์ธ ํธ๋ค๋ฌ๋ฅผ ์ฌ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค. |
|||
Loadingโฆ
Reference in new issue