github.com/carbocation/gotogether@v0.0.0-20130502180533-aa88e503440f/gotogether.go (about) 1 package gotogether 2 3 import ( 4 "archive/zip" 5 "fmt" 6 "html/template" 7 "io" 8 "io/ioutil" 9 "mime" 10 "net/http" 11 "os" 12 "path/filepath" 13 "strings" 14 "sync" 15 "time" 16 ) 17 18 const ( 19 Version = "0.3.2" 20 ) 21 22 var ResourceMap map[string]Resource = nil 23 var initMutex sync.Mutex 24 25 func loadMap() (map[string]Resource, error) { 26 this := os.Args[0] 27 file, err := os.Open(this) 28 if err != nil { 29 return nil, err 30 } 31 32 info, err := file.Stat() 33 if err != nil { 34 return nil, err 35 } 36 rdr, err := zip.NewReader(file, info.Size()) 37 if err != nil { 38 return nil, err 39 } 40 41 entries := make(map[string]Resource) 42 for _, file := range rdr.File { 43 if file.FileInfo().IsDir() { 44 continue 45 } 46 entries[file.Name] = &resource{file} 47 } 48 49 return entries, nil 50 } 51 52 func Initialize() error { 53 initMutex.Lock() 54 defer initMutex.Unlock() 55 56 if ResourceMap != nil { 57 return nil 58 } 59 var err error 60 ResourceMap, err = loadMap() 61 return err 62 } 63 64 type Resource interface { 65 Name() string 66 Open() (io.ReadCloser, error) 67 Size() int64 68 ModTime() time.Time 69 } 70 71 type resource struct { 72 entry *zip.File 73 } 74 75 func (rsc *resource) Name() string { 76 return rsc.entry.Name 77 } 78 79 func (rsc *resource) Open() (io.ReadCloser, error) { 80 return rsc.entry.Open() 81 } 82 83 func (rsc *resource) Size() int64 { 84 return rsc.entry.FileInfo().Size() 85 } 86 87 func (rsc *resource) ModTime() time.Time { 88 return rsc.entry.FileInfo().ModTime() 89 } 90 91 type handler int 92 93 func (h handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { 94 rsc := Get(req.URL.Path) 95 if rsc == nil { 96 http.NotFound(w, req) 97 return 98 } 99 100 rdr, err := rsc.Open() 101 if err != nil { 102 message := fmt.Sprintf("can't open %s - %s", rsc.Name(), err) 103 http.Error(w, message, http.StatusInternalServerError) 104 } 105 defer rdr.Close() 106 107 mtype := mime.TypeByExtension(filepath.Ext(req.URL.Path)) 108 if len(mtype) != 0 { 109 w.Header().Set("Content-Type", mtype) 110 } 111 w.Header().Set("Content-Size", fmt.Sprintf("%d", rsc.Size())) 112 w.Header().Set("Last-Modified", rsc.ModTime().UTC().Format(http.TimeFormat)) 113 114 //Set a 1-year expiration time. These ARE static resources, right? 115 w.Header().Set("Cache-Control", fmt.Sprintf("public, max-age=%d", 31536000)) 116 117 io.Copy(w, rdr) 118 } 119 120 // Get returns the named resource (nil if not found) 121 func Get(path string) Resource { 122 return ResourceMap[path] 123 } 124 125 // Handle register HTTP handler under prefix 126 func Handle(prefix string) error { 127 if err := Initialize(); err != nil { 128 return err 129 } 130 131 if !strings.HasSuffix(prefix, "/") { 132 prefix += "/" 133 } 134 var h handler 135 http.Handle(prefix, http.StripPrefix("/", h)) 136 return nil 137 } 138 139 // LoadTemplates loads named templates from resources. 140 // If the argument "t" is nil, it is created from the first resource. 141 func LoadTemplates(t *template.Template, filenames ...string) (*template.Template, error) { 142 if err := Initialize(); err != nil { 143 return nil, err 144 } 145 146 if len(filenames) == 0 { 147 // Not really a problem, but be consistent. 148 return nil, fmt.Errorf("no files named in call to LoadTemplates") 149 } 150 151 for _, filename := range filenames { 152 rsc := Get(filename) 153 if rsc == nil { 154 return nil, fmt.Errorf("can't find %s", filename) 155 } 156 157 rdr, err := rsc.Open() 158 if err != nil { 159 return nil, fmt.Errorf("can't open %s - %s", filename, err) 160 } 161 data, err := ioutil.ReadAll(rdr) 162 if err != nil { 163 return nil, err 164 } 165 166 var tmpl *template.Template 167 name := filepath.Base(filename) 168 if t == nil { 169 t = template.New(name) 170 } 171 if name == t.Name() { 172 tmpl = t 173 } else { 174 tmpl = t.New(name) 175 } 176 _, err = tmpl.Parse(string(data)) 177 if err != nil { 178 return nil, err 179 } 180 } 181 return t, nil 182 }