Web - RESTful API

Representational State Transfer Application Programming Inteface

Table of contents

RESTful

Principle of API Design

  1. Supports:

    1. API Design Guidelines (API 设计规范) r1-面试

      1. Treat all data as resources —> Result: the URL alone indicates which resource is being accessed. (将一切数据视作资源 —> 结果:看 URL 知道是什么资源)

        • All URI is a noun.
      2. 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-一文
      3. 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:     // 状态描述
        }
        
    2. Example:

      1. 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
      2. Return different resolution/format of an image per different request (specified by Accept argument in Header)

    ::: aside

    References {{{
    1. 【前端面试】当被面试官问到什么是 RESTFul-API - bilibili - 哲玄前端
    2. 为什么网络设备需要开放API! 什么是RESTful API! - bilibili - 乾颐堂
      Searched by RESTful backend API at bilibili
    3. 什么是 REST API 一文读懂 (RESTful API) - bilibili - Lellansin }}}
    :::
  2. Actions:

    1. Resources centric

      • Each resource has an URI

        每一个资源分得一个 URI(自己分配,私有配置)

    2. Representational descriptor

      • The represent of a resource’s state.

        资源状态的表述 / High-level 的表示(标识符)

    3. 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

  1. Supports:

    1. Project files:

      1
      2
      3
      4
      5
      
      zichen@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
        141
        
        from 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
      • Code: requirements.py }}}
    2. Start docker container

      1
      2
      3
      4
      5
      
      zichen@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)
      
    3. Access http://localhost:8029/docs

Built with Hugo
Theme Stack designed by Jimmy