1. 程式人生 > 實用技巧 >FastAPI框架

FastAPI框架

FastAPI 文件

目錄

1 前置基礎介紹

1.1 Web開發模式

FastAPI 適用於前後端分離模式

注:

​ 1 後端僅返回前端所需的資料

​ 2 API: 前後端分離模式中,每一個函式或試圖稱之為介面或API

1.2 基於RESTful的FastAPI的設計風格

在進行API介面設計時,應規定統一的介面設計方式採用的RESTful API設計風格

請求方式

不同的URL 採用不同的請求方式,代表不同的執行操作

常用的HTTP請求方式有下面四個:

請求方式 說明
GET 獲取資源資料(單個或多個)
POST 新增資源資料
PUT 修改資源資料
DELETE 刪除資源資料

響應資料格式

伺服器返回的響應資料格式,應該儘量使用JSON

響應的狀態碼

伺服器向客戶端返回的狀態碼和提示資訊

200 OK - [GET]:伺服器成功返回使用者請求的資料
201 CREATED - [POST/PUT/PATCH]:使用者新建或修改資料成功
202 Accepted - [*]:表示一個請求已經進入後臺排隊(非同步任務)
204 NO CONTENT - [DELETE]:使用者刪除資料成功
400 INVALID REQUEST - [POST/PUT/PATCH]:使用者發出的請求有錯誤,伺服器沒有進行新建或修改資料的操作
401 Unauthorized - [*]:表示使用者沒有許可權(令牌、使用者名稱、密碼錯誤)
403 Forbidden - [*] 表示使用者得到授權(與401錯誤相對),但是訪問是被禁止的
404 NOT FOUND - [*]:使用者發出的請求針對的是不存在的記錄,伺服器沒有進行操作
406 Not Acceptable - [GET]:使用者請求的格式不可得
410 Gone -[GET]:使用者請求的資源被永久刪除,且不會再得到的
422 Unprocesable entity - [POST/PUT/PATCH] 當建立一個物件時,發生一個驗證錯誤
500 INTERNAL SERVER ERROR - [*]:伺服器發生錯誤,使用者將無法判斷髮出的請求是否成功

1.3 REST介面開發的核心任務

將請求的資料(如JSON格式)轉換為模型類物件

操作資料庫

將模型類物件轉換為響應的資料(如JSON格式)

序列化 :將資料庫資料序列化為前端所需要的格式 如(字典、JSON、XML等)

反序列化: 將前端傳送的資料反序列化儲存到模型類物件,並儲存到資料庫中

2 FastAPI介紹

2.0 概述

為什麼選擇 FastAPI ?

FastAPI 是Python領域(3.6+)用來構建 API 服務的一個高效能框架。

一、快速

效能極高,可與 NodeJS, Go 媲美。(得益於Starlette和Pydantic)。

Starlette 是一個輕量級 ASGI 框架/工具包。它非常適合用來構建高效能的 asyncio 服務,並支援 HTTP 和 WebSockets。

官方網址:https://www.starlette.io/

Pydantic 是一個使用Python型別提示來進行資料驗證和設定管理的庫。Pydantic定義資料應該如何使用純Python規範用並進行驗證。

官方網址:https://pydantic-docs.helpmanual.io/

二、簡單易懂,易於上手

1、設計的初衷就是易於學習和使用。不需要閱讀複雜的文件。

2、良好的編輯器支援。支援自動完成,可以花更少的時間用於除錯。

3、程式碼複用性強。

4、方便的 API 除錯,生成 API 文件。

5、能提高開發人員兩到三倍的開發速度。減少40%的人為出錯機率。

三、健壯性強

企業級程式碼質量。

四、標準統一

遵循以下API解決方案的開放標準:OpenAPI (也叫Swagger) and JSON Schema

效能測試參考:

https://www.techempower.com/benchmarks/#section=test&runid=7464e520-0dc2-473d-bd34-dbdfd7e85911&hw=ph&test=query&l=zijzen-7

2.1 環境安裝與使用

FastAPI是基於python3.6+和標準python型別的一個現代化的,快速的(高效能) 的非同步框架,有自動式互動文件,非常適合構建RESTful API。

檢視官方文件: https://fastapi.tiangolo.com

2.2 FastAPI特性

詳情檢視官方文件:

https://fastapi.tiangolo.com/features/

FastAPI的特點:

基於開放的標準

  • OpenAPI的建立,包含了路徑操作,引數,請求體和安全等的宣告。
  • 通過JSON Schema自動的資料模型文件。
  • 圍繞著這些標準設計,通過嚴謹的學習,而不是在上面加一層後記。
  • 也允許在很多種語言中使用自動的客戶端程式碼生成器

3.1 使用虛擬環境

使用虛擬環境安裝fastapi

引用:https://www.cnblogs.com/chjxbt/p/10517952.html

3 FastAPI 基礎內容

本章案例在 test_fastapi/FastAPI基礎內容目錄下執行

3.1 啟動專案 main.py

from fastapi import FastAPI
import uvicorn
# 建立app應用
app = FastAPI()

# 路徑裝飾器
@app.get("/")
async def root():
    return {"message": "Hello World"}
  
# 為路徑新增引數
@app.get("/items/{item_id}")
async def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}
   
# 現測試環境採用調式模式執行
if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

進入互動式API文件

現在進入 http://127.0.0.1:8000/docs.

你會看到自動生成的互動式api文件,可以進行操作

3.2 路徑引數

我們可以宣告路徑“引數”或“變數”與Python格式字串使用相同的語法:

# 路徑引數
from fastapi import FastAPI

app = FastAPI()


@app.get("/me/xx")
async def read_item_me():
    return {"me": 'me'}


@app.get("/me/{item_id}")  # item_id 為路徑引數
async def read_item(item_id: str):
    return {"item_id": item_id}


@app.get("/")
async def main():
    return {"message": "Hello,FastAPI"}


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

路徑引數item_id的值將作為引數itemid傳遞給函式。

執行這個示例並轉到http://127.0.0.1:8000/items/foo,您將看到一個響應:

{"item_id" : "foo"}

宣告帶有型別的路徑引數

注意:宣告型別從引數為必填項

@app.get("/items/{item_id}")
async def read_item(item_id: int):  # item_id 為int型別
    return {"item_id": item_id}

進入API互動式文件中校驗

3.3 查詢引數

當您宣告不屬於路徑引數一部分的其他函式引數時,它們將被自動解釋為“查詢”引數。

from fastapi import FastAPI  

app = FastAPI()

fake_items_db = [{"item_name": "Foo"}, {"item_name": "Bar"}, {"item_name": "Baz"}]


@app.get("/items/")
async def read_item(skip: int = 0, limit: int = 10):  # 例如skip就是查詢引數
    return fake_items_db[skip : skip + limit]

查詢是在?之後的鍵-值對集合。在URL中,用&字元分隔(查詢字串)

例如,在url中: 或者進入互動式文件中

http://127.0.0.1:8000/items/?skip=0&limit=10  

查詢引數為: skip:值為0; limit:值為10

同樣我們可以設定為可選引數 為None , 以及 引數型別裝換

還有多路徑引數 @app.get("/users/{user_id}/items/{item_id}")

from fastapi import FastAPI

app = FastAPI()


@app.get("/items/{item_id}")
async def read_item(item_id: str, q: str = None, short: bool = False):
    item = {"item_id": item_id}
    if q:
        item.update({"q": q})
    if not short:
        item.update(
            {"description": "This is an amazing item that has a long description"}
        )
    return item
  

3.4 FastAPI請求體

當你需要從一個客戶端(簡單來說是瀏覽器)傳送資料給你的API,你傳送的內容叫做請求體

一個請求體資料通過客戶端傳送到你的API。你的API返回一個響應體給客戶端。

你的API總是傳送一個響應體,但是客戶端不必一直髮送請求體。

為了宣告一個請求體,使用Pydatic模型支援。

注意: 使用Pydatic 傳送請求體不能是GET 請求,必須使用POST,PUT,DELETE等傳送資料

from fastapi import FastAPI  
from pydantic import BaseModel  

# 這個是建立資料模型 繼承與BaseModel
class Item(BaseModel):
    name: str
    description: str = None  # 不必要的預設值可以用Noen
    price: float
    tax: float = None  


app = FastAPI()
@app.post("/items/")
async def create_item(item_id:int, item: Item, q:str=None):  # 宣告一個item引數指向Item資料模型
  	print(item.name)
    print(item.dict())
    result = {"item_id": item_id, **item.dict()}
    if q:
        result.update({"q": q})
    return result

檢視互動式文件

返回的為json資料

3.5 查詢引數和字串驗證

FastAPI允許宣告額外的資訊並且驗證你的引數。

從fastapi中匯入Query

from fastapi import FastAPI, Query

app = FastAPI()


@app.get("/items/")
async def read_items(q: str = Query(None, min_length=3, max_length=50)):  # 限制長度
    results = {"items": [{"item_id": "Foo"}, {"item_id": "Bar"}]}
    if q:
        results.update({"q": q})
    return results

3.6 路徑引數和數值驗證

