5 Commits

Author SHA1 Message Date
24d05dc234 Handle connection error in ws handler 2023-05-06 09:38:09 +02:00
7fd6ffca25 Multistage build 2023-05-03 17:57:46 +02:00
2dd246ee76 Add purchase product errors docs 2023-04-23 10:03:22 +02:00
8ecd0f92df Update readme 2023-04-21 15:19:35 +02:00
1dba9d1424 Merge branch 'dockerize' 2023-04-21 15:05:01 +02:00
5 changed files with 109 additions and 20 deletions

View File

@ -1,12 +1,13 @@
FROM python:3.10.11-alpine3.17 FROM python:3.10.11-alpine3.17 as env-builder
# take arguments # handle optional arguments
ARG INTERNAL_API_PORT ARG INTERNAL_API_PORT=8010
ARG INTERNAL_WS_PORT ARG INTERNAL_WS_PORT=8011
RUN \ RUN \
pip install pip -U && \ apk add --no-cache gcc musl-dev libffi-dev && \
pip install poetry --no-cache-dir pip install pip -U --no-cache-dir --prefer-binary && \
pip install poetry --no-cache-dir --prefer-binary
WORKDIR /app WORKDIR /app
@ -29,6 +30,18 @@ RUN \
# install python libs # install python libs
pip install -r requirements.txt --no-cache-dir --prefer-binary pip install -r requirements.txt --no-cache-dir --prefer-binary
FROM python:3.10.11-alpine3.17 as runner
WORKDIR /app
COPY --from=env-builder /venv /venv
# set python thingies and activate virtual environment
ENV \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1 \
PATH="/venv/bin:$PATH"
# copy all relevant files # copy all relevant files
COPY ./.docker/* ./ COPY ./.docker/* ./
COPY ./hopper ./hopper COPY ./hopper ./hopper

View File

@ -38,10 +38,11 @@ docker-clean:
docker-build: docker-build:
@docker \ @docker \
build . \ buildx build \
--build-arg INTERNAL_API_PORT=$(INTERNAL_API_PORT) \ --build-arg INTERNAL_API_PORT=$(INTERNAL_API_PORT) \
--build-arg INTERNAL_WS_PORT=$(INTERNAL_WS_PORT) \ --build-arg INTERNAL_WS_PORT=$(INTERNAL_WS_PORT) \
-t $(CONTAINER_NAME) --tag $(CONTAINER_NAME) \
.
docker-run: docker-run:
@docker \ @docker \

View File

@ -62,10 +62,59 @@ EndPlayer --> UnlockGame: Unlock game\n for all players
## FairHopper Game Server ## FairHopper Game Server
### Start server as docker container
Build image:
```sh
docker build . -t CONTAINER_NAME
```
Create docker container:
```sh
docker \
create \
--publish EXTERNAL_API_PORT:8010 \
--publish EXTERNAL_WS_PORT:8011 \
--name=CONTAINER_NAME \
IMAGE_NAME
```
Parameters:
- `EXTERNAL_API_PORT` - REST API port
- `EXTERNAL_WS_PORT` - Websockets port
- `CONTAINER_NAME` - FairHopper container name
- `IMAGE_NAME` - FairHopper image name
Start docker container:
```sh
docker start CONTAINER_NAME -d
```
Stop docker container:
```sh
docker stop CONTAINER_NAME
```
Example:
```sh
docker build . -t fairhopper-service
docker \
run \
--publish 8010:8010 \
--publish 8011:8011 \
--name=fairhopper-service \
fairhopper \
--detach
docker start fairhopper-service -d
docker stop fairhopper-service
```
### Start server on local machine
Requirements: Requirements:
- Python 3.10+ - Python 3.10+
### Install virtual envirnonment #### Install virtual envirnonment
Project uses [Poetry](https://python-poetry.org), ultimate dependency management software for Python. Project uses [Poetry](https://python-poetry.org), ultimate dependency management software for Python.
@ -79,14 +128,14 @@ Install virtual environment:
poetry install poetry install
``` ```
### Setting up #### Setting up
Copy `settings_template.py` to `settings.py`. Copy `settings_template.py` to `settings.py`.
Edit `settings.py` and customize application. Edit `settings.py` and customize application.
### Starting FairHopper Game Server #### Starting FairHopper Game Server
```sh ```sh
make run make run

View File

@ -17,7 +17,12 @@ from hopper.api.dto import (
) )
from hopper.engine import GameEngine from hopper.engine import GameEngine
from hopper.enums import Direction, PlayerMoveResult from hopper.enums import Direction, PlayerMoveResult
from hopper.errors import Collision, GameLockForMovement, PositionOutOfBounds, PurchaseForbiddenForPlayer from hopper.errors import (
Collision,
GameLockForMovement,
PositionOutOfBounds,
PurchaseForbiddenForPlayer,
)
from hopper.models.player import Player from hopper.models.player import Player
from settings import settings from settings import settings
@ -81,11 +86,11 @@ async def start_game(
responses={ responses={
status.HTTP_403_FORBIDDEN: { status.HTTP_403_FORBIDDEN: {
"model": ErrorResponseDto, "model": ErrorResponseDto,
"description": " Player inactive", "description": "Player inactive",
}, },
status.HTTP_404_NOT_FOUND: { status.HTTP_404_NOT_FOUND: {
"model": ErrorResponseDto, "model": ErrorResponseDto,
"description": " Player with id not found, probably kicked out", "description": "Player with id not found, probably kicked out",
}, },
}, },
) )
@ -106,19 +111,19 @@ async def get_player_info(
}, },
status.HTTP_403_FORBIDDEN: { status.HTTP_403_FORBIDDEN: {
"model": ErrorResponseDto, "model": ErrorResponseDto,
"description": " Player inactive", "description": "Player inactive",
}, },
status.HTTP_404_NOT_FOUND: { status.HTTP_404_NOT_FOUND: {
"model": ErrorResponseDto, "model": ErrorResponseDto,
"description": " Player with id not found, probably kicked out", "description": "Player with id not found, probably kicked out",
}, },
status.HTTP_409_CONFLICT: { status.HTTP_409_CONFLICT: {
"model": ErrorResponseDto, "model": ErrorResponseDto,
"description": " Position out of bounds or collision with an object", "description": "Position out of bounds or collision with an object",
}, },
status.HTTP_423_LOCKED: { status.HTTP_423_LOCKED: {
"model": ErrorResponseDto, "model": ErrorResponseDto,
"description": " Player reached destination. Can't move anymore.", "description": "Player reached destination. Can't move anymore.",
}, },
}, },
) )
@ -167,7 +172,24 @@ async def get_product(id: str) -> ProductDto:
) )
@router.post("/player/{id}/product/purchase", response_model=ProductDto) @router.post(
"/player/{id}/product/purchase",
response_model=ProductDto,
responses={
status.HTTP_200_OK: {
"model": ProductDto,
"description": "Product purchased",
},
status.HTTP_403_FORBIDDEN: {
"model": ErrorResponseDto,
"description": "Purchase forbidden for this player",
},
status.HTTP_404_NOT_FOUND: {
"model": ErrorResponseDto,
"description": " Player with id not found, probably kicked out",
},
},
)
async def purchase_product( async def purchase_product(
body: PurchaseProductDto, body: PurchaseProductDto,
engine: GameEngine = Depends(get_game_engine), engine: GameEngine = Depends(get_game_engine),

View File

@ -5,7 +5,7 @@ from typing import Iterable, Optional
import websockets import websockets
from websockets import WebSocketServerProtocol from websockets import WebSocketServerProtocol
from websockets.exceptions import ConnectionClosedOK from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError
from hopper.models.player import Player from hopper.models.player import Player
from hopper.models.product import Product from hopper.models.product import Product
@ -44,6 +44,10 @@ class WSServer(Thread):
# we're expecting nothing from client, but read if client sends a message # we're expecting nothing from client, but read if client sends a message
await websocket.recv() await websocket.recv()
except ConnectionClosedOK: except ConnectionClosedOK:
logging.info(f"Connection closed OK for client: {websocket.id}")
connected = False
except ConnectionClosedError:
logging.info(f"Connection closed error for client: {websocket.id}")
connected = False connected = False
finally: finally:
self.connected_clients.remove(websocket) self.connected_clients.remove(websocket)