current changes for webgateway

This commit is contained in:
2023-03-17 14:03:31 +00:00
parent ee60a40494
commit b8e7ae20f7
3 changed files with 159 additions and 149 deletions

View File

@@ -7,198 +7,208 @@ Author: Peter Kleissner
package main
import (
"crypto/tls"
"encoding/hex"
"fmt"
"io"
"net"
"net/http"
"path"
"strings"
"time"
"crypto/tls"
"encoding/hex"
"fmt"
"io"
"net"
"net/http"
"path"
"strings"
"time"
"github.com/PeernetOfficial/core"
"github.com/PeernetOfficial/core/btcec"
"github.com/PeernetOfficial/core/webapi"
"github.com/gorilla/mux"
"github.com/PeernetOfficial/core"
"github.com/PeernetOfficial/core/btcec"
"github.com/PeernetOfficial/core/webapi"
"github.com/gorilla/mux"
)
func startWebGateway(backend *core.Backend) {
router := mux.NewRouter()
router.PathPrefix("/").Handler(http.HandlerFunc(webGatewayHandler(backend))).Methods("GET")
router := mux.NewRouter()
router.PathPrefix("/").Handler(http.HandlerFunc(webGatewayHandler(backend))).Methods("GET")
for _, listen := range config.WebListen {
go startWebServer(backend, listen, config.WebUseSSL, config.WebCertificateFile, config.WebCertificateKey, router, "Web Listen", parseDuration(config.WebTimeoutRead), parseDuration(config.WebTimeoutWrite))
}
for _, listen := range config.WebListen {
go startWebServer(backend, listen, config.WebUseSSL, config.WebCertificateFile, config.WebCertificateKey, router, "Web Listen", parseDuration(config.WebTimeoutRead), parseDuration(config.WebTimeoutWrite))
}
if config.Redirect80 != "" {
go webRedirect80(config.Redirect80)
}
if config.Redirect80 != "" {
go webRedirect80(config.Redirect80)
}
// wait forever
select {}
// wait forever
select {}
}
// startWebServer starts the web-server and may block forever
func startWebServer(backend *core.Backend, WebListen string, UseSSL bool, CertificateFile, CertificateKey string, Handler http.Handler, Info string, ReadTimeout, WriteTimeout time.Duration) {
//func startWebServer(backend *core.Backend, webListen string, useSSL bool, certificateFile, certificateKey string, server *http.Server) {
backend.LogError("startWebServer", "Web Gateway to listen on '%s'", WebListen)
//func startWebServer(backend *core.Backend, webListen string, useSSL bool, certificateFile, certificateKey string, server *http.Server) {
backend.LogError("startWebServer", "Web Gateway to listen on '%s'", 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("startWebServer", "Error listening on '%s': %v\n", WebListen, err)
}
} else {
// HTTP
if err := server.ListenAndServe(); err != nil {
backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err)
}
}
if UseSSL {
// HTTPS
if err := server.ListenAndServeTLS(CertificateFile, CertificateKey); err != nil {
backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err)
}
} else {
// HTTP
if err := server.ListenAndServe(); err != nil {
backend.LogError("startWebServer", "Error listening on '%s': %v\n", WebListen, err)
}
}
}
// parseDuration is the same as time.ParseDuration without returning an error. Valid units are ms, s, m, h. For example "10s".
func parseDuration(input string) (result time.Duration) {
result, _ = time.ParseDuration(input)
return
result, _ = time.ParseDuration(input)
return
}
func redirect(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently)
http.Redirect(w, r, "https://"+r.Host+r.URL.String(), http.StatusMovedPermanently)
}
func webRedirect80(listen80 string) {
// redirect HTTP -> HTTPS
http.ListenAndServe(net.JoinHostPort(listen80, "80"), http.HandlerFunc(redirect))
// redirect HTTP -> HTTPS
http.ListenAndServe(net.JoinHostPort(listen80, "80"), http.HandlerFunc(redirect))
}
func webGatewayHandler(backend *core.Backend) func(w http.ResponseWriter, r *http.Request) {
return func(w http.ResponseWriter, r *http.Request) {
// For security and simplicity reasons, below paths are hard-coded.
// Using arbitrary user input is an avoidable security risk here.
switch r.URL.Path {
case "/", "/index.html":
http.ServeFile(w, r, path.Join(config.WebFiles, "index.html"))
return
case "/favicon.ico":
http.ServeFile(w, r, path.Join(config.WebFiles, "favicon.ico"))
return
}
return func(w http.ResponseWriter, r *http.Request) {
// For security and simplicity reasons, below paths are hard-coded.
// Using arbitrary user input is an avoidable security risk here.
switch r.URL.Path {
case "/", "/index.html":
http.ServeFile(w, r, path.Join(config.WebFiles, "index.html"))
return
case "/favicon.ico":
http.ServeFile(w, r, path.Join(config.WebFiles, "favicon.ico"))
return
}
// Assume it is in the format "/[blockchain public key]/[file hash]".
// Assume it is in the format "/[blockchain public key]/[file hash]".
// Remove slash prefix and suffix.
pathA := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/")
// Remove slash prefix and suffix.
pathA := strings.TrimPrefix(strings.TrimSuffix(r.URL.Path, "/"), "/")
pathParts := strings.Split(pathA, "/")
if len(pathParts) != 1 && len(pathParts) != 2 {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
pathParts := strings.Split(pathA, "/")
if len(pathParts) != 1 && len(pathParts) != 2 {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
// Default timeout for connection is 10 seconds. This will be an optional parameter in the future.
timeout := 10 * time.Second
// Default timeout for connection is 10 seconds. This will be an optional parameter in the future.
timeout := 10 * time.Second
// First part must be the public key as peer ID or node ID, hex encoded. Form: "/[blockchain public key]"
nodeIDA := pathParts[0]
nodeID, validNodeID := webapi.DecodeBlake3Hash(nodeIDA)
publicKey, errPK := core.PublicKeyFromPeerID(nodeIDA)
// First part must be the public key as peer ID or node ID, hex encoded. Form: "/[blockchain public key]"
nodeIDA := pathParts[0]
nodeID, validNodeID := webapi.DecodeBlake3Hash(nodeIDA)
publicKey, errPK := core.PublicKeyFromPeerID(nodeIDA)
if !validNodeID && errPK != nil {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
if !validNodeID {
nodeID = []byte{}
}
if !validNodeID && errPK != nil {
http.Error(w, "404 not found", http.StatusNotFound)
return
}
if !validNodeID {
nodeID = []byte{}
}
// Check if a blockchain is requested.
// The format must be "/[blockchain public key]". Part 1 = hex encoding, peer ID or node ID.
if len(pathParts) == 1 {
webGatewayShowBlockchain(backend, w, r, nodeID, publicKey, timeout)
} else if len(pathParts) == 2 {
// Check if a specific file on a specific blockchain is requested.
// The format must be "/[blockchain public key]/[file hash]". Part 2 = hex encoding, blake3 hash.
hash, valid := webapi.DecodeBlake3Hash(pathParts[1])
if !valid {
http.Error(w, "Invalid file hash.", http.StatusBadRequest)
return
}
// Check if a blockchain is requested.
// The format must be "/[blockchain public key]". Part 1 = hex encoding, peer ID or node ID.
if len(pathParts) == 1 {
webGatewayShowBlockchain(backend, w, r, nodeID, publicKey, timeout)
} else if len(pathParts) == 2 {
// Check if a specific file on a specific blockchain is requested.
// The format must be "/[blockchain public key]/[file hash]". Part 2 = hex encoding, blake3 hash.
hash, valid := webapi.DecodeBlake3Hash(pathParts[1])
if !valid {
http.Error(w, "Invalid file hash.", http.StatusBadRequest)
return
}
webGatewayShowFile(backend, w, r, nodeID, publicKey, hash, timeout)
}
webGatewayShowFile(backend, w, r, nodeID, publicKey, hash, timeout)
}
// Check if an arbitrary directory on a specific blockchain is requested.
// Directories are identified by name.
// TODO
// Check if an arbitrary directory on a specific blockchain is requested.
// Directories are identified by name.
// TODO
//http.Error(w, "test handler", http.StatusOK)
}
//http.Error(w, "test handler", http.StatusOK)
}
}
func webGatewayShowBlockchain(backend *core.Backend, w http.ResponseWriter, r *http.Request, nodeID []byte, publicKey *btcec.PublicKey, timeout time.Duration) {
var err error
var peer *core.PeerInfo
if len(nodeID) != 0 {
peer, err = webapi.PeerConnectNode(backend, nodeID, timeout)
} else {
peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout)
}
if err != nil {
http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound)
return
}
var err error
var peer *core.PeerInfo
if len(nodeID) != 0 {
peer, err = webapi.PeerConnectNode(backend, nodeID, timeout)
} else {
peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout)
}
if err != nil {
http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound)
return
}
// connection established!
text := fmt.Sprintf("Peer %s blockchain height %d version %d\nUser Agent: %s\n", hex.EncodeToString(peer.NodeID), peer.BlockchainHeight, peer.BlockchainVersion, peer.UserAgent)
http.Error(w, text, http.StatusOK)
// connection established!
text := fmt.Sprintf("Peer %s blockchain height %d version %d\nUser Agent: %s\n", hex.EncodeToString(peer.NodeID), peer.BlockchainHeight, peer.BlockchainVersion, peer.UserAgent)
http.Error(w, text, http.StatusOK)
}
func webGatewayShowFile(backend *core.Backend, w http.ResponseWriter, r *http.Request, nodeID []byte, publicKey *btcec.PublicKey, fileHash []byte, timeout time.Duration) {
var err error
var peer *core.PeerInfo
if len(nodeID) != 0 {
peer, err = webapi.PeerConnectNode(backend, nodeID, timeout)
} else {
peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout)
}
if err != nil {
http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound)
return
}
var err error
var peer *core.PeerInfo
if len(nodeID) != 0 {
peer, err = webapi.PeerConnectNode(backend, nodeID, timeout)
} else {
peer, err = webapi.PeerConnectPublicKey(backend, publicKey, timeout)
}
if err != nil {
http.Error(w, "Could not connect to remote peer. 😢", http.StatusNotFound)
return
}
// Todo: Try webapi.serveFileFromWarehouse
// Todo: Try webapi.serveFileFromWarehouse
// Todo: Cache.
// Todo: Cache.
offset := 0
limit := 0
offset := 0
limit := 0
// start the reader
//reader, fileSize, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done())
reader, _, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done())
if reader != nil {
defer reader.Close()
}
if err != nil || reader == nil {
http.Error(w, "File not found.", http.StatusNotFound)
return
}
// start the reader
//reader, fileSize, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done())
reader, _, transferSize, err := webapi.FileStartReader(peer, fileHash, uint64(offset), uint64(limit), r.Context().Done())
if reader != nil {
defer reader.Close()
}
if err != nil || reader == nil {
http.Error(w, "File not found.", http.StatusNotFound)
return
}
// set the right headers
//webapi.setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges)
// if get request ?download=true
// trigger download of the file
download := r.URL.Query().Get("download")
if download == "true" {
io.Copy(w, io.LimitReader(reader, int64(transferSize)))
return
} else {
// Start sending the data!
io.Copy(w, io.LimitReader(reader, int64(transferSize)))
}
// set the right headers
//webapi.setContentLengthRangeHeader(w, uint64(offset), transferSize, fileSize, ranges)
// Start sending the data!
io.Copy(w, io.LimitReader(reader, int64(transferSize)))
}