<|start_filename|>volatile.go<|end_filename|>
package httpcache

import (
	"net/http"
	"time"
)

// NewVolatile creates an http RoundTripper with a memory cache.
func NewVolatile(transport http.RoundTripper, TTL time.Duration, maxItems int) http.RoundTripper {
	return &CachedRoundTrip{
		Transport: transport,
		Cache:     newMemoryCache(maxItems),
		TTL:       TTL,
	}
}

// NewVolatileClient creates an http client with a memory cache.
func NewVolatileClient(TTL time.Duration, maxItems int) *http.Client {
	return &http.Client{Transport: NewVolatile(http.DefaultTransport, TTL, maxItems)}
}


<|start_filename|>memory.go<|end_filename|>
package httpcache

import (
	"fmt"
	"net/url"
	"sync"
	"time"

	"github.com/golang/groupcache/lru"
)

// A memoryCache implements a cache with files saved in Path.
type memoryCache struct {
	sync.Mutex
	Cache *lru.Cache
}

// newMemoryCache creates a new memory cache using groupcache's lru.
func newMemoryCache(maxItems int) *memoryCache {
	return &memoryCache{Cache: lru.New(maxItems)}
}

// Get gets data saved for an URL if present in cache.
func (m *memoryCache) Get(u *url.URL) (*entry, error) {
	m.Lock()
	defer m.Unlock()
	data, ok := m.Cache.Get(u.String())
	if !ok {
		return nil, fmt.Errorf("httpcache: %s not in cache", u)
	}
	return data.(*entry), nil
}

// Put puts data of an URL in cache.
func (m *memoryCache) Put(u *url.URL, data []byte) error {
	m.Lock()
	defer m.Unlock()
	m.Cache.Add(u.String(), &entry{data, time.Now()})
	return nil
}


<|start_filename|>file.go<|end_filename|>
package httpcache

import (
	"encoding/gob"
	"fmt"
	"net/url"
	"os"
	"path"
	"strings"
	"time"
)

// A fileCache implements a cache with files saved in Path.
// It is not safe for concurrency.
type fileCache struct {
	Path string
}

// Windows does not accept `\/:*?"<>|` and UNIX `/`, replace all with dash.
var fileNameReplacer = strings.NewReplacer(`/`, `-`, `\`, `-`, `:`, `-`, `*`,
	`-`, `?`, `-`, `"`, `-`, `<`, `-`, `>`, `-`, `|`, `-`)

// fileName builds a file name from an URL to be used as cache.
func (f *fileCache) fileName(u *url.URL) string {
	return path.Join(f.Path, fileNameReplacer.Replace(u.String()))
}

// Get gets data saved for an URL if present in cache.
func (f *fileCache) Get(u *url.URL) (*entry, error) {
	fp, err := os.Open(f.fileName(u))
	if err != nil {
		return nil, fmt.Errorf("httpcache: could not open %v: %v", u, err)
	}
	decoder := gob.NewDecoder(fp)
	defer fp.Close()

	var e entry
	if err := decoder.Decode(&e); err != nil {
		return nil, fmt.Errorf("httpcache: could not decode %s: %v", u, err)
	}
	return &e, nil
}

// Put puts data of an URL in cache.
func (f *fileCache) Put(u *url.URL, data []byte) error {
	e := entry{data, time.Now()}
	fp, err := os.Create(f.fileName(u))
	if err != nil {
		return err
	}
	defer fp.Close()

	encoder := gob.NewEncoder(fp)
	if err := encoder.Encode(e); err != nil {
		return fmt.Errorf("httpcache: could not encode %s: %v", u, err)
	}
	return nil
}


<|start_filename|>cache.go<|end_filename|>
package httpcache

import (
	"bytes"
	"errors"
	"io/ioutil"
	"net/http"
	"net/url"
	"strings"
	"time"
)

// Cache represents the ability to cache response data from URL.
type Cache interface {
	Get(u *url.URL) (*entry, error)
	Put(u *url.URL, data []byte) error
}

// An entry represents an entry in the cache and when it was saved.
type entry struct {
	Data     []byte
	SaveTime time.Time
}

// A CachedRoundTrip implements net/http RoundTripper with a cache.
type CachedRoundTrip struct {
	Transport http.RoundTripper
	Cache     Cache
	TTL       time.Duration
}

// RoundTrip loads from cache if possible or RoundTrips and saves it.
func (c *CachedRoundTrip) RoundTrip(req *http.Request) (*http.Response, error) {
	if !c.cacheableRequest(req) {
		return c.Transport.RoundTrip(req)
	}
	cache, err := c.load(req, 20)
	if err == nil {
		return cache, nil
	}

	resp, err := c.Transport.RoundTrip(req)
	if err != nil {
		return nil, err
	}
	if c.cacheableResponse(resp) {
		c.save(req, resp)
	}
	return resp, nil
}

// cacheableRequest tells whether a request is cacheable or not.
func (c CachedRoundTrip) cacheableRequest(req *http.Request) bool {
	return req.Method == "GET"
}

// cacheableResponse tells whether a response is cacheable or not.
func (c CachedRoundTrip) cacheableResponse(resp *http.Response) bool {
	return resp.StatusCode == http.StatusOK ||
		resp.StatusCode == http.StatusMovedPermanently
}

// load prepares the response of a request by loading its body from cache.
func (c CachedRoundTrip) load(req *http.Request, maxRedirects int) (*http.Response, error) {
	if maxRedirects == 0 {
		return nil, errors.New("httpcache: max redirects hit")
	}

	entry, err := c.Cache.Get(req.URL)
	if err != nil {
		return nil, err
	}
	if entry == nil {
		return nil, errors.New("httpcache: underlying cache returned nil entry")
	}

	if entry.SaveTime.Add(c.TTL).Before(time.Now()) {
		return nil, errors.New("httpcache: TTL expired")
	}

	body := entry.Data
	if strings.HasPrefix(string(body), "REDIRECT:") {
		u, err := url.Parse(strings.TrimPrefix(string(body), "REDIRECT:"))
		if err != nil {
			return nil, err
		}
		req.URL = u
		return c.load(req, maxRedirects-1)
	}

	return &http.Response{
		Status:        "200 OK",
		StatusCode:    200,
		Proto:         "HTTP/1.0",
		ProtoMajor:    1,
		ProtoMinor:    0,
		Body:          ioutil.NopCloser(bytes.NewReader(body)),
		ContentLength: int64(len(body)),
	}, nil
}

// save saves the body of a response corresponding to a request.
func (c *CachedRoundTrip) save(req *http.Request, resp *http.Response) error {
	if resp.StatusCode == http.StatusMovedPermanently {
		u, err := resp.Location()
		if err != nil {
			return err
		}

		err = c.Cache.Put(req.URL, []byte("REDIRECT:"+u.String()))
		if err != nil {
			return err
		}
		return nil
	}

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return err
	}
	resp.Body.Close()
	err = c.Cache.Put(req.URL, body)
	if err != nil {
		return err
	}
	resp.Body = ioutil.NopCloser(bytes.NewReader(body))
	return nil
}


