Skip to content

Instantly share code, notes, and snippets.

@duruyao
Last active May 13, 2024 08:33
Show Gist options
  • Save duruyao/7623fd950e448ef5f4848cf79a6046ef to your computer and use it in GitHub Desktop.
Save duruyao/7623fd950e448ef5f4848cf79a6046ef to your computer and use it in GitHub Desktop.
Sample documentation for the web application front-end API.

Front end APIs

1. Introduction

  • Protocol: HTTP
  • Interface: RESTful API
  • Base URL: http://10.0.13.134:3824

2. APIs

2.1. /v1/similarity/batch

The API {{base_url}}/v1/similarity/batch submits a form containing multiple tasks, uploads the necessary files at the same time, and then waits for the results of multiple task executions.

2.1.1. Request

Method: POST

Body: form(data)

KEY TYPE REQUIRED DESCRIPTION
file_1 file NO NPU model, image or dumped data package (zip)
file_2 file NO NPU model, image or dumped data package (zip)
file_3 file NO NPU model, image or dumped data package (zip)
... file NO NPU model, image or dumped data package (zip)
json_data text YES The text should conform to the JSON specification, and the field points to the KEY of the above file

The following are the details of the field json_data:

KEY TYPE REQUIRED DESCRIPTION
items object array YES array of JSON objects
id string YES uuid of task
project string YES vc0728 or vc0768
npu_model string YES KEY of NPU model file
input_data string YES KEY of image
dumped_data string YES KEY of dumped data compressed package

The following is an examples of the field json_data:

{
  "items": [
    {
      "id": "7892f81f-891a-49ec-871a-58ffdee52442",
      "project": "vc0728",
      "npu_model": "file_1",
      "input_data": "file_2",
      "dumped_data": "file_3"
    }
  ]
}

2.1.2. cURL client

curl --request POST --location 'http://10.0.13.134:3824/v1/similarity/batch' \
  --form 'input_1=@/home/duruyao/test/input.jpeg' \
  --form 'model_1=@/home/duruyao/test/AlexNet-8.npumodel' \
  --form 'model_2=@/home/duruyao/test/AlexNet-16.npumodel' \
  --form 'package_1=@/home/duruyao/test/AlexNet-8-dump.zip' \
  --form 'package_2=@/home/duruyao/test/AlexNet-16-dump.zip' \
  --form 'json_data={
    "items": [
        {
            "id": "7892f81f-891a-49ec-871a-58ffdee52442",
            "project": "vc0728",
            "npu_model": "model_1",
            "input_data": "input_1",
            "dumped_data": "package_1"
        },
        {
            "id": "571f4201-3844-44fe-8e94-a2ea9602ea78",
            "project": "vc0728",
            "npu_model": "model_2",
            "input_data": "input_1",
            "dumped_data": "package_2"
        }
    ]
}' --verbose | json_pp

2.1.3. Response

The data responded by the web service conforms to the JSON specification and has the following format:

KEY TYPE REQUIRED DESCRIPTION
error string NO error message
items object array YES array of JSON objects
id string YES uuid of task
results number array YES array of similarities

The following is an example response when a task fails:

{
  "error": "json_data field is required but not found"
}

The following is an example response when the task is successful:

{
  "items": [
    {
      "id": "7892f81f-891a-49ec-871a-58ffdee52442",
      "results": [
        0.114285095335321,
        0.313045494414164
      ]
    },
    {
      "id": "571f4201-3844-44fe-8e94-a2ea9602ea78",
      "results": [
        0.0723396900147865,
        0.282645558215649
      ]
    }
  ]
}

2.1.4. Python server

import os
import json
import shutil
import random
import datetime
from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route('/v1/similarity/batch', methods=['POST'])
def calculate_similarity():
    if 'json_data' not in request.form:
        return jsonify({'error': 'json_data field is required but not found'}), 400
    try:
        req_data = json.loads(request.form.get('json_data'))
    except json.JSONDecodeError:
        return jsonify({'error': 'invalid JSON data in json_data field'}), 400
    if 'items' not in req_data:
        return jsonify({'items': None}), 200

    resp_items = []
    saved_files = {}
    work_dir = os.getcwd()
    upload_date = datetime.datetime.now().isoformat()
    for item in req_data['items']:
        for key in ['npu_model', 'input_data', 'dumped_data']:
            if key not in item:
                return jsonify({'error': f'missing {key} in item'}), 400
        for key in ['npu_model', 'input_data', 'dumped_data']:
            file = request.files.get(item[key])
            if file:
                save_path = os.path.join(work_dir, 'upload', upload_date, item['id'], file.filename)
                os.makedirs(os.path.dirname(save_path), exist_ok=True)
                if file.filename not in saved_files:
                    saved_files[file.filename] = save_path
                    file.save(save_path)
                else:
                    shutil.copy(saved_files[file.filename], save_path)
            else:
                return jsonify({'error': f'missing file for {key}'}), 400
        # TODO: calculate similarity
        #
        resp_items.append({'id': item['id'], 'results': [random.random(), random.random()]})
        #
        #

    return jsonify({'items': resp_items}), 200


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3824)

2.1.5. Golang server

package main

import (
	"encoding/json"
	"github.com/gin-gonic/gin"
	"log"
	"math/rand"
	"net/http"
	"os"
	"time"
)

