@@ -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}
@@ -433,6 +472,7 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
433472 http .Error (w , fmt .Sprintf ("cannot marshal: %v" , err ), 500 )
434473 return
435474 }
475+ w .WriteHeader (200 )
436476 w .Write (out )
437477 return
438478 }
@@ -461,6 +501,8 @@ func (s *Store) debugEndpoint(w http.ResponseWriter, req *http.Request) {
461501 if err != nil {
462502 w .WriteHeader (400 )
463503 fmt .Fprint (w , err .Error ())
504+ } else {
505+ w .WriteHeader (200 )
464506 }
465507}
466508
@@ -484,25 +526,15 @@ func (s *Store) debugActionKillDownload(debugReq *debugRequestJSON) error {
484526 return nil
485527}
486528
487- func (s * Store ) debugActionReset (debugReq * debugRequestJSON ) {
529+ func (s * Store ) debugActionReset (_ * debugRequestJSON ) {
488530 s .lock .Lock ()
489531 defer s .lock .Unlock ()
490532
491533 s .killAfter = map [string ]int64 {}
492534}
493535
494- func logRangeHeader (handler http.HandlerFunc ) http.HandlerFunc {
495- return func (w http.ResponseWriter , req * http.Request ) {
496- path := req .URL .Path
497- if len (req .Header ["Range" ]) > 0 {
498- logger .Noticef (`requested range for %s is %v` , path , req .Header ["Range" ])
499- }
500- handler (w , req )
501- }
502- }
503-
504- func (s * Store ) applyKillAfter (handler http.HandlerFunc ) http.HandlerFunc {
505- return func (w http.ResponseWriter , req * http.Request ) {
536+ func (s * Store ) applyKillAfter (next http.Handler ) http.Handler {
537+ return http .HandlerFunc (func (w http.ResponseWriter , req * http.Request ) {
506538 path := req .URL .Path
507539
508540 exists := func () bool {
@@ -512,42 +544,40 @@ func (s *Store) applyKillAfter(handler http.HandlerFunc) http.HandlerFunc {
512544 return ok
513545 }()
514546
515- if ! exists {
516- handler (w , req )
517- return
518- }
547+ if exists {
548+ kaw := & killAfterWriter {
549+ ResponseWriter : w ,
550+ path : path ,
551+ consumeQuota : func (want int ) int {
552+ s .lock .Lock ()
553+ defer s .lock .Unlock ()
554+
555+ v , ok := s .killAfter [path ]
556+ if ! ok {
557+ // no quota set
558+ return want
559+ }
560+
561+ left := int (v )
562+
563+ var got int
564+ if want > left {
565+ got = left
566+ left = 0
567+ } else {
568+ got = want
569+ left -= want
570+ }
571+ s .killAfter [path ] = int64 (left )
572+
573+ return got
574+ },
575+ }
519576
520- kaw := & killAfterWriter {
521- ResponseWriter : w ,
522- path : path ,
523- consumeQuota : func (want int ) int {
524- s .lock .Lock ()
525- defer s .lock .Unlock ()
526-
527- v , ok := s .killAfter [path ]
528- if ! ok {
529- // no quota set
530- return want
531- }
532-
533- left := int (v )
534-
535- var got int
536- if want > left {
537- got = left
538- left = 0
539- } else {
540- got = want
541- left -= want
542- }
543- s .killAfter [path ] = int64 (left )
544-
545- return got
546- },
577+ w = kaw
547578 }
548- handler (kaw , req )
549-
550- }
579+ next .ServeHTTP (w , req )
580+ })
551581}
552582
553583func (s * Store ) searchEndpoint (w http.ResponseWriter , req * http.Request ) {
@@ -634,10 +664,7 @@ func (s *Store) repairsEndpoint(w http.ResponseWriter, req *http.Request) {
634664}
635665
636666func (s * Store ) detailsEndpoint (w http.ResponseWriter , req * http.Request ) {
637- pkg := strings .TrimPrefix (req .URL .Path , "/api/v1/snaps/details/" )
638- if pkg == req .URL .Path {
639- panic ("how?" )
640- }
667+ pkg := mux .Vars (req )["name" ]
641668
642669 bs , err := s .collectAssertions ()
643670 if err != nil {
0 commit comments