committed by
GitHub
48 changed files with 1245 additions and 553 deletions
After Width: | Height: | Size: 38 KiB |
After Width: | Height: | Size: 45 KiB |
@ -0,0 +1,200 @@ |
|||
# HTTPS について |
|||
|
|||
HTTPSは単に「有効」か「無効」かで決まるものだと思いがちです。 |
|||
|
|||
しかし、それよりもはるかに複雑です。 |
|||
|
|||
!!! tip |
|||
もし急いでいたり、HTTPSの仕組みについて気にしないのであれば、次のセクションに進み、さまざまなテクニックを使ってすべてをセットアップするステップ・バイ・ステップの手順をご覧ください。 |
|||
|
|||
利用者の視点から **HTTPS の基本を学ぶ**に当たっては、次のリソースをオススメします: <a href="https://howhttps.works/" class="external-link" target="_blank">https://howhttps.works/</a>. |
|||
|
|||
さて、**開発者の視点**から、HTTPSについて考える際に念頭に置くべきことをいくつかみていきましょう: |
|||
|
|||
* HTTPSの場合、**サーバ**は**第三者**によって生成された**「証明書」を持つ**必要があります。 |
|||
* これらの証明書は「生成」されたものではなく、実際には第三者から**取得**されたものです。 |
|||
* 証明書には**有効期限**があります。 |
|||
* つまりいずれ失効します。 |
|||
* そのため**更新**をし、第三者から**再度取得**する必要があります。 |
|||
* 接続の暗号化は**TCPレベル**で行われます。 |
|||
* それは**HTTPの1つ下**のレイヤーです。 |
|||
* つまり、**証明書と暗号化**の処理は、**HTTPの前**に行われます。 |
|||
* **TCPは "ドメイン "について知りません**。IPアドレスについてのみ知っています。 |
|||
* 要求された**特定のドメイン**に関する情報は、**HTTPデータ**に入ります。 |
|||
* **HTTPS証明書**は、**特定のドメイン**を「証明」しますが、プロトコルと暗号化はTCPレベルで行われ、どのドメインが扱われているかを**知る前**に行われます。 |
|||
* **デフォルトでは**、**IPアドレスごとに1つのHTTPS証明書**しか持てないことになります。 |
|||
* これは、サーバーの規模やアプリケーションの規模に寄りません。 |
|||
* しかし、これには**解決策**があります。 |
|||
* **TLS**プロトコル(HTTPの前に、TCPレベルで暗号化を処理するもの)には、**<a href="https://en.wikipedia.org/wiki/Server_Name_Indication" class="external-link" target="_blank"><abbr title="サーバー名表示">SNI</abbr></a>**と呼ばれる**拡張**があります。 |
|||
* このSNI拡張機能により、1つのサーバー(**単一のIPアドレス**を持つ)が**複数のHTTPS証明書**を持ち、**複数のHTTPSドメイン/アプリケーション**にサービスを提供できるようになります。 |
|||
* これが機能するためには、**パブリックIPアドレス**でリッスンしている、サーバー上で動作している**単一の**コンポーネント(プログラム)が、サーバー内の**すべてのHTTPS証明書**を持っている必要があります。 |
|||
|
|||
* セキュアな接続を取得した**後**でも、通信プロトコルは**HTTPのまま**です。 |
|||
* コンテンツは**HTTPプロトコル**で送信されているにもかかわらず、**暗号化**されています。 |
|||
|
|||
|
|||
サーバー(マシン、ホストなど)上で**1つのプログラム/HTTPサーバー**を実行させ、**HTTPSに関する全てのこと**を管理するのが一般的です。 |
|||
|
|||
**暗号化された HTTPS リクエスト** を受信し、**復号化された HTTP リクエスト** を同じサーバーで実行されている実際の HTTP アプリケーション(この場合は **FastAPI** アプリケーション)に送信し、アプリケーションから **HTTP レスポンス** を受け取り、適切な **HTTPS 証明書** を使用して **暗号化** し、そして**HTTPS** を使用してクライアントに送り返します。 |
|||
|
|||
このサーバーはしばしば **<a href="https://en.wikipedia.org/wiki/TLS_termination_proxy" class="external-link" target="_blank">TLS Termination Proxy</a>**と呼ばれます。 |
|||
|
|||
TLS Termination Proxyとして使えるオプションには、以下のようなものがあります: |
|||
|
|||
* Traefik(証明書の更新も対応) |
|||
* Caddy (証明書の更新も対応) |
|||
* Nginx |
|||
* HAProxy |
|||
|
|||
|
|||
## Let's Encrypt |
|||
|
|||
Let's Encrypt以前は、これらの**HTTPS証明書**は信頼できる第三者によって販売されていました。 |
|||
|
|||
これらの証明書を取得するための手続きは面倒で、かなりの書類を必要とし、証明書はかなり高価なものでした。 |
|||
|
|||
しかしその後、**<a href="https://letsencrypt.org/" class="external-link" target="_blank">Let's Encrypt</a>** が作られました。 |
|||
|
|||
これはLinux Foundationのプロジェクトから生まれたものです。 自動化された方法で、**HTTPS証明書を無料で**提供します。これらの証明書は、すべての標準的な暗号化セキュリティを使用し、また短命(約3ヶ月)ですが、こういった寿命の短さによって、**セキュリティは実際に優れています**。 |
|||
|
|||
ドメインは安全に検証され、証明書は自動的に生成されます。また、証明書の更新も自動化されます。 |
|||
|
|||
このアイデアは、これらの証明書の取得と更新を自動化することで、**安全なHTTPSを、無料で、永遠に**利用できるようにすることです。 |
|||
|
|||
## 開発者のための HTTPS |
|||
|
|||
ここでは、HTTPS APIがどのように見えるかの例を、主に開発者にとって重要なアイデアに注意を払いながら、ステップ・バイ・ステップで説明します。 |
|||
|
|||
### ドメイン名 |
|||
|
|||
ステップの初めは、**ドメイン名**を**取得すること**から始まるでしょう。その後、DNSサーバー(おそらく同じクラウドプロバイダー)に設定します。 |
|||
|
|||
おそらくクラウドサーバー(仮想マシン)かそれに類するものを手に入れ、<abbr title="変わらない">固定の</abbr> **パブリックIPアドレス**を持つことになるでしょう。 |
|||
|
|||
DNSサーバーでは、**取得したドメイン**をあなたのサーバーのパプリック**IPアドレス**に向けるレコード(「`Aレコード`」)を設定します。 |
|||
|
|||
これはおそらく、最初の1回だけあり、すべてをセットアップするときに行うでしょう。 |
|||
|
|||
!!! tip |
|||
ドメイン名の話はHTTPSに関する話のはるか前にありますが、すべてがドメインとIPアドレスに依存するため、ここで言及する価値があります。 |
|||
|
|||
### DNS |
|||
|
|||
では、実際のHTTPSの部分に注目してみよう。 |
|||
|
|||
まず、ブラウザは**DNSサーバー**に**ドメインに対するIP**が何であるかを確認します。今回は、`someapp.example.com`とします。 |
|||
|
|||
DNSサーバーは、ブラウザに特定の**IPアドレス**を使用するように指示します。このIPアドレスは、DNSサーバーで設定した、あなたのサーバーが使用するパブリックIPアドレスになります。 |
|||
|
|||
<img src="/img/deployment/https/https01.svg"> |
|||
|
|||
### TLS Handshake の開始 |
|||
|
|||
ブラウザはIPアドレスと**ポート443**(HTTPSポート)で通信します。 |
|||
|
|||
通信の最初の部分は、クライアントとサーバー間の接続を確立し、使用する暗号鍵などを決めるだけです。 |
|||
|
|||
<img src="/img/deployment/https/https02.svg"> |
|||
|
|||
TLS接続を確立するためのクライアントとサーバー間のこのやりとりは、**TLSハンドシェイク**と呼ばれます。 |
|||
|
|||
### SNI拡張機能付きのTLS |
|||
|
|||
サーバー内の**1つのプロセス**だけが、特定 の**IPアドレス**の特定の**ポート** で待ち受けることができます。 |
|||
|
|||
同じIPアドレスの他のポートで他のプロセスがリッスンしている可能性もありますが、IPアドレスとポートの組み合わせごとに1つだけです。 |
|||
|
|||
TLS(HTTPS)はデフォルトで`443`という特定のポートを使用する。つまり、これが必要なポートです。 |
|||
|
|||
このポートをリッスンできるのは1つのプロセスだけなので、これを実行するプロセスは**TLS Termination Proxy**となります。 |
|||
|
|||
TLS Termination Proxyは、1つ以上の**TLS証明書**(HTTPS証明書)にアクセスできます。 |
|||
|
|||
前述した**SNI拡張機能**を使用して、TLS Termination Proxy は、利用可能なTLS (HTTPS)証明書のどれを接続先として使用すべきかをチェックし、クライアントが期待するドメインに一致するものを使用します。 |
|||
|
|||
今回は、`someapp.example.com`の証明書を使うことになります。 |
|||
|
|||
<img src="/img/deployment/https/https03.svg"> |
|||
|
|||
クライアントは、そのTLS証明書を生成したエンティティ(この場合はLet's Encryptですが、これについては後述します)をすでに**信頼**しているため、その証明書が有効であることを**検証**することができます。 |
|||
|
|||
次に証明書を使用して、クライアントとTLS Termination Proxy は、 **TCP通信**の残りを**どのように暗号化するかを決定**します。これで**TLSハンドシェイク**の部分が完了します。 |
|||
|
|||
この後、クライアントとサーバーは**暗号化されたTCP接続**を持ちます。そして、その接続を使って実際の**HTTP通信**を開始することができます。 |
|||
|
|||
これが**HTTPS**であり、純粋な(暗号化されていない)TCP接続ではなく、**セキュアなTLS接続**の中に**HTTP**があるだけです。 |
|||
|
|||
!!! tip |
|||
通信の暗号化は、HTTPレベルではなく、**TCPレベル**で行われることに注意してください。 |
|||
|
|||
### HTTPS リクエスト |
|||
|
|||
これでクライアントとサーバー(具体的にはブラウザとTLS Termination Proxy)は**暗号化されたTCP接続**を持つことになり、**HTTP通信**を開始することができます。 |
|||
|
|||
そこで、クライアントは**HTTPSリクエスト**を送信します。これは、暗号化されたTLSコネクションを介した単なるHTTPリクエストです。 |
|||
|
|||
<img src="/img/deployment/https/https04.svg"> |
|||
|
|||
### リクエストの復号化 |
|||
|
|||
TLS Termination Proxy は、合意が取れている暗号化を使用して、**リクエストを復号化**し、**プレーン (復号化された) HTTP リクエスト** をアプリケーションを実行しているプロセス (例えば、FastAPI アプリケーションを実行している Uvicorn を持つプロセス) に送信します。 |
|||
|
|||
<img src="/img/deployment/https/https05.svg"> |
|||
|
|||
### HTTP レスポンス |
|||
|
|||
アプリケーションはリクエストを処理し、**プレーン(暗号化されていない)HTTPレスポンス** をTLS Termination Proxyに送信します。 |
|||
|
|||
<img src="/img/deployment/https/https06.svg"> |
|||
|
|||
### HTTPS レスポンス |
|||
|
|||
TLS Termination Proxyは次に、事前に合意が取れている暗号(`someapp.example.com`の証明書から始まる)を使って**レスポンスを暗号化し**、ブラウザに送り返す。 |
|||
|
|||
その後ブラウザでは、レスポンスが有効で正しい暗号キーで暗号化されていることなどを検証します。そして、ブラウザはレスポンスを**復号化**して処理します。 |
|||
|
|||
<img src="/img/deployment/https/https07.svg"> |
|||
|
|||
クライアント(ブラウザ)は、レスポンスが正しいサーバーから来たことを知ることができます。 なぜなら、そのサーバーは、以前に**HTTPS証明書**を使って合意した暗号を使っているからです。 |
|||
|
|||
### 複数のアプリケーション |
|||
|
|||
同じサーバー(または複数のサーバー)に、例えば他のAPIプログラムやデータベースなど、**複数のアプリケーション**が存在する可能性があります。 |
|||
|
|||
特定のIPとポート(この例ではTLS Termination Proxy)を扱うことができるのは1つのプロセスだけですが、他のアプリケーション/プロセスも、同じ**パブリックIPとポート**の組み合わせを使用しようとしない限り、サーバー上で実行することができます。 |
|||
|
|||
<img src="/img/deployment/https/https08.svg"> |
|||
|
|||
そうすれば、TLS Termination Proxy は、**複数のドメイン**や複数のアプリケーションのHTTPSと証明書を処理し、それぞれのケースで適切なアプリケーションにリクエストを送信することができます。 |
|||
|
|||
### 証明書の更新 |
|||
|
|||
将来のある時点で、各証明書は(取得後約3ヶ月で)**失効**します。 |
|||
|
|||
その後、Let's Encryptと通信する別のプログラム(別のプログラムである場合もあれば、同じTLS Termination Proxyである場合もある)によって、証明書を更新します。 |
|||
|
|||
<img src="/img/deployment/https/https.svg"> |
|||
|
|||
**TLS証明書**は、IPアドレスではなく、**ドメイン名に関連付けられて**います。 |
|||
|
|||
したがって、証明書を更新するために、更新プログラムは、認証局(Let's Encrypt)に対して、**そのドメインが本当に「所有」し、管理している**ことを**証明**する必要があります。 |
|||
|
|||
そのために、またさまざまなアプリケーションのニーズに対応するために、いくつかの方法があります。よく使われる方法としては: |
|||
|
|||
* **いくつかのDNSレコードを修正します。** |
|||
* これをするためには、更新プログラムはDNSプロバイダーのAPIをサポートする必要があります。したがって、使用しているDNSプロバイダーによっては、このオプションが使える場合もあれば、使えない場合もあります。 |
|||
* ドメインに関連付けられたパブリックIPアドレス上で、(少なくとも証明書取得プロセス中は)**サーバー**として実行します。 |
|||
* 上で述べたように、特定のIPとポートでリッスンできるプロセスは1つだけです。 |
|||
* これは、同じTLS Termination Proxyが証明書の更新処理も行う場合に非常に便利な理由の1つです。 |
|||
* そうでなければ、TLS Termination Proxyを一時的に停止し、証明書を取得するために更新プログラムを起動し、TLS Termination Proxyで証明書を設定し、TLS Termination Proxyを再起動しなければならないかもしれません。TLS Termination Proxyが停止している間はアプリが利用できなくなるため、これは理想的ではありません。 |
|||
|
|||
|
|||
アプリを提供しながらこのような更新処理を行うことは、アプリケーション・サーバー(Uvicornなど)でTLS証明書を直接使用するのではなく、TLS Termination Proxyを使用して**HTTPSを処理する別のシステム**を用意したくなる主な理由の1つです。 |
|||
|
|||
## まとめ |
|||
|
|||
**HTTPS**を持つことは非常に重要であり、ほとんどの場合、かなり**クリティカル**です。開発者として HTTPS に関わる労力のほとんどは、これらの**概念とその仕組みを理解する**ことです。 |
|||
|
|||
しかし、ひとたび**開発者向けHTTPS**の基本的な情報を知れば、簡単な方法ですべてを管理するために、さまざまなツールを組み合わせて設定することができます。 |
|||
|
|||
次の章では、**FastAPI** アプリケーションのために **HTTPS** をセットアップする方法について、いくつかの具体例を紹介します。🔒 |
@ -0,0 +1,17 @@ |
|||
# FastAPI를 클라우드 제공업체에서 배포하기 |
|||
|
|||
사실상 거의 **모든 클라우드 제공업체**를 사용하여 여러분의 FastAPI 애플리케이션을 배포할 수 있습니다. |
|||
|
|||
대부분의 경우, 주요 클라우드 제공업체에서는 FastAPI를 배포할 수 있도록 가이드를 제공합니다. |
|||
|
|||
## 클라우드 제공업체 - 후원자들 |
|||
|
|||
몇몇 클라우드 제공업체들은 [**FastAPI를 후원하며**](../help-fastapi.md#sponsor-the-author){.internal-link target=_blank} ✨, 이를 통해 FastAPI와 FastAPI **생태계**가 지속적이고 건전한 **발전**을 할 수 있습니다. |
|||
|
|||
이는 FastAPI와 **커뮤니티** (여러분)에 대한 진정한 헌신을 보여줍니다. 그들은 여러분에게 **좋은 서비스**를 제공할 뿐 만이 아니라 여러분이 **훌륭하고 건강한 프레임워크인** FastAPI 를 사용하길 원하기 때문입니다. 🙇 |
|||
|
|||
아래와 같은 서비스를 사용해보고 각 서비스의 가이드를 따를 수도 있습니다: |
|||
|
|||
* <a href="https://docs.platform.sh/languages/python.html?utm_source=fastapi-signup&utm_medium=banner&utm_campaign=FastAPI-signup-June-2023" class="external-link" target="_blank">Platform.sh</a> |
|||
* <a href="https://docs.porter.run/language-specific-guides/fastapi" class="external-link" target="_blank">Porter</a> |
|||
* <a href="https://www.deta.sh/?ref=fastapi" class="external-link" target="_blank">Deta</a> |
@ -0,0 +1,42 @@ |
|||
# JSON Compatible Encoder |
|||
|
|||
Існують випадки, коли вам може знадобитися перетворити тип даних (наприклад, модель Pydantic) в щось сумісне з JSON (наприклад, `dict`, `list`, і т. д.). |
|||
|
|||
Наприклад, якщо вам потрібно зберегти це в базі даних. |
|||
|
|||
Для цього, **FastAPI** надає `jsonable_encoder()` функцію. |
|||
|
|||
## Використання `jsonable_encoder` |
|||
|
|||
Давайте уявимо, що у вас є база даних `fake_db`, яка приймає лише дані, сумісні з JSON. |
|||
|
|||
Наприклад, вона не приймає об'єкти типу `datetime`, оскільки вони не сумісні з JSON. |
|||
|
|||
Отже, об'єкт типу `datetime` потрібно перетворити в рядок `str`, який містить дані в <a href="https://en.wikipedia.org/wiki/ISO_8601" class="external-link" target="_blank">ISO форматі</a>. |
|||
|
|||
Тим самим способом ця база даних не прийматиме об'єкт типу Pydantic model (об'єкт з атрибутами), а лише `dict`. |
|||
|
|||
Ви можете використовувати `jsonable_encoder` для цього. |
|||
|
|||
Вона приймає об'єкт, такий як Pydantic model, і повертає його версію, сумісну з JSON: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="4 21" |
|||
{!> ../../../docs_src/encoder/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="5 22" |
|||
{!> ../../../docs_src/encoder/tutorial001.py!} |
|||
``` |
|||
|
|||
У цьому прикладі вона конвертує Pydantic model у `dict`, а `datetime` у `str`. |
|||
|
|||
Результат виклику цієї функції - це щось, що можна кодувати з використанням стандарту Python <a href="https://docs.python.org/3/library/json.html#json.dumps" class="external-link" target="_blank">`json.dumps()`</a>. |
|||
|
|||
Вона не повертає велику строку `str`, яка містить дані у форматі JSON (як строка). Вона повертає стандартну структуру даних Python (наприклад `dict`) із значеннями та підзначеннями, які є сумісними з JSON. |
|||
|
|||
!!! Примітка |
|||
`jsonable_encoder` фактично використовується **FastAPI** внутрішньо для перетворення даних. Проте вона корисна в багатьох інших сценаріях. |
@ -0,0 +1,130 @@ |
|||
# Додаткові типи даних |
|||
|
|||
До цього часу, ви використовували загальнопоширені типи даних, такі як: |
|||
|
|||
* `int` |
|||
* `float` |
|||
* `str` |
|||
* `bool` |
|||
|
|||
Але можна також використовувати більш складні типи даних. |
|||
|
|||
І ви все ще матимете ті ж можливості, які були показані до цього: |
|||
|
|||
* Чудова підтримка редактора. |
|||
* Конвертація даних з вхідних запитів. |
|||
* Конвертація даних для відповіді. |
|||
* Валідація даних. |
|||
* Автоматична анотація та документація. |
|||
|
|||
## Інші типи даних |
|||
|
|||
Ось додаткові типи даних для використання: |
|||
|
|||
* `UUID`: |
|||
* Стандартний "Універсальний Унікальний Ідентифікатор", який часто використовується як ідентифікатор у багатьох базах даних та системах. |
|||
* У запитах та відповідях буде представлений як `str`. |
|||
* `datetime.datetime`: |
|||
* Пайтонівський `datetime.datetime`. |
|||
* У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `2008-09-15T15:53:00+05:00`. |
|||
* `datetime.date`: |
|||
* Пайтонівський `datetime.date`. |
|||
* У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `2008-09-15`. |
|||
* `datetime.time`: |
|||
* Пайтонівський `datetime.time`. |
|||
* У запитах та відповідях буде представлений як `str` в форматі ISO 8601, як: `14:23:55.003`. |
|||
* `datetime.timedelta`: |
|||
* Пайтонівський `datetime.timedelta`. |
|||
* У запитах та відповідях буде представлений як `float` загальної кількості секунд. |
|||
* Pydantic також дозволяє представляти це як "ISO 8601 time diff encoding", <a href="https://pydantic-docs.helpmanual.io/usage/exporting_models/#json_encoders" class="external-link" target="_blank">більше інформації дивись у документації</a>. |
|||
* `frozenset`: |
|||
* У запитах і відповідях це буде оброблено так само, як і `set`: |
|||
* У запитах список буде зчитано, дублікати будуть видалені та він буде перетворений на `set`. |
|||
* У відповідях, `set` буде перетворений на `list`. |
|||
* Згенерована схема буде вказувати, що значення `set` є унікальними (з використанням JSON Schema's `uniqueItems`). |
|||
* `bytes`: |
|||
* Стандартний Пайтонівський `bytes`. |
|||
* У запитах і відповідях це буде оброблено як `str`. |
|||
* Згенерована схема буде вказувати, що це `str` з "форматом" `binary`. |
|||
* `Decimal`: |
|||
* Стандартний Пайтонівський `Decimal`. |
|||
* У запитах і відповідях це буде оброблено так само, як і `float`. |
|||
* Ви можете перевірити всі дійсні типи даних Pydantic тут: <a href="https://pydantic-docs.helpmanual.io/usage/types" class="external-link" target="_blank">типи даних Pydantic</a>. |
|||
|
|||
## Приклад |
|||
|
|||
Ось приклад *path operation* з параметрами, використовуючи деякі з вищезазначених типів. |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="1 3 12-16" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="1 3 12-16" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="1 3 13-17" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="1 2 11-15" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="1 2 12-16" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001.py!} |
|||
``` |
|||
|
|||
Зверніть увагу, що параметри всередині функції мають свій звичайний тип даних, і ви можете, наприклад, виконувати звичайні маніпуляції з датами, такі як: |
|||
|
|||
=== "Python 3.10+" |
|||
|
|||
```Python hl_lines="18-19" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_an_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.9+" |
|||
|
|||
```Python hl_lines="18-19" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_an_py39.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+" |
|||
|
|||
```Python hl_lines="19-20" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_an.py!} |
|||
``` |
|||
|
|||
=== "Python 3.10+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="17-18" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001_py310.py!} |
|||
``` |
|||
|
|||
=== "Python 3.6+ non-Annotated" |
|||
|
|||
!!! tip |
|||
Бажано використовувати `Annotated` версію, якщо це можливо. |
|||
|
|||
```Python hl_lines="18-19" |
|||
{!> ../../../docs_src/extra_data_types/tutorial001.py!} |
|||
``` |
@ -0,0 +1,430 @@ |
|||
# 并发 async / await |
|||
|
|||
有关路径操作函数的 `async def` 语法以及异步代码、并发和并行的一些背景知识。 |
|||
|
|||
## 赶时间吗? |
|||
|
|||
<abbr title="too long; didn't read(长文警告)"><strong>TL;DR:</strong></abbr> |
|||
|
|||
如果你正在使用第三方库,它们会告诉你使用 `await` 关键字来调用它们,就像这样: |
|||
|
|||
```Python |
|||
results = await some_library() |
|||
``` |
|||
|
|||
然后,通过 `async def` 声明你的 *路径操作函数*: |
|||
|
|||
```Python hl_lines="2" |
|||
@app.get('/') |
|||
async def read_results(): |
|||
results = await some_library() |
|||
return results |
|||
``` |
|||
|
|||
!!! note |
|||
你只能在被 `async def` 创建的函数内使用 `await` |
|||
|
|||
--- |
|||
|
|||
如果你正在使用一个第三方库和某些组件(比如:数据库、API、文件系统...)进行通信,第三方库又不支持使用 `await` (目前大多数数据库三方库都是这样),这种情况你可以像平常那样使用 `def` 声明一个路径操作函数,就像这样: |
|||
|
|||
```Python hl_lines="2" |
|||
@app.get('/') |
|||
def results(): |
|||
results = some_library() |
|||
return results |
|||
``` |
|||
|
|||
--- |
|||
|
|||
如果你的应用程序不需要与其他任何东西通信而等待其响应,请使用 `async def`。 |
|||
|
|||
--- |
|||
|
|||
如果你不清楚,使用 `def` 就好. |
|||
|
|||
--- |
|||
|
|||
**注意**:你可以根据需要在路径操作函数中混合使用 `def` 和 `async def`,并使用最适合你的方式去定义每个函数。FastAPI 将为他们做正确的事情。 |
|||
|
|||
无论如何,在上述任何情况下,FastAPI 仍将异步工作,速度也非常快。 |
|||
|
|||
但是,通过遵循上述步骤,它将能够进行一些性能优化。 |
|||
|
|||
## 技术细节 |
|||
|
|||
Python 的现代版本支持通过一种叫**"协程"**——使用 `async` 和 `await` 语法的东西来写**”异步代码“**。 |
|||
|
|||
让我们在下面的部分中逐一介绍: |
|||
|
|||
* **异步代码** |
|||
* **`async` 和 `await`** |
|||
* **协程** |
|||
|
|||
## 异步代码 |
|||
|
|||
异步代码仅仅意味着编程语言 💬 有办法告诉计算机/程序 🤖 在代码中的某个点,它 🤖 将不得不等待在某些地方完成一些事情。让我们假设一些事情被称为 "慢文件"📝. |
|||
|
|||
所以,在等待"慢文件"📝完成的这段时间,计算机可以做一些其他工作。 |
|||
|
|||
然后计算机/程序 🤖 每次有机会都会回来,因为它又在等待,或者它 🤖 完成了当前所有的工作。而且它 🤖 将查看它等待的所有任务中是否有已经完成的,做它必须做的任何事情。 |
|||
|
|||
接下来,它 🤖 完成第一个任务(比如是我们的"慢文件"📝) 并继续与之相关的一切。 |
|||
|
|||
这个"等待其他事情"通常指的是一些相对较慢(与处理器和 RAM 存储器的速度相比)的 <abbr title="Input and Output">I/O</abbr> 操作,比如说: |
|||
|
|||
* 通过网络发送来自客户端的数据 |
|||
* 客户端接收来自网络中的数据 |
|||
* 磁盘中要由系统读取并提供给程序的文件的内容 |
|||
* 程序提供给系统的要写入磁盘的内容 |
|||
* 一个 API 的远程调用 |
|||
* 一个数据库操作,直到完成 |
|||
* 一个数据库查询,直到返回结果 |
|||
* 等等. |
|||
|
|||
这个执行的时间大多是在等待 <abbr title="Input and Output">I/O</abbr> 操作,因此它们被叫做 "I/O 密集型" 操作。 |
|||
|
|||
它被称为"异步"的原因是因为计算机/程序不必与慢任务"同步",去等待任务完成的确切时刻,而在此期间不做任何事情直到能够获取任务结果才继续工作。 |
|||
|
|||
相反,作为一个"异步"系统,一旦完成,任务就可以排队等待一段时间(几微秒),等待计算机程序完成它要做的任何事情,然后回来获取结果并继续处理它们。 |
|||
|
|||
对于"同步"(与"异步"相反),他们通常也使用"顺序"一词,因为计算机程序在切换到另一个任务之前是按顺序执行所有步骤,即使这些步骤涉及到等待。 |
|||
|
|||
### 并发与汉堡 |
|||
|
|||
上述异步代码的思想有时也被称为“并发”,它不同于“并行”。 |
|||
|
|||
并发和并行都与“不同的事情或多或少同时发生”有关。 |
|||
|
|||
但是并发和并行之间的细节是完全不同的。 |
|||
|
|||
要了解差异,请想象以下关于汉堡的故事: |
|||
|
|||
### 并发汉堡 |
|||
|
|||
你和你的恋人一起去快餐店,你排队在后面,收银员从你前面的人接单。😍 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-01.png" class="illustration"> |
|||
|
|||
然后轮到你了,你为你的恋人和你选了两个非常豪华的汉堡。🍔🍔 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-02.png" class="illustration"> |
|||
|
|||
收银员对厨房里的厨师说了一些话,让他们知道他们必须为你准备汉堡(尽管他们目前正在为之前的顾客准备汉堡)。 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-03.png" class="illustration"> |
|||
|
|||
你付钱了。 💸 |
|||
|
|||
收银员给你轮到的号码。 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-04.png" class="illustration"> |
|||
|
|||
当你在等待的时候,你和你的恋人一起去挑选一张桌子,然后你们坐下来聊了很长时间(因为汉堡很豪华,需要一些时间来准备)。 |
|||
|
|||
当你和你的恋人坐在桌子旁,等待汉堡的时候,你可以用这段时间来欣赏你的恋人是多么的棒、可爱和聪明✨😍✨。 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-05.png" class="illustration"> |
|||
|
|||
在等待中和你的恋人交谈时,你会不时地查看柜台上显示的号码,看看是否已经轮到你了。 |
|||
|
|||
然后在某个时刻,终于轮到你了。你去柜台拿汉堡然后回到桌子上。 |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-06.png" class="illustration"> |
|||
|
|||
你们享用了汉堡,整个过程都很开心。✨ |
|||
|
|||
<img src="/img/async/concurrent-burgers/concurrent-burgers-07.png" class="illustration"> |
|||
|
|||
!!! info |
|||
漂亮的插画来自 <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨 |
|||
|
|||
--- |
|||
|
|||
在那个故事里,假设你是计算机程序 🤖 。 |
|||
|
|||
当你在排队时,你只是闲着😴, 轮到你前不做任何事情(仅排队)。但排队很快,因为收银员只接订单(不准备订单),所以这一切都还好。 |
|||
|
|||
然后,当轮到你时,需要你做一些实际性的工作,比如查看菜单,决定你想要什么,让你的恋人选择,支付,检查你是否提供了正确的账单或卡,检查你的收费是否正确,检查订单是否有正确的项目,等等。 |
|||
|
|||
此时,即使你仍然没有汉堡,你和收银员的工作也"暂停"了⏸, 因为你必须等待一段时间 🕙 让你的汉堡做好。 |
|||
|
|||
但是,当你离开柜台并坐在桌子旁,在轮到你的号码前的这段时间,你可以将焦点切换到 🔀 你的恋人上,并做一些"工作"⏯ 🤓。你可以做一些非常"有成效"的事情,比如和你的恋人调情😍. |
|||
|
|||
之后,收银员 💁 把号码显示在显示屏上,并说到 "汉堡做好了",而当显示的号码是你的号码时,你不会立刻疯狂地跳起来。因为你知道没有人会偷你的汉堡,因为你有你的号码,而其他人又有他们自己的号码。 |
|||
|
|||
所以你要等待你的恋人完成故事(完成当前的工作⏯ /正在做的事🤓), 轻轻微笑,说你要吃汉堡⏸. |
|||
|
|||
然后你去柜台🔀, 到现在初始任务已经完成⏯, 拿起汉堡,说声谢谢,然后把它们送到桌上。这就完成了与计数器交互的步骤/任务⏹. 这反过来又产生了一项新任务,即"吃汉堡"🔀 ⏯, 上一个"拿汉堡"的任务已经结束了⏹. |
|||
|
|||
### 并行汉堡 |
|||
|
|||
现在让我们假设不是"并发汉堡",而是"并行汉堡"。 |
|||
|
|||
你和你的恋人一起去吃并行快餐。 |
|||
|
|||
你站在队伍中,同时是厨师的几个收银员(比方说8个)从前面的人那里接单。 |
|||
|
|||
你之前的每个人都在等待他们的汉堡准备好后才离开柜台,因为8名收银员都会在下一份订单前马上准备好汉堡。 |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-01.png" class="illustration"> |
|||
|
|||
然后,终于轮到你了,你为你的恋人和你订购了两个非常精美的汉堡。 |
|||
|
|||
你付钱了 💸。 |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-02.png" class="illustration"> |
|||
|
|||
收银员去厨房。 |
|||
|
|||
你站在柜台前 🕙等待着,这样就不会有人在你之前抢走你的汉堡,因为没有轮流的号码。 |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-03.png" class="illustration"> |
|||
|
|||
当你和你的恋人忙于不让任何人出现在你面前,并且在他们到来的时候拿走你的汉堡时,你无法关注到你的恋人。😞 |
|||
|
|||
这是"同步"的工作,你被迫与服务员/厨师 👨🍳"同步"。你在此必须等待 🕙 ,在收银员/厨师 👨🍳 完成汉堡并将它们交给你的确切时间到达之前一直等待,否则其他人可能会拿走它们。 |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-04.png" class="illustration"> |
|||
|
|||
你经过长时间的等待 🕙 ,收银员/厨师 👨🍳终于带着汉堡回到了柜台。 |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-05.png" class="illustration"> |
|||
|
|||
你拿着汉堡,和你的情人一起上桌。 |
|||
|
|||
你们仅仅是吃了它们,就结束了。⏹ |
|||
|
|||
<img src="/img/async/parallel-burgers/parallel-burgers-06.png" class="illustration"> |
|||
|
|||
没有太多的交谈或调情,因为大部分时间 🕙 都在柜台前等待😞。 |
|||
|
|||
!!! info |
|||
漂亮的插画来自 <a href="https://www.instagram.com/ketrinadrawsalot" class="external-link" target="_blank">Ketrina Thompson</a>. 🎨 |
|||
|
|||
--- |
|||
|
|||
在这个并行汉堡的场景中,你是一个计算机程序 🤖 且有两个处理器(你和你的恋人),都在等待 🕙 ,并投入他们的注意力 ⏯ 在柜台上等待了很长一段时间。 |
|||
|
|||
这家快餐店有 8 个处理器(收银员/厨师)。而并发汉堡店可能只有 2 个(一个收银员和一个厨师)。 |
|||
|
|||
但最终的体验仍然不是最好的。😞 |
|||
|
|||
--- |
|||
|
|||
这将是与汉堡的类似故事。🍔 |
|||
|
|||
一种更"贴近生活"的例子,想象一家银行。 |
|||
|
|||
直到最近,大多数银行都有多个出纳员 👨💼👨💼👨💼👨💼 还有一条长长排队队伍🕙🕙🕙🕙🕙🕙🕙🕙。 |
|||
|
|||
所有收银员都是一个接一个的在客户面前做完所有的工作👨💼⏯. |
|||
|
|||
你必须经过 🕙 较长时间排队,否则你就没机会了。 |
|||
|
|||
你可不会想带你的恋人 😍 和你一起去银行办事🏦. |
|||
|
|||
### 汉堡结论 |
|||
|
|||
在"你与恋人一起吃汉堡"的这个场景中,因为有很多人在等待🕙, 使用并发系统更有意义⏸🔀⏯. |
|||
|
|||
大多数 Web 应用都是这样的。 |
|||
|
|||
你的服务器正在等待很多很多用户通过他们不太好的网络发送来的请求。 |
|||
|
|||
然后再次等待 🕙 响应回来。 |
|||
|
|||
这个"等待" 🕙 是以微秒为单位测量的,但总的来说,最后还是等待很久。 |
|||
|
|||
这就是为什么使用异步对于 Web API 很有意义的原因 ⏸🔀⏯。 |
|||
|
|||
这种异步机制正是 NodeJS 受到欢迎的原因(尽管 NodeJS 不是并行的),以及 Go 作为编程语言的优势所在。 |
|||
|
|||
这与 **FastAPI** 的性能水平相同。 |
|||
|
|||
您可以同时拥有并行性和异步性,您可以获得比大多数经过测试的 NodeJS 框架更高的性能,并且与 Go 不相上下, Go 是一种更接近于 C 的编译语言(<a href="https://www.techempower.com/benchmarks/#section=data-r17&hw=ph&test=query&l=zijmkf-1" class="external-link" target="_blank">全部归功于 Starlette</a>)。 |
|||
|
|||
### 并发比并行好吗? |
|||
|
|||
不!这不是故事的本意。 |
|||
|
|||
并发不同于并行。而是在需要大量等待的特定场景下效果更好。因此,在 Web 应用程序开发中,它通常比并行要好得多,但这并不意味着全部。 |
|||
|
|||
因此,为了平衡这一点,想象一下下面的短篇故事: |
|||
|
|||
> 你必须打扫一个又大又脏的房子。 |
|||
|
|||
*是的,这就是完整的故事。* |
|||
|
|||
--- |
|||
|
|||
在任何地方, 都不需要等待 🕙 ,只需要在房子的多个地方做着很多工作。 |
|||
|
|||
你可以像汉堡的例子那样轮流执行,先是客厅,然后是厨房,但因为你不需要等待 🕙 ,对于任何事情都是清洁,清洁,还是清洁,轮流不会影响任何事情。 |
|||
|
|||
无论是否轮流执行(并发),都需要相同的时间来完成,而你也会完成相同的工作量。 |
|||
|
|||
但在这种情况下,如果你能带上 8 名前收银员/厨师,现在是清洁工一起清扫,他们中的每一个人(加上你)都能占据房子的一个区域来清扫,你就可以在额外的帮助下并行的更快地完成所有工作。 |
|||
|
|||
在这个场景中,每个清洁工(包括您)都将是一个处理器,完成这个工作的一部分。 |
|||
|
|||
由于大多数执行时间是由实际工作(而不是等待)占用的,并且计算机中的工作是由 <abbr title="Central Processing Unit">CPU</abbr> 完成的,所以他们称这些问题为"CPU 密集型"。 |
|||
|
|||
--- |
|||
|
|||
CPU 密集型操作的常见示例是需要复杂的数学处理。 |
|||
|
|||
例如: |
|||
|
|||
* **音频**或**图像**处理; |
|||
* **计算机视觉**: 一幅图像由数百万像素组成,每个像素有3种颜色值,处理通常需要同时对这些像素进行计算; |
|||
* **机器学习**: 它通常需要大量的"矩阵"和"向量"乘法。想象一个包含数字的巨大电子表格,并同时将所有数字相乘; |
|||
* **深度学习**: 这是机器学习的一个子领域,同样适用。只是没有一个数字的电子表格可以相乘,而是一个庞大的数字集合,在很多情况下,你需要使用一个特殊的处理器来构建和使用这些模型。 |
|||
|
|||
### 并发 + 并行: Web + 机器学习 |
|||
|
|||
使用 **FastAPI**,您可以利用 Web 开发中常见的并发机制的优势(NodeJS 的主要吸引力)。 |
|||
|
|||
并且,您也可以利用并行和多进程(让多个进程并行运行)的优点来处理与机器学习系统中类似的 **CPU 密集型** 工作。 |
|||
|
|||
这一点,再加上 Python 是**数据科学**、机器学习(尤其是深度学习)的主要语言这一简单事实,使得 **FastAPI** 与数据科学/机器学习 Web API 和应用程序(以及其他许多应用程序)非常匹配。 |
|||
|
|||
了解如何在生产环境中实现这种并行性,可查看此文 [Deployment](deployment/index.md){.internal-link target=_blank}。 |
|||
|
|||
## `async` 和 `await` |
|||
|
|||
现代版本的 Python 有一种非常直观的方式来定义异步代码。这使它看起来就像正常的"顺序"代码,并在适当的时候"等待"。 |
|||
|
|||
当有一个操作需要等待才能给出结果,且支持这个新的 Python 特性时,您可以编写如下代码: |
|||
|
|||
```Python |
|||
burgers = await get_burgers(2) |
|||
``` |
|||
|
|||
这里的关键是 `await`。它告诉 Python 它必须等待 ⏸ `get_burgers(2)` 完成它的工作 🕙 ,然后将结果存储在 `burgers` 中。这样,Python 就会知道此时它可以去做其他事情 🔀 ⏯ (比如接收另一个请求)。 |
|||
|
|||
要使 `await` 工作,它必须位于支持这种异步机制的函数内。因此,只需使用 `async def` 声明它: |
|||
|
|||
```Python hl_lines="1" |
|||
async def get_burgers(number: int): |
|||
# Do some asynchronous stuff to create the burgers |
|||
return burgers |
|||
``` |
|||
|
|||
...而不是 `def`: |
|||
|
|||
```Python hl_lines="2" |
|||
# This is not asynchronous |
|||
def get_sequential_burgers(number: int): |
|||
# Do some sequential stuff to create the burgers |
|||
return burgers |
|||
``` |
|||
|
|||
使用 `async def`,Python 就知道在该函数中,它将遇上 `await`,并且它可以"暂停" ⏸ 执行该函数,直至执行其他操作 🔀 后回来。 |
|||
|
|||
当你想调用一个 `async def` 函数时,你必须"等待"它。因此,这不会起作用: |
|||
|
|||
```Python |
|||
# This won't work, because get_burgers was defined with: async def |
|||
burgers = get_burgers(2) |
|||
``` |
|||
|
|||
--- |
|||
|
|||
因此,如果您使用的库告诉您可以使用 `await` 调用它,则需要使用 `async def` 创建路径操作函数 ,如: |
|||
|
|||
```Python hl_lines="2-3" |
|||
@app.get('/burgers') |
|||
async def read_burgers(): |
|||
burgers = await get_burgers(2) |
|||
return burgers |
|||
``` |
|||
|
|||
### 更多技术细节 |
|||
|
|||
您可能已经注意到,`await` 只能在 `async def` 定义的函数内部使用。 |
|||
|
|||
但与此同时,必须"等待"通过 `async def` 定义的函数。因此,带 `async def` 的函数也只能在 `async def` 定义的函数内部调用。 |
|||
|
|||
那么,这关于先有鸡还是先有蛋的问题,如何调用第一个 `async` 函数? |
|||
|
|||
如果您使用 **FastAPI**,你不必担心这一点,因为"第一个"函数将是你的路径操作函数,FastAPI 将知道如何做正确的事情。 |
|||
|
|||
但如果您想在没有 FastAPI 的情况下使用 `async` / `await`,则可以这样做。 |
|||
|
|||
### 编写自己的异步代码 |
|||
|
|||
Starlette (和 **FastAPI**) 是基于 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> 实现的,这使得它们可以兼容 Python 的标准库 <a href="https://docs.python.org/3/library/asyncio-task.html" class="external-link" target="_blank">asyncio</a> 和 <a href="https://trio.readthedocs.io/en/stable/" class="external-link" target="_blank">Trio</a>。 |
|||
|
|||
特别是,你可以直接使用 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> 来处理高级的并发用例,这些用例需要在自己的代码中使用更高级的模式。 |
|||
|
|||
即使您没有使用 **FastAPI**,您也可以使用 <a href="https://anyio.readthedocs.io/en/stable/" class="external-link" target="_blank">AnyIO</a> 编写自己的异步程序,使其拥有较高的兼容性并获得一些好处(例如, 结构化并发)。 |
|||
|
|||
### 其他形式的异步代码 |
|||
|
|||
这种使用 `async` 和 `await` 的风格在语言中相对较新。 |
|||
|
|||
但它使处理异步代码变得容易很多。 |
|||
|
|||
这种相同的语法(或几乎相同)最近也包含在现代版本的 JavaScript 中(在浏览器和 NodeJS 中)。 |
|||
|
|||
但在此之前,处理异步代码非常复杂和困难。 |
|||
|
|||
在以前版本的 Python,你可以使用多线程或者 <a href="https://www.gevent.org/" class="external-link" target="_blank">Gevent</a>。但代码的理解、调试和思考都要复杂许多。 |
|||
|
|||
在以前版本的 NodeJS / 浏览器 JavaScript 中,你会使用"回调",因此也可能导致<a href="http://callbackhell.com/" class="external-link" target="_blank">回调地狱</a>。 |
|||
|
|||
## 协程 |
|||
|
|||
**协程**只是 `async def` 函数返回的一个非常奇特的东西的称呼。Python 知道它有点像一个函数,它可以启动,也会在某个时刻结束,而且它可能会在内部暂停 ⏸ ,只要内部有一个 `await`。 |
|||
|
|||
通过使用 `async` 和 `await` 的异步代码的所有功能大多数被概括为"协程"。它可以与 Go 的主要关键特性 "Goroutines" 相媲美。 |
|||
|
|||
## 结论 |
|||
|
|||
让我们再来回顾下上文所说的: |
|||
|
|||
> Python 的现代版本可以通过使用 `async` 和 `await` 语法创建**协程**,并用于支持**异步代码**。 |
|||
|
|||
现在应该能明白其含义了。✨ |
|||
|
|||
所有这些使得 FastAPI(通过 Starlette)如此强大,也是它拥有如此令人印象深刻的性能的原因。 |
|||
|
|||
## 非常技术性的细节 |
|||
|
|||
!!! warning |
|||
你可以跳过这里。 |
|||
|
|||
这些都是 FastAPI 如何在内部工作的技术细节。 |
|||
|
|||
如果您有相当多的技术知识(协程、线程、阻塞等),并且对 FastAPI 如何处理 `async def` 与常规 `def` 感到好奇,请继续。 |
|||
|
|||
### 路径操作函数 |
|||
|
|||
当你使用 `def` 而不是 `async def` 来声明一个*路径操作函数*时,它运行在外部的线程池中并等待其结果,而不是直接调用(因为它会阻塞服务器)。 |
|||
|
|||
如果您使用过另一个不以上述方式工作的异步框架,并且您习惯于用普通的 `def` 定义普通的仅计算路径操作函数,以获得微小的性能增益(大约100纳秒),请注意,在 FastAPI 中,效果将完全相反。在这些情况下,最好使用 `async def`,除非路径操作函数内使用执行阻塞 <abbr title="输入/输出:磁盘读写,网络通讯.">I/O</abbr> 的代码。 |
|||
|
|||
在这两种情况下,与您之前的框架相比,**FastAPI** 可能[仍然很快](/#performance){.internal-link target=_blank}。 |
|||
|
|||
### 依赖 |
|||
|
|||
这同样适用于[依赖](/tutorial/dependencies/index.md){.internal-link target=_blank}。如果一个依赖是标准的 `def` 函数而不是 `async def`,它将被运行在外部线程池中。 |
|||
|
|||
### 子依赖 |
|||
|
|||
你可以拥有多个相互依赖的依赖以及[子依赖](/tutorial/dependencies/sub-dependencies.md){.internal-link target=_blank} (作为函数的参数),它们中的一些可能是通过 `async def` 声明,也可能是通过 `def` 声明。它们仍然可以正常工作,这些通过 `def` 声明的函数将会在外部线程中调用(来自线程池),而不是"被等待"。 |
|||
|
|||
### 其他函数 |
|||
|
|||
您可直接调用通过 `def` 或 `async def` 创建的任何其他函数,FastAPI 不会影响您调用它们的方式。 |
|||
|
|||
这与 FastAPI 为您调用*路径操作函数*和依赖项的逻辑相反。 |
|||
|
|||
如果你的函数是通过 `def` 声明的,它将被直接调用(在代码中编写的地方),而不会在线程池中,如果这个函数通过 `async def` 声明,当在代码中调用时,你就应该使用 `await` 等待函数的结果。 |
|||
|
|||
--- |
|||
|
|||
再次提醒,这些是非常技术性的细节,如果你来搜索它可能对你有用。 |
|||
|
|||
否则,您最好应该遵守的指导原则<a href="#_1">赶时间吗?</a>. |
Loading…
Reference in new issue