func main() {
	router := gin.Default()
	router.POST("/v1/similarity/batch", func(c *gin.Context) {
		reqData := struct {
			Items []struct {
				Id         string `json:"id"   binding:"required"`
				Project    string ` json:"project"  binding:"required"`
				NpuModel   string ` json:"npu_model"  binding:"required"`
				InputData  string ` json:"input_data"  binding:"required"`
				DumpedData string ` json:"dumped_data"  binding:"required"`
			} `json:"items" binding:"required"`
		}{}
		jsonData, ok := c.GetPostForm("json_data")
		if !ok {
			c.JSON(http.StatusBadRequest, gin.H{"error": "json_data field is required but not found"})
			return
		}
		if err := json.Unmarshal([]byte(jsonData), &reqData); err != nil {
			c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
			return
		}

		type RespItem struct {
			Id      string    `json:"id"   binding:"required"`
			Results []float64 ` json:"results"  binding:"required"`
		}
		respBody := struct {
			Items []RespItem `json:"items" binding:"required"`
		}{}
		workDir, _ := os.Getwd()
		uploadDate := time.Now().Format(time.RFC3339)
		for _, it := range reqData.Items {
			for _, key := range []string{it.NpuModel, it.InputData, it.DumpedData} {
				file, err := c.FormFile(key)
				if err != nil {
					c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
					return
				}
				if err := c.SaveUploadedFile(file,
					workDir+"/upload/"+uploadDate+"/"+it.Id+"/"+file.Filename); err != nil {
					c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
					return
				}
			}
			// TODO: calculate similarity
			//
			respBody.Items = append(respBody.Items,
				RespItem{Id: it.Id, Results: []float64{rand.Float64(), rand.Float64()}})
			//
			//
		}
		c.JSON(http.StatusOK, respBody)
	})
	log.Fatalln(router.Run("0.0.0.0:3824"))
}

2.2. /v1/update/:project

The API {{base_url}}/v1/update/:project submits multiple files in order to update them on the board.

2.2.1. Request

Method: POST

Body: form(data)

KEY TYPE REQUIRED DESCRIPTION
upload file array YES shared libraries or kernel libraries

2.2.2. cURL client

curl --request POST --location 'http://10.0.13.134:3824/v1/update/vc0728' \
  --form 'upload[]=@/home/duruyao/test/02.DebugTools/arm/libnpu.so' \
  --form 'upload[]=@/home/duruyao/test/02.DebugTools/ko/base.ko' \
  --form 'upload[]=@/home/duruyao/test/02.DebugTools/ko/nmz.ko' \
  --form 'upload[]=@/home/duruyao/test/02.DebugTools/ko/npu.ko' --verbose | json_pp

2.2.3. Response

The data responded by the web service conforms to the JSON specification and has the following format:

KEY TYPE REQUIRED DESCRIPTION
error string NO error message
result string NO some information

The following is an example response when a task fails:

{
  "error": "Unknown project: vc0778"
}

The following is an example response when the task is successful:

{
  "result": "4 files uploaded"
}

2.2.4. Python server

import datetime
import os

from flask import Flask, request, jsonify

app = Flask(__name__)


@app.route('/v1/update/<project>', methods=['POST'])
def update(project):
    if project not in ['vc0728', 'vc0768']:
        return jsonify({"error": f"Unknown project: {project}"}), 500

    files = request.files.getlist('upload[]')
    work_dir = os.getcwd()
    upload_date = datetime.datetime.now().isoformat()
    for file in files:
        save_path = os.path.join(work_dir, "upload", upload_date, project, file.filename)
        os.makedirs(os.path.dirname(save_path), exist_ok=True)
        file.save(save_path)

    # TODO: update *.so and *.ko files
    #
    entries = os.listdir(os.path.join(work_dir, "upload", upload_date, project))
    for entry in entries:
        print(os.path.join(work_dir, "upload", upload_date, project, entry))
    #
    #

    return jsonify({"result": f"{len(files)} files uploaded"}), 200


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=3824)

2.2.5. Golang server

package main

import (
	"encoding/json"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"math/rand"
	"net/http"
	"os"
	"time"
)

func main() {
	router := gin.Default()
	router.POST("/v1/update/:project", func(c *gin.Context) {
		project := c.Param("project")
		if project != "vc0728" && project != "vc0768" {
			c.JSON(http.StatusInternalServerError, gin.H{"error": "Unknown project: " + project})
			return
		}
		form, _ := c.MultipartForm()
		files := form.File["upload[]"]
		workDir, _ := os.Getwd()
		uploadDate := time.Now().Format(time.RFC3339)
		for _, file := range files {
			if err := c.SaveUploadedFile(file,
				workDir+"/upload/"+uploadDate+"/"+project+"/"+file.Filename); err != nil {
				c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
				return
			}
		}
		// TODO: update *.so and *.ko files
		//
		entries, _ := os.ReadDir(workDir + "/upload/" + uploadDate + "/" + project)
		for _, entry := range entries {
			fmt.Println(workDir + "/upload/" + uploadDate + "/" + project + "/" + entry.Name())
		}
		//
		//
		c.JSON(http.StatusOK, gin.H{"result": fmt.Sprintf("%d files uploaded", len(files))})
	})
	log.Fatalln(router.Run("0.0.0.0:3824"))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment