initial import

This commit is contained in:
2026-01-23 14:41:49 +02:00
commit 772657d9be
37 changed files with 11382 additions and 0 deletions
+20
View File
@@ -0,0 +1,20 @@
package config
type Config struct {
Service Service `json:"service" yaml:"service"`
}
func NewConfig() (*Config, error) {
var err error
return &Config{
Service: Service{
Address: "0.0.0.0",
Port: 1025,
},
}, err
}
type Service struct {
Address string `json:"address" yaml:"address"`
Port int64 `json:"port" yaml:"port"`
}
+12
View File
@@ -0,0 +1,12 @@
package database
type DatabaseParams struct {
}
type Database struct {
}
func NewDatabase(params *DatabaseParams) (*Database, error) {
var err error
return &Database{}, err
}
+1
View File
@@ -0,0 +1 @@
package descr
+14
View File
@@ -0,0 +1,14 @@
package handler
import (
"mstore/app/operator"
"mstore/app/router"
)
func (hand *Handler) FileExists(rctx *router.Context) {
hand.logg.Debugf("handle FileExists")
params := &operator.FileExistsParams{}
res, _ := hand.oper.FileExists(params)
rctx.SetStatus(res.Code)
}
+24
View File
@@ -0,0 +1,24 @@
package handler
import (
"mstore/app/logger"
"mstore/app/operator"
)
type HandlerParams struct {
Operator *operator.Operator
}
type Handler struct {
oper *operator.Operator
logg *logger.Logger
}
func NewHandler(params *HandlerParams) (*Handler, error) {
var err error
hand := &Handler{
oper: params.Operator,
}
hand.logg = logger.NewLogger("handler")
return hand, err
}
+27
View File
@@ -0,0 +1,27 @@
package handler
import (
"mstore/app/router"
)
type Response struct {
Error bool `json:"error" yaml:"error"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
Result any `json:"result,omitempty" yaml:"result,result"`
}
func (hand *Handler) SendResult(rctx *router.Context, result any) {
response := &Response{
Error: false,
Result: result,
}
rctx.SendJSON(response)
}
func (hand *Handler) SendError(rctx *router.Context, err error) {
response := &Response{
Error: true,
Message: err.Error(),
}
rctx.SendJSON(response)
}
+14
View File
@@ -0,0 +1,14 @@
package handler
import (
"mstore/app/operator"
"mstore/app/router"
)
func (hand *Handler) SendHello(rctx *router.Context) {
hand.logg.Debugf("handle sendHello")
params := &operator.SendHelloParams{}
res, _ := hand.oper.SendHello(params)
hand.SendResult(rctx, res)
}
+58
View File
@@ -0,0 +1,58 @@
package logger
import (
"bytes"
"fmt"
"io"
"os"
"sync"
"time"
)
var (
mtx sync.Mutex
output io.Writer = os.Stderr
)
type Logger struct {
subject string
}
func NewLogger(subj string) *Logger {
return &Logger{
subject: subj,
}
}
func SetWriter(newOut io.Writer) {
mtx.Lock()
output = newOut
mtx.Unlock()
}
func (logg *Logger) Debugf(message string, args ...any) {
logg.printf("debug", message, args...)
}
func (logg *Logger) Infof(message string, args ...any) {
logg.printf("info", message, args...)
}
func (logg *Logger) Warningf(message string, args ...any) {
logg.printf("warning", message, args...)
}
func (logg *Logger) Errorf(message string, args ...any) {
logg.printf("error", message, args...)
}
func (logg *Logger) printf(level, message string, args ...any) {
timestamp := time.Now().Format(time.RFC3339)
buffer := bytes.NewBuffer([]byte{})
fmt.Fprintf(buffer, "%s %s.%s: ", timestamp, logg.subject, level)
fmt.Fprintf(buffer, message, args...)
fmt.Fprintf(buffer, "\n")
mtx.Lock()
fmt.Fprint(output, buffer.String())
mtx.Unlock()
}
+30
View File
@@ -0,0 +1,30 @@
package logger
import (
"io/ioutil"
"testing"
)
func TestLogger(t *testing.T) {
logg := NewLogger("test")
logg.Debugf("foo: %s", "bar")
}
func BenchmarkLoggerL(b *testing.B) {
SetWriter(ioutil.Discard)
logg := NewLogger("test")
for i := 0; i < b.N; i++ {
logg.Debugf("foo: %s", "bar")
}
}
func BenchmarkLoggerP(b *testing.B) {
SetWriter(ioutil.Discard)
logg := NewLogger("test")
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
logg.Debugf("foo: %s", "bar")
}
})
}
+19
View File
@@ -0,0 +1,19 @@
package operator
import (
"net/http"
)
type FileExistsParams struct{}
type FileExistsResult struct {
Code int
}
func (oper *Operator) FileExists(param *FileExistsParams) (*FileExistsResult, error) {
var err error
res := &FileExistsResult{
Code: http.StatusOK,
}
return res, err
}
+21
View File
@@ -0,0 +1,21 @@
package operator
import (
"mstore/app/database"
)
type OperatorParams struct {
Database *database.Database
}
type Operator struct {
db *database.Database
}
func NewOperator(params *OperatorParams) (*Operator, error) {
var err error
oper := &Operator{
db: params.Database,
}
return oper, err
}
+14
View File
@@ -0,0 +1,14 @@
package operator
type SendHelloParams struct{}
type SendHelloResult struct {
Message string `json:"message"`
}
func (oper *Operator) SendHello(param *SendHelloParams) (*SendHelloResult, error) {
var err error
res := &SendHelloResult{}
res.Message = "hello"
return res, err
}
+39
View File
@@ -0,0 +1,39 @@
package router
import (
"context"
"encoding/json"
"net/http"
)
type Context struct {
Ctx context.Context
Request *http.Request
Writer http.ResponseWriter
PathMap map[string]string
}
func NewContext(writer http.ResponseWriter, request *http.Request) *Context {
ctx := context.Background()
rctx := &Context{
Writer: writer,
Request: request,
Ctx: ctx,
PathMap: make(map[string]string),
}
return rctx
}
func (rctx *Context) SetStatus(httpStatus int) {
rctx.Writer.WriteHeader(httpStatus)
}
func (rctx *Context) SendJSON(payload any) {
rctx.Writer.Header().Set("Content-Type", "application/json")
json.NewEncoder(rctx.Writer).Encode(payload)
}
func (rctx *Context) SendText(payload string) {
rctx.Writer.Header().Set("Content-Type", "text/plain")
rctx.Writer.Write([]byte(payload))
}
+71
View File
@@ -0,0 +1,71 @@
package router
import (
"fmt"
"strings"
)
const (
compContextPlain int = iota
compContextRegex
startRegex byte = '{'
stopRegex byte = '}'
)
func pathCompiler(path string) (string, error) {
var err error
res := make([]byte, 0)
var pos int = compContextPlain
var depth int = 0
pattern := make([]byte, 0)
for _, b := range []byte(path) {
switch pos {
case compContextPlain:
switch b {
case stopRegex:
depth -= 1
res = append(res, b)
case startRegex:
depth += 1
pos = compContextRegex // pattern started
pattern = make([]byte, 0)
default:
res = append(res, b)
}
case compContextRegex:
switch b {
case startRegex:
depth += 1
case stopRegex:
depth -= 1
if depth == 0 {
pattern = convertRegexp(pattern)
res = append(res, pattern...)
pos = compContextPlain // pattern ended
}
default:
pattern = append(pattern, b)
}
}
}
if depth != 0 {
err = fmt.Errorf("Unbalanced brackets into pattern")
}
return string(res), err
}
const (
defaultRegexp = `[a-zA-Z0-9_][\-\.a-zA-Z0-9_%%=:~]+`
)
func convertRegexp(src []byte) []byte {
var res string
const patternSeps = ":"
parts := strings.SplitN(string(src), patternSeps, 2)
if len(parts) == 1 {
parts = append(parts, defaultRegexp)
}
res = fmt.Sprintf("(?<%s>%s)", parts[0], parts[1])
return []byte(res)
}
+46
View File
@@ -0,0 +1,46 @@
package router
import (
"fmt"
"regexp"
"testing"
"github.com/stretchr/testify/require"
)
func tDebugf(msg string, args ...any) {
fmt.Printf("debug: ")
fmt.Printf(msg, args...)
fmt.Printf("\n")
}
func TestPatchCompilerA(t *testing.T) {
var err error
srcPath := `/v1/file/{collection:[a-zA-Z]+}/{name}`
reSource, err := pathCompiler(srcPath)
require.NoError(t, err)
tDebugf("re: %s\n", reSource)
re, err := regexp.Compile(reSource)
require.NoError(t, err)
reqPath := `/v1/file/foo/bare`
match := re.MatchString(reqPath)
require.True(t, match)
submatch := re.FindStringSubmatch(reqPath)
subnames := re.SubexpNames()
submap := make(map[string]string)
for i, val := range subnames {
tDebugf("subname: %d = %s", i, val)
}
for i, val := range submatch {
key := subnames[i]
if key != "" {
submap[key] = val
}
tDebugf("sub: %d = %s", i, val)
}
tDebugf("submap: %v", submap)
}
+127
View File
@@ -0,0 +1,127 @@
package router
import (
"fmt"
"net/http"
"regexp"
)
type Handler interface {
ServeHTTP(rctx *Context)
}
type HandlerFunc func(rctx *Context)
func (handlerFunc HandlerFunc) ServeHTTP(rctx *Context) {
handlerFunc(rctx)
}
type Router struct {
routeHandler *Selector
}
func NewRouter() *Router {
return &Router{
routeHandler: NewSelector(),
}
}
func (rout *Router) AddRoute(method, path string, handlerFunc HandlerFunc) {
rout.routeHandler.AddRoute(method, path, handlerFunc)
}
func (rout *Router) Get(path string, handlerFunc HandlerFunc) {
rout.routeHandler.AddRoute("GET", path, handlerFunc)
}
func (rout *Router) Post(path string, handlerFunc HandlerFunc) {
rout.routeHandler.AddRoute("POST", path, handlerFunc)
}
func (rout *Router) ServeHTTP(writer http.ResponseWriter, req *http.Request) {
rctx := NewContext(writer, req)
rout.routeHandler.ServeHTTP(rctx)
}
func (rout *Router) NotFound(handlerFunc HandlerFunc) {
rout.routeHandler.notFound = handlerFunc
}
type Selector struct {
Routes []*Route
notFound Handler
}
func NewSelector() *Selector {
notFound := HandlerFunc(func(ctx *Context) {
http.NotFound(ctx.Writer, ctx.Request)
})
return &Selector{
Routes: make([]*Route, 0),
notFound: notFound,
}
}
const globalRoutePattern = `^%s$`
func (hand *Selector) AddRoute(method, path string, handlerFunc HandlerFunc) error {
var err error
path, err = pathCompiler(path)
if err != nil {
return err
}
path = fmt.Sprintf(globalRoutePattern, path)
re, err := regexp.Compile(path)
if err != nil {
return err
}
route := &Route{
Method: method,
Path: path,
Handler: handlerFunc,
Regexp: re,
}
hand.Routes = append(hand.Routes, route)
return err
}
func (hand *Selector) ServeHTTP(rctx *Context) {
realHandler := hand.notFound
for _, route := range hand.Routes {
match := route.Match(rctx.Request)
if !match {
continue
}
subvals := route.Regexp.FindStringSubmatch(rctx.Request.URL.Path)
subkeys := route.Regexp.SubexpNames()
for i, val := range subvals {
key := subkeys[i]
if key != "" {
rctx.PathMap[key] = val
}
}
realHandler = route.Handler
break
}
realHandler.ServeHTTP(rctx)
}
type Route struct {
Method string
Regexp *regexp.Regexp
Path string
Handler HandlerFunc
}
func (route Route) Match(req *http.Request) bool {
if req.Method != route.Method {
return false
}
match := route.Regexp.MatchString(req.URL.Path)
if match {
return true
}
return false
}
+109
View File
@@ -0,0 +1,109 @@
package router
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/require"
)
func TestRouterStatus(t *testing.T) {
reqPath := "/hello"
handler := NewRouter()
helloHandler := func(rctx *Context) {
rctx.SetStatus(http.StatusOK)
}
handler.Get(reqPath, helloHandler)
request, err := http.NewRequest("GET", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
require.Equal(t, http.StatusOK, recorder.Code)
fmt.Printf("Response code: %d\n", recorder.Code)
}
func TestRouterSendText(t *testing.T) {
testText := "hello, world"
reqPath := "/hello"
rout := NewRouter()
helloHandler := func(rctx *Context) {
rctx.SendText(testText)
}
rout.Get(reqPath, helloHandler)
request, err := http.NewRequest("GET", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
rout.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
require.Equal(t, http.StatusOK, recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, string(bodyBytes), testText)
}
func TestRouterSendJSON(t *testing.T) {
type testStruct struct {
Message string `json:"message"`
}
testVar := testStruct{
Message: "hello, world",
}
testData, err := json.Marshal(testVar)
require.NoError(t, err)
reqPath := "/hello"
handler := NewRouter()
helloHandler := func(rctx *Context) {
rctx.SendJSON(&testVar)
}
handler.Get(reqPath, helloHandler)
request, err := http.NewRequest("GET", reqPath, nil)
require.NoError(t, err)
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
fmt.Printf("Response code: %d\n", recorder.Code)
require.Equal(t, http.StatusOK, recorder.Code)
bodyReader := recorder.Body
bodyBytes, err := io.ReadAll(bodyReader)
require.NoError(t, err)
bodyBytes = bytes.Trim(bodyBytes, "\n\r")
fmt.Printf("Response body: %s\n", string(bodyBytes))
require.Equal(t, string(testData), string(bodyBytes))
}
func BenchmarkLoggerL(b *testing.B) {
reqPath := "/hello"
helloHandler := func(rctx *Context) {
rctx.SetStatus(http.StatusOK)
}
handler := NewRouter()
handler.Get(reqPath, helloHandler)
request, err := http.NewRequest("GET", reqPath, nil)
require.NoError(b, err)
for i := 0; i < b.N; i++ {
recorder := httptest.NewRecorder()
handler.ServeHTTP(recorder, request)
require.Equal(b, http.StatusOK, recorder.Code)
}
}
+123
View File
@@ -0,0 +1,123 @@
package server
import (
//"io/ioutil"
"os"
"os/signal"
"os/user"
//"os/user"
//"path/filepath"
//"strconv"
//"sync"
"syscall"
//"time"
"mstore/app/database"
"mstore/app/handler"
"mstore/app/logger"
"mstore/app/operator"
"mstore/app/service"
)
type Server struct {
oper *operator.Operator
svc *service.Service
db *database.Database
hand *handler.Handler
logg *logger.Logger
}
func NewServer() (*Server, error) {
var err error
srv := &Server{}
srv.logg = logger.NewLogger("server")
return srv, err
}
func (srv *Server) Handler() *handler.Handler {
return srv.hand
}
func (srv *Server) Configure() error {
var err error
srv.logg.Infof("Server configure")
return err
}
func (srv *Server) Build() error {
var err error
srv.logg.Infof("Server build")
// Database create
databaseParams := &database.DatabaseParams{}
srv.db, err = database.NewDatabase(databaseParams)
if err != nil {
return err
}
// Operator create
operatorParams := &operator.OperatorParams{
Database: srv.db,
}
srv.oper, err = operator.NewOperator(operatorParams)
if err != nil {
return err
}
// Handler create
handlerParams := &handler.HandlerParams{
Operator: srv.oper,
}
srv.hand, err = handler.NewHandler(handlerParams)
if err != nil {
return err
}
// Service create
serviceParams := &service.ServiceParams{
Handler: srv.hand,
}
srv.svc, err = service.NewService(serviceParams)
if err != nil {
return err
}
// Service build
err = srv.svc.Build()
if err != nil {
return err
}
return err
}
func (srv *Server) Run() error {
var err error
currUser, err := user.Current()
if err != nil {
return err
}
srv.logg.Infof("Server run as user %s", currUser.Username)
sigs := make(chan os.Signal, 1)
svcDone := make(chan error, 1)
// Service run
startService := func(svc *service.Service, done chan error) {
err = svc.Run()
if err != nil {
srv.logg.Errorf("Service error: %v", err)
done <- err
}
}
go startService(srv.svc, svcDone)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
var signal os.Signal
select {
case signal = <-sigs:
srv.logg.Infof("Services stopped by signal: %v", signal)
srv.svc.Stop()
case err = <-svcDone:
srv.logg.Infof("Service stopped by service error: %v", err)
srv.svc.Stop()
}
return err
}
+16
View File
@@ -0,0 +1,16 @@
package service
import (
"fmt"
)
const (
PathServiceHello = "/service/hello"
)
var (
pathRegexp = `{path:[a-zA-Z_][\-a-zA-Z0-9_\/\.%~]+}`
fileOperPrefix = "/v3/file"
PathFileExists = fmt.Sprintf("%s/%s", fileOperPrefix, pathRegexp)
)
+74
View File
@@ -0,0 +1,74 @@
package service
import (
"context"
"fmt"
"net"
"net/http"
"time"
"mstore/app/handler"
"mstore/app/logger"
"mstore/app/router"
)
const protocol = "tcp"
type ServiceParams struct {
Handler *handler.Handler
}
type Service struct {
hand *handler.Handler
rout *router.Router
logg *logger.Logger
address string
portnum int64
listen net.Listener
hsrv *http.Server
}
func NewService(params *ServiceParams) (*Service, error) {
var err error
svc := &Service{
hand: params.Handler,
}
svc.logg = logger.NewLogger("service")
return svc, err
}
func (svc *Service) Build() error {
var err error
svc.logg.Infof("Service build ")
svc.rout = router.NewRouter()
svc.rout.Get(PathServiceHello, svc.hand.SendHello)
svc.rout.Get(PathFileExists, svc.hand.FileExists)
listenAddress := fmt.Sprintf("%s:%d", svc.address, svc.portnum)
svc.listen, err = net.Listen(protocol, listenAddress)
svc.hsrv = &http.Server{
Handler: svc.rout,
}
return err
}
func (svc *Service) Run() error {
var err error
svc.logg.Infof("Service run")
err = svc.hsrv.Serve(svc.listen)
if err != nil {
return err
}
return err
}
func (svc *Service) Stop() {
svc.logg.Infof("Service stop")
if svc.hsrv != nil {
downWaiting := 5 * time.Second
ctx, _ := context.WithTimeout(context.Background(), downWaiting)
svc.hsrv.Shutdown(ctx)
}
}
+1
View File
@@ -0,0 +1 @@
package storage