可以使用Query宣告查詢引數的更多驗證和元資料的方式相同,也可以使用Path宣告相同型別的驗證和路徑引數的元資料。

從fastapi中匯入Path

from fastapi import FastAPI, Path, Query

app = FastAPI()


@app.get("/items/{item_id}")
async def read_items(
    item_id: int = Path(..., title="The ID of the item to get",ge=1),
    q: str = Query(None, alias="item-query"),
):
    results = {"item_id": item_id}
    if q:
        results.update({"q": q})
    return results

始終需要一個路徑引數,因為它必須是路徑的一部分。

.. 作為改標記的必填項 gt:大於,le:小於等於,lt:小於

3.7 宣告主體多引數

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):  # 專案資料模型
    name: str
    description: str = None
    price: float
    tax: float = None


class User(BaseModel): # 使用者資料模型
    username: str
    full_name: str = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item, user: User, importance: int = Body(...)):
    results = {"item_id": item_id, "item": item, "user": user, "importance": importance}
    return results

在這種情況下,FastAPI將注意到該函式中有多個主體引數(兩個引數是Pydantic模型)。

因此,它將使用引數名稱作為正文中的鍵(欄位名稱),並期望一個類似於以下內容的正文:

{
    "item": {
        "name": "Foo",
        "description": "The pretender",
        "price": 42.0,
        "tax": 3.2
    },
    "user": {
        "username": "dave",
        "full_name": "Dave Grohl"
    },
    "importance": 5
}

3.8 body 欄位

您可以使用QueryPathBody在路徑操作函式引數中宣告其他驗證和元資料的方式相同,也可以使用Pydantic的Field在Pydantic模型內部宣告驗證和元資料。

from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

app = FastAPI()

# 請求體引數驗證,使用 Field 欄位
class Item(BaseModel):
    name: str
    description: str = Field(None, title="The description of the item", max_length=300)
    price: float = Field(..., gt=0, description="The price must be greater than zero")
    tax: float = None


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item = Body(..., embed=True)):
    results = {"item_id": item_id, "item": item}
    return results

demo2

from fastapi import Body, FastAPI
from pydantic import BaseModel, Field

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float = Field(..., gt=0)
    tax: float = None


@app.put("/items/{item_id}")
async def update_item(
    *,
    item_id: int,
    item: Item = Body(...,
        example={   # example是Body裡沒有的欄位;不會新增任何驗證,而只會添加註釋;不是example也不行
            "name": "Foo",
            "description": "A very nice Item",  # 預設值
            "price": 0,
            "toooo": 3.2,
        },
    )
):
    results = {"item_id": item_id, "item": item}
    return results


if __name__ == '__main__':
    import uvicorn
    uvicorn.run(app, host="127.0.0.1", port=8000)

Field的工作方式與QueryPathBody相同,它具有所有相同的引數,等等。

3.9 body巢狀模型

使用FastAPI,可以定義,驗證,記錄和使用任意深度巢狀的模型(這要歸功於Pydantic)

from typing import Optional, List

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# 定義一個數據模型,子模型
class Image(BaseModel):
		url: str
		name: str
  

class Item(BaseModel):
    name: str
    description: Optional[str] = None
    price: float
    tax: Optional[float] = None
    tags: List[str] = []  # 列表型別
    tags2: Set[str] = set() # 集合型別
   	# 巢狀另一個子模型
    image: Optional[Image] = None
      


@app.put("/items/{item_id}")
async def update_item(item_id: int, item: Item):
    results = {"item_id": item_id, "item": item}
    return results

這意味著FastAPI將期望類似於以下內容的主體:

{
    "name": "Foo",
    "description": "The pretender",
    "price": 42.0,
    "tax": 3.2,
    "tags": ["rock", "metal", "bar"],
  	"tags2": [
      "aaa",
      "aaaa"
    ],
    "image": {
        "url": "http://example.com/baz.jpg",
        "name": "The Foo live"
    }
}

3.10 額外的資料型別

目前為止我們看到的常用的資料型別有int,str,float,bool

除此之外還以更多的混合資料型別

UUID

* 一個標準的“通用唯一識別符號”,在許多資料庫和系統中通常作為ID使用。

*在請求和響應中將以str表示。

datetime.datetime 例如:2008-09-15T15:53:00 + 05:00

datetime.date: 例如:2008-09-15

datetime.time: 例如: 14:23:55.003

datetime.timedelta: 例如: Pydantic還允許將其表示為“ ISO 8601時間差異編碼

# 額外的資料型別

from datetime import datetime, time, timedelta
from uuid import UUID
from uuid import uuid1
from fastapi import Body, FastAPI

app = FastAPI()


# 額外資料型別
# https://fastapi.tiangolo.com/tutorial/extra-data-types/
@app.put("/items/{item_id}")
async def read_items(
        item_id: UUID,
        start_datetime: datetime = Body(None),
        end_datetime: datetime = Body(None),
        repeat_at: time = Body(None),
        process_after: timedelta = Body(None),
):
    start_process = start_datetime + process_after
    duration = end_datetime - start_process

    return {
        "item_id": item_id,
        "start_datetime": start_datetime,
        "end_datetime": end_datetime,
        "repeat_at": repeat_at,
        "process_after": process_after,
        "start_process": start_process,
        "duration": duration,
    }


if __name__ == '__main__':
    import uvicorn

    print('uuid:', uuid1())
    uvicorn.run(app, host="127.0.0.1", port=8000)

本章具體解釋請看官方文件 # https://fastapi.tiangolo.com/tutorial/extra-data-types/

3.11 cookie引數和header引數

cookie demo

from fastapi import Cookie, FastAPI

app = FastAPI()


@app.get("/items/")
async def read_items(*, ads_id: str = Cookie(None)):
    return {"ads_id": ads_id}


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

header demo

from fastapi import FastAPI, Header
from typing import List

app = FastAPI()


# 要宣告標頭,您需要使用Header,否則引數將被解釋為查詢引數。

@app.get("/items/")
async def read_items(*, user_agent: str = Header(None), users_agent: str =Header(None)):
    
    return {"User-Agent": user_agent}, {"AAAAA": user_agent}, {'ABCD': users_agent}

if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

3.12 響應模型 Response Model

可以在任何路徑操作中使用引數response_model宣告用於響應的模型

@app.get(), @app.post, @app.put @app.delete 等

from typing import List

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class Item(BaseModel):
    name: str
    description: str = None
    price: float
    tax: float = None
    tags: List[str] = []


@app.post("/items/", response_model=Item)  # 響應輸出pydantic模型
async def create_item(item: Item):
    return item

注意,response_model是“decorator”方法的引數(get,post等)。不像所有引數和主體一樣,具有路徑操作功能。

它接收的型別與您為Pydantic模型屬性宣告的型別相同,因此它可以是Pydantic模型,但也可以是例如一個list的Pydantic模型,例如List[Item]

FastAPI將使用此response_model進行以下操作:

  • 把輸出資料轉化為型別宣告的格式
  • 驗證資料
  • 給響應新增一個json 模式, 在OpenAPI的路徑操作下。
  • 將由自動文件系統使用
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):
    username: str
    password: str
    email: EmailStr
    full_name: str = None


# Don't do this in production!
@app.post("/user/", response_model=UserIn)  # 宣告響應模型
async def create_user(*, user: UserIn): 
    return user	# 同一模型宣告輸出

現在,每當瀏覽器使用密碼建立使用者時,API都會在響應中返回相同的密碼。

在這種情況下,這可能不是問題,因為使用者自己正在傳送密碼。

但是,如果我們對另一個路徑操作使用相同的模型,則可能會將使用者的密碼傳送給每個客戶端。

應該新增一個輸出模型

我們可以改用純文字密碼建立輸入模型,而沒有明文密碼則建立輸出模型:

from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserIn(BaseModel):  # 輸入模型,在互動式文件中輸入的
    username: str
    password: str
    email: EmailStr
    full_name: str = None
      
class UserOut(BaseModel):  # 輸出模型 執行後所輸出的模型資料
  	username:str
    email: EmailStr
    fill_name: str = None


# Don't do this in production!
@app.post("/user/", response_mode:UserOut)  # 宣告響應模型
async def create_user(*, user: UserIn): 
    return user	# 同一模型宣告輸出
  

因此,FastAPI將負責過濾掉未在輸出模型中宣告的所有資料(使用Pydantic)。

在文件中檢視

當您看到自動文件時,可以檢查輸入模型和輸出模型是否都具有自己的JSON模式:

輸入的模型資料

輸出的資料

概括

使用路徑操作修飾器的引數response_model定義響應模型,尤其是確保私有資料被過濾掉。

3.13 擴充套件模型

繼續前面的示例,通常會有多個相關模型

使用者模型尤其如此,因為:

input model 需要有一個密碼

Output model 不需要密碼

databases model 可能需要一個雜湊密碼

減少程式碼重複是FastAPI的核心思想之一。

隨著程式碼重複的增加,錯誤,安全問題,程式碼不同步問題(當您在一個地方進行更新,而不是在另一個地方進行更新)等問題的可能性也增加了。

這些模型都共享大量資料,並且複製屬性名稱和型別。我們可以宣告一個UserBase模型作為其他模型的基礎。

