@@ -38,6 +38,8 @@ import (
3838 "sync"
3939 "time"
4040
41+ "github.com/gorilla/mux"
42+
4143 "github.com/snapcore/snapd/asserts"
4244 "github.com/snapcore/snapd/asserts/sysdb"
4345 "github.com/snapcore/snapd/asserts/systestkeys"
@@ -89,9 +91,39 @@ type Store struct {
8991 killAfter map [string ]int64
9092}
9193
94+ type wrappedWriter struct {
95+ http.ResponseWriter
96+ s int
97+ }
98+
99+ func (w * wrappedWriter ) WriteHeader (s int ) {
100+ w .s = s
101+ w .ResponseWriter .WriteHeader (s )
102+ }
103+
104+ func logit (next http.Handler ) http.Handler {
105+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
106+ ww := & wrappedWriter {ResponseWriter : w }
107+ t0 := time .Now ()
108+ next .ServeHTTP (ww , r )
109+ t := time .Since (t0 )
110+ logger .Noticef ("%s %s %s %s %d" , r .RemoteAddr , r .Method , r .URL , t , ww .s )
111+ })
112+ }
113+
114+ func logRangeHeader (next http.Handler ) http.Handler {
115+ return http .HandlerFunc (func (w http.ResponseWriter , r * http.Request ) {
116+ if len (r .Header ["Range" ]) > 0 {
117+ logger .Noticef (`%s %s %s range request %v` , r .RemoteAddr , r .Method , r .URL , r .Header ["Range" ])
118+ }
119+ next .ServeHTTP (w , r )
120+ })
121+ }
122+
92123// NewStore creates a new store server serving snaps from the given top directory and assertions from topDir/asserts. If assertFallback is true missing assertions are looked up in the main online store.
93124func NewStore (topDir , addr string , assertFallback bool ) * Store {
94- mux := http .NewServeMux ()
125+ r := mux .NewRouter ()
126+
95127 var sto * store.Store
96128 if assertFallback {
97129 snapdenv .SetUserAgentFromVersion ("unknown" , nil , "fakestore" )
@@ -107,7 +139,7 @@ func NewStore(topDir, addr string, assertFallback bool) *Store {
107139 url : fmt .Sprintf ("http://%s" , addr ),
108140 srv : & http.Server {
109141 Addr : addr ,
110- Handler : mux ,
142+ Handler : r ,
111143 },
112144 channelRepository : & ChannelRepository {
113145 rootDir : filepath .Join (topDir , "channels" ),
@@ -116,24 +148,31 @@ func NewStore(topDir, addr string, assertFallback bool) *Store {
116148 killAfter : make (map [string ]int64 ),
117149 }
118150
119- mux .HandleFunc ("/" , rootEndpoint )
120- mux .HandleFunc ("/api/v1/snaps/search" , store .searchEndpoint )
121- mux .HandleFunc ("/api/v1/snaps/details/" , store .detailsEndpoint )
122- mux .HandleFunc ("/api/v1/snaps/metadata" , store .bulkEndpoint )
151+ r .Use (logit )
152+ r .Use (store .applyKillAfter )
153+
154+ r .HandleFunc ("/" , rootEndpoint )
155+ r .HandleFunc ("/api/v1/snaps/search" , store .searchEndpoint )
156+ r .HandleFunc ("/api/v1/snaps/details/{name}" , store .detailsEndpoint ).Methods ("GET" )
157+ r .HandleFunc ("/api/v1/snaps/metadata" , store .bulkEndpoint ).Methods ("POST" )
123158
124159 fileServer := http .StripPrefix ("/download/" , http .FileServer (http .Dir (topDir )))
125- mux .Handle ("/download/" , logRangeHeader (store .applyKillAfter (fileServer .ServeHTTP )))
160+ dr := r .PathPrefix ("/download/" ).Subrouter ()
161+ dr .Use (logRangeHeader )
162+ dr .PathPrefix ("/" ).HandlerFunc (fileServer .ServeHTTP ).Methods ("GET" )
126163
127- mux .HandleFunc ("/api/v1/snaps/auth/nonces" , store .nonceEndpoint )
128- mux .HandleFunc ("/api/v1/snaps/auth/sessions" , store .sessionEndpoint )
164+ r .HandleFunc ("/api/v1/snaps/auth/nonces" , store .nonceEndpoint )
165+ r .HandleFunc ("/api/v1/snaps/auth/sessions" , store .sessionEndpoint )
129166
130167 // v2
131- mux .HandleFunc ("/v2/assertions/" , store .assertionsEndpoint )
132- mux .HandleFunc ("/v2/snaps/refresh" , store .snapActionEndpoint )
168+ // TODO: use path vars for assertion type
169+ r .PathPrefix ("/v2/assertions/" ).HandlerFunc (store .assertionsEndpoint ).Methods ("GET" )
170+ r .HandleFunc ("/v2/snaps/refresh" , store .snapActionEndpoint )
133171
134- mux .HandleFunc ("/v2/repairs/" , store .repairsEndpoint )
172+ // TODO use path vars for brand and repair IDs
173+ r .PathPrefix ("/v2/repairs/" ).HandlerFunc (store .repairsEndpoint ).Methods ("GET" )
135174
136- mux .HandleFunc ("/debug" , store .debugEndpoint )
175+ r .HandleFunc ("/debug" , store .debugEndpoint ). Methods ( "GET" , "POST" )
137176
138177 return store
139178}
@@ -415,6 +454,7 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
415454 http .Error (w , fmt .Sprintf ("cannot marshal: %v" , err ), 500 )
416455 return
417456 }
457+ w .WriteHeader (200 )
418458 w .Write (out )
419459 return
420460 }
@@ -443,6 +483,8 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
443483 if err != nil {
444484 w .WriteHeader (400 )
445485 fmt .Fprint (w , err .Error ())
486+ } else {
487+ w .WriteHeader (200 )
446488 }
447489}
448490
@@ -466,25 +508,15 @@ func (s *Store) debugActionKillDownload(debugReq *debugRequestJSON) error {
466508 return nil
467509}
468510
469- func (s * Store ) debugActionReset (debugReq * debugRequestJSON ) {
511+ func (s * Store ) debugActionReset (_ * debugRequestJSON ) {
470512 s .lock .Lock ()
471513 defer s .lock .Unlock ()
472514
473515 s .killAfter = map [string ]int64 {}
474516}
475517
476- func logRangeHeader (handler http.HandlerFunc ) http.HandlerFunc {
477- return func (w http.ResponseWriter , req * http.Request ) {
478- path := req .URL .Path
479- if len (req .Header ["Range" ]) > 0 {
480- logger .Noticef (`requested range for %s is %v` , path , req .Header ["Range" ])
481- }
482- handler (w , req )
483- }
484- }
485-
486- func (s * Store ) applyKillAfter (handler http.HandlerFunc ) http.HandlerFunc {
487- return func (w http.ResponseWriter , req * http.Request ) {
518+ func (s * Store ) applyKillAfter (next http.Handler ) http.Handler {
519+ return http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
488520 path := req .URL .Path
489521
490522 killAfter , exists := func () (int64 , bool ) {
@@ -494,28 +526,29 @@ func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
494526 return v , ok
495527 }()
496528
497- if ! exists {
498- handler (w , req )
499- return
500- }
529+ if exists {
530+ kaw := & killAfterWriter {
531+ ResponseWriter : w ,
532+ killAfter : killAfter ,
533+ }
534+ next .ServeHTTP (kaw , req )
501535
502- kaw := & killAfterWriter {
503- ResponseWriter : w ,
504- killAfter : killAfter ,
505- }
506- handler (kaw , req )
536+ if kaw .killAfter < 0 {
537+ logger .Noticef ("%s was force killed, quota exceeded" , path )
538+ }
507539
508- if kaw .killAfter < 0 {
509- logger .Noticef ("%s was force killed, quota exceeded" , path )
510- }
540+ // update killAfter for path after write finishes
541+ defer func () {
542+ s .lock .Lock ()
543+ defer s .lock .Unlock ()
544+ s .killAfter [path ] = kaw .killAfter
545+ }()
511546
512- // update killAfter for path after write finishes
513- func () {
514- s .lock .Lock ()
515- defer s .lock .Unlock ()
516- s .killAfter [path ] = kaw .killAfter
517- }()
518- }
547+ w = kaw
548+ } else {
549+ next .ServeHTTP (w , req )
550+ }
551+ })
519552}
520553
521554func (s * Store ) searchEndpoint (w http.ResponseWriter , req * http.Request ) {
@@ -602,10 +635,7 @@ func (s *Store) repairsEndpoint(w http.ResponseWriter, req *http.Request) {
602635}
603636
604637func (s * Store ) detailsEndpoint (w http.ResponseWriter , req * http.Request ) {
605- pkg := strings .TrimPrefix (req .URL .Path , "/api/v1/snaps/details/" )
606- if pkg == req .URL .Path {
607- panic ("how?" )
608- }
638+ pkg := mux .Vars (req )["name" ]
609639
610640 bs , err := s .collectAssertions ()
611641 if err != nil {
0 commit comments