From 9c23428d83b07b0d19ac485923e4df426ba4f88c Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:37:03 +0100 Subject: [PATCH 01/10] chore(server): document and maintain This commit makes sure we thoroughly document how the server works and applies some maintenance changes. The most relevant changes are: 1. using io.ReadAll instead of ioutil.ReadAll; 2. dropping rand.Seed calls. We can safely drop rand.Seed calls because: > Deprecated: As of Go 1.20 there is no reason to call Seed > with a random value. Programs that call Seed with a known value > to get a specific sequence of results should use > New(NewSource(seed)) to obtain a local random generator. We use go1.20 in Dockerfile and go.mod. --- server/server.go | 197 +++++++++++++++++++++++++++++++++--------- server/server_test.go | 10 +-- spec/spec.go | 10 ++- 3 files changed, 169 insertions(+), 48 deletions(-) diff --git a/server/server.go b/server/server.go index 9aab7e7..b12e6fd 100644 --- a/server/server.go +++ b/server/server.go @@ -1,4 +1,4 @@ -// Package server implements the DASH server +// Package server implements the DASH server. package server import ( @@ -7,7 +7,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "math/rand" "net" "net/http" @@ -24,15 +23,22 @@ import ( "github.com/neubot/dash/spec" ) +// sessionInfo contains information about an active session. type sessionInfo struct { - iteration int64 + // iteration is the number of iterations done by the active session. + iteration int64 + + // serverSchema contains the server schema for the given session. serverSchema model.ServerSchema - stamp time.Time + + // stamp is when we created this struct. + stamp time.Time } +// dependencies abstracts the dependencies used by [*Handler]. type dependencies struct { GzipNewWriterLevel func(w io.Writer, level int) (*gzip.Writer, error) - IOUtilReadAll func(r io.Reader) ([]byte, error) + IOReadAll func(r io.Reader) ([]byte, error) JSONMarshal func(v interface{}) ([]byte, error) OSMkdirAll func(path string, perm os.FileMode) error OSOpenFile func(name string, flag int, perm os.FileMode) (*os.File, error) @@ -43,43 +49,61 @@ type dependencies struct { // Handler is the DASH handler. Please use NewHandler to construct // a valid instance of this type (the zero value is invalid). +// +// You need to call the RegisterHandlers method to register the proper +// DASH handlers. You also need to call StartReaper to periodically +// get rid of sessions that have been running for too much. If you don't +// call StartReaper, you will eventually run out of RAM. type Handler struct { - // Datadir is the directory where to save measurements + // Datadir is the directory where to save measurements. Datadir string // Logger is the logger to use. This field is initialized by the // NewHandler constructor to a do-nothing logger. Logger model.Logger - deps dependencies + // deps contains the [*Handler] dependencies. + deps dependencies + + // maxIterations is the maximum allowed number of iterations. maxIterations int64 - mtx sync.Mutex - sessions map[string]*sessionInfo - stop chan interface{} + + // mtx protects the sessions map. + mtx sync.Mutex + + // sessions maps a session UUID to session info. + sessions map[string]*sessionInfo + + // stop is closed when the reaper goroutine is stopped. + stop chan any } -// NewHandler creates a new handler instance +// NewHandler creates a new [*Handler] instance. func NewHandler(datadir string) (handler *Handler) { handler = &Handler{ - Datadir: datadir, - Logger: internal.NoLogger{}, + Datadir: datadir, + Logger: internal.NoLogger{}, + deps: dependencies{ + GzipNewWriterLevel: gzip.NewWriterLevel, + IOReadAll: io.ReadAll, + JSONMarshal: json.Marshal, + OSMkdirAll: os.MkdirAll, + OSOpenFile: os.OpenFile, + RandRead: rand.Read, // math/rand is okay to use here + Savedata: handler.savedata, + UUIDNewRandom: uuid.NewRandom, + }, maxIterations: 17, + mtx: sync.Mutex{}, sessions: make(map[string]*sessionInfo), stop: make(chan interface{}), } - handler.deps = dependencies{ - GzipNewWriterLevel: gzip.NewWriterLevel, - IOUtilReadAll: ioutil.ReadAll, - JSONMarshal: json.Marshal, - OSMkdirAll: os.MkdirAll, - OSOpenFile: os.OpenFile, - RandRead: rand.Read, - Savedata: handler.savedata, - UUIDNewRandom: uuid.NewRandom, - } return } +// createSession creates a session using the given UUID. +// +// This method LOCKS and MUTATES the .sessions field. func (h *Handler) createSession(UUID string) { now := time.Now() session := &sessionInfo{ @@ -94,14 +118,22 @@ func (h *Handler) createSession(UUID string) { h.sessions[UUID] = session } +// sessionState is the state of a measurement session. type sessionState int const ( + // sessionMissing indicates that a session with the given UUID does not exist. sessionMissing = sessionState(iota) + + // sessionActive indicates that a session exists and it has not performed + // the maximum number of allowed iterations yet. sessionActive + + // sessionExpired is a session that performed all the possible iterations. sessionExpired ) +// getSessionState returns the state of the session with the given UUID. func (h *Handler) getSessionState(UUID string) sessionState { h.mtx.Lock() defer h.mtx.Unlock() @@ -115,7 +147,16 @@ func (h *Handler) getSessionState(UUID string) sessionState { return sessionActive } -func (h *Handler) updateSession(UUID string, count int) { +// updateSession updates the state of the session with the given UUID after +// we successfully performed a new iteration. +// +// When the UUID maps to an existing session, this method MUTATES the +// session's serverSchema by adding a new measurement result and by +// incrementing the number of iterations. +// +// The integer argument, currently ignored, contains the number of bytes +// that were sent as part of the current DASH iteration. +func (h *Handler) updateSession(UUID string, _ int) { now := time.Now() h.mtx.Lock() defer h.mtx.Unlock() @@ -132,6 +173,8 @@ func (h *Handler) updateSession(UUID string, count int) { } } +// popSession returns nil if a session with the given UUID does not exist, otherwise +// is SAFELY REMOVES and returns the corresponding [*sessionInfo]. func (h *Handler) popSession(UUID string) *sessionInfo { h.mtx.Lock() defer h.mtx.Unlock() @@ -143,7 +186,7 @@ func (h *Handler) popSession(UUID string) *sessionInfo { return session } -// CountSessions return the number of active sessions +// CountSessions return the number of active sessions. func (h *Handler) CountSessions() (count int) { h.mtx.Lock() defer h.mtx.Unlock() @@ -151,6 +194,7 @@ func (h *Handler) CountSessions() (count int) { return } +// reapStaleSessions SAFELY REMOVES all the sessions created more than 60 seconds ago. func (h *Handler) reapStaleSessions() { h.mtx.Lock() defer h.mtx.Unlock() @@ -169,19 +213,38 @@ func (h *Handler) reapStaleSessions() { } } +// negotiate implements the /negotiate/dash handler. +// +// Neubot originally implemented access control and parameters negotiation in +// this preliminary measurement stage. This implementation relies on m-lab's locate +// service to implement access control so we only negotiate the parameters. We +// assume that m-lab's incoming request interceptor will take care of the authorization +// token passed as part of the request itself. +// +// This method SAFELY MUTATES the sessions map by creating a new session UUID. If +// clients do not call this method first, measurements will fail for lack of a valid +// session UUID. func (h *Handler) negotiate(w http.ResponseWriter, r *http.Request) { + // Obtain the client's remote address. address, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { h.Logger.Warnf("negotiate: net.SplitHostPort: %s", err.Error()) w.WriteHeader(500) return } + + // Create a new random UUID for the session. + // + // We assume we're not going to have UUID conflicts. UUID, err := h.deps.UUIDNewRandom() if err != nil { h.Logger.Warnf("negotiate: uuid.NewRandom: %s", err.Error()) w.WriteHeader(500) return } + + // Prepare the esponse. + // // Implementation note: we do not include any vector of speeds // in the response, meaning that the client should use its predefined // vector of speeds rather than using ours. This vector of speeds @@ -197,14 +260,18 @@ func (h *Handler) negotiate(w http.ResponseWriter, r *http.Request) { RealAddress: address, Unchoked: 1, }) + + // Make sure we can properly marshal the response. if err != nil { h.Logger.Warnf("negotiate: json.Marshal: %s", err.Error()) w.WriteHeader(500) return } + + // Send the response. w.Header().Set("Content-Type", "application/json") h.createSession(UUID.String()) - w.Write(data) + _, _ = w.Write(data) } const ( @@ -220,22 +287,21 @@ const ( // the docs of MinSize for more information on how it is computed. maxSize = 30000 * 1000 / 8 * 2 + // authorization is the key for the Authorization header. authorization = "Authorization" ) -var ( - once sync.Once - minSizeString = fmt.Sprintf("%d", minSize) -) +// minSize string is the string representation of the minSize constant. +var minSizeString = fmt.Sprintf("%d", minSize) +// genbody generates the body and updates the count argument to +// be within the acceptable bounds allowed by the protocol. +// +// Implementation note: because one may be lax during refactoring +// and may end up using count rather than len(data) and because +// count may be way bigger than the real data length, I've changed +// this function to _also_ update count to the real value. func (h *Handler) genbody(count *int) (data []byte, err error) { - // Implementation note: because one may be lax during refactoring - // and may end up using count rather than len(data) and because - // count may be way bigger than the real data length, I've changed - // this function to _also_ update count to the real value. - once.Do(func() { - rand.Seed(time.Now().UTC().UnixNano()) - }) if *count < minSize { *count = minSize } @@ -247,7 +313,9 @@ func (h *Handler) genbody(count *int) (data []byte, err error) { return } +// download implements the /dash/download handler. func (h *Handler) download(w http.ResponseWriter, r *http.Request) { + // make sure we have a valid session sessionID := r.Header.Get(authorization) state := h.getSessionState(sessionID) if state == sessionMissing { @@ -255,6 +323,10 @@ func (h *Handler) download(w http.ResponseWriter, r *http.Request) { w.WriteHeader(400) return } + + // Make sure the session did not expire (i.e., that it did not + // send too many requests as part of the same session). + // // The Neubot implementation used to raise runtime error in this case // leading to 500 being returned to the client. Here we deviate from // the original implementation returning a value that seems to be much @@ -264,6 +336,9 @@ func (h *Handler) download(w http.ResponseWriter, r *http.Request) { w.WriteHeader(429) return } + + // obtain the number of bytes we should send to the client according + // to what the client would like to receive. siz := strings.Replace(r.URL.Path, "/dash/download", "", -1) siz = strings.TrimPrefix(siz, "/") if siz == "" { @@ -275,26 +350,44 @@ func (h *Handler) download(w http.ResponseWriter, r *http.Request) { w.WriteHeader(400) return } + + // generate body possibly adjusting the count if it falls out of + // the acceptable bounds for the response size. data, err := h.genbody(&count) if err != nil { h.Logger.Warnf("download: genbody: %s", err.Error()) w.WriteHeader(500) return } + + // Register that the session has done an iteration. h.updateSession(sessionID, len(data)) + + // Send the response. w.Header().Set("Content-Type", "video/mp4") w.Header().Set("Content-Length", strconv.Itoa(len(data))) - w.Write(data) + _, _ = w.Write(data) } +// savedata is an utility function saving information about this session. func (h *Handler) savedata(session *sessionInfo) error { + // obtain the directory path where to write name := path.Join(h.Datadir, "dash", session.stamp.Format("2006/01/02")) + + // make sure we have the correct directory hierarchy err := h.deps.OSMkdirAll(name, 0755) if err != nil { h.Logger.Warnf("savedata: os.MkdirAll: %s", err.Error()) return err } + + // append the file name to the path + // + // TODO(bassosimone): this code does not work as intended on Windows name += "/neubot-dash-" + session.stamp.Format("20060102T150405.000000000Z") + ".json.gz" + + // open the results file + // // My assumption here is that we have nanosecond precision and hence it's // unlikely to have conflicts. If I'm wrong, O_EXCL will let us know. filep, err := h.deps.OSOpenFile(name, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) @@ -303,68 +396,89 @@ func (h *Handler) savedata(session *sessionInfo) error { return err } defer filep.Close() + + // wrap the output file with a gzipper zipper, err := h.deps.GzipNewWriterLevel(filep, gzip.BestSpeed) if err != nil { h.Logger.Warnf("savedata: gzip.NewWriterLevel: %s", err.Error()) return err } defer zipper.Close() + + // marshal the measurement to JSON data, err := h.deps.JSONMarshal(session.serverSchema) if err != nil { h.Logger.Warnf("savedata: json.Marshal: %s", err.Error()) return err } + + // write compressed data into the file _, err = zipper.Write(data) return err } +// collect implements the /collect/dash handler. func (h *Handler) collect(w http.ResponseWriter, r *http.Request) { + // make sure we have a session session := h.popSession(r.Header.Get(authorization)) if session == nil { h.Logger.Warn("collect: session missing") w.WriteHeader(400) return } - data, err := h.deps.IOUtilReadAll(r.Body) + + // read the incoming measurements collected by the client + data, err := h.deps.IOReadAll(r.Body) if err != nil { h.Logger.Warnf("collect: ioutil.ReadAll: %s", err.Error()) w.WriteHeader(400) return } + + // un,arshal client data from JSON into the server data structure err = json.Unmarshal(data, &session.serverSchema.Client) if err != nil { h.Logger.Warnf("collect: json.Unmarshal: %s", err.Error()) w.WriteHeader(400) return } + + // serialize all data, err = h.deps.JSONMarshal(session.serverSchema.Server) if err != nil { h.Logger.Warnf("collect: json.Marshal: %s", err.Error()) w.WriteHeader(500) return } + + // save on disk err = h.deps.Savedata(session) if err != nil { // Error already printed by h.savedata() w.WriteHeader(500) return } + + // tell the client we're all good w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Length", strconv.Itoa(len(data))) - w.Write([]byte(data)) + _, _ = w.Write([]byte(data)) } // RegisterHandlers registers handlers for the URLs used by the DASH // experiment. The following prefixes are registered: // // - /negotiate/dash -// - /dash/download +// - /dash/download/{size} // - /collect/dash // // The /negotiate/dash prefix is used to create a measurement // context for a dash client. The /download/dash prefix is // used by clients to request data segments. The /collect/dash // prefix is used to submit client measurements. +// +// For historical reasons /dash/download is an alias for +// using the /dash/download prefix. func (h *Handler) RegisterHandlers(mux *http.ServeMux) { mux.HandleFunc(spec.NegotiatePath, h.negotiate) mux.HandleFunc(spec.DownloadPath, h.download) @@ -372,6 +486,7 @@ func (h *Handler) RegisterHandlers(mux *http.ServeMux) { mux.HandleFunc(spec.CollectPath, h.collect) } +// reaperLoop is the goroutine that periodically reapts expired sessions. func (h *Handler) reaperLoop(ctx context.Context) { h.Logger.Debug("reaperLoop: start") defer h.Logger.Debug("reaperLoop: done") diff --git a/server/server_test.go b/server/server_test.go index fc345fe..90c9968 100644 --- a/server/server_test.go +++ b/server/server_test.go @@ -342,7 +342,7 @@ func TestServerCollect(t *testing.T) { req := new(http.Request) req.Header = make(http.Header) req.Header.Add(authorization, session) - handler.deps.IOUtilReadAll = func(r io.Reader) ([]byte, error) { + handler.deps.IOReadAll = func(r io.Reader) ([]byte, error) { return nil, errors.New("Mocked error") } w := httptest.NewRecorder() @@ -360,7 +360,7 @@ func TestServerCollect(t *testing.T) { req := new(http.Request) req.Header = make(http.Header) req.Header.Add(authorization, session) - handler.deps.IOUtilReadAll = func(r io.Reader) ([]byte, error) { + handler.deps.IOReadAll = func(r io.Reader) ([]byte, error) { return []byte("{"), nil } w := httptest.NewRecorder() @@ -378,7 +378,7 @@ func TestServerCollect(t *testing.T) { req := new(http.Request) req.Header = make(http.Header) req.Header.Add(authorization, session) - handler.deps.IOUtilReadAll = func(r io.Reader) ([]byte, error) { + handler.deps.IOReadAll = func(r io.Reader) ([]byte, error) { return []byte("[]"), nil } handler.deps.JSONMarshal = func(v interface{}) ([]byte, error) { @@ -399,7 +399,7 @@ func TestServerCollect(t *testing.T) { req := new(http.Request) req.Header = make(http.Header) req.Header.Add(authorization, session) - handler.deps.IOUtilReadAll = func(r io.Reader) ([]byte, error) { + handler.deps.IOReadAll = func(r io.Reader) ([]byte, error) { return []byte("[]"), nil } handler.deps.JSONMarshal = func(v interface{}) ([]byte, error) { @@ -423,7 +423,7 @@ func TestServerCollect(t *testing.T) { req := new(http.Request) req.Header = make(http.Header) req.Header.Add(authorization, session) - handler.deps.IOUtilReadAll = func(r io.Reader) ([]byte, error) { + handler.deps.IOReadAll = func(r io.Reader) ([]byte, error) { return []byte("[]"), nil } handler.deps.JSONMarshal = func(v interface{}) ([]byte, error) { diff --git a/spec/spec.go b/spec/spec.go index 549a845..9867999 100644 --- a/spec/spec.go +++ b/spec/spec.go @@ -8,7 +8,10 @@ const ( // M-Lab anymore and hence we need to make a breaking change. CurrentServerSchemaVersion = 4 - // NegotiatePath is the URL path used to negotiate + // NegotiatePath is the URL path used to negotiate. We use /negotiate/dash + // rather than /dash/negotiate for historical reasons. Neubot used to + // handle all requests for negotiation by handling the /negotiate prefix + // and routing to the proper experiment. NegotiatePath = "/negotiate/dash" // DownloadPathNoTrailingSlash is like DownloadPath but has no @@ -21,7 +24,10 @@ const ( // the server to send you as part of the next chunk. DownloadPath = DownloadPathNoTrailingSlash + "/" - // CollectPath is the URL path used to collect + // CollectPath is the URL path used to collect. We use /collect/dash + // rather than /dash/collect for historical reasons. Neubot used to + // handle all requests for collection by handling the /collect prefix + // and routing to the proper experiment. CollectPath = "/collect/dash" ) From 16777d2bd9861fb163d3b499be61e175d0426585 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:41:05 +0100 Subject: [PATCH 02/10] x --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index b12e6fd..605164b 100644 --- a/server/server.go +++ b/server/server.go @@ -150,7 +150,7 @@ func (h *Handler) getSessionState(UUID string) sessionState { // updateSession updates the state of the session with the given UUID after // we successfully performed a new iteration. // -// When the UUID maps to an existing session, this method MUTATES the +// When the UUID maps to an existing session, this method SAFELY MUTATES the // session's serverSchema by adding a new measurement result and by // incrementing the number of iterations. // From 370691db90ee1767ea4e99fa5ab1ddd9f952cfe3 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:42:26 +0100 Subject: [PATCH 03/10] x --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 605164b..9f306de 100644 --- a/server/server.go +++ b/server/server.go @@ -186,7 +186,7 @@ func (h *Handler) popSession(UUID string) *sessionInfo { return session } -// CountSessions return the number of active sessions. +// CountSessions SAFELY COUNTS and returns the number of active sessions. func (h *Handler) CountSessions() (count int) { h.mtx.Lock() defer h.mtx.Unlock() From a496c6e07f7871f8cf2147a3f9769e18a6090960 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:43:17 +0100 Subject: [PATCH 04/10] x --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 9f306de..3bbc49c 100644 --- a/server/server.go +++ b/server/server.go @@ -219,7 +219,7 @@ func (h *Handler) reapStaleSessions() { // this preliminary measurement stage. This implementation relies on m-lab's locate // service to implement access control so we only negotiate the parameters. We // assume that m-lab's incoming request interceptor will take care of the authorization -// token passed as part of the request itself. +// token passed as part of the request URL. // // This method SAFELY MUTATES the sessions map by creating a new session UUID. If // clients do not call this method first, measurements will fail for lack of a valid From b7362d103d43d15c2096068f98c5421e5ebb17ca Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:43:57 +0100 Subject: [PATCH 05/10] x --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 3bbc49c..a05d4e3 100644 --- a/server/server.go +++ b/server/server.go @@ -243,7 +243,7 @@ func (h *Handler) negotiate(w http.ResponseWriter, r *http.Request) { return } - // Prepare the esponse. + // Prepare the response. // // Implementation note: we do not include any vector of speeds // in the response, meaning that the client should use its predefined From 23034294c652de4051d4b5a87065ddd638fccfd8 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:45:21 +0100 Subject: [PATCH 06/10] x --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index a05d4e3..94cf8ee 100644 --- a/server/server.go +++ b/server/server.go @@ -435,7 +435,7 @@ func (h *Handler) collect(w http.ResponseWriter, r *http.Request) { return } - // un,arshal client data from JSON into the server data structure + // unmarshal client data from JSON into the server data structure err = json.Unmarshal(data, &session.serverSchema.Client) if err != nil { h.Logger.Warnf("collect: json.Unmarshal: %s", err.Error()) From c9abebeee4db5586b7da2fb5d3088159f1d855df Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:46:07 +0100 Subject: [PATCH 07/10] x --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 94cf8ee..efc79a3 100644 --- a/server/server.go +++ b/server/server.go @@ -478,7 +478,7 @@ func (h *Handler) collect(w http.ResponseWriter, r *http.Request) { // prefix is used to submit client measurements. // // For historical reasons /dash/download is an alias for -// using the /dash/download prefix. +// using the /dash/download/ prefix. func (h *Handler) RegisterHandlers(mux *http.ServeMux) { mux.HandleFunc(spec.NegotiatePath, h.negotiate) mux.HandleFunc(spec.DownloadPath, h.download) From a48af06c8c9b9eddda61288b87fa3c632d1f9757 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:46:41 +0100 Subject: [PATCH 08/10] x --- server/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index efc79a3..355a399 100644 --- a/server/server.go +++ b/server/server.go @@ -486,7 +486,7 @@ func (h *Handler) RegisterHandlers(mux *http.ServeMux) { mux.HandleFunc(spec.CollectPath, h.collect) } -// reaperLoop is the goroutine that periodically reapts expired sessions. +// reaperLoop is the goroutine that periodically reaps expired sessions. func (h *Handler) reaperLoop(ctx context.Context) { h.Logger.Debug("reaperLoop: start") defer h.Logger.Debug("reaperLoop: done") From 5dc77288049ddf6973f6bd44769e2a0a0d2d31f4 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:56:04 +0100 Subject: [PATCH 09/10] x --- server/server.go | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/server/server.go b/server/server.go index 355a399..c9b4c62 100644 --- a/server/server.go +++ b/server/server.go @@ -81,23 +81,24 @@ type Handler struct { // NewHandler creates a new [*Handler] instance. func NewHandler(datadir string) (handler *Handler) { handler = &Handler{ - Datadir: datadir, - Logger: internal.NoLogger{}, - deps: dependencies{ - GzipNewWriterLevel: gzip.NewWriterLevel, - IOReadAll: io.ReadAll, - JSONMarshal: json.Marshal, - OSMkdirAll: os.MkdirAll, - OSOpenFile: os.OpenFile, - RandRead: rand.Read, // math/rand is okay to use here - Savedata: handler.savedata, - UUIDNewRandom: uuid.NewRandom, - }, + Datadir: datadir, + Logger: internal.NoLogger{}, + deps: dependencies{}, // initialized later maxIterations: 17, mtx: sync.Mutex{}, sessions: make(map[string]*sessionInfo), stop: make(chan interface{}), } + handler.deps = dependencies{ + GzipNewWriterLevel: gzip.NewWriterLevel, + IOReadAll: io.ReadAll, + JSONMarshal: json.Marshal, + OSMkdirAll: os.MkdirAll, + OSOpenFile: os.OpenFile, + RandRead: rand.Read, // math/rand is okay to use here + Savedata: handler.savedata, + UUIDNewRandom: uuid.NewRandom, + } return } From 44e74ce7af9c064c5e5994fd2b3f1c0fda77ced7 Mon Sep 17 00:00:00 2001 From: Simone Basso Date: Mon, 29 Jan 2024 20:56:54 +0100 Subject: [PATCH 10/10] x --- server/server.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/server.go b/server/server.go index c9b4c62..d6d413c 100644 --- a/server/server.go +++ b/server/server.go @@ -79,8 +79,8 @@ type Handler struct { } // NewHandler creates a new [*Handler] instance. -func NewHandler(datadir string) (handler *Handler) { - handler = &Handler{ +func NewHandler(datadir string) *Handler { + handler := &Handler{ Datadir: datadir, Logger: internal.NoLogger{}, deps: dependencies{}, // initialized later @@ -99,7 +99,7 @@ func NewHandler(datadir string) (handler *Handler) { Savedata: handler.savedata, UUIDNewRandom: uuid.NewRandom, } - return + return handler } // createSession creates a session using the given UUID.