func CreateKHIServer()

in pkg/server/server.go [59:428]


func CreateKHIServer(inspectionServer *inspection.InspectionTaskServer, serverConfig *ServerConfig) *gin.Engine {
	engine := instanciateGinServer(parameters.Debug.Verbose != nil && *parameters.Debug.Verbose)
	corsConfig := cors.DefaultConfig()
	corsConfig.AllowAllOrigins = true

	appHtmlPath := path.Join(serverConfig.StaticFolderPath, "/index.html")

	basePathWithoutTrailingSlash := strings.TrimSuffix(serverConfig.ServerBasePath, "/")
	engine.Use(redirectMiddleware(basePathWithoutTrailingSlash+"/", basePathWithoutTrailingSlash+"/session/0")) // Request for `/` shouldn't be handled by `static.Serve`, redirect `/session/0` to be handled by patternToString
	engine.Use(static.Serve(basePathWithoutTrailingSlash+"/", static.LocalFile(serverConfig.StaticFolderPath, false)))
	engine.Use(gin.Recovery())
	engine.Use(cors.New(corsConfig))
	router := engine.Group(basePathWithoutTrailingSlash)

	// frontend uses Angular router. All frontend routing path should return the app html
	router.GET("/session/*wild", func(ctx *gin.Context) {
		ctx.Header("Content-Type", "text/html")
		file, err := os.ReadFile(appHtmlPath)
		if err != nil {
			ctx.String(http.StatusInternalServerError, err.Error())
			return
		}
		originalIndexHTML := string(file)
		replacedIndexHtml, err := replaceDynamicPartOfIndex(originalIndexHTML)
		if err != nil {
			ctx.String(http.StatusInternalServerError, err.Error())
			return
		}
		ctx.Writer.Write([]byte(replacedIndexHtml))
	})
	// GET /api/v3/config
	// Returns configuration map used in frontend.
	router.GET("/api/v3/config", func(ctx *gin.Context) {
		ctx.JSON(http.StatusOK, config.NewGetConfigResponseFromParameters())
	})

	if !serverConfig.ViewerMode {
		// GET /api/v3/inspection/types
		// Returns the list of inspection types available on the inspection server.
		router.GET("/api/v3/inspection/types", func(ctx *gin.Context) {
			ctx.JSON(http.StatusOK, &GetInspectionTypesResponse{
				Types: inspectionServer.GetAllInspectionTypes(),
			})
		})

		// GET /api/v3/inspection
		// Returns the all started inspections on the inspection server.
		router.GET("/api/v3/inspection", func(ctx *gin.Context) {
			inspections := inspectionServer.GetAllRunners()
			responseInspections := map[string]SerializedMetadata{}
			for _, inspection := range inspections {
				if inspection.Started() {
					md, err := inspection.GetCurrentMetadata()
					if err != nil {
						ctx.String(http.StatusInternalServerError, err.Error())
						return
					}

					m, err := metadata.GetSerializableSubsetMapFromMetadataSet(md, filter.NewEnabledFilter(metadata.LabelKeyIncludedInTaskListFlag, false))
					if err != nil {
						ctx.String(http.StatusInternalServerError, err.Error())
						return
					}
					responseInspections[inspection.ID] = m
				}
			}

			ctx.JSON(http.StatusOK, &GetInspectionsResponse{
				Inspections: responseInspections,
				ServerStat: &ServerStat{
					TotalMemoryAvailable: serverConfig.ResourceMonitor.GetUsedMemory(),
				},
			})
		})

		// POST /api/v3/inspection/tasks
		router.POST("/api/v3/inspection/types/:typeID", func(ctx *gin.Context) {
			typeID := ctx.Param("typeID")
			inspectionId, err := inspectionServer.CreateInspection(typeID)
			if err != nil {
				// only the not found error is expected here
				ctx.String(http.StatusNotFound, err.Error())
				return
			}
			ctx.JSON(http.StatusAccepted, &PostInspectionResponse{InspectionID: inspectionId})
		})
		// PUT /api/v3/inspection/<inspection-id>/features
		router.PUT("/api/v3/inspection/:inspectionID/features", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			task := inspectionServer.GetInspection(inspectionID)
			if task == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}
			var reqBody PutInspectionFeatureRequest
			if err := ctx.ShouldBindJSON(&reqBody); err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			err := task.SetFeatureList(reqBody.Features)
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.String(http.StatusAccepted, "ok")
		})
		// PATCH /api/v3/inspection/<inspection-id>/features
		router.PATCH("/api/v3/inspection/:inspectionID/features", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			task := inspectionServer.GetInspection(inspectionID)
			if task == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}
			var reqBody PatchInspectionFeatureRequest
			if err := ctx.ShouldBindJSON(&reqBody); err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			err := task.UpdateFeatureMap(reqBody.Features)
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.String(http.StatusAccepted, "ok")
		})
		// GET /api/v3/inspection/<inspection-id>/features
		router.GET("/api/v3/inspection/:inspectionID/features", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			task := inspectionServer.GetInspection(inspectionID)
			if task == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}
			features, err := task.FeatureList()
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.JSON(http.StatusOK, GetInspectionFeatureResponse{
				Features: features,
			})
		})

		router.POST("/api/v3/inspection/:inspectionID/dryrun", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			currentTask := inspectionServer.GetInspection(inspectionID)
			if currentTask == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}
			var reqBody PostInspectionDryRunRequest
			if err := ctx.ShouldBindJSON(&reqBody); err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			result, err := currentTask.DryRun(ctx, &inspection_task.InspectionRequest{
				Values: reqBody,
			})
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.JSON(http.StatusOK, result)
		})

		router.POST("/api/v3/inspection/:inspectionID/run", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			currentTask := inspectionServer.GetInspection(inspectionID)
			if currentTask == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}
			var reqBody PostInspectionDryRunRequest
			if err := ctx.ShouldBindJSON(&reqBody); err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			err := currentTask.Run(ctx, &inspection_task.InspectionRequest{
				Values: reqBody,
			})
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.String(http.StatusAccepted, "ok")
		})

		router.POST("/api/v3/inspection/:inspectionID/cancel", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			currentTask := inspectionServer.GetInspection(inspectionID)
			if currentTask == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}
			err := currentTask.Cancel()
			if err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			ctx.String(http.StatusOK, "ok")
		})

		router.GET("/api/v3/inspection/:inspectionID/metadata", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			currentTask := inspectionServer.GetInspection(inspectionID)
			if currentTask == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}
			result, err := currentTask.Metadata()
			if err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			ctx.JSON(http.StatusOK, result)
		})

		router.GET("/api/v3/inspection/:inspectionID/data", func(ctx *gin.Context) {
			inspectionID := ctx.Param("inspectionID")
			currentTask := inspectionServer.GetInspection(inspectionID)
			if currentTask == nil {
				ctx.String(http.StatusNotFound, fmt.Sprintf("inspecton %s was not found", inspectionID))
				return
			}

			// parse range queries
			var rangeStart int64
			var maxSize int64 = math.MaxInt64
			startQueryStr := ctx.Query("start")
			maxSizeQueryStr := ctx.Query("maxSize")
			if startQueryStr != "" {
				var err error
				rangeStart, err = strconv.ParseInt(startQueryStr, 10, 64)
				if err != nil {
					ctx.String(http.StatusBadRequest, err.Error())
					return
				}
			}
			if maxSizeQueryStr != "" {
				var err error
				maxSize, err = strconv.ParseInt(maxSizeQueryStr, 10, 64)
				if err != nil {
					ctx.String(http.StatusBadRequest, err.Error())
					return
				}
			}

			result, err := currentTask.Result()
			if err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			inspectionDataReader, err := result.ResultStore.GetRangeReader(rangeStart, maxSize)
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			defer inspectionDataReader.Close()
			fileSize, err := result.ResultStore.GetInspectionResultSizeInBytes()
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.DataFromReader(http.StatusOK, int64(math.Min(float64(maxSize), float64(fileSize-int(rangeStart)))), "application/octet-stream", inspectionDataReader, map[string]string{})
		})

		router.GET("/api/v3/popup", func(ctx *gin.Context) {
			currentPopup := popup.Instance.GetCurrentPopup()
			if currentPopup == nil {
				ctx.String(http.StatusOK, "")
				return
			}
			ctx.JSON(http.StatusOK, currentPopup)
		})

		router.POST("/api/v3/popup/validate", func(ctx *gin.Context) {
			request := &popup.PopupAnswerResponse{}
			if err := ctx.ShouldBindJSON(request); err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			result, err := popup.Instance.Validate(request)
			if errors.Is(err, popup.NoCurrentPopup) {
				ctx.String(http.StatusNotFound, err.Error())
				return
			}
			if errors.Is(err, popup.CurrentPopupIsntMatchingWithGivenId) {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.JSON(http.StatusOK, result)
		})

		router.POST("/api/v3/popup/answer", func(ctx *gin.Context) {
			request := &popup.PopupAnswerResponse{}
			if err := ctx.ShouldBindJSON(request); err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			err := popup.Instance.Answer(request)
			if errors.Is(err, popup.NoCurrentPopup) {
				ctx.String(http.StatusNotFound, err.Error())
				return
			}
			if errors.Is(err, popup.CurrentPopupIsntMatchingWithGivenId) {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			ctx.String(http.StatusOK, "")
		})

		router.POST("/api/v3/upload", func(ctx *gin.Context) {
			localUploadFileStoreProvider, convertible := serverConfig.UploadFileStore.StoreProvider.(*upload.LocalUploadFileStoreProvider)
			if !convertible {
				ctx.String(http.StatusBadRequest, "invalid operation. Current UploadFileStore.StoreProvider is not supporting to be written directly")
				return
			}
			file, err := ctx.FormFile("file")
			if err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}

			id := ctx.Request.FormValue("upload-token-id")
			if id == "" {
				ctx.String(http.StatusBadRequest, "missing upload-token-id")
				return
			}

			token := &upload.DirectUploadToken{ID: id}
			if parameters.Server.MaxUploadFileSizeInBytes != nil && *parameters.Server.MaxUploadFileSizeInBytes < int(file.Size) {
				ctx.String(http.StatusBadRequest, fmt.Sprintf("file size exceeds the limit (%d bytes)", *parameters.Server.MaxUploadFileSizeInBytes))
				return
			}

			err = serverConfig.UploadFileStore.SetResultOnStartingUpload(token)
			if err != nil {
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}

			multipart, err := file.Open()
			if err != nil {
				ctx.String(http.StatusBadRequest, err.Error())
				return
			}
			defer multipart.Close()

			err = localUploadFileStoreProvider.Write(token, multipart)
			if err != nil {
				serverConfig.UploadFileStore.SetResultOnCompletedUpload(token, err)
				ctx.String(http.StatusInternalServerError, err.Error())
				return
			}
			serverConfig.UploadFileStore.SetResultOnCompletedUpload(token, nil)

			ctx.String(http.StatusOK, "")
		})
	}
	return engine
}