[WAFFLE] 01-1 HTTP Request
Published:
In this post, basic concepts about HTTP Request is introduced.
Accepting HTTP Request - server
from fastapi import FastAPI, status
app = FastAPI()
@app.get("/")
async def root():
return {"message": "Hello World"}
- FastAPI()로 ASGI application 만들기
- @app.get()으로 GET 엔드포인트 추가하기
- 엔드포인트 구현하기
HTTP Request
HTTP Request consists of 3 parts - Request Line, Headers, and Body. HTTP Request Message is US-ASCII encoded.
POST /api/v1/items/ HTTP1.1
Host: sns.wafflestudio.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...
Accept: applicaion/json
Accept-Encoding: gzip, deflate
Content-Length: 29
Content-Type: application/json; charset=utf-8
{"message": "Hellow World"}
Request Line
Below is the Request Line example of HTTP Request.
GET /breands/10/items HTTP/1.1
METHOD
METHOD is the first token of the Request line. It tells the purpose of client’s request. You can use any token in HTTP Method but in most times, words below are widely used.
- GET : resource request
- POST : resource create
- PUT : resource create or overwrite
- PATCH : partially edit the resource
- DELETE : resourse deletion
Methods that are frequently used are implemented as separate decorator factories(Path Operation Decorator). You can learn about decorator factory at https://arcstone09.github.io/study/2024-10-04-waffle-1-2
@app.get("/brands/10/items/{item_id}", status_code=status.HTTP_200_OK)
@app.put("/brands/10/items/{item_id}", status_code=status.HTTP_200_OK)
@app.delete("/brands/10/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT)
@app.post("/brands/10/items/", status_code=status.HTTP_201_CREATED)
@app.patch("/brands/10/items/{item_id}", status_code=status.HTTP_200_OK)
When using custom HTTP methods or allowing multiple methods, the general-purpose decorator factory ‘app.api_route’ is used.
@app.api_route("/brands/10/items/{items_id}", methods=["GET", "PUT", "HOIZZA"])
PATH
- PATH : 계층 구조 리소스 식별자
- slash로 구분되는 segment 들로 이루어짐
- 각 segment마다 “;”로 구분되는 “key=value” 형태의 파라미터가 존재할 수 있는데, 잘 쓰이지 않음
- 리소스를 표현하는 데이터 중 계층 구조로 표현하기 쉬운 경우, PATH로 전달하는 것이 바람직함
@app.get("/brands/{brands_id}/items", status_code=status.HTTP_200_OK)
def list_brand_items(brand_id: int):
...
- PATH로 정적인 문자열뿐만 아니라 동적인 문자열을 받는 것도 가능함. 위 코드에서 정적인 부분은 “/nbrands/”, “/items”와 같이 고정된 문자열 경로임. 즉, 요청 URL의 이 부분은 항상 고정되어 있어야 함. 동적인 부분은 {brand_id} 와 같이 클라이언트가 요청할 때 구체적인 값을 입력할 수 있는 부분임. 클라이언트가 /brands/5/items/로 요청을 보내면 brand_id는 5가 됨.
- 이 함수의 매개변수 brand_id는 type hint로 int 타입을 지정하고 있음. 이는 FastAPI에게 brand_id가 정수형이어야 한다고 알려줌.
- 클라이언트가 요청을 보낼 때 경로의 동적 파라미터로 문자열을 전달하더라도, FastAPI는 자동으로 이를 정수로 변환해줌.
- 예를 들어, /brands/5/items 로 요청하면, brand_id가 문자열 “5”로 전달되지만, FastAPI는 이를 자동으로 int(5)로 변환해줌. 즉 문자열 “5”가 정수 5로 변환되어 함수에 전달됨.
- 만약 클라이언트가 /brands/abc/items 와 같이 brand_id에 정수가 아닌 문자열 “abc”를 전달하려고 하면, FastAPI는 이를 올바르지 않은 입력으로 간주하고 400 Bad Reqeuest 응답을 반환함. 이는 type hint 덕분에 가능한 동작. You can learn more about type hint and pydantic at https://arcstone09.github.io/study/2024-10-05-waffle-1-4
- FastAPI가 list_brand_items을 호출하는 부분은 사용자 코드에서 명시적으로 호출하지 않아도 됩니다. 대신 요청이 들어오면 FastAPI가 내부적으로 호출합니다.
@app.get("/brands/{brands_id}/items", status_code=status.HTTP_200_OK)
def list_brand_items(brand_id: int = Path(...)):
...
- Path 라는 함수를 기본값으로 전달해서 추가적인 정보를 전달할 수 있음. Path는 FastAPI에서 경로 매개변수의 유효성을 검사하고 추가 메타데이터를 정의하기 위해 사용함. 위 코드에서 Path는 brand_id라는 경로 매개변수를 선언하고, 기본적으로 FastAPI에 아래와 같은 정보를 제공함. 이를 통해 간단한 validation이나, API 문서를 보강하는 등의 작업을 할 수 있음.
- brand_id가 경로에서 제공되어야 하는 매개변수임을 FastAPI에 알립니다. 예를 들어, 클라이언트가 /brands/123/items에 요청을 보낸다면, brand_id는 123으로 전달됩니다.
- Path(…)에서 …은 brand_id가 필수 매개변수임을 나타냅니다. 값을 제공하지 않으면 FastAPI는 422 Unprocessable Entity 에러를 반환합니다.
- Path를 통해 값의 유효성을 검사할 수 있습니다. 예를 들어
brand_id: int = Path(..., ge=1)ge=1은 brand_id가 1 이상이어야 함을 명시합니다. 이를 통해 경로 매개변수가 유효하지 않은 경우 FastAPI가 자동으로 에러를 반환합니다.
- Path를 사용하면 설명과 예제 등을 추가할 수 있습니다.
brand_id: int = Path(..., description="The ID of the brand", example=123)description: Swagger 문서에 표시될 설명. example: 경로 매개변수의 예제 값.
- 변수 이름 brand_id: int과 URL 경로 매개변수 이름 {brand_id}이 반드시 같아야 하는 것은 아닙니다. 하지만, 둘의 이름이 다를 경우, FastAPI에 추가적인 매핑 작업이 필요합니다. URL 경로 매개변수 {brand_id}와 Python 함수의 매개변수 이름이 동일하면, FastAPI가 이를 자동으로 매핑합니다. 가독성과 유지보수를 위해 권장되는 방식입니다. 만약 URL 경로 매개변수 이름({brand_id})과 Python 함수의 매개변수 이름이 다르면, FastAPI에 alias를 명시적으로 지정해야 합니다.
@app.get("/brands/{brand_id}/items") def list_brand_items(brandid: int = Path(..., alias="brand_id")): # URL 경로 {brand_id}의 값을 brandid 변수로 가져옴 ...
Query String
- Query String : 리소스 필터링, 정렬, 페이징 등 계층 구조가 아닌 리소스 식별자
- Path 끝에서 “?” 뒤에서부터, “#” 앞 또는 URL의 끝 부분까지
- “key=value” 형태의 값을 “&”로 구분하여 전달하는 것이 사실상 표준
- 몇몇 특수문자는 사용을 지양하거나 인코딩을 필요로 함
- ”#” : Query String의 끝을 나타내는 문자
- “&, =” : key, value 구분자
- ”/” : Path와 헷갈릴 수 있으므로 key에서는 사용을 지양
- value로 전달이 불가피하다면 인코딩을 적용
@app.get("/items", status_code=status.HTTP_200_OK) def list_items(min_price: int = Query(0), max_price: int = Query(99999)): ...
- 엔드포인트의 파라미터 중 Path가 아니면 자동으로 Query String으로 추론됨
- Path 와 마찬가지로 Query 함수를 이용해서 추가적인 기능을 제공할 수 있음
- 파라미터를 옵셔널하게 만들려면, 기본값을 전달하거나(None) 타입을 Optional로 지정.
min_price: Optional[int] = Quere(None) - 파라미터를 필수로 만들려면, 기본값을 전달하지 않거나, Query(…)으로 지정.
min_price: int = Query(...)
Header
Below is the Header example of HTTP Request.
Host: sns.wafflestudio.com
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...
Accept: applicaion/json
Accept-Encoding: gzip, deflate
Content-Length: 29
Content-Type: application/json; charset=utf-8
{"message": "Hellow World"} // this line is body
- Header : 요청의 메타데이터
- Request Line 다음부터, Body 전까지
- “key: value” 형태의 줄들로 이루어짐
- Header와 Body 사이에는 빈 줄(CRLF)이 존재해야 함
@app.post("/itmes", status_code=status.HTTP_201_CREATED)
def create_item(item: Item, user_agent: str = Header(None)):
...
- Path, Querey와 달리 반드시 Header 함수를 이용해서 명시적으로 지정해야 함. Path, Query는 Path나 Query 함수를 쓰지 않아도 자동으로 매핑됨.
- Query와 마찬가지로, 기본값이나 옵셔널 여부 지정 가능
- 일부 헤더들은 브라우저에 의해 자동으로 전달되며, 특별한 의미를 가지고 있음
- Host: 요청을 받는 서버의 호스트 정보
- Referer: 이전 페이지의 URL 정보
- User-Agent: 클라이언트의 소프트웨어 정보
- Content-Type: Body의 타입을 지정
- Content-Length: Body의 길이를 지정
- Accept: 클라이언트가 받아들일 수 있는 타입
- Accept-Encoding: 클라이언트가 받아들일 수 있는 압축 방식
- Authorization: 클라이언트의 인증 정보
- Cookie: 클라이언트의 세션 정보
- …
Body
Below is the Body example of HTTP Request.
{"message": "Hellow World"}
- Body : Header의 마지막 빈 줄 다음부터 끝까지
- Body가 존재하는 경우에는 컨텐츠의 메타데이터를 Header에서 전달해야 함.
- Content-Length, Content-Type, Transfer-Encoding 등
class Item(BaseModel):
name: str
price: float
brand_id: int
@app.post("/items", status_code=status.HTTP_201_CREATED)
def create_item(item: Item):
...
- Body는 pydantic을 통해 파싱됨. Pydantic 모델은 Body 데이터를 처리하는 데 사용된다는 암묵적인 규칙이 있음. item: Item 에서 매개변수의 타입이 Pydantic 모델(이 경우 Item)으로 선언되어 있으므로 Body에서 데이터를 가져옴.
- Pydantic은 type hint에 기반한 JSON 직렬화(Python 객체 -> JSON 변환)/ 역직렬화(JSON -> Python 객체로 변환) 라이브러리
- Nested Model, Optional Field, Default Value, Validation 등을 지원.
class Item(BaseModel):
name: str = Field(alias="item_name", max_length=100)
price: float
brand_id: int
model_config = ConfigDict(
str_to_lower = True,
str_max_length = 100,
extra = "ignore",
faux_immutable = True,
)
- Field를 이용해서 필드별 옵션을 지정할 수 있음
- model_config를 통해 모델의 옵션을 지정할 수 있음
- v1에서는 Config 클래스를 이용해서 모델 전체의 옵션을 지정할 수 있었음
- FastAPI 버전에 따라 Pydantic 버전도 달라지므로, 사용하는 버전에 유의할 것
Request
@app.post("/items", status_code=status.HTTP_201_CREATED)
async def create_item(request: Request):
print(request.method)
print(request.url)
print(request.headers)
print(request.query_params)
print(await request.body())
...
- 흔하진 않지만, 다양한 이유로 Request 객체가 직접 필요할 수 있음
- Request 객체에는 거의 날 것의 Request Message를 포함해서 다양한 메타데이터가 포함되어 있음
POST
http://localhost:8000/items?some_key=some_value
Headers({'host': 'localhost:8000', 'user-agent': 'curl/8.7.1', 'accept': '*/*', 'some-header': 'Some Value', 'content-type': 'application/json', 'content-length': '27'})
some_key=some_value
b'{"some_json": "some_value"}'

Leave a Comment