katbox/stream/main.go

339 lines
8.2 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"log"
"math"
"net/http"
"os"
"os/user"
"path/filepath"
"strconv"
"strings"
"syscall"
_ "github.com/paypal/katbox/stream/docs"
httpSwagger "github.com/swaggo/http-swagger"
)
type StreamData struct {
Data string `json:"data"`
Offset int64 `json:"offset"`
}
type FileInformation struct {
Mode string `json:"mode"`
Nlink string `json:"nlink"`
UID string `json:"uid"`
GID string `json:"gid"`
Size string `json:"size"`
Mtime int64 `json:"mtime"`
Path string `json:"path"`
}
type Result struct {
Data []FileInformation `json:"data`
}
type Error struct {
Error string `json:"error"`
}
// Download a file from sandbox filesystem with given offset and length godoc
// @Summary Download a file from Filesystem
// @Description Download any file from sandbox logs filesystem
// @Accept json
// @Produce octet-stream
// @Success 200 {object} main.StreamData
// @Router /files/download [get]
// @Param path query string true "Path"
func downloadhandler(w http.ResponseWriter, r *http.Request) {
//Get query params
params := r.URL.Query()
path := params["path"]
if len(path) == 0 {
log.Println("path not found in URL")
error := &Error{
Error: "path not found in URL"}
retObj, err := json.Marshal(error)
if err != nil {
log.Print(err)
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write(retObj)
return
}
file, err := os.Open(path[0])
if err != nil {
log.Print(err)
}
filename := filepath.Base(path[0])
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%q", filename))
io.Copy(w, file)
file.Close()
}
// read a file from sandbox filesystem with given offset and length godoc
// @Summary Read a file from Filesystem
// @Description Reads any file from sandbox logs filesystem and serves as a json object
// @Accept json
// @Success 200 {object} main.StreamData
// @Router /files/read [get]
// @Param path query string true "Path"
// @Param offset query int true "Offset"
// @Param length query int true "Length"
// @Param jsonp query string true "jsonp"
func readHandler(w http.ResponseWriter, r *http.Request) {
// Get query path params
params := r.URL.Query()
path := params["path"]
//check if path exists in the query params if not send error response
if len(path) == 0 {
log.Println("path not found in URL")
error := &Error{
Error: "path not found in URL"}
retObj, err := json.Marshal(error)
if err != nil {
log.Print(err)
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write(retObj)
return
}
var err error
//check if offset exists in the query params if not send error response
if len(params["offset"]) == 0 {
error := &Error{
Error: "offset not found in URL"}
retObj, err := json.Marshal(error)
if err != nil {
log.Print(err)
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write(retObj)
return
}
offset, err := strconv.ParseInt(params["offset"][0], 10, 64)
if err != nil {
log.Print(err)
}
//check if length exists in the query params if not send error response
if len(params["length"]) == 0 {
error := &Error{
Error: "length not found in URL"}
retObj, err := json.Marshal(error)
if err != nil {
log.Print(err)
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write(retObj)
return
}
length, err := strconv.ParseInt(params["length"][0], 10, 64)
if err != nil {
log.Print(err)
}
size := getSize(path[0])
// if offset is invalid set it to size of the file
if offset == -1 {
offset = size
}
//set length if it is invalid
if length == -1 {
length = size - offset
}
// Cap the read length at 16 pages.
length = int64(math.Min(float64(length), float64(os.Getpagesize()*16)))
// return empty data when offset is greater than size of the file
if offset >= size {
streamData := &StreamData{
Data: "",
Offset: size}
respObj, err := json.Marshal(streamData)
if err != nil {
fmt.Fprintf(w, "json encode error")
return
}
callbackName := r.URL.Query().Get("jsonp")
if callbackName == "" {
fmt.Fprintf(w, "Please give callback name in query string")
return
}
w.Header().Set("Content-Type", "application/javascript")
w.Header().Set("Access-Control-Allow-Origin", "*")
fmt.Fprintf(w, "%s(%s);", callbackName, respObj)
return
}
file, err := os.Open(path[0])
if err != nil {
log.Print(err)
}
defer file.Close()
s := io.NewSectionReader(file, 0, size)
buf2 := make([]byte, length)
_, err = s.ReadAt(buf2, offset)
if err != nil {
log.Print(err)
}
staremData := &StreamData{
Data: string(buf2),
Offset: offset}
respObj, err := json.Marshal(staremData)
if err != nil {
fmt.Fprintf(w, "json encode error")
return
}
callbackName := r.URL.Query().Get("jsonp")
if callbackName == "" {
fmt.Fprintf(w, "Please give callback name in query string")
return
}
w.Header().Set("Content-Type", "application/javascript")
w.Header().Set("Access-Control-Allow-Origin", "*")
fmt.Fprintf(w, "%s(%s);", callbackName, respObj)
}
// browse sandbox filesystem godoc
// @Summary Browse Filesystem
// @Description serves sandbox logs filesystem as a json object
// @Accept json
// @Produce json
// @Success 200 {object} main.Result
// @Router /files/browse [get]
// @Param path query string true "Path"
func browseHandler(w http.ResponseWriter, r *http.Request) {
//Get query Params
params := r.URL.Query()
path := params["path"]
//check if path exists in the query params if not send error response
if len(path) == 0 {
log.Println("path not found in URL")
error := &Error{
Error: "path not found in URL"}
retObj, err := json.Marshal(error)
if err != nil {
log.Print(err)
}
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write(retObj)
return
}
// walk through the directory structure and build fileInformation struct and append to result array
result := []FileInformation{}
//test := strings.Split(path[0], "/")[len(strings.Split(path[0], "/"))-1]
//fmt.Print(test, " ")
//fmt.Print(test, " ")
err := filepath.WalkDir(path[0],
func(filePath string, dirEntry os.DirEntry, err error) error {
if len(strings.Split(filePath, "/")) < len(strings.Split(path[0], "/"))+2 {
if err != nil {
return err
}
fileInfo, err := dirEntry.Info()
fileStat, err := os.Stat(filePath)
if err != nil {
return err
}
// retrieve file ownership information and number of hardlinks to the file
var nlink, uid, gid uint64
if sys := fileStat.Sys(); sys != nil {
if stat, ok := sys.(*syscall.Stat_t); ok {
nlink = uint64(stat.Nlink)
uid = uint64(stat.Uid)
gid = uint64(stat.Gid)
}
}
usr, err := user.LookupId(strconv.FormatUint(uid, 10))
group, err := user.LookupGroupId(strconv.FormatUint(gid, 10))
if err != nil {
return err
}
jsonData := FileInformation{
Mode: fileInfo.Mode().String(),
Nlink: string(strconv.FormatUint(nlink, 10)),
UID: usr.Username,
GID: group.Name,
Size: strconv.Itoa(int(fileInfo.Size())),
Mtime: fileInfo.ModTime().Unix(),
Path: filePath,
}
if err == nil {
result = append(result, jsonData)
}
}
return nil
})
if err != nil {
log.Println(err)
}
resultdata := Result{result}
c, err := json.Marshal(resultdata)
if err == nil {
w.Header().Set("Content-Type", "application/json")
w.Header().Set("Access-Control-Allow-Origin", "*")
w.Write(c)
}
}
//Get the size of a file
func getSize(p string) int64 {
if stat, err := os.Stat(p); err == nil {
return stat.Size()
}
return 0
}
// @title k8s Sandbox Go Restful API with Swagger
// @version 1.0
// @description Rest API doc for sandbox API's
// @contact.name Revanth Chandra
// @host localhost:8080
// @BasePath /
func main() {
http.HandleFunc("/files/read", readHandler)
http.HandleFunc("/files/browse", browseHandler)
http.HandleFunc("/files/download", downloadhandler)
http.HandleFunc("/swagger/", httpSwagger.WrapHandler)
http.ListenAndServe(":5051", nil)
}