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  }