July 27, 2024

[ad_1]

With Cloud Run — the fully-managed serverless container platform on Google Cloud — you’ll be able to rapidly and simply deploy functions utilizing normal containers. On this article, we’ll clarify the right way to construct a chat server with Cloud Run utilizing Python as the event language. We are going to construct it with the FastAPI framework, based mostly on this FastAPI pattern supply code.

[Note that this article does not provide detailed descriptions of each service. Refer to other articles for details like Cloud Run settings and the cloudbuild.yaml file format.]

Chat server structure

Chat Server Architecture

The chat server consists of two Cloud Run companies: frontend and backend. Code administration is finished on GitHub. Cloud Construct deploys the code, and chat messages are handed between customers with Redis pub/sub and Memorystore.

Set the “Authentication” possibility on the Cloud Run frontend service to “Permit all site visitors” for frontend and backend. The 2 companies talk with a WebSocket, and backend and Memorystore will be related utilizing a serverless VPC entry connector.

Let’s check out every service one after the other.

Frontend

index.html

The frontend service is written solely in HTML. Solely modify the WebSocket connection half with a URL of backend Cloud Run within the center. This code isn’t good as it’s only a pattern to point out the chat in motion.

code_block
[StructValue([(u’code’, u'<!DOCTYPE html>rn<html>rn <head>rn <title>Chat</title>rn </head>rn <body>rn <h1>Chat</h1>rn <h2>Room: <span id=”room-id”></span><br> Your ID: <span id=”client-id”></span></h2>rn <label>Room: <input type=”text” id=”channelId” autocomplete=”off” value=”foo”/></label>rn <button onclick=”connect(event)”>Connect</button>rn <hr>rn <form style=”position: absolute; bottom:0″ action=”” onsubmit=”sendMessage(event)”>rn <input type=”text” id=”messageText” autocomplete=”off”/>rn <button>Send</button>rn </form>rn <ul id=’messages’>rn </ul>rn <script>rn var ws = null;rn function connect(event) rn function sendMessage(event) rn var input = document.getElementById(“messageText”)rn ws.send(input.value)rn input.value = ”rn event.preventDefault()rn document.getElementById(“messageText”).focus()rn rn </script>rn </body>rn</html>’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e70510483d0>)])]

Dockerfile

The Dockerfile may be very easy. As a result of it’s deployed as HTML, nginx:alpine is an effective match.

code_block
[StructValue([(u’code’, u’FROM nginx:alpinernrnCOPY index.html /usr/share/nginx/html’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e7053061a50>)])]

cloudbuild.yaml

The final a part of the frontend service is the cloudbuild.yaml file. You solely have to edit the project_id and “frontend”.

code_block
[StructValue([(u’code’, u”steps:rn # Build the container imagern – name: ‘gcr.io/cloud-builders/docker’rn args: [‘build’, ‘-t’, ‘gcr.io/project_id/frontend:$COMMIT_SHA’, ‘.’]rn # Push the container picture to Container Registryrn – title: ‘gcr.io/cloud-builders/docker’rn args: [‘push’, ‘gcr.io/project_id/frontend:$COMMIT_SHA’]rn # Deploy container picture to Cloud Runrn – title: ‘gcr.io/google.com/cloudsdktool/cloud-sdk’rn entrypoint: gcloudrn args:rn – ‘run’rn – ‘deploy’rn – ‘frontend’rn – ‘–image’rn – ‘gcr.io/project_id/frontend:$COMMIT_SHA’rn – ‘–region’rn – ‘asia-northeast3’rn – ‘–port’rn – ’80’rn pictures:rn – ‘gcr.io/project_id/frontend:$COMMIT_SHA'”), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e70423bbdd0>)])]

Backend Service

predominant.py

Let’s take a look at the server Python code first, beginning with the core ChatServer class.

code_block
[StructValue([(u’code’, u’class RedisService:rn def __init__(self):rn self.redis_host = f””rnrn async def get_conn(self):rn return await aioredis.from_url(self.redis_host, encoding=”utf-8″, decode_responses=True)rnrnrnclass ChatServer(RedisService):rn def __init__(self, websocket, channel_id, client_id):rn super().__init__()rn self.ws: WebSocket = websocketrn self.channel_id = channel_idrn self.client_id = client_idrn self.redis = RedisService()rnrn async def publish_handler(self, conn: Redis):rn try:rn while True:rn message = await self.ws.receive_text()rn if message:rn now = datetime.now()rn date_time = now.strftime(“%Y-%m-%d %H:%M:%S”)rn chat_message = ChatMessage(rn channel_id=self.channel_id, client_id=self.client_id, time=date_time, message=messagern )rn await conn.publish(self.channel_id, json.dumps(asdict(chat_message)))rn except Exception as e:rn logger.error(e)rnrn async def subscribe_handler(self, pubsub: PubSub):rn await pubsub.subscribe(self.channel_id)rn try:rn while True:rn message = await pubsub.get_message(ignore_subscribe_messages=True)rn if message:rn data = json.loads(message.get(“data”))rn chat_message = ChatMessage(**data)rn await self.ws.send_text(f”[chat_message.time] chat_message.message (chat_message.client_id)”)rn besides Exception as e:rn logger.error(e)rnrn async def run(self):rn conn: Redis = await self.redis.get_conn()rn pubsub: PubSub = conn.pubsub()rnrn duties = [self.publish_handler(conn), self.subscribe_handler(pubsub)]rn outcomes = await asyncio.collect(*duties)rnrn logger.information(f”Achieved activity: “)’), (u’language’, u’lang-py’), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e70423bb5d0>)])]

This can be a widespread chat server code. Contained in the ChatServer class, there’s a publish_handler methodology and a subscribe_handler methodology. publish_handler serves to publish a message to the chat room (Redis) when a message is available in by way of the WebSocket. subscribe_handler delivers a message from the chat room (redis) to the related WebSocket. Each are coroutine strategies. Join redis in run methodology and run coroutine methodology.

This brings us to the endpoint. When a request is available in, this code connects to the WebSocket and connects to the chat server.

code_block
[StructValue([(u’code’, u’@app.websocket(“/ws/channel_id/client_id”)rnasync def websocket_endpoint(websocket: WebSocket, channel_id: str, client_id: int):rn await manager.connect(websocket)rnrn chat_server = ChatServer(websocket, channel_id, client_id)rn await chat_server.run()’), (u’language’, u’lang-py’), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e70423bba90>)])]

Right here is the remainder of the code. Mixed, you get the entire code.

code_block
[StructValue([(u’code’, u’import asynciornimport jsonrnimport loggingrnimport osrnfrom dataclasses import dataclass, asdictrnfrom datetime import datetimernfrom typing import Listrnrnimport aioredisrnfrom aioredis.client import Redis, PubSubrnfrom fastapi import FastAPI, WebSocketrnrnlogging.basicConfig(level=logging.INFO)rnlogger = logging.getLogger(__name__)rnrnapp = FastAPI()rnrnrnclass ConnectionManager:rn def __init__(self):rn self.active_connections: List[WebSocket] = []rnrn async def join(self, websocket: WebSocket):rn await websocket.settle for()rn self.active_connections.append(websocket)rnrn def disconnect(self, websocket: WebSocket):rn self.active_connections.take away(websocket)rnrn async def send_personal_message(self, message: str, websocket: WebSocket):rn await websocket.send_text(message)rnrn async def broadcast(self, message: dict):rn for connection in self.active_connections:rn await connection.send_json(message, mode=”textual content”)rnrnrnmanager = ConnectionManager()rnrnrn@dataclassrnclass ChatMessage:rn channel_id: strrn client_id: intrn time: strrn message: str’), (u’language’, u’lang-py’), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e7051bb3510>)])]

Dockerfile

The next is the Dockerfile for the backend service. Run this software with Uvicorn.

code_block
[StructValue([(u’code’, u’FROM python:3.8-slimrnWORKDIR /usr/src/apprnCOPY requirements.txt ./rnRUN pip install -r requirements.txtrnCOPY . .rnCMD [ “uvicorn”, “main:app”, “–host”, “0.0.0.0” ]’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e7051bb3550>)])]

necessities.txt

Put the packages for FastAPI and Redis into necessities.txt.

code_block
[StructValue([(u’code’, u’aioredis==2.0.1rnfastapi==0.85.0rnuvicorn[standard]’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e7051bb3a50>)])]

cloudbuild.yaml

The final step is the cloudbuild.yaml file. Similar to the frontend service, you’ll be able to edit the half composed of project_id and backend, and add the IP of the memorystore created on the again into REDIS_HOST.

code_block
[StructValue([(u’code’, u”steps:rn # Build the container imagern – name: ‘gcr.io/cloud-builders/docker’rn args: [‘build’, ‘-t’, ‘gcr.io/project_id/backend:$COMMIT_SHA’, ‘.’]rn # Push the container picture to Container Registryrn – title: ‘gcr.io/cloud-builders/docker’rn args: [‘push’, ‘gcr.io/project_id/backend:$COMMIT_SHA’]rn # Deploy container picture to Cloud Runrn – title: ‘gcr.io/google.com/cloudsdktool/cloud-sdk’rn entrypoint: gcloudrn args:rn – ‘run’rn – ‘deploy’rn – ‘backend’rn – ‘–image’rn – ‘gcr.io/project_id/backend:$COMMIT_SHA’rn – ‘–region’rn – ‘asia-northeast3’rn – ‘–port’rn – ‘8000’rn – ‘–update-env-vars’rn – ‘REDIS_HOST=redis://10.87.130.75’rn pictures:rn – ‘gcr.io/project_id/backend:$COMMIT_SHA'”), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e70530e2710>)])]

Cloud Construct

You possibly can set Cloud Construct to routinely construct and deploy from Cloud Run when the supply code is pushed to GitHub. Simply choose “Create set off” and enter the required values. First, choose “Push to a department” for Occasion.

Create trigger - Cloud Build

Subsequent, go to the Supply Repository. If that is your first time, you will have GitHub authentication. Our repository additionally has cloudbuild.yaml, so we additionally choose the “Location” setting because the repository.

Edit Trigger - Cloud Build

Serverless VPC entry connector

Since each the Frontend service and the Backend service presently exist within the Web community, you’ll want a serverless VPC entry connector  to hook up with the memorystore within the non-public band. You are able to do this by following this instance code:

code_block
[StructValue([(u’code’, u’bashrngcloud compute networks vpc-access connectors create chat-connector rn–region=us-central1 rn–network=default rn–range=10.100.0.0/28 rn–min-instances=2 rn–max-instances=10 rn–machine-type=e2-micro’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e70530e2750>)])]

Create memorystore

To create the memorystore that can move chat messages, use this code:

code_block
[StructValue([(u’code’, u’bashrngcloud redis instances create myinstance –size=2 –region=us-central1 rn –redis-version=redis_6_X’), (u’language’, u”), (u’caption’, <wagtail.wagtailcore.rich_text.RichText object at 0x3e70530610d0>)])]

chat take a look at

To reveal what it’s best to see, we put two customers right into a dialog in a chat room referred to as “take a look at”. This can work no matter what number of customers you have got, and customers won’t see the conversations in different chat rooms till they be part of.

HashChat room test

Wrap-up

On this article, I constructed a serverless chat server utilizing Cloud Run. Through the use of Firestore as a substitute of Memorystore, it is usually attainable to take the complete structure serverless. Additionally, because the code is written on a container foundation, it’s straightforward to alter to a different atmosphere corresponding to GKE Autopilot, however Cloud Run is already an incredible platform for deploying microservices. Cases develop rapidly and elastically in accordance with the variety of customers connecting, so why would I want to decide on one other platform? Attempt it out now within the Cloud Console.

Associated Article

Hidden gems of Google BigQuery

Learn on to find out about BigQuery options I didn’t find out about till lately. As soon as I found them, I beloved them instantly. I hope yo…

Learn Article

Associated Article

Environment friendly File Administration utilizing Batch Requests with Google Apps Script

Google Drive can deal with small file administration however in terms of bigger batches of recordsdata, with Google Apps Script, even giant batches c…

Learn Article

[ad_2]

Source link