Table of contents
RESTful
Principle of API Design
-
Supports:
-
API Design Guidelines (API 设计规范) r1-面试:
-
Treat all data as resources —> Result: the URL alone indicates which resource is being accessed. (将一切数据视作资源 —> 结果:看 URL 知道是什么资源)
- All URI is a noun.
-
Use HTTP request methods to describe operations on resources (create/read/update/delete) —> Result: the HTTP method indicates which operation will be performed on the resource. 利用 HTTP 请求方式,描述对资源的操作(增/删/改/查) —> 结果:看 Method 知道要对资源进行什么操作
1 2 3 4 5# RESTful API GET /api/users/:user_id POST /api/users PUT /api/users DELETE /api/users/:user_id- Leverage HTTP methods to avoid the type of URL: “behaviour+resource”, which causes too much URL r3-一文
-
Use HTTP response status codes to describe the outcome of operations on resources (e.g. 200, 5xx) —> Result: the response code indicates whether the operation was successful. 通过 HTTP 响应状态码,描述对资源的操作结果(如: 200/5xx) —> 结果:看 Response Code 知道操作是否成功
1 2 3 4 5{ code: // 描述状态 data: // 返回数据 message: // 状态描述 }
-
-
Example:
-
Resources of a network device: interfaces, routing table, CPU utilization. (网络设备的资源:接口,路由表,CPU利用率) r1-网络
- Read a interface:
GET https://10.1.1.1/restconf/data/Cisco-IOS-XE-native:native/interface
- Read a interface:
-
Return different resolution/format of an image per different request (specified by
Acceptargument in Header)
-
::: aside
:::References
{{{- 【前端面试】当被面试官问到什么是 RESTFul-API - bilibili - 哲玄前端
- 为什么网络设备需要开放API! 什么是RESTful API! - bilibili - 乾颐堂
Searched byRESTful backend APIat bilibili - 什么是 REST API 一文读懂 (RESTful API) - bilibili - Lellansin }}}
-
-
Actions:
-
Resources centric
-
Each resource has an URI
每一个资源分得一个 URI(自己分配,私有配置)
-
-
Representational descriptor
-
The represent of a resource’s state.
资源状态的表述 / High-level 的表示(标识符)
-
-
Process representational state instead of resource itself
-
The transfer of ‘representational state’ attributes to the manipulate of resources.
对资源的操作/处理(5 种方法)—-> 结果: 状态的转化
-
Five http methods: GET (read), POST (create), PUT (update all, most recommd), PATCH (partial update), DELETE
-
传递资源的表现形式,而不是对资源本身做操作。
-
-
FastAPI Implementation
Demo App
-
Supports:
-
Project files:
1 2 3 4 5zichen@zichen-X570-AORUS-PRO-WIFI:~/Projects/FastAPI-Laser_Profiler-Docker$ tree . ├── Dockerfile ├── main.py └── requirements.txt-
Code: Dockerfile
{{{1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18# 1) Use a small Python base image FROM python:3.11-slim # 2) Set work directory inside the container WORKDIR /app # 3) Install dependencies first (better layer caching) COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # 4) Copy your source code COPY . . # 5) Expose the FastAPI port EXPOSE 8000 # 6) Run the app with uvicorn CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]}}}
-
Code: main.py
{{{1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from enum import Enum from typing import Dict, Optional from datetime import datetime, timedelta import uuid app = FastAPI(title="Laser Profiler Simulator API", version="1.0") class LaserState(str, Enum): idle = "IDLE" scanning = "SCANNING" error = "ERROR" class ScanConfig(BaseModel): laser_power: float = Field(1.0, ge=0.1, le=5.0) exposure_ms: int = Field(10, ge=1, le=1000) resolution_mm: float = Field(0.1, ge=0.01, le=5.0) class JobStatus(str, Enum): queued = "QUEUED" running = "RUNNING" completed = "COMPLETED" failed = "FAILED" class JobCreateRequest(BaseModel): config: ScanConfig comment: Optional[str] = None class JobSummary(BaseModel): job_id: str status: JobStatus created_at: datetime finished_at: Optional[datetime] message: str class PointCloudStats(BaseModel): job_id: str num_points: int bbox_min: tuple bbox_max: tuple avg_height: float # --- In-memory "backend state" (simulating a device + job DB) --- laser_state: LaserState = LaserState.idle current_config = ScanConfig() jobs: Dict[str, JobSummary] = {} job_stats: Dict[str, PointCloudStats] = {} job_logs: Dict[str, str] = {} # --- Laser control endpoints --- @app.get("/api/v1/laser/state", response_model=dict) def get_laser_state(): return { "state": laser_state, "config": current_config } @app.put("/api/v1/laser/config", response_model=ScanConfig) def update_config(config: ScanConfig): global current_config current_config = config return current_config # --- Job control & 3D data endpoints --- @app.post("/api/v1/jobs", response_model=JobSummary, status_code=201) def create_job(request: JobCreateRequest): global laser_state if laser_state == LaserState.error: raise HTTPException(status_code=503, detail="Laser in error state") job_id = str(uuid.uuid4()) created = datetime.utcnow() laser_state = LaserState.scanning # Simulated processing – in a real system this would be asynchronous finished = created + timedelta(seconds=2) status = JobStatus.completed summary = JobSummary( job_id=job_id, status=status, created_at=created, finished_at=finished, message=request.comment or "Scan completed successfully" ) jobs[job_id] = summary # Fake point cloud statistics stats = PointCloudStats( job_id=job_id, num_points=123456, bbox_min=(0.0, 0.0, 0.0), bbox_max=(100.0, 50.0, 10.0), avg_height=4.2 ) job_stats[job_id] = stats # Fake diagnostic logs job_logs[job_id] = ( f"[{created.isoformat()}] Job created with config={request.config}\n" f"[{finished.isoformat()}] Scan finished OK, points={stats.num_points}" ) laser_state = LaserState.idle return summary @app.get("/api/v1/jobs/{job_id}", response_model=JobSummary) def get_job(job_id: str): if job_id not in jobs: raise HTTPException(status_code=404, detail="Job not found") return jobs[job_id] @app.get("/api/v1/jobs/{job_id}/stats", response_model=PointCloudStats) def get_job_stats(job_id: str): if job_id not in job_stats: raise HTTPException(status_code=404, detail="Stats not found") return job_stats[job_id] @app.get("/api/v1/jobs/{job_id}/logs", response_model=dict) def get_job_logs(job_id: str): if job_id not in job_logs: raise HTTPException(status_code=404, detail="Logs not found") return {"job_id": job_id, "log": job_logs[job_id]}}}}
- Run the app with uvicorn:
uvicorn main:app --host 0.0.0.0 --port 8029
- Run the app with uvicorn:
-
Code: requirements.py
}}}
-
-
Start docker container
1 2 3 4 5zichen@zichen-X570-AORUS-PRO-WIFI:~/Projects/FastAPI-Laser_Profiler-Docker$ docker run -p 8029:8000 laser-profiler-api INFO: Started server process [1] INFO: Waiting for application startup. INFO: Application startup complete. INFO: Uvicorn running on http://0.0.0.0:8000 (Press CTRL+C to quit) -
Access
http://localhost:8029/docs
-