{"id":303884,"date":"2020-05-19T21:00:29","date_gmt":"2020-05-19T21:00:29","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=303884"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=303884","title":{"rendered":"\u0410\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u043f\u0440\u0438 \u043f\u043e\u043c\u043e\u0449\u0438 Starlette + Vue.js"},"content":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/502814\/\">\n<h3>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h3>\n<p>  <\/p>\n<hr\/>\n<p>  \u0417\u0430\u0434\u0430\u0447\u0430 \u2014 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432 Starlette (<a href=\"https:\/\/www.starlette.io\/\">https:\/\/www.starlette.io\/<\/a>) \u0438 Vue.js *, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b\u043b \u0431\u044b \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043a\u043e\u043c\u0444\u043e\u0440\u0442\u043d\u044b\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c Django \u0434\u043b\u044f \u00ab\u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438\u00bb \u0432 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043a. <\/p>\n<p>  \u041f\u043e\u0447\u0435\u043c\u0443 Starlette? \u0412 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c. Starlette \u0443\u043b\u044c\u0442\u0438\u043c\u0430\u0442\u0438\u0432\u043d\u043e \u0431\u044b\u0441\u0442\u0440, \u0438 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 \u0443\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e BlackSheep (<a href=\"https:\/\/pypi.org\/project\/blacksheep\/\">https:\/\/pypi.org\/project\/blacksheep\/<\/a>). \u0412\u043e \u0432\u0442\u043e\u0440\u044b\u0445 Starlette \u0432\u0435\u0441\u044c\u043c\u0430 \u043f\u0440\u043e\u0441\u0442 \u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043d\u0435\u043c \u0432 \u0441\u0438\u043b\u0443 \u0435\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043c\u0430\u043d\u043d\u043e\u0441\u0442\u0438 \u043b\u0435\u0433\u043a\u043e \u0438 \u043f\u0440\u0438\u044f\u0442\u043d\u043e. <\/p>\n<p>  \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 ORM \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Tortoise ORM (\u0441\u043e \u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438 \u0438 \u0432\u044b\u0431\u043e\u0440\u043a\u0430\u043c\u0438 \u00ab\u0430\u043b\u044f Django ORM\u00bb). <\/p>\n<p>  \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0435\u0441\u0441\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JWT.<\/p>\n<p>  <i>* \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u043d\u0430 Vue.js \u043d\u0435 \u0432\u0445\u043e\u0434\u0438\u0442 \u0432 \u0434\u0430\u043d\u043d\u0443\u044e \u0437\u0430\u043c\u0435\u0442\u043a\u0443.<\/i><br \/>  <a name=\"habracut\"><\/a>  <\/p>\n<h4>\u0421\u0442\u0440\u0443\u043a\u0442\u0443\u0440\u0430 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<\/h4>\n<p>  <b>apps\/user\/models.py <\/b> \u2014 \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f <br \/>  <b>apps\/user\/urls.py <\/b> \u2014 \u0440\u043e\u0443\u0442\u0435\u0440 <br \/>  <b>apps\/user\/views.py <\/b> \u2014 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438 \u043b\u043e\u0433\u0438\u043d<br \/>  <b>.env<\/b> \u2014 \u043d\u0430\u0448\u0438 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435<br \/>  <b>settings.py<\/b> \u2014 \u043e\u0431\u0449\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430<br \/>  <b>app.py<\/b> \u2014 \u0442\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430<br \/>  <b>middleware.py<\/b> \u2014 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 JWT  <\/p>\n<h4>\u0424\u0430\u0439\u043b \u0441 \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u043c\u0438 .env <\/h4>\n<p>  \u041e\u0431\u044a\u044f\u0432\u0438\u043c \u0437\u0434\u0435\u0441\u044c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u043d\u0430\u043c \u0432 \u0434\u0430\u043b\u044c\u043d\u0435\u0439\u0448\u0435\u043c \u043f\u043e\u043d\u0430\u0434\u043e\u0431\u044f\u0442\u0441\u044f \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b:<\/p>\n<pre><code class=\"python\">DEBUG=True DATABASE_URL=postgres:\/\/user:123456@localhost\/svue_backend_db ALLOWED_HOSTS=127.0.0.1, localhost, local SECRET_KEY=AGe-lJvQslHjNdqOa2_Wwy9JB3GE3d8GzMfC418I6jc JWT_PREFIX=Bearer JWT_ALGORITHM=HS256 <\/code><\/pre>\n<p>  <\/p>\n<h4>\u041e\u0431\u0449\u0438\u0435 \u043d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043f\u0440\u043e\u0435\u043a\u0442\u0430 settings.py<\/h4>\n<p>  <\/p>\n<pre><code class=\"python\">config = Config(&quot;.env&quot;) DEBUG = config(&quot;DEBUG&quot;, cast=bool, default=False) DATABASE_URL = config(&quot;DATABASE_URL&quot;, cast=str) SECRET_KEY = config(&quot;SECRET_KEY&quot;, cast=Secret) ALLOWED_HOSTS = config(&quot;ALLOWED_HOSTS&quot;, cast=CommaSeparatedStrings) JWT_PREFIX = config(&quot;JWT_PREFIX&quot;, cast=str) JWT_ALGORITHM = config(&quot;JWT_ALGORITHM&quot;, cast=str) <\/code><\/pre>\n<p>  \u0414\u043b\u044f \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u0430 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f \u0432\u044b\u043d\u0435\u0441\u0435\u043c \u043f\u0435\u0440\u0435\u043c\u0435\u043d\u043d\u044b\u0435 \u0438\u0437 \u0444\u0430\u0439\u043b\u0430 .env \u0432 \u043e\u0442\u0434\u0435\u043b\u044c\u043d\u044b\u0439 \u0444\u0430\u0439\u043b \u043d\u0430\u0441\u0442\u0440\u043e\u0435\u043a.<\/p>\n<h4>\u0422\u043e\u0447\u043a\u0430 \u0432\u0445\u043e\u0434\u0430 app.py<\/h4>\n<p>  <\/p>\n<pre><code class=\"python\">middleware = [     Middleware(CORSMiddleware, allow_origins=[&quot;*&quot;], allow_methods=[&quot;*&quot;], allow_headers=[&quot;*&quot;]),     Middleware(         AuthenticationMiddleware, backend=JWTAuthenticationBackend(secret_key=str(SECRET_KEY), algorithm=JWT_ALGORITHM, prefix=JWT_PREFIX)     ),  # str(SECRET_KEY) is important     Middleware(SessionMiddleware, secret_key=SECRET_KEY),     Middleware(CustomHeaderMiddleware), ]  routes = [     Mount(&quot;\/user&quot;, routes=user_routes),     Mount(&quot;\/&quot;, routes=main_routes), ]  entry_point = Starlette(debug=DEBUG, routes=routes, middleware=middleware)  tortoise_models = [     &quot;apps.user.models&quot;, ]  register_tortoise(entry_point, db_url=DATABASE_URL, modules={&quot;models&quot;: tortoise_models}, generate_schemas=True) <\/code><\/pre>\n<p>  \u041e\u0431\u0440\u0430\u0442\u0438\u0442\u0435 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u043d\u0430 \u043f\u043e\u0440\u044f\u0434\u043e\u043a \u0441\u043b\u0435\u0434\u043e\u0432\u0430\u043d\u0438\u044f middleware, \u0438 \u043d\u0430 \u0442\u043e \u0447\u0442\u043e Tortoise ORM \u043c\u044b \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0430\u0435\u043c \u0432 \u0441\u0430\u043c\u043e\u043c \u043a\u043e\u043d\u0446\u0435.<\/p>\n<h4>\u041f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u0441\u043b\u043e\u0439 \u0434\u043b\u044f \u0440\u0430\u0431\u043e\u0442\u044b \u0441 JWT middleware.py<\/h4>\n<p>  \u041f\u043e\u0441\u043a\u043e\u043b\u044c\u043a\u0443 Starlette \u0435\u0449\u0435 \u0434\u043e\u0441\u0442\u0430\u0442\u043e\u0447\u043d\u043e \u043c\u043e\u043b\u043e\u0434\u043e\u0439 \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a, \u0443\u0434\u043e\u0431\u043d\u043e\u0439 \u00ab\u0431\u0430\u0442\u0430\u0440\u0435\u0439\u043a\u0438\u00bb JWT \u043a \u043d\u0435\u043c\u0443 \u0435\u0449\u0435 \u043d\u0435 \u043d\u0430\u043f\u0438\u0441\u0430\u043d\u043e. \u0418\u0441\u043f\u0440\u0430\u0432\u0438\u043c \u044d\u0442\u043e\u0442 \u043d\u0435\u0434\u043e\u0447\u0435\u0442.<\/p>\n<pre><code class=\"python\">class JWTUser(BaseUser):     def __init__(self, username: str, user_id: int, email: str, token: str, **kw) -&gt; None:         self.username = username         self.user_id = user_id         self.email = email         self.token = token      @property     def is_authenticated(self) -&gt; bool:         return True      @property     def display_name(self) -&gt; str:         return self.username      def __str__(self) -&gt; str:         return f&quot;JWT user: username={self.username}, id={self.user_id}, email={self.email}&quot;   class JWTAuthenticationBackend(AuthenticationBackend):     def __init__(self, secret_key: str, algorithm: str = &quot;HS256&quot;, prefix: str = &quot;Bearer&quot;):         self.secret_key = secret_key         self.algorithm = algorithm         self.prefix = prefix      @classmethod     def get_token_from_header(cls, authorization: str, prefix: str):          if DEBUG:             sprint_f(f&quot;JWT token from headers: {authorization}&quot;, &quot;cyan&quot;)  # debug part, do not forget to remove it         try:             scheme, token = authorization.split()         except ValueError:             if DEBUG:                 sprint_f(f&quot;Could not separate Authorization scheme and token&quot;, &quot;red&quot;)             raise AuthenticationError(&quot;Could not separate Authorization scheme and token&quot;)         if scheme.lower() != prefix.lower():             if DEBUG:                 sprint_f(f&quot;Authorization scheme {scheme} is not supported&quot;, &quot;red&quot;)             raise AuthenticationError(f&quot;Authorization scheme {scheme} is not supported&quot;)         return token      async def authenticate(self, request):          if &quot;Authorization&quot; not in request.headers:             return None          authorization = request.headers[&quot;Authorization&quot;]         token = self.get_token_from_header(authorization=authorization, prefix=self.prefix)          try:             jwt_payload = jwt.decode(token, key=str(self.secret_key), algorithms=self.algorithm)         except jwt.InvalidTokenError:             if DEBUG:                 sprint_f(f&quot;Invalid JWT token&quot;, &quot;red&quot;)             raise AuthenticationError(&quot;Invalid JWT token&quot;)         except jwt.ExpiredSignatureError:             if DEBUG:                 sprint_f(f&quot;Expired JWT token&quot;, &quot;red&quot;)             raise AuthenticationError(&quot;Expired JWT token&quot;)          if DEBUG:             sprint_f(f&quot;Decoded JWT payload: {jwt_payload}&quot;, &quot;green&quot;)  # debug part, do not forget to remove it          return (             AuthCredentials([&quot;authenticated&quot;]),             JWTUser(username=jwt_payload[&quot;username&quot;], user_id=jwt_payload[&quot;user_id&quot;], email=jwt_payload[&quot;email&quot;], token=token),         ) <\/code><\/pre>\n<p>  <\/p>\n<h4>\u041c\u043e\u0434\u0435\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f apps\/user\/models.py<\/h4>\n<p>  Tortoise ORM \u0437\u0430\u043c\u0435\u0447\u0430\u0442\u0435\u043b\u044c\u043d\u043e\u0435 \u0440\u0435\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0442\u0435\u0445, \u043a\u0442\u043e \u0445\u043e\u0447\u0435\u0442 \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c asyncpg (https:\/\/github.com\/MagicStack\/asyncpg), \u0438 \u0443\u0434\u043e\u0431\u0441\u0442\u0432\u043e \u043a\u043b\u0430\u0441\u0441\u0438\u0447\u0435\u0441\u043a\u043e\u0433\u043e Django ORM. \u041e\u0431\u044a\u044f\u0432\u0438\u043c \u043c\u043e\u0434\u0435\u043b\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f.<\/p>\n<pre><code class=\"python\">from tortoise.models import Model from tortoise import fields  class User(Model):          id = fields.IntField(pk=True)     username = fields.CharField(max_length=255)          email = fields.CharField(max_length=255)       password = fields.CharField(max_length=255)         creation_date = fields.data.DatetimeField(auto_now_add=True)     last_login_date = fields.data.DatetimeField(null=True, blank=True)      def __str__(self):         return self.username     class Meta:         table = &quot;user_user&quot; <\/code><\/pre>\n<p>  \u041a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c, \u0432\u0441\u0435 \u043e\u0447\u0435\u043d\u044c \u043f\u0440\u043e\u0441\u0442\u043e \u0438 \u043f\u043e\u0445\u043e\u0436\u0435 \u043d\u0430 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0435 \u043d\u0430\u043c \u043c\u043e\u0434\u0435\u043b\u0438 Django.<\/p>\n<h4>\u0420\u043e\u0443\u0442\u0435\u0440 apps\/user\/urls.py<\/h4>\n<p>  <\/p>\n<pre><code class=\"python\">&lt;code&gt; from starlette.routing import Route from .views import refresh_token from .views import user_login from .views import user_register  routes = [     Route(&quot;\/register&quot;, endpoint=user_register, methods=[&quot;POST&quot;, &quot;OPTIONS&quot;], name=&quot;user__register&quot;),     Route(&quot;\/login&quot;, endpoint=user_login, methods=[&quot;POST&quot;, &quot;OPTIONS&quot;], name=&quot;user__login&quot;),     Route(&quot;\/refresh-token\/&quot;, endpoint=refresh_token, methods=[&quot;POST&quot;, &quot;OPTIONS&quot;], name=&quot;user__refresh_token&quot;), ] &lt;\/code&gt;<\/code><\/pre>\n<p>  \u0420\u043e\u0443\u0442\u0435\u0440 Starlette \u043a\u0430\u043a \u043c\u044b \u0432\u0438\u0434\u0438\u043c \u0442\u0430\u043a\u0436\u0435 \u0432\u0435\u0441\u044c\u043c\u0430 \u043f\u0440\u043e\u0441\u0442 \u0438 \u043f\u043e\u0445\u043e\u0436 \u043d\u0430 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u044b\u0439 \u043d\u0430\u043c \u0440\u043e\u0443\u0442\u0435\u0440 Django.<\/p>\n<h4>\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0430\u0446\u0438\u044f \u0438 \u043b\u043e\u0433\u0438\u043d apps\/user\/views.py<\/h4>\n<p>  <\/p>\n<pre><code class=\"python\">&lt;code&gt; from .models import User from settings import JWT_ALGORITHM from settings import JWT_PREFIX from settings import SECRET_KEY  async def create_token(token_config: dict) -&gt; str:      exp = datetime.utcnow() + timedelta(minutes=token_config[&quot;expiration_minutes&quot;])     token = {         &quot;username&quot;: token_config[&quot;username&quot;],         &quot;user_id&quot;: token_config[&quot;user_id&quot;],         &quot;email&quot;: token_config[&quot;email&quot;],         &quot;iat&quot;: datetime.utcnow(),         &quot;exp&quot;: exp,     }      if &quot;get_expired_token&quot; in token_config:         token[&quot;sub&quot;] = &quot;token&quot;     else:         token[&quot;sub&quot;] = &quot;refresh_token&quot;      token = jwt.encode(token, str(SECRET_KEY), algorithm=JWT_ALGORITHM)     return token.decode(&quot;UTF-8&quot;)   async def user_register(request: Request) -&gt; JSONResponse:      try:         payload = await request.json()     except JSONDecodeError:         raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=&quot;Can't parse json request&quot;)      username = payload[&quot;username&quot;]     email = payload[&quot;email&quot;]     password = pbkdf2_sha256.hash(payload[&quot;password&quot;])      user_exist = await User.filter(email=email).first()     if user_exist:         raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=&quot;Already registred&quot;)      new_user = User()     new_user.username = username     new_user.email = email     new_user.password = password     await new_user.save()      token = await create_token({&quot;email&quot;: email, &quot;username&quot;: username, &quot;user_id&quot;: new_user.id, &quot;get_expired_token&quot;: 1, &quot;expiration_minutes&quot;: 30})     refresh_token = await create_token({&quot;email&quot;: email, &quot;username&quot;: username, &quot;user_id&quot;: new_user.id, &quot;get_refresh_token&quot;: 1, &quot;expiration_minutes&quot;: 10080})      return JSONResponse({&quot;id&quot;: new_user.id, &quot;username&quot;: new_user.username, &quot;email&quot;: new_user.email, &quot;token&quot;: f&quot;{JWT_PREFIX} {token}&quot;, &quot;refresh_token&quot;: f&quot;{JWT_PREFIX} {refresh_token}&quot;,}, status_code=200,)   async def user_login(request: Request) -&gt; JSONResponse:      try:         payload = await request.json()     except JSONDecodeError:         raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=&quot;Can't parse json request&quot;)      email = payload[&quot;email&quot;]     password = payload[&quot;password&quot;]      user = await User.filter(email=email).first()     if user:         if pbkdf2_sha256.verify(password, user.password):             user.last_login_date = datetime.now()             await user.save()              token = await create_token({&quot;email&quot;: user.email, &quot;username&quot;: user.username, &quot;user_id&quot;: user.id, &quot;get_expired_token&quot;: 1, &quot;expiration_minutes&quot;: 30})             refresh_token = await create_token({&quot;email&quot;: user.email, &quot;username&quot;: user.username, &quot;user_id&quot;: user.id, &quot;get_refresh_token&quot;: 1, &quot;expiration_minutes&quot;: 10080})              return JSONResponse({&quot;id&quot;: user.id, &quot;username&quot;: user.username, &quot;email&quot;: user.email, &quot;token&quot;: f&quot;{JWT_PREFIX} {token}&quot;, &quot;refresh_token&quot;: f&quot;{JWT_PREFIX} {refresh_token}&quot;,}, status_code=200,)         else:             raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=f&quot;Invalid login or password&quot;)     else:         raise HTTPException(status_code=HTTP_400_BAD_REQUEST, detail=f&quot;Invalid login or password&quot;) <\/code><\/pre>\n<p>  \u041d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u043e \u0437\u0430\u043c\u0435\u0447\u0430\u043d\u0438\u0439 \u043f\u043e \u043a\u043e\u0434\u0443. \u0412\u043e \u043f\u0435\u0440\u0432\u044b\u0445 \u0432\u0441\u0435 \u0432\u0430\u0448\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0434\u043e\u043b\u0436\u043d\u044b \u043d\u0430\u0447\u0438\u043d\u0430\u0442\u044c\u0441\u044f \u0441 \u043a\u043b\u044e\u0447\u0435\u0432\u043e\u0433\u043e \u0441\u043b\u043e\u0432\u0430 async. \u0412\u0442\u043e\u0440\u043e\u0439 \u043c\u043e\u043c\u0435\u043d\u0442 \u0432\u044b\u0437\u043e\u0432 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u0432\u043d\u0443\u0442\u0440\u0438 \u0444\u0443\u043d\u043a\u0446\u0438\u0438 \u043e\u0431\u044f\u0437\u0430\u0442\u0435\u043b\u044c\u043d\u043e \u0434\u043e\u043b\u0436\u0435\u043d \u0441\u043e\u043f\u0440\u043e\u0432\u043e\u0436\u0434\u0430\u0442\u044c\u0441\u044f \u043a\u043b\u044e\u0447\u0435\u0432\u044b\u043c \u0441\u043b\u043e\u0432\u043e\u043c await. \u0412 \u043e\u0441\u0442\u0430\u043b\u044c\u043d\u043e\u043c \u0432\u0441\u0435 \u0442\u043e\u0436\u0435 \u0441\u0430\u043c\u043e\u0435 \u043a\u0430\u043a \u0438 \u0432 \u043f\u0440\u0438\u0432\u044b\u0447\u043d\u043e\u0439 \u043d\u0430\u043c Django.<\/p>\n<h4>\u0421\u0441\u044b\u043b\u043a\u0438<\/h4>\n<p>  \u041f\u043e\u043b\u043d\u044b\u0439 \u043a\u043e\u0434 \u043d\u0430 Github:<\/p>\n<p>  <a href=\"https:\/\/github.com\/Sobolev5\/starlette-vue-backend\">\u0411\u0435\u043a\u0435\u043d\u0434 \u043d\u0430 Starlette<\/a><\/p>\n<p>  <a href=\"https:\/\/github.com\/Sobolev5\/starlette-vue-backend\">\u0424\u0440\u043e\u043d\u0442\u0435\u043d\u0434 \u043d\u0430 Vue.js<\/a><\/p>\n<p>  <a href=\"http:\/\/starlette-vue.site\/\">\u041f\u0440\u0438\u043c\u0435\u0440 \u0440\u0430\u0431\u043e\u0442\u044b<\/a><\/p>\n<p>  \u0421\u043f\u0430\u0441\u0438\u0431\u043e \u0437\u0430 \u0432\u043d\u0438\u043c\u0430\u043d\u0438\u0435 \u0423\u0434\u0430\u0447\u043d\u044b\u0445 \u0438\u043d\u0442\u0435\u0433\u0440\u0430\u0446\u0438\u0439.<\/p><\/div>\n<p> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/post\/502814\/\"> https:\/\/habr.com\/ru\/post\/502814\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"\n<div class=\"post__text post__text-html post__text_v1\" id=\"post-content-body\" data-io-article-url=\"https:\/\/habr.com\/ru\/post\/502814\/\">\n<h3>\u0412\u0441\u0442\u0443\u043f\u043b\u0435\u043d\u0438\u0435<\/h3>\n<p>  <\/p>\n<hr\/>\n<p>  \u0417\u0430\u0434\u0430\u0447\u0430 \u2014 \u0441\u043e\u0437\u0434\u0430\u0442\u044c \u043f\u0440\u0438\u043c\u0435\u0440 \u0430\u0432\u0442\u043e\u0440\u0438\u0437\u0430\u0446\u0438\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0441 \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u0435\u043c \u0444\u0440\u0435\u0439\u043c\u0432\u043e\u0440\u043a\u043e\u0432 Starlette (<a href=\"https:\/\/www.starlette.io\/\">https:\/\/www.starlette.io\/<\/a>) \u0438 Vue.js *, \u043a\u043e\u0442\u043e\u0440\u044b\u0439 \u0431\u044b\u043b \u0431\u044b \u043c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u043e \u043a\u043e\u043c\u0444\u043e\u0440\u0442\u043d\u044b\u043c \u0440\u0430\u0437\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0430\u043c Django \u0434\u043b\u044f \u00ab\u043c\u0438\u0433\u0440\u0430\u0446\u0438\u0438\u00bb \u0432 \u0430\u0441\u0438\u043d\u0445\u0440\u043e\u043d\u043d\u044b\u0439 \u0441\u0442\u0435\u043a. <\/p>\n<p>  \u041f\u043e\u0447\u0435\u043c\u0443 Starlette? \u0412 \u043f\u0435\u0440\u0432\u0443\u044e \u043e\u0447\u0435\u0440\u0435\u0434\u044c \u0441\u043a\u043e\u0440\u043e\u0441\u0442\u044c. Starlette \u0443\u043b\u044c\u0442\u0438\u043c\u0430\u0442\u0438\u0432\u043d\u043e \u0431\u044b\u0441\u0442\u0440, \u0438 \u0432 \u0442\u0435\u0441\u0442\u0430\u0445 \u0443\u0441\u0442\u0443\u043f\u0430\u0435\u0442 \u0442\u043e\u043b\u044c\u043a\u043e BlackSheep (<a href=\"https:\/\/pypi.org\/project\/blacksheep\/\">https:\/\/pypi.org\/project\/blacksheep\/<\/a>). \u0412\u043e \u0432\u0442\u043e\u0440\u044b\u0445 Starlette \u0432\u0435\u0441\u044c\u043c\u0430 \u043f\u0440\u043e\u0441\u0442 \u0438 \u043f\u0438\u0441\u0430\u0442\u044c \u043d\u0430 \u043d\u0435\u043c \u0432 \u0441\u0438\u043b\u0443 \u0435\u0433\u043e \u043f\u0440\u043e\u0434\u0443\u043c\u0430\u043d\u043d\u043e\u0441\u0442\u0438 \u043b\u0435\u0433\u043a\u043e \u0438 \u043f\u0440\u0438\u044f\u0442\u043d\u043e. <\/p>\n<p>  \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 ORM \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c Tortoise ORM (\u0441\u043e \u043c\u043e\u0434\u0435\u043b\u044f\u043c\u0438 \u0438 \u0432\u044b\u0431\u043e\u0440\u043a\u0430\u043c\u0438 \u00ab\u0430\u043b\u044f Django ORM\u00bb). <\/p>\n<p>  \u0412 \u043a\u0430\u0447\u0435\u0441\u0442\u0432\u0435 \u0441\u0435\u0441\u0441\u0438\u043e\u043d\u043d\u043e\u0433\u043e \u043c\u0435\u0445\u0430\u043d\u0438\u0437\u043c\u0430 \u043c\u044b \u0431\u0443\u0434\u0435\u043c \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u044c JWT.<\/p>\n<p>  <i>* \u041e\u043f\u0438\u0441\u0430\u043d\u0438\u0435 \u0444\u0440\u043e\u043d\u0442\u0435\u043d\u0434\u0430 \u043d\u0430 Vue.js \u043d\u0435 \u0432\u0445\u043e\u0434\u0438\u0442 \u0432 \u0434\u0430\u043d\u043d\u0443\u044e \u0437\u0430\u043c\u0435\u0442\u043a\u0443.<\/i>  <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-303884","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/303884","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=303884"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/303884\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=303884"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=303884"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=303884"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}