Files
mstore/app/service/service.go
T
2026-02-04 12:32:54 +02:00

259 lines
7.6 KiB
Go

package service
import (
"context"
"crypto/tls"
"fmt"
"net"
"net/http"
"time"
"mstore/app/handler"
"mstore/app/logger"
"mstore/app/router"
)
const protocol = "tcp"
type ServiceParams struct {
Handler *handler.Handler
X509Cert string
X509Key string
Portnum int64
Address string
}
type Service struct {
hand *handler.Handler
rout *router.Router
logg *logger.Logger
address string
portnum int64
x509cert []byte
x509key []byte
listen net.Listener
hsrv *http.Server
}
func NewService(params *ServiceParams) (*Service, error) {
var err error
svc := &Service{
hand: params.Handler,
x509cert: []byte(params.X509Cert),
x509key: []byte(params.X509Key),
portnum: params.Portnum,
address: params.Address,
}
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.Use(router.NewRecoveryMiddleware(svc.logg.Errorf))
svc.rout.Use(router.NewLoggingMiddleware(svc.logg.Infof))
svc.rout.Use(router.NewCorsMiddleware())
svc.rout.Get("/v3/api/service/hello", svc.hand.SendHello)
svc.rout.Head("/v3/api/file/{filepath}", svc.hand.FileExists)
svc.rout.Put("/v3/api/file/{filepath}", svc.hand.PutFile)
svc.rout.Get("/v3/api/file/{filepath}", svc.hand.GetFile)
svc.rout.Delete("/v3/api/file/{filepath}", svc.hand.DeleteFile)
svc.rout.Get("/v3/api/files/{filepath}", svc.hand.ListFiles)
svc.rout.Get("/v2/", svc.hand.GetVersion)
// https://github.com/opencontainers/distribution-spec/blob/main/spec.md
//
// Pulling manifests
//
// To pull a manifest, perform a GET request to a URL in the following form:
// /v2/<name>/manifests/<reference> end-3
//
// <name> refers to the namespace of the repository.
// <reference> MUST be either (a) the digest of the manifest or (b) a tag.
// The <reference> MUST NOT be in any other format.
//
// Throughout this document, <name> MUST match the following regular expression:
//
// [a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*
//
// Throughout this document, <reference> as a tag MUST be at most 128 characters
// in length and MUST match the following regular expression:
//
// [a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}
// Pushing Manifests
//
// To push a manifest, perform a PUT request to a path in the following format,
// and with the following headers and body: /v2/<name>/manifests/<reference>
const reference = `{reference:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127}}`
svc.rout.Head(`/v2/{name}/manifests/{reference}`, svc.hand.ManifestExists)
svc.rout.Put(`/v2/{name}/manifests/{reference}`, svc.hand.PutManifest)
// Pulling blobs
//
// To pull a blob, perform a GET request to a URL in the following form:
// /v2/<name>/blobs/<digest>
//
// <name> is the namespace of the repository, and <digest> is the blob's digest.
//
// A GET request to an existing blob URL MUST provide the expected blob,
// with a response code that MUST be 200 OK. A successful response SHOULD contain
// the digest of the uploaded blob in the header Docker-Content-Digest. //
// If present, the value of this header MUST be a digest matching that of the
// response body. Most clients MAY ignore the value, but if it is used,
// the client MUST verify the value matches the returned response body.
// Clients SHOULD verify that the response body matches the requested digest.
//
// If the blob is not found in the repository, the response code MUST be 404 Not Found.
svc.rout.Head(`/v2/{name}/blobs/{digest}`, svc.hand.BlobExists)
// Single POST
//
// Registries MAY support pushing blobs using a single POST request.
//
// To push a blob monolithically by using a single POST request, perform a POST request
// to a URL in the following form, and with the following headers and body:
//
// /v2/<name>/blobs/uploads/?digest=<digest>
//
// Content-Length: <length>
// Content-Type: application/octet-stream
//
// <upload byte stream>
//
// Here, <name> is the repository's namespace, <digest> is the blob's digest,
// and <length> is the size (in bytes) of the blob.
//
// The Content-Length header MUST match the blob's actual content length.
// Likewise, the <digest> MUST match the blob's digest.
//
// Registries that do not support single request monolithic uploads SHOULD
// return a 202 Accepted status code and Location header and clients SHOULD
// proceed with a subsequent PUT request, as described by the POST then PUT upload method.
//
// Successful completion of the request MUST return a 201 Created and MUST include the following header:
//
// Location: <blob-location>
//
// Here, <blob-location> is a pullable blob URL. This location does not necessarily
// have to be served by your registry, for example, in the case of a signed URL from
// some cloud storage provider that your registry generates.
svc.rout.Post(`/v2/{name}/blobs/uploads/`, svc.hand.PostUpload)
// Pushing a blob in chunks
//
// A chunked blob upload is accomplished in three phases:
//
// - Obtain a session ID (upload URL) (POST)
// - Upload the chunks (PATCH)
// - Close the session (PUT)
//
// For information on obtaining a session ID, reference the above section on pushing
// a blob monolithically via the POST/PUT method. The process remains unchanged for
// chunked upload, except that the post request MUST include the following header:
//
// Content-Length: 0
//
// If the registry has a minimum chunk size, the POST response SHOULD include
// the following header, where <size> is the size in bytes
// (see the blob PATCH definition for usage):
//
// OCI-Chunk-Min-Length: <size>
//
// Please reference the above section for restrictions on the <location>.
//
// To upload a chunk, issue a PATCH request to a URL path in the following format,
// and with the following headers and body:
//
// URL path: <location>
//
// Content-Type: application/octet-stream
// Content-Range: <range>
// Content-Length: <length>
//
// <upload byte stream of chunk>
//
// The <location> refers to the URL obtained from the preceding POST request.
svc.rout.Patch(`/v2/{name}/blobs/uploads/{reference}`, svc.hand.PatchUpload)
svc.rout.NotFound(svc.hand.NotFound)
selector := svc.rout.Selector()
for _, item := range selector.Routes {
svc.logg.Infof("%s\t%s", item.Method, item.RawPath)
}
const useTLS = true
if useTLS {
tlsCert, err := tls.X509KeyPair(svc.x509cert, svc.x509key)
if err != nil {
return err
}
tlsConfig := tls.Config{
Certificates: []tls.Certificate{tlsCert},
ClientAuth: tls.NoClientCert,
InsecureSkipVerify: true,
}
listenAddress := fmt.Sprintf("%s:%d", svc.address, svc.portnum)
svc.listen, err = tls.Listen(protocol, listenAddress, &tlsConfig)
if err != nil {
return err
}
} else {
listenAddress := fmt.Sprintf("%s:%d", svc.address, svc.portnum)
svc.listen, err = net.Listen(protocol, listenAddress)
if err != nil {
return err
}
}
svc.logg.Infof("Service listening at %v", svc.listen.Addr())
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 == http.ErrServerClosed {
svc.logg.Warningf("Service Closed")
err = nil
}
if err != nil {
return err
}
return err
}
func (svc *Service) Stop() {
if svc.hsrv != nil {
svc.logg.Infof("Service stop")
downWaiting := 10 * time.Second
ctx, cancel := context.WithTimeout(context.Background(), downWaiting)
defer cancel()
err := svc.hsrv.Shutdown(ctx)
if err != nil {
svc.logg.Errorf("Error service shutdown: %v", err)
}
}
}