Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(531)

Delta Between Two Patch Sets: store/server.go

Issue 5901058: cmd/juju: working bootstrap and destroy commands
Left Patch Set: cmd/juju: working bootstrap and destroy commands Created 13 years ago
Right Patch Set: cmd/juju: working bootstrap and destroy commands Created 12 years, 11 months ago
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
Right: Side by side diff | Download
« no previous file with change/comment | « store/charmload/main.go ('k') | store/server_test.go » ('j') | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
(no file at all)
1 package store 1 package store
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "io" 5 "io"
6 "launchpad.net/juju/go/charm" 6 "launchpad.net/juju/go/charm"
7 "launchpad.net/juju/go/log" 7 "launchpad.net/juju/go/log"
8 "net/http" 8 "net/http"
9 "strconv"
9 "strings" 10 "strings"
10 ) 11 )
11 12
12 // Server is an http.Handler that serves the HTTP API of juju 13 // Server is an http.Handler that serves the HTTP API of juju
13 // so that juju clients can retrieve published charms. 14 // so that juju clients can retrieve published charms.
14 type Server struct { 15 type Server struct {
15 store *Store 16 store *Store
16 mux *http.ServeMux 17 mux *http.ServeMux
17 } 18 }
18 19
19 // New returns a new *Server using store. 20 // New returns a new *Server using store.
20 func NewServer(store *Store) (*Server, error) { 21 func NewServer(store *Store) (*Server, error) {
21 s := &Server{ 22 s := &Server{
22 store: store, 23 store: store,
23 mux: http.NewServeMux(), 24 mux: http.NewServeMux(),
24 } 25 }
25 s.mux.HandleFunc("/charm-info", func(w http.ResponseWriter, r *http.Requ est) { 26 s.mux.HandleFunc("/charm-info", func(w http.ResponseWriter, r *http.Requ est) {
26 s.serveInfo(w, r) 27 s.serveInfo(w, r)
27 }) 28 })
28 s.mux.HandleFunc("/charm/", func(w http.ResponseWriter, r *http.Request) { 29 s.mux.HandleFunc("/charm/", func(w http.ResponseWriter, r *http.Request) {
29 s.serveCharm(w, r) 30 s.serveCharm(w, r)
30 }) 31 })
32 s.mux.HandleFunc("/stats/counter/", func(w http.ResponseWriter, r *http. Request) {
33 s.serveStats(w, r)
34 })
35
36 // This is just a validation key to allow blitz.io to run
37 // performance tests against the site.
38 s.mux.HandleFunc("/mu-35700a31-6bf320ca-a800b670-05f845ee", func(w http. ResponseWriter, r *http.Request) {
39 s.serveBlitzKey(w, r)
40 })
31 return s, nil 41 return s, nil
32 } 42 }
33 43
34 // ServeHTTP serves an http request. 44 // ServeHTTP serves an http request.
35 // This method turns *Server into an http.Handler. 45 // This method turns *Server into an http.Handler.
36 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 46 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
47 if r.URL.Path == "/" {
48 http.Redirect(w, r, "https://juju.ubuntu.com", http.StatusSeeOth er)
49 return
50 }
37 s.mux.ServeHTTP(w, r) 51 s.mux.ServeHTTP(w, r)
38 } 52 }
39 53
40 type responseCharm struct { 54 type responseCharm struct {
41 // These are the fields effectively used by the client as of 55 // These are the fields effectively used by the client as of
42 // this writing. 56 // this writing.
43 Revision int `json:"revision"` // Zero is valid. Can't omitempty. 57 Revision int `json:"revision"` // Zero is valid. Can't omitempty.
44 Sha256 string `json:"sha256,omitempty"` 58 Sha256 string `json:"sha256,omitempty"`
45 Errors []string `json:"errors,omitempty"` 59 Errors []string `json:"errors,omitempty"`
46 Warnings []string `json:"warnings,omitempty"` 60 Warnings []string `json:"warnings,omitempty"`
47 } 61 }
48 62
63 func statsEnabled(req *http.Request) bool {
64 // It's fine to parse the form more than once, and it avoids
65 // bugs from not parsing it.
66 req.ParseForm()
67 return req.Form.Get("stats") != "0"
68 }
69
70 func charmStatsKey(curl *charm.URL, kind string) []string {
71 if curl.User == "" {
72 return []string{kind, curl.Series, curl.Name}
73 }
74 return []string{kind, curl.Series, curl.Name, curl.User}
75 }
76
49 func (s *Server) serveInfo(w http.ResponseWriter, r *http.Request) { 77 func (s *Server) serveInfo(w http.ResponseWriter, r *http.Request) {
50 if r.URL.Path != "/charm-info" { 78 if r.URL.Path != "/charm-info" {
51 w.WriteHeader(http.StatusNotFound) 79 w.WriteHeader(http.StatusNotFound)
52 return 80 return
53 } 81 }
54 r.ParseForm() 82 r.ParseForm()
55 response := map[string]*responseCharm{} 83 response := map[string]*responseCharm{}
56 for _, url := range r.Form["charms"] { 84 for _, url := range r.Form["charms"] {
57 » » r := &responseCharm{} 85 » » c := &responseCharm{}
58 » » response[url] = r 86 » » response[url] = c
59 curl, err := charm.ParseURL(url) 87 curl, err := charm.ParseURL(url)
60 var info *CharmInfo 88 var info *CharmInfo
61 if err == nil { 89 if err == nil {
62 info, err = s.store.CharmInfo(curl) 90 info, err = s.store.CharmInfo(curl)
63 } 91 }
92 var skey []string
64 if err == nil { 93 if err == nil {
65 » » » r.Sha256 = info.BundleSha256() 94 » » » skey = charmStatsKey(curl, "charm-info")
66 » » » r.Revision = info.Revision() 95 » » » c.Sha256 = info.BundleSha256()
96 » » » c.Revision = info.Revision()
67 } else { 97 } else {
68 » » » r.Errors = append(r.Errors, err.Error()) 98 » » » if err == ErrNotFound {
99 » » » » skey = charmStatsKey(curl, "charm-missing")
100 » » » }
101 » » » c.Errors = append(c.Errors, err.Error())
102 » » }
103 » » if skey != nil && statsEnabled(r) {
104 » » » go s.store.IncCounter(skey)
69 } 105 }
70 } 106 }
71 data, err := json.Marshal(response) 107 data, err := json.Marshal(response)
72 if err == nil { 108 if err == nil {
73 w.Header().Set("Content-Type", "application/json") 109 w.Header().Set("Content-Type", "application/json")
74 _, err = w.Write(data) 110 _, err = w.Write(data)
75 } 111 }
76 if err != nil { 112 if err != nil {
77 log.Printf("can't write content: %v", err) 113 log.Printf("can't write content: %v", err)
78 w.WriteHeader(http.StatusInternalServerError) 114 w.WriteHeader(http.StatusInternalServerError)
79 return 115 return
80 } 116 }
81 } 117 }
82 118
83 func (s *Server) serveCharm(w http.ResponseWriter, r *http.Request) { 119 func (s *Server) serveCharm(w http.ResponseWriter, r *http.Request) {
84 if !strings.HasPrefix(r.URL.Path, "/charm/") { 120 if !strings.HasPrefix(r.URL.Path, "/charm/") {
85 panic("serveCharm: bad url") 121 panic("serveCharm: bad url")
86 } 122 }
87 curl, err := charm.ParseURL("cs:" + r.URL.Path[len("/charm/"):]) 123 curl, err := charm.ParseURL("cs:" + r.URL.Path[len("/charm/"):])
88 if err != nil { 124 if err != nil {
89 w.WriteHeader(http.StatusNotFound) 125 w.WriteHeader(http.StatusNotFound)
90 return 126 return
91 } 127 }
92 » _, rc, err := s.store.OpenCharm(curl) 128 » info, rc, err := s.store.OpenCharm(curl)
93 if err == ErrNotFound { 129 if err == ErrNotFound {
94 w.WriteHeader(http.StatusNotFound) 130 w.WriteHeader(http.StatusNotFound)
95 return 131 return
96 } 132 }
97 if err != nil { 133 if err != nil {
98 w.WriteHeader(http.StatusInternalServerError) 134 w.WriteHeader(http.StatusInternalServerError)
99 log.Printf("can't open charm %q: %v", curl, err) 135 log.Printf("can't open charm %q: %v", curl, err)
100 return 136 return
101 } 137 }
138 if statsEnabled(r) {
139 go s.store.IncCounter(charmStatsKey(curl, "charm-bundle"))
140 }
102 defer rc.Close() 141 defer rc.Close()
142 w.Header().Set("Connection", "close") // No keep-alive for now.
103 w.Header().Set("Content-Type", "application/octet-stream") 143 w.Header().Set("Content-Type", "application/octet-stream")
144 w.Header().Set("Content-Length", strconv.FormatInt(info.BundleSize(), 10 ))
104 _, err = io.Copy(w, rc) 145 _, err = io.Copy(w, rc)
105 if err != nil { 146 if err != nil {
106 log.Printf("failed to stream charm %q: %v", curl, err) 147 log.Printf("failed to stream charm %q: %v", curl, err)
107 } 148 }
108 } 149 }
150
151 func (s *Server) serveStats(w http.ResponseWriter, r *http.Request) {
152 // TODO: Adopt a smarter mux that simplifies this logic.
153 const dir = "/stats/counter/"
154 if !strings.HasPrefix(r.URL.Path, dir) {
155 panic("bad url")
156 }
157 base := r.URL.Path[len(dir):]
158 if strings.Index(base, "/") > 0 {
159 w.WriteHeader(http.StatusNotFound)
160 return
161 }
162 if base == "" {
163 w.WriteHeader(http.StatusForbidden)
164 return
165 }
166 key := strings.Split(base, ":")
167 prefix := false
168 if key[len(key)-1] == "*" {
169 prefix = true
170 key = key[:len(key)-1]
171 if len(key) == 0 {
172 // No point in counting something unknown.
173 w.WriteHeader(http.StatusForbidden)
174 return
175 }
176 }
177 r.ParseForm()
178 sum, err := s.store.SumCounter(key, prefix)
179 if err != nil {
180 log.Printf("can't sum counter: %v", err)
181 w.WriteHeader(http.StatusInternalServerError)
182 return
183 }
184 data := []byte(strconv.FormatInt(sum, 10))
185 w.Header().Set("Content-Type", "text/plain")
186 w.Header().Set("Content-Length", strconv.Itoa(len(data)))
187 _, err = w.Write(data)
188 if err != nil {
189 log.Printf("can't write content: %v", err)
190 w.WriteHeader(http.StatusInternalServerError)
191 }
192 }
193
194 func (s *Server) serveBlitzKey(w http.ResponseWriter, r *http.Request) {
195 w.Header().Set("Connection", "close")
196 w.Header().Set("Content-Type", "text/plain")
197 w.Header().Set("Content-Length", "2")
198 w.Write([]byte("42"))
199 }
LEFTRIGHT

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b