From 501c9705100657db5310c039a1986aff72773b5c Mon Sep 17 00:00:00 2001 From: Keenan Nemetz Date: Tue, 2 Mar 2021 13:22:07 -0800 Subject: [PATCH] merge http into rpc --- pkg/command/daemon.go | 2 - pkg/http/http.go | 106 ----------------------- pkg/http/util.go | 40 --------- pkg/rpc/file/file.go | 10 +++ pkg/{http/file.go => rpc/file/search.go} | 70 ++++++++------- pkg/rpc/rpc.go | 57 +++++++++--- 6 files changed, 97 insertions(+), 188 deletions(-) delete mode 100644 pkg/http/http.go delete mode 100644 pkg/http/util.go create mode 100644 pkg/rpc/file/file.go rename pkg/{http/file.go => rpc/file/search.go} (54%) diff --git a/pkg/command/daemon.go b/pkg/command/daemon.go index bacebd8..34fc426 100644 --- a/pkg/command/daemon.go +++ b/pkg/command/daemon.go @@ -5,7 +5,6 @@ import ( "os" "os/signal" - "github.com/multiverse-vcs/go-multiverse/pkg/http" "github.com/multiverse-vcs/go-multiverse/pkg/remote" "github.com/multiverse-vcs/go-multiverse/pkg/rpc" "github.com/nasdf/ulimit" @@ -41,7 +40,6 @@ func NewDaemonCommand() *cli.Command { return err } - go http.ListenAndServe(server) go rpc.ListenAndServe(server) fmt.Printf(Banner) diff --git a/pkg/http/http.go b/pkg/http/http.go deleted file mode 100644 index 865ca29..0000000 --- a/pkg/http/http.go +++ /dev/null @@ -1,106 +0,0 @@ -package http - -import ( - "io" - "net/http" - "net/rpc" - "net/rpc/jsonrpc" - "os" - "path/filepath" - - "github.com/julienschmidt/httprouter" - "github.com/multiverse-vcs/go-multiverse/pkg/remote" - "github.com/multiverse-vcs/go-multiverse/pkg/rpc/author" - "github.com/multiverse-vcs/go-multiverse/pkg/rpc/repo" -) - -// ApiFile is the name of the api file. -const ApiFile = "api" - -// HttpConn wraps an HTTP request. -type HttpConn struct { - r io.Reader - w io.Writer -} - -// Read reads bytes from the reader. -func (c *HttpConn) Read(p []byte) (n int, err error) { - return c.r.Read(p) -} - -// Write writes bytes to the writer. -func (c *HttpConn) Write(d []byte) (n int, err error) { - return c.w.Write(d) -} - -// Close does nothing. -func (c *HttpConn) Close() error { - return nil -} - -// Route is an http handler that returns an error -type Route func(http.ResponseWriter, *http.Request) error - -// ServeHTTP serves the request and handles any errors. -func (r Route) ServeHTTP(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "*") - w.Header().Set("Access-Control-Allow-Headers", "*") - - if err := r(w, req); err != nil { - http.Error(w, err.Error(), http.StatusBadRequest) - } -} - -// Server is an HTTP gateway. -type Server struct { - *remote.Server -} - -// ApiAddress returns the address for the api. -func ApiAddress(home string) (string, error) { - path := filepath.Join(home, remote.DotDir, ApiFile) - data, err := os.ReadFile(path) - if err != nil { - return "", err - } - - return string(data), nil -} - -// ListenAndServe starts the HTTP listener. -func ListenAndServe(s *remote.Server) error { - path := filepath.Join(s.Root, ApiFile) - data := []byte(s.Config.HttpAddress) - if err := os.WriteFile(path, data, 0644); err != nil { - return err - } - - server := &Server{s} - router := httprouter.New() - router.Handler(http.MethodGet, "/:peer/:repo/:branch/*file", Route(server.File)) - - router.GlobalOPTIONS = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "*") - w.Header().Set("Access-Control-Allow-Headers", "*") - w.WriteHeader(http.StatusNoContent) - }) - - rpc.RegisterName("Author", &author.Service{s}) - rpc.RegisterName("Repo", &repo.Service{s}) - - http.HandleFunc("/_jsonRPC_", func(w http.ResponseWriter, req *http.Request) { - w.Header().Set("Access-Control-Allow-Origin", "*") - w.Header().Set("Access-Control-Allow-Methods", "*") - w.Header().Set("Access-Control-Allow-Headers", "*") - w.WriteHeader(http.StatusOK) - - if req.Method == http.MethodPost { - jsonrpc.ServeConn(&HttpConn{req.Body, w}) - } - }) - - http.Handle("/", router) - return http.ListenAndServe(s.Config.HttpAddress, nil) -} diff --git a/pkg/http/util.go b/pkg/http/util.go deleted file mode 100644 index dfcd5d5..0000000 --- a/pkg/http/util.go +++ /dev/null @@ -1,40 +0,0 @@ -package http - -import ( - "io" - - "github.com/alecthomas/chroma" - "github.com/alecthomas/chroma/formatters/html" - "github.com/alecthomas/chroma/lexers" - "github.com/alecthomas/chroma/styles" -) - -// formatter outputs highlighted code. -var formatter = html.New( - html.WithLineNumbers(true), - html.LineNumbersInTable(true), -) - -// Highlight returns a syntax highlighted version of the given code. -func Highlight(name, source, theme string, w io.Writer) error { - lexer := lexers.Match(name) - if lexer == nil { - lexer = lexers.Analyse(source) - } - - if lexer == nil { - lexer = lexers.Fallback - } - - style := styles.Get(theme) - if style == nil { - style = styles.Fallback - } - - token, err := chroma.Coalesce(lexer).Tokenise(nil, source) - if err != nil { - return err - } - - return formatter.Format(w, style, token) -} diff --git a/pkg/rpc/file/file.go b/pkg/rpc/file/file.go new file mode 100644 index 0000000..64baaa7 --- /dev/null +++ b/pkg/rpc/file/file.go @@ -0,0 +1,10 @@ +package file + +import ( + "github.com/multiverse-vcs/go-multiverse/pkg/remote" +) + +// Service wraps a remote and provides RPC. +type Service struct { + *remote.Server +} diff --git a/pkg/http/file.go b/pkg/rpc/file/search.go similarity index 54% rename from pkg/http/file.go rename to pkg/rpc/file/search.go index 964c715..f8f847c 100644 --- a/pkg/http/file.go +++ b/pkg/rpc/file/search.go @@ -1,35 +1,56 @@ -package http +package file import ( - "encoding/json" + "context" "errors" - "net/http" + "strings" path "github.com/ipfs/go-path" unixfs "github.com/ipfs/go-unixfs" - "github.com/julienschmidt/httprouter" "github.com/libp2p/go-libp2p-core/peer" "github.com/multiverse-vcs/go-multiverse/pkg/fs" "github.com/multiverse-vcs/go-multiverse/pkg/object" ) -// File returns file contents from the repository tree. -func (s *Server) File(w http.ResponseWriter, req *http.Request) error { - ctx := req.Context() +// SearchArgs contains the args. +type SearchArgs struct { + // Remote is the remote path. + Remote string + // Branch is the branch name. + Branch string + // Path is the file path. + Path string +} + +// SearchReply contains the reply. +type SearchReply struct { + // Content contains file content. + Content string + // Entries contains directory entries. + Entries []*fs.DirEntry + // IsDir specifies if the file is a directory. + IsDir bool +} + +// Search returns the contents of a file at the given remote path. +func (s *Service) Search(args *SearchArgs, reply *SearchReply) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + parts := strings.Split(args.Remote, "/") + if len(parts) != 2 { + return errors.New("invalid remote path") + } - highlight := req.URL.Query().Get("highlight") - params := httprouter.ParamsFromContext(ctx) - pname := params.ByName("peer") - rname := params.ByName("repo") - bname := params.ByName("branch") - fname := params.ByName("file") + pname := parts[0] + rname := parts[1] peerID, err := peer.Decode(pname) if err != nil { return err } - authorID, err := s.Namesys.Resolve(ctx, peerID) + authorID, err := s.Namesys.Search(ctx, peerID) if err != nil { return err } @@ -49,12 +70,12 @@ func (s *Server) File(w http.ResponseWriter, req *http.Request) error { return err } - head, ok := repo.Branches[bname] + head, ok := repo.Branches[args.Branch] if !ok { return errors.New("branch does not exist") } - fpath, err := path.FromSegments("/ipfs/", head.String(), "tree", fname) + fpath, err := path.FromSegments("/ipfs/", head.String(), "tree", args.Path) if err != nil { return err } @@ -76,25 +97,16 @@ func (s *Server) File(w http.ResponseWriter, req *http.Request) error { return err } - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(tree) - case highlight != "": - blob, err := fs.Cat(ctx, s.Peer.DAG, fnode.Cid()) - if err != nil { - return err - } - - w.Header().Set("Content-Type", "text/html") - Highlight(fname, blob, highlight, w) + reply.Entries = tree default: blob, err := fs.Cat(ctx, s.Peer.DAG, fnode.Cid()) if err != nil { return err } - w.Header().Set("Content-Type", "text/plain") - w.Write([]byte(blob)) + reply.Content = blob } + reply.IsDir = fsnode.IsDir() return nil -} +} \ No newline at end of file diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go index aff08c1..b673393 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/rpc.go @@ -1,21 +1,21 @@ package rpc import ( + "io" "log" "net" "net/http" "net/rpc" + "net/rpc/jsonrpc" "os" "path/filepath" "github.com/multiverse-vcs/go-multiverse/pkg/remote" "github.com/multiverse-vcs/go-multiverse/pkg/rpc/author" + "github.com/multiverse-vcs/go-multiverse/pkg/rpc/file" "github.com/multiverse-vcs/go-multiverse/pkg/rpc/repo" ) -// SockFile is the name of the unix socket file. -const SockFile = "rpc.sock" - // DialErrMsg is an error message for failed RPC connections. var DialErrMsg = ` Could not connect to local RPC server. @@ -23,29 +23,52 @@ Make sure the Multiverse daemon is up. See 'multi help daemon' for more info. ` +// HttpConn wraps an HTTP request. +type HttpConn struct { + r io.Reader + w io.Writer +} + +// Read reads bytes from the reader. +func (c *HttpConn) Read(p []byte) (n int, err error) { + return c.r.Read(p) +} + +// Write writes bytes to the writer. +func (c *HttpConn) Write(d []byte) (n int, err error) { + return c.w.Write(d) +} + +// Close does nothing. +func (c *HttpConn) Close() error { + return nil +} + // NewClient returns a new RPC client. func NewClient() (*rpc.Client, error) { home, err := os.UserHomeDir() if err != nil { return nil, err } + + config := remote.NewConfig(filepath.Join(home, remote.DotDir)) + if err := config.Read(); err != nil { + return nil, err + } - socket := filepath.Join(home, remote.DotDir, SockFile) - return rpc.DialHTTP("unix", socket) + return rpc.DialHTTP("tcp", config.HttpAddress) } // ListenAndServe starts the RPC listener. func ListenAndServe(server *remote.Server) error { - socket := filepath.Join(server.Root, SockFile) - if err := os.RemoveAll(socket); err != nil { - return err - } - rpc.RegisterName("Author", &author.Service{server}) + rpc.RegisterName("File", &file.Service{server}) rpc.RegisterName("Repo", &repo.Service{server}) + rpc.HandleHTTP() + http.HandleFunc("/_jsonRPC_", ServeHTTP) - listener, err := net.Listen("unix", socket) + listener, err := net.Listen("tcp", server.Config.HttpAddress) if err != nil { log.Fatal(err) } @@ -53,3 +76,15 @@ func ListenAndServe(server *remote.Server) error { return http.Serve(listener, nil) } + +// ServeHTTP serves json rpc connections over http. +func ServeHTTP(w http.ResponseWriter, req *http.Request) { + w.Header().Set("Access-Control-Allow-Origin", "*") + w.Header().Set("Access-Control-Allow-Methods", "*") + w.Header().Set("Access-Control-Allow-Headers", "*") + w.WriteHeader(http.StatusOK) + + if req.Method == http.MethodPost { + jsonrpc.ServeConn(&HttpConn{req.Body, w}) + } +} \ No newline at end of file