<|start_filename|>examples/volatile/volatile.go<|end_filename|>
// Binary volatile demonstrates using a volatile (in memory) httpcache.
// Run the binary once with repeated arguments to use the cache.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/StalkR/httpcache"
)

var ttl = flag.Duration("ttl", time.Minute, "cache expiration")

func main() {
	flag.Parse()
	if flag.NArg() == 0 {
		fmt.Fprintf(os.Stderr, "Usage: %s [-ttl <duration>] <url> [<url> ...]\n", os.Args[0])
		os.Exit(1)
	}

	client := httpcache.NewVolatileClient(*ttl, 1024)
	for _, url := range flag.Args() {
		resp, err := client.Get(url)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(2)
		}
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(3)
		}
		fmt.Print(string(body))
	}
}


<|start_filename|>doc.go<|end_filename|>
/*
Package httpcache implements net/http RoundTripper with caching.
Persistent with a cache using files. Volatile with a cache in memory.

The following rules applies for caching:
 - only responses of GET requests are cached (no POST, etc.)
 - only body response is cached (no headers)
 - only response status 200 (OK) and response status 301 (Moved Permanently)
*/
package httpcache


<|start_filename|>persistent.go<|end_filename|>
package httpcache

import (
	"fmt"
	"net/http"
	"os"
	"time"
)

// NewPersistent creates an http RoundTripper with a file cache.
// Cache files will be created under path.
func NewPersistent(transport http.RoundTripper, path string, TTL time.Duration) http.RoundTripper {
	return &CachedRoundTrip{
		Transport: transport,
		Cache:     &fileCache{Path: path},
		TTL:       TTL,
	}
}

// NewPersistentClient creates an http client with a file cache.
func NewPersistentClient(path string, TTL time.Duration) (*http.Client, error) {
	if _, err := os.Stat(path); os.IsNotExist(err) {
		if err := os.Mkdir(path, os.ModeDir|os.ModePerm); err != nil {
			return nil, fmt.Errorf("httpcache: could not create dir %s: %v", path, err)
		}
	}
	return &http.Client{Transport: NewPersistent(http.DefaultTransport, path, TTL)}, nil
}


<|start_filename|>examples/persistent/persistent.go<|end_filename|>
// Binary persistent demonstrates using a persistent (with files) httpcache.
// Run the binary twice with the same URL to use the cache.
package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"time"

	"github.com/StalkR/httpcache"
)

var (
	cache = flag.String("cache", ".", "directory to use as cache")
	ttl   = flag.Duration("ttl", time.Minute, "cache expiration")
)

func main() {
	flag.Parse()
	if flag.NArg() == 0 {
		fmt.Fprintf(os.Stderr, "Usage: %s [-cache <dir>] [-ttl <duration>] <url>\n", os.Args[0])
		os.Exit(1)
	}
	url := flag.Arg(0)

	client, err := httpcache.NewPersistentClient(*cache, *ttl)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(2)
	}
	resp, err := client.Get(url)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(3)
	}
	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error: %v\n", err)
		os.Exit(4)
	}
	fmt.Print(string(body))
}