然後,我們可以使該模型的子類繼承其屬性(型別宣告,驗證等)。

這樣,我們可以只宣告模型之間的差異(使用純文字密碼,使用hashed_password和不使用密碼):

import uvicorn
from fastapi import FastAPI
from pydantic import BaseModel, EmailStr

app = FastAPI()


class UserBase(BaseModel):
    username: str
    email: EmailStr
    full_name: str = None


# UserBase 作為模型的基礎,其繼承,相同的資料不要寫 減少程式碼重複是FastAPI的核心思想之一
class UserOut(UserBase):
    pass


class UserInDB(UserBase):
    hashed_password: str


class UserIn(UserBase):
    password: str


# 進行雜湊密碼
def fake_password_hasher(raw_password: str):
    return "supersecret" + raw_password


# 儲存使用者
def fake_save_user(user_in: UserIn):
    hash_password = fake_password_hasher(user_in.password)
    # 儲存使用者
    # **user_id.dict() 表示一個完整的模型資料dict **稱之為一個關鍵字引數
    user_in_db = UserInDB(**user_in.dict(), hashed_password=hash_password) # 偽雜湊加密

    print("user saved! ... not really")

    print(user_in_db)

    return user_in_db


@app.post("/user/", response_model=UserOut)  # 響應輸入模型
async def create_user(*, user_in: UserIn):  # 響應輸出模型
    user_saved = fake_save_user(user_in)
    return user_saved


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

可以進行互動式文件檢視

響應模型列表

同樣,您可以宣告物件列表的響應。

為此,請使用標準的Python typing.List

from typing import List
from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

class Item(BaseModel):
    name: str
    description: str


items = [
    {"name": "Foo", "description": "There comes my hero"},
    {"name": "Red", "description": "It's my aeroplane"},
]


@app.get("/items/", response_model=List[Item])
async def read_items():
    return items

得到的響應結果

200 ok
  {
    "name": "Foo",
    "description": "There comes my hero"
  },
  {
    "name": "Red",
    "description": "It's my aeroplane"
  }
]

同樣你可以帶dict中的響應

概括
使用多個Pydantic模型,並針對每種情況自由繼承。

如果每個實體必須能夠具有不同的“狀態”,則無需為每個實體擁有單個數據模型。以使用者“實體”為例,其狀態包括password,password_hash和無密碼。

3.14 響應狀態碼

響應狀態碼我們可以在任何路徑操作中使用status_code 宣告用於響應的HTTP狀態程式碼

例:

from fastapi import FastAPI

app = FastAPI()


@app.post("/items/", status_code=201)
async def create_item(name: str):
    return {"name": name}

每一個狀態表示著不同操作

我們還可以用 fastapi.status的便捷變數

from fastapi import FastAPI, status

app = FastAPI()


@app.post("/items/", status_code=status.HTTP_201_CREATED)
async def create_item(name: str):
    return {"name": name}

3.15 表單資料

安裝 pip install python-multipart

匯入

from fastapi import FastAPI, Form

使用正向或查詢的方式建立表單引數

from fastapi import FastAPI, Form

app = FastAPI()


@app.post("/login/")
async def login(*, username: str = Form(...), password: str = Form(...)):
    return {"username": username}

在登入註冊項 或許需要傳送usernamepassword作為表單欄位

規範要求這些欄位必須準確命名為usernamepassword,並作為表單欄位(而不是JSON)傳送。

3.16 請求檔案

我們可以使用檔案定義客戶端要上傳的檔案

安裝:pip install python-multipart

匯入 File

from fastapi import FastAPI, File, UploadFile

定義File引數

from fastapi import FastAPI, File, UploadFile

app = FastAPI()


@app.post("/files/")
async def create_file(file: bytes = File(...)):
    return {"file_size": len(file)}

這些檔案將作為“表單資料”上傳。

如果將路徑操作函式引數的型別宣告為位元組(bytes),FastAPI將為您讀取檔案,並且您將接收內容(位元組)。

請記住,這意味著全部內容將儲存在記憶體中。這對於小檔案將很好地工作。

但是在某些情況下,您可以從使用UploadFile中受益。

@app.post("/uploadfile/")
async def create_upload_file(file: UploadFile = File(...)):
    return {"filename": file.filename}

該返回檔名

3.17 錯誤處理

在許多情況下,您需要將錯誤通知給正在使用API的客戶端。

該客戶端可以是帶有前端的瀏覽器,其他人的程式碼,IoT裝置等。

我們需要告知客戶端: 客戶端沒有足夠的許可權進行該操作。客戶端無權訪問該資源。客戶端嘗試訪問的專案不存在。

在這些情況下,您通常會返回400(從400到499)範圍內的HTTP狀態程式碼

使用 HTTPException

返回有錯誤的http響應給客戶端使用 HTTPException

匯入 from fastapi import FastAPI, HTTPException

HTTPException是普通的Python異常,其中包含與API相關的其他資料。

因為它是Python異常,所以您不返回它,而是raise它。

在此示例中,當客戶端通過不存在的ID請求商品時,引發狀態程式碼為404的異常:

from fastapi import FastAPI, HTTPException

app = FastAPI()

items = {"foo": "The Foo Wrestlers"}


@app.get("/items/{item_id}")
async def read_item(item_id: str):
    if item_id not in items:
        raise HTTPException(status_code=404, 
                            detail="Item not found"
                           headers={"X-Error": "There goes my error"})  # 自定義請求頭
                    # X-Error 自定義。例如,對於某些型別的安全性。OAuth 2.0和某些安全實用程式在內部需要/使用此功能。

    return {"item": items[item_id]}  

結果響應

如果客戶端請求http://example.com/items/fooitem_id為“bar”),則他將收到HTTP狀態程式碼200和JSON響應:

{
  "item": "The Foo Wrestlers"
}

但是,如果客戶端請求http://example.com/items/bar(不存在的item_id“ bar”),則他將收到HTTP狀態碼404(“找不到”錯誤)和JSON響應:

{
  "detail": "Item not found"
}

3.18 路徑操作配置

您可以將幾個引數傳遞給路徑操作裝飾器以對其進行配置。

注意,這些引數直接傳遞給路徑操作裝飾器,而不是傳遞給路徑操作函式

官方文件: https://fastapi.tiangolo.com/tutorial/path-operation-configuration/

3.20 JSON相容編碼

在某些情況下,您可能需要將資料型別(例如Pydantic模型)轉換為與JSON相容的資料(例如dict,list等)。

例如,如果您需要將其儲存在資料庫中。

為此,FastAPI提供了jsonable_encoder()函式。

假設您有一個僅接收JSON相容資料的資料庫fake_db

例如,它不接收日期時間物件,因為它們與JSON不相容。

因此,datetime物件必須轉換為包含ISO格式資料的str。

同樣,該資料庫將不會接收Pydantic模型(具有屬性的物件),而只會接收dict

您可以為此使用jsonable_encoder

它接收一個物件,如Pydantic模型,並返回JSON相容版本:

from datetime import datetime

from fastapi import FastAPI
from fastapi.encoders import jsonable_encoder
from pydantic import BaseModel

fake_db = {}


class Item(BaseModel):
    title: str
    timestamp: datetime
    description: str = None


app = FastAPI()


@app.put("/items/{id}")
def update_item(id: str, item: Item):
    json_compatible_item_data = jsonable_encoder(item)
    fake_db[id] = json_compatible_item_data
    # jsonable_encoder實際上由FastAPI在內部用於轉換資料。但這在許多其他情況下很有用。
    print(json_compatible_item_data)
    print(type(json_compatible_item_data))
    print(fake_db)
    print(type(fake_db))
    
    return fake_db
    
# 在此示例中,它將Pydantic模型轉換為dict,並將日期時間轉換為str。
#呼叫它的結果是可以用Python標準json.dumps()進行編碼的東西、。

3.21 body更新

官方文件:https://fastapi.tiangolo.com/tutorial/body-updates/

3.22 FastAPI 依賴部分

FastAPI有一個非常強大但直觀的依賴注入系統。

依賴是在函式的執行前呼叫

當進行需要執行依賴操作時,這將非常有用,前提是:

  • 具有共享邏輯(一次又一次地使用相同的程式碼邏輯)。
  • 共享資料庫連線
  • 強制執行安全性,身份驗證,角色要求等

所有這些,同時最大限度的減少了程式碼的重複

建立一個依賴項或“ dependable”

讓我們首先關注依賴關係

它只是一個函式,可以採用路徑操作函式可以採用的所有相同引數:

from fastapi import Depends, FastAPI

app = FastAPI()


# 被依賴的函式
async def common_parameters(q: str = None, skip: int = 0, limit: int = 100):
    return {"q": q, "skip": skip, "limit": limit}


# Depends 接收的引數必須是類似於函式
# 這裡的 Depends 當每次請求前會呼叫 Depends()中的依賴項,
# 從這個依賴項函式中獲取結果,最終將該結果分配給路徑操作函式中的引數,也就是 common引數
# 這樣,我們只需編寫一次共享程式碼,FastAPI便會為路徑操作呼叫共享程式碼。
@app.get("/items/")
async def read_items(common: dict = Depends(common_parameters)):
    common["test"] = "test"

    return common


