Skip to content

Commit

Permalink
Merge pull request #33 from MarketSquare/32-make-synchronous-calls-no…
Browse files Browse the repository at this point in the history
…t-block-the-complete-webservice

32 make synchronous calls not block the complete webservice
  • Loading branch information
Noordsestern authored Nov 9, 2023
2 parents 026a343 + 4191403 commit c7d7659
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 109 deletions.
30 changes: 20 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

A web service managing Robot Framework tasks.

**Status: Prototype**

# Goal

This web service shall start Robot Framework tasks and return and cache the according reports.
Expand Down Expand Up @@ -55,13 +53,25 @@ Call robot task with variables:

http://localhost:5003/robotframework/run/mytask?myVariable1=42&anotherVariable=Mustermann

Response contains a header field `x-request-id` that can be used to retrieve logs and reports of this execution asynchronously.
Response contains a header field `x-request-id` that can be used to retrieve logs and reports of this execution asynchronously (see reporting endpoints)

There are endpoints for synchronous and asynchronous request:

```
# connection remains open for duration of my task
http://localhost:5003/robotframework/run/mytask
# connection closes immediately - result must be requested with the x-request-id
http://localhost:5003/robotframework/run/mytask/async
```

**There is no limitation on executed Robot processes! It is easy to push the webservice in DOS with too many requests at once**

## Reporting
Endpoints that provide `log.html` and `report.html` for a specific task execution. You require the `x-request-id` from a previous response that triggered the execution.


## Start web service
# Start web service

The web service starts automatically with uvicorn inside. Simply call:

Expand All @@ -71,17 +81,17 @@ You can check available options with

python -m RobotFrameworkService.main --help

### Example:
## Example:

python -m RobotFrameworkService.main -p 5003 -t path_to_my_taskfolder

### Example - Variablefiles:
## Example - Variablefiles:

You can provide variable files that are passed to all robot suites on execution:

python -m RobotFrameworkService.main -p 5003 -t path_to_my_taskfolder --variablefiles config/env/test.py

## Custom WSGI server
# Custom WSGI server

You can start RobotFrameworkService with bare WSGI servers:

Expand All @@ -91,16 +101,16 @@ Or start web service with other WSGI server, i.e waitress:

waitress-serve --port 5003 RotbotFrameworkService.main:app

## SwaggerUi
# SwaggerUi
Swagger-UI is available under `http://localhost:5003/docs`


## Demo-Tasks
# Demo-Tasks

This project contains some tasks for demonstration. They are located in ``tasks`` folder. You may add
your own task suites in that directory, if you like.

## Task name with spaces in URL
# Task name with spaces in URL

Tasks may contain spaces, URL must not. Luckily, Robot Framework supports CamelCase as well as snake_case syntax.
Meaning: "Another Task" can be trigger in url with parameter `AnotherTask` or ``another_task``
7 changes: 3 additions & 4 deletions RobotFrameworkService/Config.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
class Config:
class Default:
taskfolder = 'tasks'
taskfolder = "tasks"
variablefiles = None
debugfile = None

"""
Service Config as Singleton
"""
Expand All @@ -13,7 +13,6 @@ class Default:

def __new__(cls):
if cls._instance is None:
print('Creatin the object')
cls._instance = super(Config, cls).__new__(cls)
cls._instance.cmd_args = Config.Default()
return cls._instance
Expand All @@ -24,4 +23,4 @@ def cmd_args(self) -> str:

@cmd_args.setter
def cmd_args(self, value: str):
self._cmd_args = value
self._cmd_args = value
75 changes: 54 additions & 21 deletions RobotFrameworkService/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from concurrent.futures import ProcessPoolExecutor
from contextlib import asynccontextmanager
import os
import pathlib
import sys
Expand All @@ -14,21 +16,25 @@
from .constants import APP_NAME, LOGS


@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.executor = ProcessPoolExecutor()
yield
app.state.executor.shutdown()


pathlib.Path(LOGS).mkdir(exist_ok=True)
app = FastAPI(title=APP_NAME, version=get_version())
app = FastAPI(title=APP_NAME, version=get_version(), lifespan=lifespan)
app.include_router(robotframework.router)
app.mount(f"/{LOGS}", StaticFiles(directory=LOGS), name="robotlog")


@app.middleware("http")
async def request_middleware(request: Request, call_next):
request_id = str(uuid.uuid4())

request.headers.__dict__["_list"].append(
(
"request-id".encode(),
request_id.encode()
)
("request-id".encode(), request_id.encode())
)
try:
response = await call_next(request)
Expand All @@ -42,17 +48,19 @@ async def request_middleware(request: Request, call_next):
return response


@app.get('/')
@app.get("/")
async def greetings(request: Request):
return 'web service for starting robot tasks'
return "web service for starting robot tasks"


@app.get('/status/')
@app.get("/status/")
async def server_status():
status = {'python version': sys.version,
'platform': sys.platform,
'arguments': sys.argv,
'application': APP_NAME}
status = {
"python version": sys.version,
"platform": sys.platform,
"arguments": sys.argv,
"application": APP_NAME,
}
return status


Expand All @@ -64,17 +72,42 @@ def get_config():
import argparse

parser = argparse.ArgumentParser()
parser.add_argument("-t", "--taskfolder", default='tasks', help="Folder with tasks service will executed")
parser.add_argument('--version', action='version', version=f'Robot Framework Webservice {get_version()}')
parser.add_argument("-p", "--port", default=os.environ.get('RFS_PORT', default=5003), type=int, help="Port of Robot Framework Webservice")
parser.add_argument("-V", "--variablefiles", nargs='*', default=None, help="List of files containing variables")
parser.add_argument(
"-t",
"--taskfolder",
default="tasks",
help="Folder with tasks service will executed",
)
parser.add_argument(
"--version",
action="version",
version=f"Robot Framework Webservice {get_version()}",
)
parser.add_argument(
"-p",
"--port",
default=os.environ.get("RFS_PORT", default=5003),
type=int,
help="Port of Robot Framework Webservice",
)
parser.add_argument(
"-V",
"--variablefiles",
nargs="*",
default=None,
help="List of files containing variables",
)
parser.add_argument("-b", "--debugfile", default=None, help="Debug output file")
parser.add_argument("--removekeywords", default="tag:secret", help="Remove keyword details from reports")
parser.add_argument(
"--removekeywords",
default="tag:secret",
help="Remove keyword details from reports",
)
args = parser.parse_args()

RFS_Config().cmd_args = args

server = Server(config=(Config(app=app, loop="asyncio", host="0.0.0.0", port=args.port)))
server = Server(
config=(Config(app=app, loop="asyncio", host="0.0.0.0", port=args.port))
)
server.run()


Loading

0 comments on commit c7d7659

Please sign in to comment.