Skip to content

Commit db0c805

Browse files
add a python3 example
Signed-off-by: stoneshi <stoneshi@kubesphere.io>
1 parent cc0cc25 commit db0c805

4 files changed

Lines changed: 191 additions & 0 deletions

File tree

‎Dockerfile‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
FROM python:3-slim
2+
3+
WORKDIR /app
4+
5+
RUN groupadd -r appgroup && useradd -r -g appgroup appuser
6+
7+
COPY server.py ./
8+
9+
RUN chown -R appuser:appgroup /app
10+
USER appuser
11+
12+
EXPOSE 8080
13+
14+
ENV PORT=8080
15+
16+
CMD ["python", "server.py"]

‎Makefile‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
PORT ?= 8080
2+
IMAGE_NAME := python-server:latest
3+
CONTAINER_NAME := python-server
4+
5+
.PHONY: run test build docker-build docker-run stop clean
6+
7+
run:
8+
python3 server.py
9+
10+
test:
11+
@echo "Starting server on port $(PORT)..."
12+
@( \
13+
set -e; \
14+
python3 server.py & SERVER_PID=$$!; \
15+
trap "kill -9 $$SERVER_PID 2>/dev/null || true" EXIT; \
16+
sleep 1; \
17+
echo "Testing / endpoint..."; \
18+
curl -s -o /dev/null -w "%{http_code}" http://localhost:$(PORT)/ | grep -q '^200$$'; \
19+
echo "Testing /health endpoint..."; \
20+
curl -s http://localhost:$(PORT)/health | grep -q '"status":"ok"'; \
21+
echo "Smoke tests passed."; \
22+
)
23+
24+
build:
25+
python3 -m py_compile server.py
26+
@echo "Build check passed."
27+
28+
docker-build:
29+
docker build -t $(IMAGE_NAME) .
30+
31+
docker-run:
32+
docker run -d --rm --name $(CONTAINER_NAME) -e PORT=$(PORT) -p $(PORT):$(PORT) $(IMAGE_NAME)
33+
34+
stop:
35+
-docker stop $(CONTAINER_NAME)
36+
-docker rm $(CONTAINER_NAME) 2>/dev/null || true
37+
38+
clean:
39+
find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true
40+
find . -type f -name '*.pyc' -delete 2>/dev/null || true

‎README.md‎

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Non-Stoppable Python 3 HTTP Server Example
2+
3+
A minimal Python 3 HTTP server that keeps running even when it receives graceful shutdown signals (`SIGTERM` / `SIGINT`). Useful for demonstrating long-running containerized processes.
4+
5+
## Requirements
6+
7+
- Python 3
8+
- `make` (optional, for convenience)
9+
- Docker (optional, for containerized run)
10+
11+
## Quick Start
12+
13+
Run locally:
14+
15+
```bash
16+
make run
17+
```
18+
19+
Or without `make`:
20+
21+
```bash
22+
python3 server.py
23+
```
24+
25+
The server listens on `0.0.0.0:8080` by default. Change the port with the `PORT` environment variable:
26+
27+
```bash
28+
PORT=9000 python3 server.py
29+
```
30+
31+
## Endpoints
32+
33+
| Method | Path | Response |
34+
|--------|-----------|---------------------------------|
35+
| GET | `/` | `Hello, World!` (text/plain) |
36+
| GET | `/health` | `{"status":"ok"}` (application/json) |
37+
| * | * | `404 Not Found` |
38+
39+
## Testing
40+
41+
```bash
42+
make test
43+
```
44+
45+
This starts the server in the background, verifies both endpoints with `curl`, and then forcefully terminates the process.
46+
47+
## Docker
48+
49+
Build and run:
50+
51+
```bash
52+
make docker-build
53+
make docker-run
54+
```
55+
56+
Stop the container:
57+
58+
```bash
59+
make stop
60+
```
61+
62+
## "Non-Stoppable" Behavior
63+
64+
The server registers handlers for `SIGTERM` and `SIGINT`. When either signal arrives, the handler logs the signal and returns, allowing the server to continue serving. The process exits only on `SIGKILL` or an unhandled fatal error.

‎server.py‎

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#!/usr/bin/env python3
2+
"""A non-stoppable Python 3 HTTP server example."""
3+
4+
import json
5+
import logging
6+
import os
7+
import signal
8+
import sys
9+
from http.server import BaseHTTPRequestHandler, HTTPServer
10+
11+
12+
logging.basicConfig(
13+
level=logging.INFO,
14+
format="%(asctime)s - %(levelname)s - %(message)s",
15+
)
16+
17+
DEFAULT_PORT = 8080
18+
19+
20+
class RequestHandler(BaseHTTPRequestHandler):
21+
def _send(self, status: int, body: bytes, content_type: str = "text/plain") -> None:
22+
self.send_response(status)
23+
self.send_header("Content-Type", content_type)
24+
self.send_header("Content-Length", str(len(body)))
25+
self.end_headers()
26+
self.wfile.write(body)
27+
28+
def do_GET(self) -> None: # noqa: N802
29+
if self.path == "/":
30+
self._send(200, b"Hello, World!\n")
31+
elif self.path == "/health":
32+
self._send(200, json.dumps({"status": "ok"}, separators=(",", ":")).encode(), "application/json")
33+
else:
34+
self._send(404, b"Not Found\n")
35+
36+
def log_message(self, format: str, *args) -> None: # noqa: A002
37+
logging.info("%s - %s", self.address_string(), format % args)
38+
39+
40+
def _handle_signal(signum: int, frame) -> None:
41+
signal_name = signal.Signals(signum).name
42+
logging.info("Received %s; ignoring and continuing to serve.", signal_name)
43+
44+
45+
def get_port() -> int:
46+
raw = os.environ.get("PORT", str(DEFAULT_PORT))
47+
try:
48+
return int(raw)
49+
except ValueError:
50+
logging.warning("Invalid PORT value %r; falling back to %d.", raw, DEFAULT_PORT)
51+
return DEFAULT_PORT
52+
53+
54+
def main() -> int:
55+
port = get_port()
56+
server = HTTPServer(("0.0.0.0", port), RequestHandler)
57+
58+
signal.signal(signal.SIGTERM, _handle_signal)
59+
signal.signal(signal.SIGINT, _handle_signal)
60+
61+
logging.info("Starting non-stoppable server on 0.0.0.0:%d", port)
62+
try:
63+
server.serve_forever()
64+
except Exception as exc: # noqa: BLE001
65+
logging.exception("Server failed: %s", exc)
66+
return 1
67+
return 0
68+
69+
70+
if __name__ == "__main__":
71+
sys.exit(main())

0 commit comments

Comments
 (0)