From f7a4b71f09bbccadb5fa21710006db7b1b571abe Mon Sep 17 00:00:00 2001 From: Akilan Date: Wed, 1 Feb 2023 00:45:44 +0000 Subject: [PATCH] route /expore/node added --- webapi/API.go | 277 ++++++++++++++++++++-------------------- webapi/Search.go | 42 +++++- webapi/Shared Recent.go | 11 +- 3 files changed, 184 insertions(+), 146 deletions(-) diff --git a/webapi/API.go b/webapi/API.go index dec0d8d..fd70180 100644 --- a/webapi/API.go +++ b/webapi/API.go @@ -7,191 +7,192 @@ Author: Peter Kleissner package webapi import ( - "crypto/tls" - "encoding/json" - "errors" - "net/http" - "sync" - "time" + "crypto/tls" + "encoding/json" + "errors" + "net/http" + "sync" + "time" - "github.com/IncSW/geoip2" - "github.com/PeernetOfficial/core" - "github.com/google/uuid" - "github.com/gorilla/mux" - "github.com/gorilla/websocket" + "github.com/IncSW/geoip2" + "github.com/PeernetOfficial/core" + "github.com/google/uuid" + "github.com/gorilla/mux" + "github.com/gorilla/websocket" ) type WebapiInstance struct { - backend *core.Backend - geoipCityReader *geoip2.CityReader + backend *core.Backend + geoipCityReader *geoip2.CityReader - // Router can be used to register additional API functions - Router *mux.Router - AllowKeyInParam []string // List of paths that accept the API key as &k= parameter + // Router can be used to register additional API functions + Router *mux.Router + AllowKeyInParam []string // List of paths that accept the API key as &k= parameter - // search jobs - allJobs map[uuid.UUID]*SearchJob - allJobsMutex sync.RWMutex + // search jobs + allJobs map[uuid.UUID]*SearchJob + allJobsMutex sync.RWMutex - // download info - downloads map[uuid.UUID]*downloadInfo - downloadsMutex sync.RWMutex + // download info + downloads map[uuid.UUID]*downloadInfo + downloadsMutex sync.RWMutex } // WSUpgrader is used for websocket functionality. It allows all requests. var WSUpgrader = websocket.Upgrader{ - ReadBufferSize: 1024, - WriteBufferSize: 1024, - CheckOrigin: func(r *http.Request) bool { - // allow all connections by default - return true - }, + ReadBufferSize: 1024, + WriteBufferSize: 1024, + CheckOrigin: func(r *http.Request) bool { + // allow all connections by default + return true + }, } // Start starts the API. ListenAddresses is a list of IP:Ports. // The certificate file and key are only used if SSL is enabled. The read and write timeout may be 0 for no timeout. // The API key may be uuid.Nil to disable it although this is not recommended for security reasons. func Start(Backend *core.Backend, ListenAddresses []string, UseSSL bool, CertificateFile, CertificateKey string, TimeoutRead, TimeoutWrite time.Duration, APIKey uuid.UUID) (api *WebapiInstance) { - if len(ListenAddresses) == 0 { - return nil - } + if len(ListenAddresses) == 0 { + return nil + } - api = &WebapiInstance{ - backend: Backend, - Router: mux.NewRouter(), - AllowKeyInParam: []string{"/file/read", "/file/view"}, - allJobs: make(map[uuid.UUID]*SearchJob), - downloads: make(map[uuid.UUID]*downloadInfo), - } + api = &WebapiInstance{ + backend: Backend, + Router: mux.NewRouter(), + AllowKeyInParam: []string{"/file/read", "/file/view"}, + allJobs: make(map[uuid.UUID]*SearchJob), + downloads: make(map[uuid.UUID]*downloadInfo), + } - if APIKey != uuid.Nil { - api.Router.Use(api.authenticateMiddleware(APIKey)) - } + if APIKey != uuid.Nil { + api.Router.Use(api.authenticateMiddleware(APIKey)) + } - api.Router.HandleFunc("/test", apiTest).Methods("GET") - api.Router.HandleFunc("/status", api.apiStatus).Methods("GET") - api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET") - api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET") - api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET") - api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET") - api.Router.HandleFunc("/blockchain/append", api.apiBlockchainAppend).Methods("POST") - api.Router.HandleFunc("/blockchain/read", api.apiBlockchainRead).Methods("GET") - api.Router.HandleFunc("/blockchain/file/add", api.apiBlockchainFileAdd).Methods("POST") - api.Router.HandleFunc("/blockchain/file/list", api.apiBlockchainFileList).Methods("GET") - api.Router.HandleFunc("/blockchain/file/delete", api.apiBlockchainFileDelete).Methods("POST") - api.Router.HandleFunc("/blockchain/file/update", api.apiBlockchainFileUpdate).Methods("POST") - api.Router.HandleFunc("/profile/list", api.apiProfileList).Methods("GET") - api.Router.HandleFunc("/profile/read", api.apiProfileRead).Methods("GET") - api.Router.HandleFunc("/profile/write", api.apiProfileWrite).Methods("POST") - api.Router.HandleFunc("/profile/delete", api.apiProfileDelete).Methods("POST") - api.Router.HandleFunc("/search", api.apiSearch).Methods("POST") - api.Router.HandleFunc("/search/result", api.apiSearchResult).Methods("GET") - api.Router.HandleFunc("/search/result/ws", api.apiSearchResultStream).Methods("GET") - api.Router.HandleFunc("/search/statistic", api.apiSearchStatistic).Methods("GET") - api.Router.HandleFunc("/search/terminate", api.apiSearchTerminate).Methods("GET") - api.Router.HandleFunc("/explore", api.apiExplore).Methods("GET") - api.Router.HandleFunc("/file/format", api.apiFileFormat).Methods("GET") - api.Router.HandleFunc("/download/start", api.apiDownloadStart).Methods("GET") - api.Router.HandleFunc("/download/status", api.apiDownloadStatus).Methods("GET") - api.Router.HandleFunc("/download/action", api.apiDownloadAction).Methods("GET") - api.Router.HandleFunc("/warehouse/create", api.apiWarehouseCreateFile).Methods("POST") - api.Router.HandleFunc("/warehouse/create/path", api.apiWarehouseCreateFilePath).Methods("GET") - api.Router.HandleFunc("/warehouse/read", api.apiWarehouseReadFile).Methods("GET") - api.Router.HandleFunc("/warehouse/read/path", api.apiWarehouseReadFilePath).Methods("GET") - api.Router.HandleFunc("/warehouse/delete", api.apiWarehouseDeleteFile).Methods("GET") - api.Router.HandleFunc("/file/read", api.apiFileRead).Methods("GET") - api.Router.HandleFunc("/file/view", api.apiFileView).Methods("GET") + api.Router.HandleFunc("/test", apiTest).Methods("GET") + api.Router.HandleFunc("/status", api.apiStatus).Methods("GET") + api.Router.HandleFunc("/status/peers", api.apiStatusPeers).Methods("GET") + api.Router.HandleFunc("/account/info", api.apiAccountInfo).Methods("GET") + api.Router.HandleFunc("/account/delete", api.apiAccountDelete).Methods("GET") + api.Router.HandleFunc("/blockchain/header", api.apiBlockchainHeaderFunc).Methods("GET") + api.Router.HandleFunc("/blockchain/append", api.apiBlockchainAppend).Methods("POST") + api.Router.HandleFunc("/blockchain/read", api.apiBlockchainRead).Methods("GET") + api.Router.HandleFunc("/blockchain/file/add", api.apiBlockchainFileAdd).Methods("POST") + api.Router.HandleFunc("/blockchain/file/list", api.apiBlockchainFileList).Methods("GET") + api.Router.HandleFunc("/blockchain/file/delete", api.apiBlockchainFileDelete).Methods("POST") + api.Router.HandleFunc("/blockchain/file/update", api.apiBlockchainFileUpdate).Methods("POST") + api.Router.HandleFunc("/profile/list", api.apiProfileList).Methods("GET") + api.Router.HandleFunc("/profile/read", api.apiProfileRead).Methods("GET") + api.Router.HandleFunc("/profile/write", api.apiProfileWrite).Methods("POST") + api.Router.HandleFunc("/profile/delete", api.apiProfileDelete).Methods("POST") + api.Router.HandleFunc("/search", api.apiSearch).Methods("POST") + api.Router.HandleFunc("/search/result", api.apiSearchResult).Methods("GET") + api.Router.HandleFunc("/search/result/ws", api.apiSearchResultStream).Methods("GET") + api.Router.HandleFunc("/search/statistic", api.apiSearchStatistic).Methods("GET") + api.Router.HandleFunc("/search/terminate", api.apiSearchTerminate).Methods("GET") + api.Router.HandleFunc("/explore", api.apiExplore).Methods("GET") + api.Router.HandleFunc("/explore/node", api.apiExploreNodeID).Methods("GET") + api.Router.HandleFunc("/file/format", api.apiFileFormat).Methods("GET") + api.Router.HandleFunc("/download/start", api.apiDownloadStart).Methods("GET") + api.Router.HandleFunc("/download/status", api.apiDownloadStatus).Methods("GET") + api.Router.HandleFunc("/download/action", api.apiDownloadAction).Methods("GET") + api.Router.HandleFunc("/warehouse/create", api.apiWarehouseCreateFile).Methods("POST") + api.Router.HandleFunc("/warehouse/create/path", api.apiWarehouseCreateFilePath).Methods("GET") + api.Router.HandleFunc("/warehouse/read", api.apiWarehouseReadFile).Methods("GET") + api.Router.HandleFunc("/warehouse/read/path", api.apiWarehouseReadFilePath).Methods("GET") + api.Router.HandleFunc("/warehouse/delete", api.apiWarehouseDeleteFile).Methods("GET") + api.Router.HandleFunc("/file/read", api.apiFileRead).Methods("GET") + api.Router.HandleFunc("/file/view", api.apiFileView).Methods("GET") - for _, listen := range ListenAddresses { - go startWebAPI(Backend, listen, UseSSL, CertificateFile, CertificateKey, api.Router, "API", TimeoutRead, TimeoutWrite) - } + for _, listen := range ListenAddresses { + go startWebAPI(Backend, listen, UseSSL, CertificateFile, CertificateKey, api.Router, "API", TimeoutRead, TimeoutWrite) + } - return api + return api } // startWebAPI starts a web-server with given parameters and logs the status. If may block forever and only returns if there is an error. // The certificate file and key are only used if SSL is enabled. The read and write timeout may be 0 for no timeout. func startWebAPI(Backend *core.Backend, WebListen string, UseSSL bool, CertificateFile, CertificateKey string, Handler http.Handler, Info string, ReadTimeout, WriteTimeout time.Duration) { - Backend.LogError("startWebAPI", "Start API at '%s'\n", WebListen) + Backend.LogError("startWebAPI", "Start API at '%s'\n", WebListen) - tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} // for security reasons disable TLS 1.0/1.1 + tlsConfig := &tls.Config{MinVersion: tls.VersionTLS12} // for security reasons disable TLS 1.0/1.1 - server := &http.Server{ - Addr: WebListen, - Handler: Handler, - ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body. - WriteTimeout: WriteTimeout, // WriteTimeout is the maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take. - //IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. - TLSConfig: tlsConfig, - } + server := &http.Server{ + Addr: WebListen, + Handler: Handler, + ReadTimeout: ReadTimeout, // ReadTimeout is the maximum duration for reading the entire request, including the body. + WriteTimeout: WriteTimeout, // WriteTimeout is the maximum duration before timing out writes of the response. This includes processing time and is therefore the max time any HTTP function may take. + //IdleTimeout: IdleTimeout, // IdleTimeout is the maximum amount of time to wait for the next request when keep-alives are enabled. + TLSConfig: tlsConfig, + } - if UseSSL { - // HTTPS - if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil { - Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err) - } - } else { - // HTTP - if err := server.ListenAndServe(); err != nil { - Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err) - } - } + if UseSSL { + // HTTPS + if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil { + Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err) + } + } else { + // HTTP + if err := server.ListenAndServe(); err != nil { + Backend.LogError("startWebAPI", "Error listening on '%s': %v\n", WebListen, err) + } + } } // EncodeJSON encodes the data as JSON func EncodeJSON(Backend *core.Backend, w http.ResponseWriter, r *http.Request, data interface{}) (err error) { - w.Header().Set("Content-Type", "application/json") + w.Header().Set("Content-Type", "application/json") - err = json.NewEncoder(w).Encode(data) - if err != nil { - Backend.LogError("EncodeJSON", "Error writing data for route '%s': %v\n", r.URL.Path, err) - } + err = json.NewEncoder(w).Encode(data) + if err != nil { + Backend.LogError("EncodeJSON", "Error writing data for route '%s': %v\n", r.URL.Path, err) + } - return err + return err } // DecodeJSON decodes input JSON data server side sent either via GET or POST. It does not limit the maximum amount to read. // In case of error it will automatically send an error to the client. func DecodeJSON(w http.ResponseWriter, r *http.Request, data interface{}) (err error) { - if r.Body == nil { - http.Error(w, "", http.StatusBadRequest) - return errors.New("no data") - } + if r.Body == nil { + http.Error(w, "", http.StatusBadRequest) + return errors.New("no data") + } - err = json.NewDecoder(r.Body).Decode(data) - if err != nil { - http.Error(w, "", http.StatusBadRequest) - return err - } + err = json.NewDecoder(r.Body).Decode(data) + if err != nil { + http.Error(w, "", http.StatusBadRequest) + return err + } - return nil + return nil } // authenticateMiddleware returns a middleware function to be used with mux.Router.Use(). It handles all authentication functionality. func (api *WebapiInstance) authenticateMiddleware(APIKey uuid.UUID) func(http.Handler) http.Handler { - return (func(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - keyID, err := uuid.Parse(r.Header.Get("x-api-key")) - if err != nil { // special case for some paths - for _, exceptPath := range api.AllowKeyInParam { - if exceptPath == r.URL.Path { - r.ParseForm() - keyID, err = uuid.Parse(r.Form.Get("k")) - break - } - } - } - if err != nil { // Invalid key format - w.WriteHeader(http.StatusUnauthorized) - return - } + return (func(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + keyID, err := uuid.Parse(r.Header.Get("x-api-key")) + if err != nil { // special case for some paths + for _, exceptPath := range api.AllowKeyInParam { + if exceptPath == r.URL.Path { + r.ParseForm() + keyID, err = uuid.Parse(r.Form.Get("k")) + break + } + } + } + if err != nil { // Invalid key format + w.WriteHeader(http.StatusUnauthorized) + return + } - if keyID != APIKey { - w.WriteHeader(http.StatusUnauthorized) - return - } + if keyID != APIKey { + w.WriteHeader(http.StatusUnauthorized) + return + } - next.ServeHTTP(w, r) - }) - }) + next.ServeHTTP(w, r) + }) + }) } diff --git a/webapi/Search.go b/webapi/Search.go index 99ba139..fb4be7e 100644 --- a/webapi/Search.go +++ b/webapi/Search.go @@ -349,7 +349,7 @@ func (api *WebapiInstance) apiSearchStatistic(w http.ResponseWriter, r *http.Req apiExplore returns recently shared files in Peernet. Results are returned in real-time. The file type is an optional filter. See TypeX. Special type -2 = Binary, Compressed, Container, Executable. This special type includes everything except Documents, Video, Audio, Ebooks, Picture, Text. -Request: GET /explore?limit=[max records]&type=[file type]&offset=[offset]&nodeid=[node id] +Request: GET /explore?limit=[max records]&type=[file type]&offset=[offset] Result: 200 with JSON structure SearchResult. Check the field status. */ func (api *WebapiInstance) apiExplore(w http.ResponseWriter, r *http.Request) { @@ -359,15 +359,47 @@ func (api *WebapiInstance) apiExplore(w http.ResponseWriter, r *http.Request) { if err != nil { limit = 100 } - // ID fields for results for a specific node ID. - nodeid, _ := DecodeBlake3Hash(r.Form.Get("nodeid")) fileType, err := strconv.Atoi(r.Form.Get("type")) if err != nil { fileType = -1 } - resultFiles := api.queryRecentShared(api.backend, fileType, uint64(limit*20/100), uint64(offset), uint64(limit), nodeid) + result := api.ExploreHelper(fileType,limit,offset,[]byte{},false) + + EncodeJSON(api.backend, w, r, result) +} + +/* +apiExploreNodeID returns the shared files of a particular node in Peernet. Results are returned in real-time. The file type is an optional filter. See TypeX. +Special type -2 = Binary, Compressed, Container, Executable. This special type includes everything except Documents, Video, Audio, Ebooks, Picture, Text. + +Request: GET /explore/node?limit=[max records]&type=[file type]&offset=[offset]&NodeID=[node id] +Result: 200 with JSON structure SearchResult. Check the field status. +*/ +func (api *WebapiInstance) apiExploreNodeID(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + offset, _ := strconv.Atoi(r.Form.Get("offset")) + limit, err := strconv.Atoi(r.Form.Get("limit")) + if err != nil { + limit = 100 + } + // ID fields for results for a specific node ID. + NodeId, _ := DecodeBlake3Hash(r.Form.Get("NodeID")) + + fileType, err := strconv.Atoi(r.Form.Get("type")) + if err != nil { + fileType = -1 + } + + result := api.ExploreHelper(fileType,limit,offset,NodeId,true) + + EncodeJSON(api.backend, w, r, result) +} + +// ExploreHelper Helper function for the explore route with the possibility search based on a node ID +func (api *WebapiInstance) ExploreHelper(fileType int, limit, offset int, nodeID []byte, nodeIDState bool) *SearchResult{ + resultFiles := api.queryRecentShared(api.backend, fileType, uint64(limit*20/100), uint64(offset), uint64(limit), nodeID, nodeIDState) var result SearchResult result.Files = []apiFile{} @@ -383,7 +415,7 @@ func (api *WebapiInstance) apiExplore(w http.ResponseWriter, r *http.Request) { result.Status = 1 // No more results to expect - EncodeJSON(api.backend, w, r, result) + return &result } func (input *SearchRequest) Parse() (Timeout time.Duration) { diff --git a/webapi/Shared Recent.go b/webapi/Shared Recent.go index 6fbdc55..ed44227 100644 --- a/webapi/Shared Recent.go +++ b/webapi/Shared Recent.go @@ -14,7 +14,7 @@ import ( ) // queryRecentShared returns recently shared files on the network from random peers until the limit is reached. -func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int, limitPeer, offsetTotal, limitTotal uint64, nodeID []byte) (files []blockchain.BlockRecordFile) { +func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int, limitPeer, offsetTotal, limitTotal uint64, nodeID []byte, nodeIDState bool) (files []blockchain.BlockRecordFile) { if limitPeer == 0 { limitPeer = 1 } @@ -23,11 +23,16 @@ func (api *WebapiInstance) queryRecentShared(backend *core.Backend, fileType int var peerList []*core.PeerInfo // check if the NodeID is provided or not - if len(nodeID) == 0 { + if len(nodeID) == 0 && !nodeIDState { // Use the peer list to know about active peers. Random order! peerList = api.backend.PeerlistGet() } else { - _, peerList[0], _ = api.backend.FindNode(nodeID, time.Second*5) + // Get peer information based on the NodeID provided + _, peer, err := api.backend.FindNode(nodeID, time.Second*5) + if err != nil { + return + } + peerList = append(peerList, peer) } // Files from peers exceeding the limit. It is used if from all peers the total limit is not reached.