From eb7211c6f9f77a7d9f80d427f7f96c1a8309b358 Mon Sep 17 00:00:00 2001 From: just1valery Date: Fri, 11 Oct 2024 19:19:17 +0400 Subject: [PATCH 1/2] Init. --- vaksman_valeria_lab_3/Test_commands.txt | 24 ++++ vaksman_valeria_lab_3/docker-compose.yaml | 27 ++++ .../document_server/Dockerfile | 17 +++ vaksman_valeria_lab_3/document_server/main.py | 115 +++++++++++++++ vaksman_valeria_lab_3/nginx.conf | 25 ++++ .../product_server/Dockerfile | 17 +++ vaksman_valeria_lab_3/product_server/main.py | 136 ++++++++++++++++++ vaksman_valeria_lab_3/requirements.txt | 17 +++ 8 files changed, 378 insertions(+) create mode 100644 vaksman_valeria_lab_3/Test_commands.txt create mode 100644 vaksman_valeria_lab_3/docker-compose.yaml create mode 100644 vaksman_valeria_lab_3/document_server/Dockerfile create mode 100644 vaksman_valeria_lab_3/document_server/main.py create mode 100644 vaksman_valeria_lab_3/nginx.conf create mode 100644 vaksman_valeria_lab_3/product_server/Dockerfile create mode 100644 vaksman_valeria_lab_3/product_server/main.py create mode 100644 vaksman_valeria_lab_3/requirements.txt diff --git a/vaksman_valeria_lab_3/Test_commands.txt b/vaksman_valeria_lab_3/Test_commands.txt new file mode 100644 index 0000000..b4827e4 --- /dev/null +++ b/vaksman_valeria_lab_3/Test_commands.txt @@ -0,0 +1,24 @@ +http://document_server:8008/documents/all/ - get + +http://document_server:8008/documents/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0 - get + +http://document_server:8008/documents/full/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0 - get + +http://document_server:8008/documents/ - post +title content manager + +http://document_server:8008/ - /documents/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0 title ПРОВЕРКА manager ПОЛЬЗОВАТЕЛЬ МЕНЕДЖЕР + +http://document_server:8008documents/9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0 + +http://product_server:8009/products/all/ - get +http://product_server:8009/products/085a7833-e486-4a55-9289-d253e21fe64e - get +http://product_server:8009/products/full/085a7833-e486-4a55-9289-d253e21fe64e - get +http://product_server:8009/products/list/documents/4a3f30c3-823f-4278-8f98-76177ad083bd - get + +http://product_server:8009/products/ - post +name price uuid_document + +http://product_server:8009/products/085a7833-e486-4a55-9289-d253e21fe64e - put + +http://product_server:8009/products/085a7833-e486-4a55-9289-d253e21fe64e - delete diff --git a/vaksman_valeria_lab_3/docker-compose.yaml b/vaksman_valeria_lab_3/docker-compose.yaml new file mode 100644 index 0000000..a9ee963 --- /dev/null +++ b/vaksman_valeria_lab_3/docker-compose.yaml @@ -0,0 +1,27 @@ +services: + + document_server: + container_name: document_server + build: + context: . + dockerfile: ./document_server/Dockerfile + expose: + - 8008 + + product_server: + container_name: product_server + build: + context: . + dockerfile: ./product_server/Dockerfile + expose: + - 8009 + + nginx: + image: nginx:latest + ports: + - "80:80" + volumes: + - ./nginx.conf:/etc/nginx/nginx.conf + depends_on: + - document_server + - product_server \ No newline at end of file diff --git a/vaksman_valeria_lab_3/document_server/Dockerfile b/vaksman_valeria_lab_3/document_server/Dockerfile new file mode 100644 index 0000000..cabb514 --- /dev/null +++ b/vaksman_valeria_lab_3/document_server/Dockerfile @@ -0,0 +1,17 @@ +# Использую базовый образ Python +FROM python:3.10-slim + +# Устанавливаю рабочую директорию внутри контейнера +WORKDIR /app + +# Копирую файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаю зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копирую исполняемый файл +COPY document_server/main.py . + +# Команда для запуска Python-скрипта +CMD ["python", "main.py"] \ No newline at end of file diff --git a/vaksman_valeria_lab_3/document_server/main.py b/vaksman_valeria_lab_3/document_server/main.py new file mode 100644 index 0000000..aebc391 --- /dev/null +++ b/vaksman_valeria_lab_3/document_server/main.py @@ -0,0 +1,115 @@ +from fastapi import FastAPI, HTTPException +from fastapi.responses import RedirectResponse +from fastapi.responses import JSONResponse +from pydantic import BaseModel, Field +from uuid import UUID, uuid4 +import requests + +app = FastAPI(title="Document service") + +product_url = 'http://product_server:8009' + +# Добавили автогенерацию уникального идентификатора +class Document(BaseModel): + uuid_: UUID = Field(default_factory=uuid4) + title: str | None + content: str | None + manager: str | None + + +# Наше хранилище данных +# Пару id указываем явно, остальные генерируются случайно +DATA_LIST: list[Document] = [ + Document(uuid_="4a3f30c3-823f-4278-8f98-76177ad083bd", title="Document 1", content="Content 1", manager="Manager 1"), + Document(uuid_="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0", title="Document 2", content="Content 2", manager="Manager 2"), + Document(title="Document 3", content="Content 3", manager="Manager 3"), + Document(title="Document 4", content="Content 4", manager="Manager 4"), +] + + +@app.get("/") +async def redirect_to_docs(): + return RedirectResponse(url="/docs") + +@app.get("/documents/all/", tags=["Document"]) +def all_documents(): + return DATA_LIST + +@app.get("/documents/{document_id}", tags=["Document"]) +def get_document(document_id: UUID): + document = next((x for x in DATA_LIST if x.uuid_ == document_id), None) + + if not document: + return HTTPException(status_code=404, detail="Документ не найден") + + return document + +@app.get("/documents/full/{document_id}", tags=["Document"]) +def get_full_document(document_id: UUID): + + document = next((x for x in DATA_LIST if x.uuid_ == document_id), None) + + if not document: + return HTTPException(status_code=404, detail="Продукт не найден") + + result_dict = document.model_dump() + + if type(result_dict['uuid_']) is UUID: + products = requests.get(f"{product_url}/products/list/documents/{document_id}") + + if not products: + return HTTPException(status_code=404, detail="Продукты не найдены") + + result_dict["products"] = products.json() + + return result_dict + +@app.post("/documents/", tags=["Document"]) +def create_document(title: str, content: str, manager: str): + document = Document(title=title, content=content, manager=manager) + + try: + DATA_LIST.append(document) + + return JSONResponse(content={"message": "Успешное создание документа"}, status_code=200) + + except Exception as e: + + return HTTPException(status_code=404, detail={"Неудачная попытка добавления документа": str(e)}) + + +@app.put("/documents/{document_id}", tags=["Document"]) +def update_document(document_id: UUID, title: str = None, content: str = None, manager: str = None): + document = get_document(document_id) + + if type(document) is Document: + index = DATA_LIST.index(document) + + if title: + DATA_LIST[index].title = title + if content: + DATA_LIST[index].content = content + if manager: + DATA_LIST[index].manager = manager + + return JSONResponse(content={"message": "Успешное обновление документа"}, status_code=201) + + else: + return HTTPException(status_code=404, detail=document) + +@app.delete("/documents/{document_id}", tags=["Document"]) +def delete_document(document_id: UUID): + document = get_document(document_id) + + if type(document) is Document: + index = DATA_LIST.index(document) + print(index) + del DATA_LIST[index] + + return JSONResponse(content={"message": "Успешное удаление документа"}, status_code=201) + + return HTTPException(status_code=404, detail=document) + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8008) \ No newline at end of file diff --git a/vaksman_valeria_lab_3/nginx.conf b/vaksman_valeria_lab_3/nginx.conf new file mode 100644 index 0000000..0ce7ddf --- /dev/null +++ b/vaksman_valeria_lab_3/nginx.conf @@ -0,0 +1,25 @@ +events { worker_connections 1024; } + +http { + server { + listen 80; + listen [::]:80; + server_name localhost; + + location /document_server/ { + proxy_pass http://document_server:8008/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + + location /product_server/ { + proxy_pass http://product_server:8009/; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + } + } +} \ No newline at end of file diff --git a/vaksman_valeria_lab_3/product_server/Dockerfile b/vaksman_valeria_lab_3/product_server/Dockerfile new file mode 100644 index 0000000..8427988 --- /dev/null +++ b/vaksman_valeria_lab_3/product_server/Dockerfile @@ -0,0 +1,17 @@ +# Использую базовый образ Python +FROM python:3.10-slim + +# Устанавливаю рабочую директорию внутри контейнера +WORKDIR /app + +# Копирую файл requirements.txt в контейнер +COPY requirements.txt . + +# Устанавливаю зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Копирую исполняемый файл +COPY product_server/main.py . + +# Команда для запуска Python-скрипта +CMD ["python", "main.py"] \ No newline at end of file diff --git a/vaksman_valeria_lab_3/product_server/main.py b/vaksman_valeria_lab_3/product_server/main.py new file mode 100644 index 0000000..bfc309e --- /dev/null +++ b/vaksman_valeria_lab_3/product_server/main.py @@ -0,0 +1,136 @@ +from fastapi import FastAPI, HTTPException +from fastapi.responses import RedirectResponse +from fastapi.responses import JSONResponse +from pydantic import BaseModel, Field +from uuid import UUID, uuid4 +import requests + +app = FastAPI(title="Product service") + +document_url = 'http://document_server:8008' + +# Добавили автогенерацию уникального идентификатора +class Product(BaseModel): + uuid_: UUID = Field(default_factory=uuid4) + name: str | None + price: int | None + uuid_document: UUID | None + + +# Наше хранилище данных +DATA_LIST: list[Product] = [ + Product(uuid_="085a7833-e486-4a55-9289-d253e21fe64e",name="Товар 1", price=100, uuid_document="4a3f30c3-823f-4278-8f98-76177ad083bd"), + Product(name="Товар 2", price=200, uuid_document="4a3f30c3-823f-4278-8f98-76177ad083bd"), + Product(name="Товар 3", price=300, uuid_document="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0"), + Product(name="Товар 4", price=400, uuid_document="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0"), + Product(name="Товар 5", price=500, uuid_document="9c7f1ecb-1aac-4337-bcd0-f863cc73aeb0"), +] + + +@app.get("/") +async def redirect_to_docs(): + return RedirectResponse(url="/docs") + +@app.get("/products/all/", tags=["Product"]) +def all_products(): + return DATA_LIST + +@app.get("/products/{product_id}", tags=["Product"]) +def get_product(product_id: UUID): + product = next((x for x in DATA_LIST if x.uuid_ == product_id), None) + + if not product: + return HTTPException(status_code=404, detail="Продукт не найден") + + return product + +@app.get("/products/full/{product_id}", tags=["Product"]) +def get_full_product(product_id: UUID): + + product = next((x for x in DATA_LIST if x.uuid_ == product_id), None) + + if not product: + return HTTPException(status_code=404, detail="Продукт не найден") + + result_dict = product.model_dump() + + if type(result_dict['uuid_document']) is UUID: + document = requests.get(f"{document_url}/documents/{result_dict['uuid_document']}") + + if not document: + return HTTPException(status_code=404, detail="Документ не найден") + + result_dict["document"] = document.json() + + del result_dict['uuid_document'] + + return result_dict + +@app.get("/products/list/documents/{document_id}", tags=["Product"]) +def get_list_product_document(document_id: UUID): + + products = (x for x in DATA_LIST if x.uuid_document == document_id) + + if not products: + return HTTPException(status_code=404, detail="Продукты не найдены") + + return products + +@app.get("/products/{product_id}", tags=["Product"]) +def get_product(product_id: UUID): + product = next((x for x in DATA_LIST if x.uuid_ == product_id), None) + + if not product: + return HTTPException(status_code=404, detail="Продукт не найден") + + return product + +@app.post("/products/", tags=["Product"]) +def create_product(name: str, price: int, uuid_document: UUID): + product = Product(name=name, price=price, uuid_document=uuid_document) + + try: + DATA_LIST.append(product) + + return JSONResponse(content={"message": "Успешное создание продукта"}, status_code=200) + + except Exception as e: + + return HTTPException(status_code=404, detail={"Неудачная попытка добавления продукта": str(e)}) + + +@app.put("/products/{product_id}", tags=["Product"]) +def update_product(product_id: UUID, name: str = None, price: int = None, uuid_document: UUID = None): + product = get_product(product_id) + + if type(product) is Product: + index = DATA_LIST.index(product) + + if name: + DATA_LIST[index].name = name + if price: + DATA_LIST[index].price = price + if uuid_document: + DATA_LIST[index].uuid_document = uuid_document + + return JSONResponse(content={"message": "Успешное обновление продукта"}, status_code=201) + + else: + return HTTPException(status_code=404, detail=product) + +@app.delete("/products/{product_id}", tags=["Product"]) +def delete_product(product_id: UUID): + product = get_product(product_id) + + if type(product) is Product: + index = DATA_LIST.index(product) + print(index) + del DATA_LIST[index] + + return JSONResponse(content={"message": "Успешное удаление продукта"}, status_code=201) + + return HTTPException(status_code=404, detail=product) + +if __name__ == "__main__": + import uvicorn + uvicorn.run(app, host="0.0.0.0", port=8009) \ No newline at end of file diff --git a/vaksman_valeria_lab_3/requirements.txt b/vaksman_valeria_lab_3/requirements.txt new file mode 100644 index 0000000..54dd3ab --- /dev/null +++ b/vaksman_valeria_lab_3/requirements.txt @@ -0,0 +1,17 @@ +annotated-types==0.7.0 +anyio==4.6.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +click==8.1.7 +exceptiongroup==1.2.2 +fastapi==0.115.0 +h11==0.14.0 +idna==3.10 +pydantic==2.9.2 +pydantic_core==2.23.4 +requests==2.32.3 +sniffio==1.3.1 +starlette==0.38.6 +typing_extensions==4.12.2 +urllib3==2.2.3 +uvicorn==0.31.0 From 53f96303bc74889ed67f212c4aeada902c7dbdbb Mon Sep 17 00:00:00 2001 From: just1valery Date: Fri, 11 Oct 2024 19:32:26 +0400 Subject: [PATCH 2/2] commit for commit --- vaksman_valeria_lab_3/README.md | 64 +++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 vaksman_valeria_lab_3/README.md diff --git a/vaksman_valeria_lab_3/README.md b/vaksman_valeria_lab_3/README.md new file mode 100644 index 0000000..415ba42 --- /dev/null +++ b/vaksman_valeria_lab_3/README.md @@ -0,0 +1,64 @@ +# Лабораторная работа №3 - REST API, Gateway и синхронный обмен между микросервисами + +## Задание + +#### Цель: + +Изучение шаблона проектирования gateway, построения синхронного обмена между микросервисами и архитектурного стиля RESTful API. + +#### Задачи: + +* Создать 2 микросервиса, реализующих CRUD на связанных сущностях. +* Реализовать механизм синхронного обмена сообщениями между микросервисами. +* Реализовать шлюз на основе прозрачного прокси-сервера nginx. + +### Сервисы: + 1. ```product_server``` - сервис, отвечающий за продукты + + 2. ```document_server``` - сервис, отвечающий за документы, которые содержат в себе продукты + +#### Связь между сервисами: + ```document (single) <- product (many)``` + +## Как запустить программу: +``` +docker compose up +``` + +## Файловая иерархия + +``` +Лаба 3/ +|-- document_server/ +| |-- Dockerfile +| |-- document_server.py +|-- product_server/ +| |-- Dockerfile +| |-- product_server.py +|-- nginx.conf +|-- docker-compose.yml +|-- requirements.txt +|-- Test_commands.txt +``` + +## Описание работы: + +Для разработки приложений был выбран язык программирования - ```python``` + +#### Синхронный обмен сообщениями +`product_server` будет отправлять http-запрос на `document_server` при определенных crud операциях. + +### Docker-compose + + +Конфигурационный файл ```docker-сompose``` представляет собой многоконтейнерное приложение с тремя сервисами: ```product_server```, ```document_server``` и ```nginx```. Обязанности маршрутизатора возложены на сервер Nginx + + +### Nginx + + +Этот файл представляет собой конфигурацию для ```Nginx```, который является веб-сервером и обратным прокси. + + +# ВК +https://vk.com/video256017065_456239873 \ No newline at end of file