summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShulhan <ms@kilabit.info>2023-09-28 15:30:18 +0700
committerShulhan <ms@kilabit.info>2023-09-28 15:30:18 +0700
commit3cfb38dd0f7a793f27ca6d348316e53ae9ec7105 (patch)
treed9929bf74b9bbf8c2dc8e5422ec894239453acd2
parentdb2e69b62ddb2038847237fb4434702057560b22 (diff)
downloadawwan-3cfb38dd0f7a793f27ca6d348316e53ae9ec7105.tar.xz
all: create struct to handle HTTP server
All fields that use to serve HTTP API now moved inside one struct, including memfsBase, bufout, and buferr.
-rw-r--r--awwan.go63
-rw-r--r--http_server.go176
2 files changed, 137 insertions, 102 deletions
diff --git a/awwan.go b/awwan.go
index 02b855d..8b6b3a9 100644
--- a/awwan.go
+++ b/awwan.go
@@ -4,17 +4,15 @@
package awwan
import (
- "bytes"
"fmt"
"log"
"os"
"path/filepath"
"strings"
- "git.sr.ht/~shulhan/awwan/internal"
- "github.com/shuLhan/share/lib/http"
- "github.com/shuLhan/share/lib/memfs"
"github.com/shuLhan/share/lib/ssh/config"
+
+ "git.sr.ht/~shulhan/awwan/internal"
)
// Version current version of this module (library and program).
@@ -30,12 +28,11 @@ const (
)
const (
- defCacheDir = ".cache"
- defEnvFileName = "awwan.env" // The default awwan environment file name.
- defListenAddress = "127.0.0.1:17600"
- defSshConfig = "config" // The default SSH config file name.
- defSshDir = ".ssh" // The default SSH config directory name.
- defTmpDir = "/tmp"
+ defCacheDir = `.cache`
+ defEnvFileName = `awwan.env` // The default awwan environment file name.
+ defSshConfig = `config` // The default SSH config file name.
+ defSshDir = `.ssh` // The default SSH config directory name.
+ defTmpDir = `/tmp`
)
// defEncryptExt default file extension for encrypted file.
@@ -56,18 +53,14 @@ var (
// Awwan is the service that run script in local or remote.
// Awwan contains cache of sessions and cache of environment files.
type Awwan struct {
- BaseDir string
-
cryptoc *cryptoContext
// All the Host values from SSH config files.
sshConfig *config.Config
- httpd *http.Server // The HTTP server.
- memfsBase *memfs.MemFS // The files caches.
+ httpd *httpServer
- bufout bytes.Buffer
- buferr bytes.Buffer
+ BaseDir string
}
// New create and initialize new Awwan service using baseDir as the root of
@@ -312,48 +305,20 @@ func (aww *Awwan) Play(req *Request) (err error) {
// Serve start the web-user interface that serve awwan actions through HTTP.
func (aww *Awwan) Serve() (err error) {
var (
- logp = "Serve"
- envDev = os.Getenv(internal.EnvDevelopment)
- memfsBaseOpts = &memfs.Options{
- Root: aww.BaseDir,
- Excludes: []string{
- `.*/\.git`,
- "node_modules",
- "vendor",
- `.*\.(bz|bz2|gz|iso|jar|tar|xz|zip)`,
- },
- TryDirect: true, // Only store the file structures in the memory.
- }
-
- serverOpts *http.ServerOptions
+ logp = `Serve`
+ envDev = os.Getenv(internal.EnvDevelopment)
)
- aww.memfsBase, err = memfs.New(memfsBaseOpts)
- if err != nil {
- return fmt.Errorf("%s: %w", logp, err)
- }
-
if len(envDev) > 0 {
go internal.Watch()
}
- serverOpts = &http.ServerOptions{
- Memfs: internal.MemfsWww,
- Address: defListenAddress,
- }
- aww.httpd, err = http.NewServer(serverOpts)
+ aww.httpd, err = newHttpServer(aww)
if err != nil {
- return fmt.Errorf("%s: %w", logp, err)
+ return fmt.Errorf(`%s: %w`, logp, err)
}
- err = aww.registerHttpApis()
- if err != nil {
- return fmt.Errorf("%s: %w", logp, err)
- }
-
- fmt.Printf("--- Starting HTTP server at http://%s\n", serverOpts.Address)
-
- return aww.httpd.Start()
+ return aww.httpd.start()
}
// loadSshConfig load all SSH config from user's home and the awwan base
diff --git a/http_server.go b/http_server.go
index 2491831..ab98992 100644
--- a/http_server.go
+++ b/http_server.go
@@ -4,6 +4,7 @@
package awwan
import (
+ "bytes"
"encoding/json"
"fmt"
"net/http"
@@ -15,70 +16,132 @@ import (
libbytes "github.com/shuLhan/share/lib/bytes"
libhttp "github.com/shuLhan/share/lib/http"
"github.com/shuLhan/share/lib/memfs"
+
+ "git.sr.ht/~shulhan/awwan/internal"
)
const (
- httpApiFs = "/awwan/api/fs"
- httpApiExecute = "/awwan/api/execute"
-
- paramNamePath = "path"
+ pathAwwanApiFs = `/awwan/api/fs`
+ pathAwwanApiExecute = `/awwan/api/execute`
)
-func (aww *Awwan) registerHttpApis() (err error) {
+const paramNamePath = `path`
+
+const defListenAddress = `127.0.0.1:17600`
+
+// httpServer awwan HTTP server for HTTP API and web user interface feature.
+type httpServer struct {
+ *libhttp.Server
+
+ aww *Awwan
+ memfsBase *memfs.MemFS // The files caches.
+
+ baseDir string
+
+ bufout bytes.Buffer
+ buferr bytes.Buffer
+}
+
+// newHttpServer create and initialize HTTP server to serve awwan HTTP API
+// and web user interface.
+func newHttpServer(aww *Awwan) (httpd *httpServer, err error) {
var (
- logp = "registerHttpApis"
+ logp = `newHttpServer`
)
- err = aww.httpd.RegisterEndpoint(&libhttp.Endpoint{
+ httpd = &httpServer{
+ aww: aww,
+ baseDir: aww.BaseDir,
+ }
+
+ var memfsBaseOpts = &memfs.Options{
+ Root: aww.BaseDir,
+ Excludes: []string{
+ `.*/\.git`,
+ `node_modules`,
+ `vendor`,
+ `.*\.(bz|bz2|gz|iso|jar|tar|xz|zip)`,
+ },
+ TryDirect: true, // Only store the file structures in the memory.
+ }
+
+ httpd.memfsBase, err = memfs.New(memfsBaseOpts)
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ var serverOpts = &libhttp.ServerOptions{
+ Memfs: internal.MemfsWww,
+ Address: defListenAddress,
+ }
+
+ httpd.Server, err = libhttp.NewServer(serverOpts)
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ err = httpd.registerEndpoints()
+ if err != nil {
+ return nil, fmt.Errorf(`%s: %w`, logp, err)
+ }
+
+ return httpd, nil
+}
+
+// registerEndpoints register endpoint to be served by HTTP server.
+func (httpd *httpServer) registerEndpoints() (err error) {
+ var logp = `registerEndpoints`
+
+ err = httpd.RegisterEndpoint(&libhttp.Endpoint{
Method: libhttp.RequestMethodGet,
- Path: httpApiFs,
+ Path: pathAwwanApiFs,
RequestType: libhttp.RequestTypeQuery,
ResponseType: libhttp.ResponseTypeJSON,
- Call: aww.httpApiFs,
+ Call: httpd.awwanApiFsGet,
})
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
}
- err = aww.httpd.RegisterEndpoint(&libhttp.Endpoint{
+ err = httpd.RegisterEndpoint(&libhttp.Endpoint{
Method: libhttp.RequestMethodDelete,
- Path: httpApiFs,
+ Path: pathAwwanApiFs,
RequestType: libhttp.RequestTypeJSON,
ResponseType: libhttp.ResponseTypeJSON,
- Call: aww.httpApiFsDelete,
+ Call: httpd.awwanApiFsDelete,
})
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
}
- err = aww.httpd.RegisterEndpoint(&libhttp.Endpoint{
+ err = httpd.RegisterEndpoint(&libhttp.Endpoint{
Method: libhttp.RequestMethodPost,
- Path: httpApiFs,
+ Path: pathAwwanApiFs,
RequestType: libhttp.RequestTypeJSON,
ResponseType: libhttp.ResponseTypeJSON,
- Call: aww.httpApiFsPost,
+ Call: httpd.awwanApiFsPost,
})
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
}
- err = aww.httpd.RegisterEndpoint(&libhttp.Endpoint{
+ err = httpd.RegisterEndpoint(&libhttp.Endpoint{
Method: libhttp.RequestMethodPut,
- Path: httpApiFs,
+ Path: pathAwwanApiFs,
RequestType: libhttp.RequestTypeJSON,
ResponseType: libhttp.ResponseTypeJSON,
- Call: aww.httpApiFsPut,
+ Call: httpd.awwanApiFsPut,
})
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
}
- err = aww.httpd.RegisterEndpoint(&libhttp.Endpoint{
+ err = httpd.RegisterEndpoint(&libhttp.Endpoint{
Method: libhttp.RequestMethodPost,
- Path: httpApiExecute,
+ Path: pathAwwanApiExecute,
RequestType: libhttp.RequestTypeJSON,
ResponseType: libhttp.ResponseTypeJSON,
- Call: aww.httpApiExecute,
+ Call: httpd.awwanApiExecute,
})
if err != nil {
return fmt.Errorf("%s: %w", logp, err)
@@ -87,9 +150,16 @@ func (aww *Awwan) registerHttpApis() (err error) {
return nil
}
-// httpApiFs get the list of files or specific file using query parameter
-// "path".
-func (aww *Awwan) httpApiFs(epr *libhttp.EndpointRequest) (resb []byte, err error) {
+// start the HTTP server.
+func (httpd *httpServer) start() (err error) {
+ fmt.Printf("--- Starting HTTP server at http://%s\n", httpd.Server.Options.Address)
+
+ return httpd.Server.Start()
+}
+
+// awwanApiFsGet get the list of files or specific file using query
+// parameter "path".
+func (httpd *httpServer) awwanApiFsGet(epr *libhttp.EndpointRequest) (resb []byte, err error) {
var (
res = &libhttp.EndpointResponse{}
@@ -100,11 +170,11 @@ func (aww *Awwan) httpApiFs(epr *libhttp.EndpointRequest) (resb []byte, err erro
path = epr.HttpRequest.Form.Get(paramNamePath)
if len(path) == 0 {
res.Code = http.StatusOK
- res.Data = aww.memfsBase
+ res.Data = httpd.memfsBase
return json.Marshal(res)
}
- node, err = aww.memfsBase.Get(path)
+ node, err = httpd.memfsBase.Get(path)
if err != nil {
return nil, err
}
@@ -114,7 +184,7 @@ func (aww *Awwan) httpApiFs(epr *libhttp.EndpointRequest) (resb []byte, err erro
return json.Marshal(res)
}
-// httpApiFsDelete an HTTP API to delete a file.
+// awwanApiFsDelete an HTTP API to delete a file.
//
// # Request
//
@@ -144,9 +214,9 @@ func (aww *Awwan) httpApiFs(epr *libhttp.EndpointRequest) (resb []byte, err erro
// - 400: Bad request.
// - 401: Unauthorized.
// - 404: File not found.
-func (aww *Awwan) httpApiFsDelete(epr *libhttp.EndpointRequest) (resb []byte, err error) {
+func (httpd *httpServer) awwanApiFsDelete(epr *libhttp.EndpointRequest) (resb []byte, err error) {
var (
- logp = "httpApiFsDelete"
+ logp = `awwanApiFsDelete`
res = &libhttp.EndpointResponse{}
req = &fsRequest{}
@@ -164,7 +234,7 @@ func (aww *Awwan) httpApiFsDelete(epr *libhttp.EndpointRequest) (resb []byte, er
}
parentPath = path.Dir(req.Path)
- nodeParent = aww.memfsBase.PathNodes.Get(parentPath)
+ nodeParent = httpd.memfsBase.PathNodes.Get(parentPath)
if nodeParent == nil {
res.Message = fmt.Sprintf("%s: invalid path %s", logp, req.Path)
return nil, res
@@ -176,7 +246,7 @@ func (aww *Awwan) httpApiFsDelete(epr *libhttp.EndpointRequest) (resb []byte, er
res.Message = fmt.Sprintf("%s: %s", logp, err)
return nil, res
}
- if !strings.HasPrefix(sysPath, aww.memfsBase.Opts.Root) {
+ if !strings.HasPrefix(sysPath, httpd.memfsBase.Opts.Root) {
res.Message = fmt.Sprintf("%s: invalid path %q", logp, sysPath)
return nil, res
}
@@ -194,7 +264,7 @@ func (aww *Awwan) httpApiFsDelete(epr *libhttp.EndpointRequest) (resb []byte, er
return json.Marshal(res)
}
-// httpApiFsPost create new directory or file.
+// awwanApiFsPost create new directory or file.
//
// # Request
//
@@ -207,9 +277,9 @@ func (aww *Awwan) httpApiFsDelete(epr *libhttp.EndpointRequest) (resb []byte, er
// "path": <string>, the path to new directory or file.
// "is_dir": <boolean>, true if its directory.
// }
-func (aww *Awwan) httpApiFsPost(epr *libhttp.EndpointRequest) (rawBody []byte, err error) {
+func (httpd *httpServer) awwanApiFsPost(epr *libhttp.EndpointRequest) (rawBody []byte, err error) {
var (
- logp = "httpApiFsPost"
+ logp = `awwanApiFsPost`
res = &libhttp.EndpointResponse{}
req = &fsRequest{}
@@ -229,12 +299,12 @@ func (aww *Awwan) httpApiFsPost(epr *libhttp.EndpointRequest) (rawBody []byte, e
}
parentPath = path.Dir(req.Path)
- nodeParent = aww.memfsBase.PathNodes.Get(parentPath)
+ nodeParent = httpd.memfsBase.PathNodes.Get(parentPath)
if nodeParent == nil {
res.Message = fmt.Sprintf("%s: invalid path %s", logp, req.Path)
return nil, res
}
- node = aww.memfsBase.PathNodes.Get(req.Path)
+ node = httpd.memfsBase.PathNodes.Get(req.Path)
if node != nil {
res.Message = fmt.Sprintf("%s: file exist", logp)
return nil, res
@@ -248,7 +318,7 @@ func (aww *Awwan) httpApiFsPost(epr *libhttp.EndpointRequest) (rawBody []byte, e
res.Message = fmt.Sprintf("%s: %s", logp, err)
return nil, res
}
- if !strings.HasPrefix(sysPath, aww.memfsBase.Opts.Root) {
+ if !strings.HasPrefix(sysPath, httpd.memfsBase.Opts.Root) {
res.Message = fmt.Sprintf("%s: invalid path %q", logp, sysPath)
return nil, res
}
@@ -268,7 +338,7 @@ func (aww *Awwan) httpApiFsPost(epr *libhttp.EndpointRequest) (rawBody []byte, e
return nil, res
}
- node, err = aww.memfsBase.AddChild(nodeParent, fi)
+ node, err = httpd.memfsBase.AddChild(nodeParent, fi)
if err != nil {
res.Message = fmt.Sprintf("%s: %s", logp, err)
return nil, res
@@ -280,10 +350,10 @@ func (aww *Awwan) httpApiFsPost(epr *libhttp.EndpointRequest) (rawBody []byte, e
return json.Marshal(res)
}
-// httpApiFsPut save the content of file.
-func (aww *Awwan) httpApiFsPut(epr *libhttp.EndpointRequest) (rawBody []byte, err error) {
+// awwanApiFsPut save the content of file.
+func (httpd *httpServer) awwanApiFsPut(epr *libhttp.EndpointRequest) (rawBody []byte, err error) {
var (
- logp = "httpApiFsPut"
+ logp = "awwanApiFsPut"
res = &libhttp.EndpointResponse{}
req = &fsRequest{}
@@ -298,7 +368,7 @@ func (aww *Awwan) httpApiFsPut(epr *libhttp.EndpointRequest) (rawBody []byte, er
return nil, res
}
- node = aww.memfsBase.PathNodes.Get(req.Path)
+ node = httpd.memfsBase.PathNodes.Get(req.Path)
if node == nil {
res.Code = http.StatusNotFound
res.Message = fmt.Sprintf("%s: invalid or empty path %s", logp, req.Path)
@@ -317,9 +387,9 @@ func (aww *Awwan) httpApiFsPut(epr *libhttp.EndpointRequest) (rawBody []byte, er
return json.Marshal(res)
}
-func (aww *Awwan) httpApiExecute(epr *libhttp.EndpointRequest) (resb []byte, err error) {
+func (httpd *httpServer) awwanApiExecute(epr *libhttp.EndpointRequest) (resb []byte, err error) {
var (
- logp = "httpApiExecute"
+ logp = "awwanApiExecute"
req = &Request{}
res = &libhttp.EndpointResponse{}
@@ -334,19 +404,19 @@ func (aww *Awwan) httpApiExecute(epr *libhttp.EndpointRequest) (resb []byte, err
return nil, res
}
- req.Script = filepath.Join(aww.memfsBase.Opts.Root, req.Script)
+ req.Script = filepath.Join(httpd.memfsBase.Opts.Root, req.Script)
req.lineRange = parseLineRange(req.LineRange)
- aww.bufout.Reset()
- aww.buferr.Reset()
+ httpd.bufout.Reset()
+ httpd.buferr.Reset()
- req.stdout = &aww.bufout
- req.stderr = &aww.buferr
+ req.stdout = &httpd.bufout
+ req.stderr = &httpd.buferr
if req.Mode == CommandModeLocal {
- err = aww.Local(req)
+ err = httpd.aww.Local(req)
} else {
- err = aww.Play(req)
+ err = httpd.aww.Play(req)
}
if err != nil {
res.Message = fmt.Sprintf("%s: %s", logp, err)
@@ -355,8 +425,8 @@ func (aww *Awwan) httpApiExecute(epr *libhttp.EndpointRequest) (resb []byte, err
data = &HttpResponse{
Request: req,
- Stdout: libbytes.Copy(aww.bufout.Bytes()),
- Stderr: libbytes.Copy(aww.buferr.Bytes()),
+ Stdout: libbytes.Copy(httpd.bufout.Bytes()),
+ Stderr: libbytes.Copy(httpd.buferr.Bytes()),
}
res.Code = http.StatusOK