@app.get("/users/")
async def read_users():
    return {"code": 200, "message": "ok"}


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

路徑操作裝飾器新增依賴項

from fastapi import Depends, FastAPI, Header, HTTPException

app = FastAPI()


async def verify_token(x_token: str = Header(...)):
    if x_token != "fake-super-secret-token":
        raise HTTPException(status_code=400, detail="X-Token header invalid")


async def verify_key(x_key: str = Header(...)):
    if x_key != "fake-super-secret-key":
        raise HTTPException(status_code=400, detail="X-Key header invalid")
    return x_key

@app.get("/items/", dependencies=[Depends(verify_token), Depends(verify_key)])
async def read_items():
    return [{"item": "Foo"}, {"item": "Bar"}]

這些依賴將會在安全會中起到很大用處

3.23 FastAPI 中介軟體

“中介軟體”是一種功能,該功能可在任何請求通過任何特定路徑操作處理之前與每個請求一起使用。以及返回之前的每個響應。

要建立中介軟體,請在函式頂部使用修飾符@app.middleware("http")

import time

from fastapi import FastAPI, Request

app = FastAPI()

# 中介軟體,該功能在任何路徑操作之前,先執行該中介軟體
# 計算http請求和生成響應所花費的時間
@app.middleware("http")
async def add_process_time_header(request: Request, call_next):
    start_time = time.time()
    response = await call_next(request)
    process_time = time.time() - start_time
    response.headers["X-Process-Time"] = str(process_time)
    return response
  
  
@app.get("/")
async def main():
    return {"message": "Hello World"}

例如新增CORSMiddleware 中介軟體

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://localhost",
    "http://localhost:8080",
]
# app.add_middleware()`接收中介軟體類作為第一個引數,並接收任何其他要傳遞給中介軟體的引數。
app.add_middleware(         # 新增中介軟體
    CORSMiddleware,         # CORS中介軟體類
    allow_origins=origins,  # 允許起源
    allow_credentials=True,  # 允許憑據
    allow_methods=["*"],    # 允許方法
    allow_headers=["*"],    # 允許頭部
)



@app.get("/")
async def main():
    return {"message": "Hello World"}

有關CORS的更多資訊,請檢視Mozilla CORS文件

3.24 FastAPI Cors 跨越請求

CORS或“跨源資源共享”是指瀏覽器中執行的前端具有與後端進行通訊的JavaScript程式碼,並且後端與前端具有不同的“來源”的情況。

Origin是協議(httphttps),域(myapp.comlocalhostlocalhost.tiangolo.com)和埠(804438080)的組合。

使用 CORSMiddleware

from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

origins = [
    "http://localhost.tiangolo.com",
    "https://localhost.tiangolo.com",
    "http://localhost",
    "http://localhost:8080",
]

app.add_middleware( # 新增中介軟體
    CORSMiddleware,  # CORS中介軟體類
    allow_origins=origins,  # allow_origins=['*'], # 允許起源所有
    allow_credentials=True, # 允許憑據
    allow_methods=["*"],  # 允許方法
    allow_headers=["*"],  # 允許頭部
)


@app.get("/")
async def main():
    return {"message": "Hello World"}

3.25 FastAPI 大應用-多個檔案

如果要構建應用程式或Web API,則很少將所有內容都放在一個檔案中。 FastAPI提供了一種方便的工具,可在保持所有靈活性的同時構建應用程式。

相當與flask中的藍圖

檔案結構

fastapi-nano
├── app                                 # primary application folder
│   ├── apis                            # this houses all the API packages
│   │   ├── api_a                       # api_a package
│   │   │   ├── __init__.py             # empty init file to make the api_a folder a package
│   │   │   ├── mainmod.py              # main module of api_a package
│   │   │   └── submod.py               # submodule of api_a package
│   │   └── api_b                       # api_b package
│   │       ├── __init__.py             # empty init file to make the api_b folder a package
│   │       ├── mainmod.py              # main module of api_b package
│   │       └── submod.py               # submodule of api_b package
│   ├── core                            # this is where the configs live
│   │   ├── auth.py                     # authentication with OAuth2
│   │   ├── config.py                   # sample config file
│   │   └── __init__.py                 # empty init file to make the config folder a package
│   ├── __init__.py                     # empty init file to make the app folder a package
│   ├── main.py                         # main file where the fastAPI() class is called
│   ├── routes                          # this is where all the routes live
│   │   └── views.py                    # file containing the endpoints of api_a and api_b
│   └── tests                           # test package
│       ├── __init__.py                 # empty init file to make the tests folder a package
│       └── test_api.py                 # test files
├── docker-compose.yml                  # docker-compose file
├── Dockerfile                          # dockerfile
├── LICENSE                             # MIT license
├── Makefile                            # Makefile to apply Python linters
├── mypy.ini                            # type checking configs
├── requirements.txt                    # app dependencies
└── requirements-dev.txt                # development dependencies

這樣做的好處就是能夠將獨立功能的程式碼分開來進行管理編寫

參照文件: https://fastapi.tiangolo.com/tutorial/bigger-applications/

4 FastAPI進階內容

本節案例,請訪問 test_fastapi/FastAPI進階內容目錄

4.1 自定義響應

預設情況下FastAPI中將使用JSONResponse 返回響應

但是除了JSONResponse 響應之外,還有 Response,HTML Response,RedirectResponse(重定向)響應等

HTML Response 響應

要直接從FastAPI返回帶有HTML的響應,請使用HTMLResponse。

  • 匯入HTMLResponse。
  • 將HTMLResponse作為路徑操作的引數content_type傳遞。
from fastapi import FastAPI
from fastapi.responses import HTMLResponse

app = FastAPI()


@app.get("/items/", response_class=HTMLResponse)
async def read_items():
    return """
    <html>
        <head>
            <title>Some HTML in here</title>
        </head>
        <body>
            <h1>Look ma! HTML!</h1>
        </body>
    </html>
    """

引數response_class也將用於定義響應的“媒體型別”。

HTTP標頭Content-Type將設定為text/html。

RedirectResponse 重定向響應

# 重定向
# 返回HTTP重定向,預設情況下使用307狀態程式碼(臨時重定向)
@app.get("/typer")
async def read_typer():
    return RedirectResponse("https://typer.tiangolo.com")

當我們訪問url時,預設情況會跳轉到我們指定的url

Response響應

Response 返回的是文字或位元組

它接受以下引數:

  • content 一個strbytes`。
  • status_code - 一個int HTTP狀態碼
  • headers - 一個字串dict
  • media_type - 一個給定媒體型別的str, 例如 "text/html"
from fastapi import FastAPI, Response

app = FastAPI()


@app.get("/legacy/")
def get_legacy_data():
    data = """<?xml version="1.0"?>
    <shampoo>
    <Header>
        Apply shampoo here.
    </Header>
    <Body>
        You'll have to use soap here.
    </Body>
    </shampoo>
    """
    return Response(content=data, media_type="application/xml")

4.2自定義響應cookie

當我們在響應頭中響應cookie時,使用 response 引數

我們可以在路徑操作函式中宣告型別為Response的引數。然後,您可以在該時間響應物件中設定cookie

from fastapi import FastAPI, Response

app = FastAPI()


@app.post("/cookie-and-object/")
def create_cookie(response: Response):
    response.set_cookie(key="fakesession", value="fake-cookie-session-value")
    return {"message": "Come to the dark side, we have cookies"}

或者直接返回一個Response 響應

當我們直接在程式碼中返回Response時,也可以建立cookie。

from fastapi import FastAPI
from fastapi.responses import JSONResponse

app = FastAPI()


@app.post("/cookie/")
def create_cookie():
    content = {"message": "Come to the dark side, we have cookies"}
    response = JSONResponse(content=content)
    response.set_cookie(key="fakesession", value="fake-cookie-session-value")
    return response

4.3 自定義設定響應頭

# 使用一個Response引數
# 您可以在路徑操作函式中宣告型別為Response的引數(就像對cookie一樣)。然後,您可以在該時間響應物件中設定標題。
import uvicorn
from fastapi import FastAPI, Response
from starlette.responses import JSONResponse

app = FastAPI()


@app.get("/headers-and-object/")
def get_headers(response: Response):
    response.headers["X-Cat-Dog"] = "alone in the world"
    return {"message": "Hello World"}


# 或者直接返回一個響應頭  -- 通常情況下會這樣使用
@app.get("/headers/")
def get_headers():
    content = {"message": "Hello World"}
    headers = {"X-Cat-Dog": "alone in the world", "Content-Language": "en-US"}
    return JSONResponse(content=content, headers=headers)

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)


# 自定義 Headers
# 請記住,可以使用“X-”字首新增自定義專有標題。
#
# 但是,如果您希望瀏覽器中的客戶端看到自定義標頭,則需要使用Starlette's中記錄的引數Exposure_headers將它們新增到CORS配置中(在CORS(跨源資源共享)中瞭解更多資訊)。 CORS檔案。

4.4 HTTP的基本驗證

為了安全起見,我們可以使用最簡單的情況,可以使用HTTP基本身份驗證

使用簡單的 Http Basic Auth 來進行驗證,應該程式需要包含使用者名稱和密碼的標記來進行驗證,如果未驗證成功,則會返回HTTP 401 未經授權的錯誤。

  • 匯入HTTPBasicHTTPBasicCredentials.
  • 使用HTTPBasic建立一個"security 模式`
  • 在路徑操作中將此安全性與依賴項結合使用。
  • 它返回HTTPBasicCredentials型別的物件:
    • 它包含傳送的使用者名稱和密碼。
import secrets

import uvicorn
from fastapi import Depends, FastAPI, HTTPException, status
from fastapi.security import HTTPBasic, HTTPBasicCredentials

app = FastAPI()

# http驗證
security = HTTPBasic()


# 被依賴項函式,用於檢查使用者名稱和密碼是否正確
def get_current_username(credentials: HTTPBasicCredentials = Depends(security)):
    # 驗證使用者名稱和密碼
    correct_username = secrets.compare_digest(credentials.username, "admin")
    correct_password = secrets.compare_digest(credentials.password, "admin")
    if not (correct_username and correct_password):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Incorrect email or password",
            headers={"WWW-Authenticate": "Basic"},
        )

    return credentials.username


@app.get("/users/me")
def read_current_user(username: str = Depends(get_current_username)):
    return {"username": username}


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

4.5 Request 物件

在FastAPI中 提供了Request 物件,在某些情況下,我們可能需要直接訪問Request請求物件做一些額外的事情,例如 獲取前端傳遞過來的引數。

假設您要在路徑操作功能中獲取客戶端的IP地址/主機。

為此,您需要直接訪問請求。

import uvicorn
from fastapi import FastAPI, Request, Form

app = FastAPI()


@app.get("/items/{item_id}")
def read_root(item_id: str, request: Request):
    client_host = request.client.host  # 獲取客戶端的主機ip
    client_path = request.url.path  # 獲取當前url路徑
    return {"client_host": client_host, "item_id": item_id, "path": client_path, "form": client_username}

# 通過宣告型別為Request的路徑操作函式引數,FastAPI將知道在該引數中傳遞Request。


if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)

具體詳情功能請訪問Request文件

https://www.starlette.io/requests/

4.6 高階中介軟體

由於FastAPI基於Starlette並實現了ASGI規範,因此可以使用任何ASGI中介軟體。

Fastapi是基於Starlette的一個ASGI框架

ASGI,為了規範支援非同步的Python Web伺服器、框架和應用之間的通訊而定製的一個協議

ASGI中介軟體定義

from unicorn import UnicornMiddleware

app = SomeASGIApp()
new_app = UnicornMiddleware(app, some_config="rainbow")

FastAPI 集成了幾種常用的中介軟體

1 HTTPSRedirectMiddleware

強制所有傳入請求必須為https或wss。

app.add_middleware()接收中介軟體類作為第一個引數,並接收任何其他要傳遞給中介軟體的引數。

import uvicorn
from fastapi import FastAPI
from fastapi.middleware.httpsredirect import HTTPSRedirectMiddleware

app = FastAPI()

# 強制所有傳入請求必須為https或wss。
app.add_middleware(HTTPSRedirectMiddleware)


@app.get("/")
async def main():
    return {"message": "Hello World"}


if __name__ == "__main__":
    uvicorn.run(app, host="127.0.0.1", port=8000)

FastAPI在fastapi.middleware中提供了幾種中介軟體,以方便開發人員。但是大多數可用的中介軟體直接來自Starlette。

為了檢視其他的中介軟體,檢視Starlette's Middleware docsASGI Awesome List

4.7 事件啟動與結束

您可以定義在應用程式啟動之前或在應用程式關閉時需要執行的事件處理程式(函式)。

startup事件 與 shutdown結束事件

from fastapi import FastAPI

app = FastAPI()

items = {}

# 程式啟動前要做的事情
@app.on_event("startup")
async def startup_event():
    print("程式啟動之前執行")
    items["foo"] = {"name": "Fighers"}
    items["bar"] = {"name": "Tenders"}


@app.get("/items/{item_id}")
async def read_items(item_id: str):

    return items[item_id]

# 程式結束後要做的事情
# shutdown
@app.on_event("shutdown")
def shutdown_event():
    # 程式結束之後執行
    with open("log.txt", mode="a") as log:
        log.write("Application shutdown")

4.8 WebSocket

HTML5定義了WebSocket協議,能更好的節省伺服器資源和頻寬,並且能夠更實時地進行及時通訊。

簡單示例 單個客戶端連線

檢視文件 :https://www.starlette.io/websockets/

main.py

from fastapi import FastAPI
from starlette.responses import HTMLResponse
from starlette.websockets import WebSocket, WebSocketDisconnect

app = FastAPI()

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off"/>
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            var ws = new WebSocket("ws://localhost:5000/ws");
            ws.onmessage = function(event) {
                var messages = document.getElementById('messages')
                var message = document.createElement('li')
                var content = document.createTextNode(event.data)
                message.appendChild(content)
                messages.appendChild(message)
            };
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()
            }
        </script>
    </body>
</html>
"""


# @app.get("/")告訴FastAPI如何去處理請求
# 路徑 /
# 使用get操作
@app.get("/")
async def get():
    # 返回表單資訊
    return HTMLResponse(html)


@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    # 接收連線數
    await websocket.accept()

    try:
        while True:
            # 接收文字
            data = await websocket.receive_text()
            # 傳送文字資料
            await websocket.send_text(f"Message text was: {data}")
    # 當WebSocket連線關閉時,await websocket.receive_text()會引發WebSocketDisconnect異常,
    except WebSocketDisconnect:
        # 斷開連線
        await websocket.close()
        print("客戶端關閉成功")




if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=5000)

多個客戶端連線收發訊息

from fastapi import FastAPI
from starlette.endpoints import WebSocketEndpoint, HTTPEndpoint
from starlette.responses import HTMLResponse
from starlette.routing import Route, WebSocketRoute
from starlette.websockets import WebSocketDisconnect

info = {}

html = """
<!DOCTYPE html>
<html>
    <head>
        <title>Chat</title>
    </head>
    <body>
        <h1>WebSocket Chat</h1>
        <form action="" onsubmit="sendMessage(event)">
            <input type="text" id="messageText" autocomplete="off" placeholder="" />
            <button>Send</button>
        </form>
        <ul id='messages'>
        </ul>
        <script>
            document.getElementById("messageText").placeholder="第一次輸入內容為暱稱";

            var ws = new WebSocket("ws://localhost:5000/ws");

            // 接收
            ws.onmessage = function(event) {
                // 獲取id為messages的ul標籤內
                var messages = document.getElementById('messages')
                // 建立li標籤
                var message = document.createElement('li')
                // 建立內容
                var content = document.createTextNode(event.data)
                // 內容新增到li標籤內
                message.appendChild(content)
                // li標籤新增到ul標籤內
                messages.appendChild(message)
            };

            var name = 0;
            // 傳送
            function sendMessage(event) {
                var input = document.getElementById("messageText")
                ws.send(input.value)
                input.value = ''
                event.preventDefault()

                if (name == 0){
                    document.getElementById("messageText").placeholder="";
                    name = 1;
                }
            }
        </script>
    </body>
</html>
"""


# HTTPEndpoint HTTP端點 提供了基於類的檢視模式來處理HTTP方法
class Homepage(HTTPEndpoint):
    async def get(self, request):
        # 接受一些文字或位元組並返回HTML響應。
        return HTMLResponse(html)


# WebSocketEndpoint WebSocketEndpoint
# 該WebSocketEndpoint 是一個ASGI應用呈現,封裝了WebSocket例項。
class Echo(WebSocketEndpoint):
    encoding = "text"

    # 修改socket
    async def alter_socket(self, websocket):
        socket_str = str(websocket)[1:-1]
        socket_list = socket_str.split(' ')
        socket_only = socket_list[3]
        return socket_only

    # 連線 儲存
    async def on_connect(self, websocket):
        # 接受連線
        await websocket.accept()

        # 使用者輸入名稱  receive_text() 接收資料
        name = await websocket.receive_text()

        socket_only = await self.alter_socket(websocket)
        # 新增連線池 儲存使用者名稱
        info[socket_only] = [f'{name}', websocket]  # 儲存到字典中

        # 先迴圈 告訴之前的使用者有新使用者加入了
        for wbs in info:
            await info[wbs][1].send_text(f"{info[socket_only][0]}-加入了聊天室")

    # 收發
    async def on_receive(self, websocket, data):

        socket_only = await self.alter_socket(websocket)

        for wbs in info:
            await info[wbs][1].send_text(f"{info[socket_only][0]}:{data}")

            print(wbs)

    # 斷開 刪除
    async def on_disconnect(self, websocket, close_code):
        try:
            socket_only = await self.alter_socket(websocket)
            # 刪除連線池
            info.pop(socket_only)
            print(info)
        except WebSocketDisconnect:
            await websocket.close()


routes = [
    Route("/", Homepage),
    WebSocketRoute("/ws", Echo)
]

app = FastAPI(routes=routes)

if __name__ == "__main__":
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=5000)

5 FastAPI 認證

5.1 安全介紹

官方文件:https://fastapi.tiangolo.com/tutorial/security/

5.2 Jwt 介紹

JSON Web Token(JWT) 是目前前後端分離開發中使用者身份認證的一種解決方案

這是將JSON物件編碼為長而密集的字串且沒有空格的標準。看起來像這樣:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZ

如果要使用JWT令牌並檢視其工作原理,請檢視https://jwt.io。

JWT 認證流程:

  1. 使用者使用使用者名稱和密碼來請求伺服器進行登入
  2. 伺服器驗證使用者的登入資訊
  3. 伺服器通過驗證,生成一個token並返回給使用者
  4. 客戶端儲存token,並在每次驗證請求攜帶上這個token值
  5. 服務端驗證token值,並返回資料

5.3 使用密碼和JTW憑證進行認證

1 需要安裝依賴的包

​ 安裝pyjwt PyJWT來生成和驗證Python中的JWT令牌

pip install pyjwt

​ 密碼 passlib

​ PassLib是一個很棒的Python程式包,用於處理密碼雜湊。

​ 它支援許多安全的雜湊演算法和實用程式來使用它們。推薦的演算法是“ Bcrypt”。

	pip install passlib[bcrypt]

demo

from fastapi import Depends, FastAPI, HTTPException  # , status
from starlette import status
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
from pydantic import BaseModel

# 使用者資料(模擬)
fake_users_db = {
    "johndoe": {
        "username": "johndoe",
        "full_name": "John Doe",
        "email": "[email protected]",
        "hashed_password": "fakehashedsecret",
        "disabled": False,
    },
    "alice": {
        "username": "alice",
        "full_name": "Alice Wonderson",
        "email": "[email protected]",
        "hashed_password": "fakehashedsecret2",
        "disabled": True,  # 關閉此使用者
    },
}

app = FastAPI()


# 雜湊密碼(模擬)
def fake_hash_password(password: str):
    return "fakehashed" + password


# oauth2_scheme是令牌物件,token: str = Depends(oauth2_scheme)後就是之前加密的令牌
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/token")  # Bearer 持票人(承載、資料載體)


# 使用者資訊模型
class User(BaseModel):
    username: str
    email: str = None
    full_name: str = None
    disabled: bool = None


# 使用者輸入模型
class UserInDB(User):
    hashed_password: str  # 雜湊密碼
    username: str  # 此行多餘,為了vscode不報錯而已。


# 獲取使用者
def get_user(db, username: str):
    if username in db:
        user_dict = db[username]
        return UserInDB(**user_dict)


# 解碼令牌(模擬)
def fake_decode_token(token):
    # This doesn't provide any security at all
    # Check the next version
    user = get_user(fake_users_db, token)
    return user


# 獲取當前使用者
# 如果使用者不存在或處於非活動狀態,則這兩個依賴項都將僅返回HTTP錯誤。
async def get_current_user(token: str = Depends(oauth2_scheme)):
    print('oauth2_scheme', oauth2_scheme)
    print('token', token)
    user = fake_decode_token(token)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid authentication credentials",  # 無效的身份驗證憑據
            headers={"WWW-Authenticate": "Bearer"},
        )
    return user


# 獲取當前活躍使用者,get(read_users_me)專屬
async def get_current_active_user(current_user: User = Depends(get_current_user)):
    if current_user.disabled:
        raise HTTPException(status_code=400, detail="Inactive user")
    return current_user


@app.post("/token")  # name = johndoe,alice     password = secret,secret2
async def login(form_data: OAuth2PasswordRequestForm = Depends()):
    # 使用username來自表單欄位的從(假)資料庫獲取使用者資料。
    user_dict = fake_users_db.get(form_data.username)
    if not user_dict:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    # 先將這些資料放到Pydantic UserInDB模型中
    user = UserInDB(**user_dict)
    hashed_password = fake_hash_password(form_data.password)
    if not hashed_password == user.hashed_password:
        raise HTTPException(status_code=400, detail="Incorrect username or password")
    # 如果正確返回
    return {"access_token": user.username, "token_type": "bearer"}


@app.get("/users/me")
async def read_users_me(current_user: User = Depends(get_current_active_user)):
    return current_user


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

具體認證篇請看

6 資料庫

6.1 SQLAlchemy 資料庫

**1 ORM **

ORM 稱之為物件關係對映。

ORM 將資料庫中的表與面向物件中的類建立了一種對應關係。

當我們去操作資料庫,資料庫中的表或者表中的一條記錄就可以直接通過操作類或者類例項來完成

SQLAlchemy 物件

SQLAlchemy 是ORM 知名的工具之一,為高效和高效能的資料庫訪問設計。

SQLAlchemy 實際上就是利用Python來構建關係型資料庫結構與表示式的系統

SQLAlchemy 是對資料庫操作的封裝,讓開發者不用直接和sql打交道,而是通過python物件

詳情請看官方文件 https://www.sqlalchemy.org/

6.2 alembic 資料庫遷移篇

alembic是用來做ORM模型與資料庫的遷移與對映。alembic使用方式跟git有點類似,表現在兩個方面,第一個,alemibi的所有命令都是以alembic開頭;

整體目錄

.
├── alembic.ini  # alembic 配置檔案
├── db # 資料庫連結配置
│ ├── __init__.py
│ └── base.py  
├── alembic  # 初始化倉庫
│ ├── README
│ ├── __pycache__
│ │ └── env.cpython-37.pyc
│ ├── env.py
│ ├── script.py.mako
│ └── versions
│     └── __pycache__
└── models # 資料庫模型
    ├── __init__.py
    └── user_models.py  

base.py

from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base


DB_URI = "mysql+pymysql://root:[email protected]:3306/test6?charset=utf8"

engine = create_engine(DB_URI)
# 建立基類
Base = declarative_base(engine)

1 定義資料庫模型 以SQLAlchemy 為例子 假設為user使用者模型

user_models

# 資料庫模型所對應的SQLAlchemy
from sqlalchemy import Column,Integer,String,create_engine
from db.base import Base

class User(Base):
    # 對應MySQL中資料表的名字
    __tablename__ = 'users'

    # 建立欄位
    id = Column(Integer, primary_key=True)  # users表中的id欄位(主鍵)
    username = Column(String(64), nullable=False, index=True)  # users表中的username欄位
  

2 在終端中初始化,建立一個倉庫

alembic init alembic

初始化完成後,會生成一個alembic.ini 配置檔案以及一個alembic目錄

3 修改配置檔案

3.1 修改alembic.ini 配置檔案,只修改資料庫連線部分即可

sqlalchemy.url = driver://user:pass@localhost:port/dbname

修改為

sqlalchemy.url = mysql+pymysql://root:[email protected]:3306/test6

3.2 修改alembic/env.py

target_metadata = None

修改為

import sys
import os

# 1.__file__:當前檔案(env.py)
# 2.os.path.dirname(__file__):獲取當前檔案的目錄
# 3.os.path.dirname(os.path.dirname(__file__)):獲取當前檔案目錄的上一級目錄
# 4.sys.path: python尋找匯入的包的所有路徑
sys.path.append(os.path.dirname(os.path.dirname(__file__)))
from db.base import Base
from models import user_models  # 這裡要引入orm模型,不然遷移檔案裡面沒有表字段
target_metadata = Base.metadata  # 新增模型的元資料物件

4 生成遷移指令碼

# 由於提供了模型類,所以可以用–autogenerate引數自動生成遷移指令碼。執行
alembic revision --autogenerate -m "alembic table init"

生成成功後會得到一個遷移檔案 在 alembic/versions 目錄下

5 將生成的遷移指令碼對映到資料庫中

alembic upgrade head

如果後續要新增模型,修改模型欄位,重複4、5步驟即可,

降級資料庫的話

alembic downgrade 版本號

具體詳情請檢視附件

https://alembic.sqlalchemy.org/en/latest/

6.3 SQLAlchemy 案例

具體詳情案例請訪問 test_fastapi/ databases 目錄下檔案執行

from fastapi import FastAPI, Depends, HTTPException
from passlib.context import CryptContext
from pydantic import BaseModel
from sqlalchemy import create_engine, Column, Integer, String, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from starlette import status

# ===================== 資料庫操作初始化 ====================

# 連結是需要指定要用到的MySQL資料庫
engine = create_engine('mysql+pymysql://root:python12@localhost:3306/test6?charset=utf8', echo=True)
Base = declarative_base()  # 生成SQLORM基類

# SessionLocal類的每個例項將是一個數據庫會話。該類本身還不是資料庫會話。
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

# 建立session物件
session = SessionLocal()  # 生成連結資料庫的例項


class User_My(Base):  # 宣告資料庫某表的屬性與結構
    __tablename__ = "users"

    id = Column(Integer, primary_key=True, index=True)  # 主鍵
    email = Column(String(64), unique=True, index=True)
    hashed_password = Column(String(128))
    is_active = Column(Boolean, default=True)


# 執行下面的程式碼,會建立userinfo表(所有表結構)
# Base.metadata.create_all(engine)

# 建立 pydantic 資料模型
class UserBase(BaseModel):
    email: str


class UserCreate(UserBase):  # 繼承子模型 UserBase
    password: str


class User(UserBase):
    id: int
    is_active: bool

    class Config:
        orm_mode = True  # Pydantic的orm_mode將告訴Pydantic模型讀取資料,即使它不是dict而是ORM模型


# 建立app應用
app = FastAPI()


# ==================== 資料庫操作 使用依賴項 ======================

def get_db():
    try:
        db = SessionLocal()
        yield db

    finally:
        db.close()
        print("資料庫關閉")


# Context是上下文  CryptContext是密碼上下文 schemes是計劃 deprecated是不贊成(強烈反對)
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


# 驗證雜湊密碼
# verify_password驗證密碼   plain_password普通密碼      hashed_password雜湊密碼
def verify_password(plain_password, hashed_password):
    return pwd_context.verify(plain_password, hashed_password)


# 獲取密碼雜湊
def get_password_hash(password):
    return pwd_context.hash(password)


# =================== 資料庫操作方法 =======================
# 通過id查詢使用者資訊  查詢前先新增一個數據
def get_user(db: session, user_id: int):
    users = db.query(User_My).filter(User_My.id == user_id).first()
    print("users_id", users)
    return users


# 新建使用者到資料庫中
def db_create_user(db: session, user: UserCreate):
    # if user.email:
    #     raise HTTPException(status_code=400,
    #                         detail="user exist")
    # 1 對密碼進行加密
    hashed_password = get_password_hash(user.password)
    # 新增到資料庫中
    db_user = User_My(email=user.email, hashed_password=hashed_password)
    # 新增到session中
    db.add(db_user)

    # 新增到資料庫中
    db.commit()
    db.refresh(db_user)  # 重新整理

    return db_user


# ============== post和get請求 ===============
# 新增使用者請求
@app.post("/users/", response_model=User)
def db_create_users(user: UserCreate, db: session = Depends(get_db)):
    # Depends(get_db)使用依賴關係可防止不同請求意外共享同一連結
    return db_create_user(db=db, user=user)


# 查詢使用者請求
@app.get("/users/{user_id}", response_model=User)
def read_user(user_id: int, db: session = Depends(get_db)):
    db_user = get_user(db=db, user_id=user_id)

    print(db_user)
    if db_user is None:
        raise HTTPException(status_code=status.HTTP_404_NOT_FOUND,
                            detail="User not found")

    return db_user


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

6.4 tortoise-orm (資料庫)

1 tortoise-orm 介紹

Tortoise ORM是受Django啟發的易於使用的 非同步 ORM (物件關係對映器)

**2 安裝 tortoise-orm **

pip install tortoise-orm

以及MySQL 或其他db驅動程式

pip install tortoise-orm[aiomysql]
pip install tortoise-orm[asyncpg]

3 tortoise-orm 案例

執行此案例在 test_fastapi/databases 目錄下執行

具體資料庫使用及功能請檢視下方

tortoise-orm遷移: https://tortoise-orm.readthedocs.io/en/latest/migration.html

tortoise-orm文件:https://tortoise-orm.readthedocs.io/en/latest/

7 FastAPI 日誌管理

Loguru是一個旨在以Python帶來令人愉悅的日誌記錄的庫。

Loguru的主要概念是隻有一個日誌記錄器。

pip install loguru

基本輸出日誌

from loguru import logger
logger.debug("這是一條debug日誌")

整合到FastAPI 新建了一個檔案包extensions/專門存放擴充套件檔案
然後在檔案目錄下建立了extensions/logger.py檔案, 簡單配置

logger.py

import os
import sys
import time
import traceback
from loguru import logger

# 專案所在目錄
# abspath 返回絕對路徑
# dirname 返回檔案路徑
basedir = os.path.abspath(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))


# 定位到log日誌檔案中
log_path = os.path.join(basedir, 'logs')

# print(log_path)
# 如果資料夾不存在就新建
if not os.path.exists(log_path):
    os.mkdir(log_path)
# 定義log檔名
log_path_error = os.path.join(log_path, f'{time.strftime("%Y-%m-%d")}_error.log')

# 日誌簡單配置
# 具體其他配置 可自行參考 https://github.com/Delgan/loguru
# retention  Cleanup after some time
# rotation New file is created each day at noon
# enqueue 確保日誌完整的輸出儲存 非同步寫入
logger.add(log_path_error, rotation="12:00", retention="5 days", enqueue=True)

_ init _.py

from extensions import logger

使用

# 得先在 extensions/__init__.py匯入logger 才可以這樣匯入logger
from extensions.logger import logger

logger.debug(f"日誌記錄")
logger.info(f"日誌記錄")
logger.error(f"xxx")

最後輸出在 logs檔案中

具體詳情請看附件(文件)

https://pypi.org/project/loguru/

8 FastAPI 快取

本案例在 test_fastapi/fastapi-cache目錄下執行

1 安裝第三方快取

pip install fastapi-cache

2 demo

from fastapi import FastAPI, Depends, HTTPException
import json
from fastapi_cache import caches, close_caches
from fastapi_cache.backends.redis import CACHE_KEY, RedisCacheBackend

app = FastAPI()


def redis_cache():
    return caches.get(CACHE_KEY)


@app.get('/')
async def hello(
        cache: RedisCacheBackend = Depends(redis_cache)
):
    print(cache)
    # 獲取redis物件
    in_cache = await cache.get('some_cached_key')
    if not in_cache:
        cache_data = {
            "name": "xiaoming",
            "age": 20,
            "address": "Shanghai"
        }
        # 設定redis物件
        await cache.set('some_cached_key', json.dumps(cache_data))

    return {'response': in_cache or 'default'}

# 清除快取
@app.delete("/delete_redis_cache")
async def test_del_redis(cache: RedisCacheBackend = Depends(redis_cache)):
    # 1 獲取redis物件
    in_cache_redis = await cache.get("some_cached_key")

    if in_cache_redis:
        print(in_cache_redis)
        # await cache.delete("some_cached_key")
    else:
        raise HTTPException(status_code=200, detail="未找到reids中的key")
    return {"resposne": "ok"}

# 事件處理函式

# 程式啟動前要做的事情
@app.on_event('startup')
async def on_startup() -> None:
    print("啟動")
    rc = RedisCacheBackend('redis://127.0.0.1:6379')
    caches.set(CACHE_KEY, rc)


# 程式結束後要執行的事情
@app.on_event('shutdown')
async def on_shutdown() -> None:
    print("退出")
    await close_caches()


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=5000)

9 FastAPI Users 使用者管理

本章案例在test_fastapi/FastAPI_User目錄下執行

fastapi-user 能夠快速向我們的FastAPI 專案添加註冊和身份驗證系統,FastAPI使用者旨在儘可能地自定義和適應。

2.1 特徵

  • 可擴充套件的基本使用者模型

  • 即用型註冊,登入,忘記和重置密碼路徑

  • 即用型OAuth2流程

  • 依賴可呼叫以將當前使用者注入路由

  • 可定製的資料庫後端

    • 得益於databases 資料庫包括 SQLALchemy非同步後端
    • 支援mongodb 非同步後端
    • Tortoise ORM backend included
  • 多個可定製的身份驗證碼後端

    • 包括JWT身份驗證後端
    • 包括Cookie身份驗證後端
  • 全面的OpenAPI架構支援,即使有多個身份驗證後端

2.2 安裝

具有SQLAlchemy支援

pip install fastapi-users[sqlalchemy]

包括Tortoise-orm支援

pip install fastapi-users[mongodb]

這裡我們使用的tortoise-orm 資料庫來作為此案例

本章案例在 test_fastapi/fastapi-users 執行,結合文件看詳情說明。

tortoise-orm遷移: https://tortoise-orm.readthedocs.io/en/latest/migration.html

fastapi-users文件 https://frankie567.github.io/fastapi-users/

2.3 說明

混合提供了四種Pydantic模型變體

  • BaseUser提供基礎領域和驗證;
  • BaseCreateUser,專門用於使用者註冊,由必填欄位emailpassword必填欄位組成;
  • BaseUpdateUser,專門用於使用者個人資料更新,其中添加了一個可選password欄位;
  • BaseUserDB,這是資料庫中使用者的表示形式,並添加了一個hashed_password欄位。

10 FastAPI 定期任務

本案例在 test_fastapi/fastapi-utils-tasks 目錄下執行

1 安裝 第三方模組

pip install fastapi-utils

2 定時任務

啟動和關閉事件是觸發與伺服器生命週期相關的操作的好方法。

但是,有時您希望任務不僅在伺服器啟動時觸發,而且要定期觸發。例如,可能想定期重置內部快取,或從資料庫中刪除過期的令牌。

可以通過 該fastapi_utils.tasks.repeat_every裝飾實現定時任務

呼叫用@repeat_every(...)修飾器修飾的函式時,將啟動迴圈,並以seconds提供給修飾器的引數確定的延遲定期呼叫該函式。

例子:

from fastapi import FastAPI
from sqlalchemy.orm import Session

from fastapi_utils.session import FastAPISessionMaker
from fastapi_utils.tasks import repeat_every

# 建立app應用
app = FastAPI()

ret = 0


async def tasks_message():
    global ret

    ret = ret + 1

    print(ret)

# 程式啟動後呼叫該函式
@app.on_event("startup")
@repeat_every(seconds=60 * 1)  # 1 min 每一分鐘呼叫一次
async def remove_expired_tokens_task() -> None:
    print("啟動")
    # 呼叫函式
    await tasks_message()


if __name__ == '__main__':
    import uvicorn

    uvicorn.run(app, host="127.0.0.1", port=8000)

關鍵字引數

以下是針對以下各項的各種關鍵字引數的詳細說明repeat_every

  • seconds: float :連續兩次呼叫之間等待的秒數
  • wait_first: bool = False:如果False(預設值),則在第一次呼叫修飾函式時立即呼叫包裝函式。如果為True,則裝飾函式將等待一個時間段,然後再呼叫包裝函式
  • logger: Optional[logging.Logger] = None :如果您通過記錄器,則在重複執行迴圈中引發的任何異常都將記錄(帶有回溯)到提供的記錄器中。

具體詳情文件請點選:https://fastapi-utils.davidmontague.xyz/user-guide/repeated-tasks/

11 FastAPI後臺介面文件

部落格型別專案實現了基本的增刪改查

資料庫為SQLAlchemy ORM

前臺目錄:

  1. 在blog-crud/frontend目錄下
  2. 安裝依賴: npm install
  3. 啟動開發: npm run dev
  4. 打包命令: npm run build

後臺專案:

  1. 在blog-crud/fastapi-blog目錄下
  2. 資料庫為 SQLAlchemy ORM

1 介面文件說明:

前端:http://127.0.0.1:8000/

後端API互動文件地址:http://127.0.0.1:5000/redoc

2 登入

2.1 登入驗證介面

請求路徑: /api/v1/login/access-token

請求方法: post

請求引數

username 使用者名稱

password 密碼

響應引數

access_token 基於jwt的令牌
token_type 令牌型別

3 使用者管理

3.1 新增使用者

  • 請求路徑: /api/v1/users
  • 請求方法: POST
  • 請求引數
    • email 郵箱
    • Username 使用者姓名
    • Password 雜湊密碼
  • 響應引數
    • email: 郵箱
    • Username: 使用者姓名
    • id:使用者id
    • Is_active 啟用狀態
    • is_admin 管理員

4 首頁

4.1 首頁文章

請求路徑:GET api/v1/posts?page=1&limit=10 顯示所有文章

4.2 首頁分類

請求路徑:GET api/v1/categories 顯示所有分類

4.3 管理員

管理員(前提登入並且是管理員) 在User模型上中is_admin欄位改為True

請求路徑: /manage/posts 文章管理
請求路徑: /manage/post? 文章編輯/建立
請求路徑: /manage/categories 分類管理
請求路徑: /manage/category? 分類編輯/建立

4 後臺管理

4.1 文章管理

  • 請求路徑: GET /api/v1/posts/admin 後臺顯示所有文章

  • 請求路徑: POST /api/v1/posts/ 後臺建立文章

  • 請求引數:

  • title: 標題 content: 內容 cover_image 圖片 is_publish 是否釋出 can_comment 是否評論 category_id 分類id(外來鍵)

  • 響應引數

    content: 建立文章成功 201

4.2 分類管理

  • 請求路徑: GET /api/v1/categories/ 顯示所有分類

  • 請求路徑: POST /api/v1/categories/ 新增分類

  • 請求引數: name: 分類姓名 img: 圖片

  • 請求路徑: /api/v1/categories/<int: categories>

  • 請求方法:DELETE

  • 請求路徑引數: category_id 分類id

9 docker + Nginx 部署FastAPI

1 使用Pycharm 將本地專案上傳到遠端伺服器

注意只有Pycharm專業版才具有部署的功能,社群版(無需破解)是沒有此功能

新增配置,起個名字

以上都配置好了,然後我們將我們本地的專案上傳到遠端伺服器中

最後進入到我們的伺服器檢視上傳的專案。

2 docker部署專案

  • 轉到您的專案目錄。

    都需要在同一路徑中

  • 建立一個Dockerfile

    FROM python:3.7
    # 複製檔案到容器
    
    # 將本地專案和依賴包新增到容器 /home 路徑
    ADD requirements.txt /home
    ADD ./app /home
    
    # 跳轉到指定目錄
    WORKDIR /home
    
    # 執行安裝 requirements.txt
    RUN pip install -r requirements.txt -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com
    
    # 暴露埠
    EXPOSE 5000
    
    CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "5000"]
    
  • 建立一個docker-compose

    Compose專案是 Docker 官方的開源專案,負責實現對 Docker 容器叢集的快速編排,同時也是對dockerfile進行管理

    建立 docker-compose.yml 配置檔案

    version: '3'
    services:
      fastapi2:
        build: .  # 這個是查詢當前dockerfile檔案進行構建
        ports:
         - "5000:5000"
    
      mysql:
            image: mysql:5.7
            expose:
                - 3306
            ports:
                - "3307:3306"
            environment:
                MYSQL_ROOT_PASSWORD: python
                MYSQL_USER: root
    
  • 執行構建映象命令

    • docker-compose up -d --build

      用於部署一個 Compose 應用

      預設情況下該命令會讀取名為 docker-compose.yml 或 docker-compose.yaml 的檔案當然使用者也可以使用 -f 指定其他檔名。通常情況下,會使用 -d 引數令應用在後臺啟動

    • docker-compose stop

      停止 Compose 應用相關的所有容器,但不會刪除它們

      被停止的應用可以很容易地通過 docker-compose restart 命令重新啟動

    • docker-compose rm

      用於刪除已停止的 Compose 應用

      它會刪除容器和網路,但是不會刪除卷和映象

    • docker-compose restart

      重啟已停止的 Compose 應用

      如果使用者在停止該應用後對其進行了變更,那麼變更的內容不會反映在重啟後的應用中,這時需要重新部署應用使變更生效

    • docker-compose ps

      用於列出 Compose 應用中的各個容器

      輸出內容包括當前狀態、容器執行的命令以及網路埠

    • docker-compose down
      停止並刪除執行中的 Compose 應用

      它會刪除容器和網路,但是不會刪除卷和映象

    最後當我們構建成功之後,會看到我們執行的程式,同時也可以訪問伺服器ip和埠

4 nginx 轉發

nginx 是一個高效能HTTP伺服器和反向代理伺服器,最終的目的是實現網站的負載均衡;

nginx 作為web伺服器,對處理索引檔案和靜態檔案效率非常高,例如我們專案中的靜態檔案(HTML、CSS、JS)就交給nginx進行處理。

強大的反向代理和負載均衡功能,平衡叢集中各個伺服器的負載壓力

安裝nginx

sudo apt-get install nginx

2 執行(啟動)nginx

nginx

3 伺服器檢視執行狀態 或瀏覽器開啟

3 停止/重啟 nginx

nginx -s stop/reload

4 查詢nginx路徑

nginx -t   # 騰訊雲的同學要加sudo
# 第一個檔案路徑是等會我們要去新增配置的檔案
# nginx: the configuration file /etc/nginx/nginx.conf syntax is ok 
# nginx: configuration file /etc/nginx/nginx.conf test is successful

修改nginx.conf 檔案 /etc/nginx/nginx.conf 使用sudo vim

注意重要的一點,這裡部分截圖的最後有兩行檔案

include /etc/nginx/conf.d/*.conf;

include /etc/nginx/sites-enabled/*; 

先說下原因: 因為nginx中有個預設配置檔案default, 裡面的預設監聽埠是80埠,那麼我們需要在阿里雲配置的埠也是80埠,此時就會出現上我們自身配置的80埠是起衝突的,也就是說最終我們配置好了nginx資訊,訪問的時候還是nginx最初的頁面 welcome to nginx 。 所以我們需要刪除掉nginx預設的配置檔案,讓nginx訪問我們配置的埠資訊。

步驟:

cd 進入 /etc/nginx/sites-enabled/ 路徑下能夠看到有個default檔案, 通過cat default 能夠看到預設配置資訊, 我們需要做的就是 刪除 或者 對檔案進行移動到上一層, mv deault ../ 此時nginx就不會載入預設的配置了,通過curl 122.51.67.247:80 是不會訪問最初的配置了,而是顯示的拒絕連線的了。

新增需要配置的檔案到 vim /etc/nginx/nginx.conf

配置資訊

 upstream app {
            server 139.129.99.129:5000;  # docker 配置
        }

        server {

             listen 80;
             location / {
                 proxy_pass http://app;  # 新增到這裡
                 }

配置好nginx資訊後 通過 nginx -t 檢測配置是否正確

# 測試nginx配置檔案是否正確
sudo nginx -t

# 如列印以下內容,表示配置沒有問題
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
  
# 這時我們需要重新載入下nginx配置
sudo nginx -s reload 

最後訪問我們伺服器ip就可以了